Module: Lich::DragonRealms::DRCT

Defined in:
documented/dragonrealms/commons/common-travel.rb

Overview

DragonRealms Common Travel: utilities for navigation, merchant interactions, and room finding.

Provides methods for walking between rooms, buying/selling/asking merchants, disposing items, and locating empty rooms suitable for spellcasting or hunting.

Constant Summary collapse

DIRECTION_REVERSE =

Direction reversal mapping for path reversal Note: With frozen_string_literal: true, string literals are already frozen

{
  'northeast' => 'southwest',
  'southwest' => 'northeast',
  'northwest' => 'southeast',
  'southeast' => 'northwest',
  'north'     => 'south',
  'south'     => 'north',
  'east'      => 'west',
  'west'      => 'east',
  'up'        => 'down',
  'down'      => 'up'
}.freeze
SELL_SUCCESS_PATTERNS =

Patterns indicating successful sale at a merchant

[
  /hands? you \d+ (?:kronars|lirums|dokoras)/i
].freeze
SELL_FAILURE_PATTERNS =

Patterns indicating failed sale attempt

[
  /I need to examine the merchandise first/,
  /That's not worth anything/,
  /I only deal in pelts/,
  /There's folk around here that'd slit a throat for this/
].freeze
BUY_PRICE_PATTERNS =

Patterns matching merchant price quotes in response to a BUY request.

Extracted named capture group 'amount' contains the numeric price in kronars, lirums, or dokoras. Each pattern accounts for merchant personality variation across GemStone and DragonRealms towns.

Examples:

Merchant quote matching

"prepared to offer it to you for 500 kronars" #=> captures amount: "500"
"Cost you just 250 lirums?, okie dokie?" #=> captures amount: "250"

See Also:

  • #buy_item
[
  /prepared to offer it to you for (?<amount>.*) (?:kronar|lirum|dokora)s?/,
  /Let me but ask the humble sum of (?<amount>.*) coins/,
  /it would be just (?<amount>\d*) (?:kronar|lirum|dokora)s?/,
  /for a (?:mere )?(?<amount>\d*) (?:kronar|lirum|dokora)s?/,
  /I can let that go for\.\.\.(?<amount>.*) (?:kronar|lirum|dokora)s?/,
  /cost you (?:just )?(?<amount>.*) (?:kronar|lirum|dokora)s?/,
  /it may be yours for just (?<amount>.*) (?:kronar|lirum|dokora)s?/,
  /I'll give that to you for (?<amount>.*) (?:kronar|lirum|dokora)s?/,
  /I'll let you have it for (?<amount>.*) (?:kronar|lirum|dokora)s?/i,
  /I ask that you give (?<amount>.*) copper (?:kronar|lirum|dokora)s?/,
  /it'll be (?<amount>.*) (?:kronar|lirum|dokora)s?/,
  /the price of (?<amount>.*) coins? is all I ask/,
  /tis only (?<amount>.*) (?:kronar|lirum|dokora)s?/,
  /That will be (?<amount>.*) copper (?:kronar|lirum|dokora)s? please/,
  /That'll be (?<amount>.*) copper (?:kronar|lirum|dokora)s?/,
  /to you for (?<amount>.*) (?:kronar|lirum|dokora)s?/,
  /I ask (?<amount>.*) copper (?:kronar|lirum|dokora)s or if you would prefer/,
  /Cost you just (?<amount>.*) (?:kronar|lirum|dokora)s?, okie dokie\?/i,
  /It will cost just (?<amount>.*) (?:kronar|lirum|dokora)s?/i,
  /I would suggest (?<amount>.*) (?:kronar|lirum|dokora)s?/i,
  /to you for (?<amount>.*) (?:kronar|lirum|dokora)s?/i,
  /asking (?<amount>.*) (?:kronar|lirum|dokora)s?/i
].freeze
BUY_NON_PRICE_PATTERNS =

Patterns indicating buy action without price info

[
  'You decide to purchase',
  'Buy what'
].freeze
ASK_SUCCESS_PATTERNS =

Patterns indicating successful ASK request

[
  /hands you/
].freeze
ASK_FAILURE_PATTERNS =

Patterns indicating failed ASK request

[
  /does not seem to know anything about that/,
  /All I know about/,
  /To whom are you speaking/,
  /Usage: ASK/
].freeze
MAX_WALK_TO_RETRIES =

Maximum number of times walk_to will retry navigation before giving up. Prevents unbounded recursion (and eventual SystemStackError) when the game server connection is lost or go2 repeatedly fails.

3

Class Method Summary collapse

Class Method Details

.ask_for_item?(room, name, item) ⇒ Boolean

Visit a merchant and ask for an item. Usually this is for bundling ropes or gem pouches.

Returns:

  • (Boolean)


126
127
128
129
130
131
132
133
134
135
# File 'documented/dragonrealms/commons/common-travel.rb', line 126

def ask_for_item?(room, name, item)
  walk_to(room)

  case DRC.bput("ask #{name} for #{item}", *ASK_SUCCESS_PATTERNS, *ASK_FAILURE_PATTERNS)
  when *ASK_SUCCESS_PATTERNS
    true
  else
    false
  end
end

.buy_item(room, item) ⇒ Object

Visit a merchant and buy an item by offering their asking price. Usually for restocking ammunition or other misc items.



112
113
114
115
116
117
118
119
120
121
122
# File 'documented/dragonrealms/commons/common-travel.rb', line 112

def buy_item(room, item)
  walk_to(room)

  all_patterns = BUY_PRICE_PATTERNS + BUY_NON_PRICE_PATTERNS
  result = DRC.bput("buy #{item}", *all_patterns)

  match_data = BUY_PRICE_PATTERNS.lazy.filter_map { |p| p.match(result) }.first
  amount = match_data[:amount] if match_data

  fput("offer #{amount}") if amount
end

.dispose(item, trash_room = nil, worn_trashcan = nil, worn_trashcan_verb = nil) ⇒ void

This method returns an undefined value.

Discards an item by walking to a trash room and disposing of it in a trashcan.

If no trash room is provided, disposes in the current room. If a worn trashcan (e.g., a backpack or sack) is specified, deposits the item there instead.

Examples:

DRCT.dispose("my junk", 4711)
DRCT.dispose("my lockpick", 5000, "my lockpick container", "on")

Parameters:

  • item (String)

    the item name or pronoun (e.g. "my lockpick", "junk")

  • trash_room (Integer, nil) (defaults to: nil)

    room id to walk to before disposing; if nil, disposes in place

  • worn_trashcan (String, nil) (defaults to: nil)

    worn container to put item into (e.g. "my backpack")

  • worn_trashcan_verb (String, nil) (defaults to: nil)

    preposition for inserting into the container (e.g. "on", "in")



159
160
161
162
163
164
# File 'documented/dragonrealms/commons/common-travel.rb', line 159

def dispose(item, trash_room = nil, worn_trashcan = nil, worn_trashcan_verb = nil)
  return unless item

  DRCT.walk_to(trash_room) unless trash_room.nil?
  DRCI.dispose_trash(item, worn_trashcan, worn_trashcan_verb)
end

.find_empty_room(search_rooms, idle_room, predicate = nil, min_mana = 0, strict_mana = false, max_search_attempts = Float::INFINITY, prioritize_buddies = false) ⇒ Boolean

Note:

Messages are logged to indicate search attempts and outcomes.

Searches for a suitable room (empty or matching a condition) across multiple locations.

Walks through search_rooms and applies a suitability check. On each attempt, evaluates each room using a predicate (or the default: room is empty and contains no non-grouped PCs). If min_mana > 0 and not a Moon Mage or Trader, also checks mana percentage.

If no suitable room is found on an attempt, walks to idle_room (or a random one if it's an Array), pauses 20-40 seconds, and retries up to max_search_attempts times.

With prioritize_buddies=true, prefers rooms containing friends (UserVars.friends) over empty rooms on the first pass.

Returns true immediately when a suitable room is found. Returns false if all attempts exhaust.

Examples:

DRCT.find_empty_room([5000, 5001, 5002], 5010, nil, 50, true, 5)

Parameters:

  • search_rooms (Array<Integer>)

    room ids to check in order

  • idle_room (Integer, Array<Integer>, nil)

    room(s) to idle in between attempts; no idle if nil

  • predicate (Proc, nil) (defaults to: nil)

    custom suitability check: receives search_attempt (1-indexed) and returns Boolean

  • min_mana (Integer) (defaults to: 0)

    minimum mana percentage required if mana checking is enabled (0 disables)

  • strict_mana (Boolean) (defaults to: false)

    if false, allows empty rooms without mana after first pass

  • max_search_attempts (Integer) (defaults to: Float::INFINITY)

    maximum search attempts before giving up

  • prioritize_buddies (Boolean) (defaults to: false)

    on first pass, prefer rooms with friends over empty rooms

Returns:

  • (Boolean)

    true if a suitable room was found, false otherwise



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'documented/dragonrealms/commons/common-travel.rb', line 404

def find_empty_room(search_rooms, idle_room, predicate = nil, min_mana = 0, strict_mana = false, max_search_attempts = Float::INFINITY, prioritize_buddies = false)
  search_attempt = 0
  check_mana = min_mana > 0
  rooms_searched = 0
  loop do
    search_attempt += 1
    Lich::Messaging.msg('plain', "DRCT: Search attempt #{search_attempt} of #{max_search_attempts} to find a suitable room.")
    found_empty = false
    search_rooms.each do |room_id|
      walk_to(room_id)
      pause 0.1 until room_id == Room.current.id

      rooms_searched += 1

      if prioritize_buddies && (rooms_searched <= search_rooms.size)
        suitable_room = ((DRRoom.pcs & UserVars.friends).any? && (DRRoom.pcs & UserVars.hunting_nemesis).none?)
        if rooms_searched == search_rooms.size && (DRRoom.pcs & UserVars.friends).empty? && (DRRoom.pcs & UserVars.hunting_nemesis).empty?
          Lich::Messaging.msg('plain', 'DRCT: Reached last room in list, and found no buddies. Retrying for empty room.')
          return find_empty_room(search_rooms, idle_room, predicate, min_mana, strict_mana, max_search_attempts, false)
        end
      else
        suitable_room = predicate ? predicate.call(search_attempt) : (DRRoom.pcs - DRRoom.group_members).empty?
      end
      if suitable_room && check_mana && !(DRStats.moon_mage? || DRStats.trader?)
        found_empty = true
        suitable_room = (DRCA.perc_mana >= min_mana)
      end
      return true if suitable_room
    end

    if found_empty && check_mana && !strict_mana
      check_mana = false
      Lich::Messaging.msg('plain', 'DRCT: Empty rooms found, but not with the right mana. Going to use those anyway.')
      next
    end

    check_mana = min_mana > 0

    if idle_room && search_attempt < max_search_attempts
      idle_room = idle_room.sample if idle_room.is_a?(Array)
      walk_to(idle_room)
      wait_time = rand(20..40)
      Lich::Messaging.msg('plain', "DRCT: Failed to find an empty room, pausing #{wait_time} seconds.")
      pause wait_time
    else
      Lich::Messaging.msg('plain', 'DRCT: Failed to find an empty room, stopping the search.')
      return false
    end
  end
end

.find_sorted_empty_room(search_rooms, idle_room, predicate = nil) ⇒ Boolean

Finds an empty room by searching destinations sorted by shortest path distance.

Sorts search_rooms by distance from the current room using #sort_destinations, then searches them in that order using #find_empty_room with default parameters.

Examples:

DRCT.find_sorted_empty_room([5000, 5001, 5002], 5010)

Parameters:

  • search_rooms (Array<Integer>)

    room ids to search

  • idle_room (Integer, Array<Integer>, nil)

    room(s) to idle in between search attempts

  • predicate (Proc, nil) (defaults to: nil)

    optional custom suitability check

Returns:

  • (Boolean)

    true if an empty room was found, false otherwise



492
493
494
495
# File 'documented/dragonrealms/commons/common-travel.rb', line 492

def find_sorted_empty_room(search_rooms, idle_room, predicate = nil)
  sorted_rooms = sort_destinations(search_rooms)
  find_empty_room(sorted_rooms, idle_room, predicate)
end

.get_hometown_target_id(hometown, target) ⇒ Integer?

Note:

Debug logging is controlled by the global $common_travel_debug flag.

Retrieves the room id of a target location (e.g. "bank", "healer") in a specific hometown.

Looks up the target in town data for the hometown. If not found on first attempt, pauses 2 seconds and retries. Logs debug messages (if $common_travel_debug is true) on first failure, second success, and final result.

Examples:

DRCT.get_hometown_target_id("Wehnimer's Landing", "bank") #=> 5000

Parameters:

  • hometown (String)

    the hometown key in town data (e.g. "Wehnimer's Landing", "River's Rest")

  • target (String)

    the location key (e.g. "bank", "healer", "council")

Returns:

  • (Integer, nil)

    the room id, or nil if not found after two attempts



552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
# File 'documented/dragonrealms/commons/common-travel.rb', line 552

def get_hometown_target_id(hometown, target)
  hometown_data = get_data('town')[hometown]
  target_id = hometown_data[target] && hometown_data[target]['id']
  unless target_id
    Lich::Messaging.msg('plain', "DRCT: get_hometown_target_id failed first attempt for #{target} in #{hometown}. Trying again.") if $common_travel_debug
    pause 2
    hometown_data = get_data('town')[hometown]
    target_id = hometown_data[target] && hometown_data[target]['id']
    unless target_id
      Lich::Messaging.msg('plain', "DRCT: get_hometown_target_id failed second attempt for #{target} in #{hometown}. Likely target doesn't exist.") if $common_travel_debug
      target_id = nil
    else
      Lich::Messaging.msg('plain', "DRCT: get_hometown_target_id succeeded second attempt for #{target} in #{hometown}.") if $common_travel_debug
    end
  end
  Lich::Messaging.msg('plain', "DRCT: target_id = #{target_id}") if $common_travel_debug
  target_id
end

.order_item(room, item_number) ⇒ Object

Visit a merchant and order from a menu.



138
139
140
141
142
143
144
# File 'documented/dragonrealms/commons/common-travel.rb', line 138

def order_item(room, item_number)
  walk_to(room)

  return if DRC.bput("order #{item_number}", 'Just order it again', 'you don\'t have enough coins') == 'you don\'t have enough coins'

  DRC.bput("order #{item_number}", 'takes some coins from you')
end

.refill_lockpick_container(lockpick_type, hometown, container, count) ⇒ void

Note:

Logs messages if locksmith location is not found or if put-away fails.

This method returns an undefined value.

Buys lockpicks from the locksmith in a hometown and stores them in a container.

Walks to the locksmith location (looked up from town data), buys the specified number of lockpicks of a given type, and places each into a worn container. Stops early if a put-away fails (e.g., due to container type mismatch). Fixes standing and exits the locksmith room afterward to be polite to thieves.

Returns silently if count < 1 or if no locksmith location is found for the hometown.

Examples:

DRCT.refill_lockpick_container("iron", "Wehnimer's Landing", "my lockpick container", 10)

Parameters:

  • lockpick_type (String)

    lockpick type (e.g. "iron", "silver")

  • hometown (String)

    the character's hometown (used to look up locksmith location)

  • container (String)

    worn container name for storage (e.g. "my lockpick container")

  • count (Integer)

    number of lockpicks to buy; skipped if < 1



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'documented/dragonrealms/commons/common-travel.rb', line 183

def refill_lockpick_container(lockpick_type, hometown, container, count)
  return if count < 1

  room = get_data('town')[hometown]['locksmithing']['id']

  if room.nil?
    Lich::Messaging.msg('bold', 'DRCT: No locksmith location found for current hometown. Skipping refilling.')
    return
  end

  walk_to(room)
  if Room.current.id != room
    Lich::Messaging.msg('bold', 'DRCT: Could not reach locksmith location. Skipping refilling.')
    return
  end

  count.times do
    buy_item(room, "#{lockpick_type} lockpick")
    unless DRCI.put_away_item_unsafe?('my lockpick', "my #{container}", 'on')
      Lich::Messaging.msg('bold', "DRCT: Failed to put lockpick on #{container}. Check your lockpick settings - mixing types in a container is not allowed.")
      break
    end
  end

  # Be polite to Thieves, who need the room to be empty
  DRC.fix_standing
  move('out') if XMLData.room_exits.include?('out')
end

.retreat(ignored_npcs = []) ⇒ void

This method returns an undefined value.

Retreats from combat if there are non-ignored NPCs in the room.

Checks if the room contains any NPCs other than those in ignored_npcs. If so, calls DRC.retreat. Otherwise does nothing.

Examples:

DRCT.retreat(["Grukk"])

Parameters:

  • ignored_npcs (Array) (defaults to: [])

    NPC names to exclude from retreat logic



373
374
375
376
377
# File 'documented/dragonrealms/commons/common-travel.rb', line 373

def retreat(ignored_npcs = [])
  return if (DRRoom.npcs - ignored_npcs).empty?

  DRC.retreat(ignored_npcs)
end

.reverse_path(path) ⇒ Array<String>?

Note:

Direction names must be spelled out in full (e.g. "northeast" not "ne").

Reverses a path of compass directions into the opposite sequence.

Takes an array of full direction names (e.g. ["north", "east", "northeast"]) and returns an array of reversed directions in reverse order (e.g. ["southwest", "west", "south"]). Unknown directions cause a message to be logged and nil to be returned.

Examples:

DRCT.reverse_path(["north", "east"]) #=> ["west", "south"]
DRCT.reverse_path(["up"]) #=> ["down"]

Parameters:

  • path (Array<String>)

    array of full direction names (e.g. "northeast", "up", "south")

Returns:

  • (Array<String>, nil)

    reversed path, or nil if an unknown direction is encountered



529
530
531
532
533
534
535
536
537
538
# File 'documented/dragonrealms/commons/common-travel.rb', line 529

def reverse_path(path)
  path.reverse.map do |dir|
    reversed = DIRECTION_REVERSE[dir]
    unless reversed
      Lich::Messaging.msg('bold', "DRCT: No reverse direction found for '#{dir}'. Use full direction names (e.g., 'northeast' not 'ne'). Path must be an array.")
      return nil
    end
    reversed
  end
end

.sell_item(room, item) ⇒ Object

Visit a merchant and sell an item. Usually to sell junk at pawn shops.



97
98
99
100
101
102
103
104
105
106
107
108
# File 'documented/dragonrealms/commons/common-travel.rb', line 97

def sell_item(room, item)
  return false unless DRCI.in_hands?(item)

  walk_to(room)

  case DRC.bput("sell my #{item}", *SELL_SUCCESS_PATTERNS, *SELL_FAILURE_PATTERNS)
  when *SELL_SUCCESS_PATTERNS
    true
  when *SELL_FAILURE_PATTERNS
    false
  end
end

.sort_destinations(target_list) ⇒ Array<Integer>

Sorts a list of destination room ids by shortest path distance from the current room.

Uses Dijkstra pathfinding to compute shortest distances from the current room to all destinations. Unreachable rooms are filtered out. Returns the sorted list in ascending order of distance.

If pathfinding fails or a room is unreachable, it is removed (except the current room itself, which is preserved even without a distance).

Examples:

DRCT.sort_destinations([5000, 5001, 4999]) #=> [4999, 5000, 5001] (assuming those distances)

Parameters:

  • target_list (Array<Integer, String>)

    room ids to sort (converted to Integer)

Returns:

  • (Array<Integer>)

    target_list sorted by distance from current room (nearest first)



468
469
470
471
472
473
474
475
476
477
478
479
# File 'documented/dragonrealms/commons/common-travel.rb', line 468

def sort_destinations(target_list)
  target_list = target_list.collect(&:to_i)
  _previous, shortest_distances = Map.dijkstra(Room.current.id)
  # Pathfinding failed; hand back what was asked for rather than nothing.
  return target_list if shortest_distances.nil?

  target_list.delete_if { |room_num| shortest_distances[room_num].nil? && room_num != Room.current.id }
  # The current room is kept above even without a distance, so sort on a
  # fallback rather than handing nil to the comparator. dijkstra seeds the
  # source at 0, so this is defensive rather than a live case.
  target_list.sort_by { |room_num| shortest_distances[room_num] || Float::INFINITY }
end

.tag_to_id(target) ⇒ Integer?

Note:

Messages are logged (at 'bold' level) on failure.

Converts a map tag (e.g. "bank", "healer") to the room id of the closest reachable room with that tag.

Uses Dijkstra pathfinding to find the shortest path from the current room to any room tagged with the target. If already in a room with the target tag, returns the current room. Returns nil if no rooms match, no path exists, or pathfinding fails.

Examples:

DRCT.tag_to_id("bank") #=> 5000

Parameters:

  • target (String)

    a map tag such as "bank", "healer", "council"

Returns:

  • (Integer, nil)

    the room id of the closest tagged room, or nil if not found



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'documented/dragonrealms/commons/common-travel.rb', line 331

def tag_to_id(target)
  start_room = Room.current.id
  target_list = Map.rooms_by_tag(target)

  if target_list.empty?
    Lich::Messaging.msg('bold', "DRCT: No go2 targets matching '#{target}' found.")
    return nil
  end

  if target_list.include?(start_room)
    Lich::Messaging.msg('plain', "DRCT: You're already here.")
    return start_room
  end
  _previous, shortest_distances = Room.current.dijkstra(target_list)
  if shortest_distances.nil?
    Lich::Messaging.msg('bold', "DRCT: Pathfinding failed while looking for a '#{target}' tag.")
    return nil
  end

  target_list.delete_if { |room_id| shortest_distances[room_id].nil? }
  if target_list.empty?
    Lich::Messaging.msg('bold', "DRCT: Couldn't find a path from here to any room with a '#{target}' tag.")
    return nil
  end

  target_id = target_list.sort { |a, b| shortest_distances[a] <=> shortest_distances[b] }.first
  unless target_id && (destination = Map[target_id])
    Lich::Messaging.msg('bold', "DRCT: Something went wrong! Debug failed with target_id=#{target_id}, destination=#{destination}, tag='#{target}'.")
    return nil
  end
  target_id
end

.time_to_room(origin, destination) ⇒ Integer?

Computes the shortest number of moves required to travel between two rooms.

Uses Dijkstra pathfinding from origin to destination. Returns the distance (number of moves), or nil if no path exists or pathfinding fails.

Examples:

DRCT.time_to_room(5000, 5001) #=> 2

Parameters:

  • origin (Integer)

    starting room id

  • destination (Integer)

    target room id

Returns:

  • (Integer, nil)

    minimum number of moves to reach destination, or nil if unreachable



507
508
509
510
511
512
513
514
515
# File 'documented/dragonrealms/commons/common-travel.rb', line 507

def time_to_room(origin, destination)
  # Results are keyed by Integer room id, and dijkstra only terminates
  # early when the destination compares as an Integer.
  destination = destination.to_i
  _previous, shortest_paths = Map.dijkstra(origin, destination)
  return nil if shortest_paths.nil?

  shortest_paths[destination]
end

.walk_to(target_room, restart_on_fail = true, retry_depth: 0) ⇒ Boolean

Navigate to target_room using the go2 script.

Parameters:

  • target_room (Integer, String)

    room id or map tag (e.g. 'bank')

  • restart_on_fail (Boolean) (defaults to: true)

    retry navigation on failure

  • retry_depth (Integer) (defaults to: 0)

    internal counter -- callers should not set this

Returns:

  • (Boolean)

    true if the character is now in the target room



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'documented/dragonrealms/commons/common-travel.rb', line 223

def walk_to(target_room, restart_on_fail = true, retry_depth: 0)
  target_room = tag_to_id(target_room) if target_room.is_a?(String) && target_room.count("a-zA-Z") > 0

  return false if target_room.nil?

  room_num = target_room.to_i
  # Room.current is nil when the room could not be resolved. Under Lich's
  # NilClass patch nil.id yielded nil, so the comparison simply failed.
  return true if room_num == Room.current&.id

  DRC.fix_standing

  if Room.current&.id.nil?
    Lich::Messaging.msg('plain', "DRCT: In an unknown room, manually attempting to navigate to #{room_num}")
    rooms = Map.list.compact.select { |room| room.description.include?(XMLData.room_description.strip) && room.title.include?(XMLData.room_title) }
    if rooms.empty? || rooms.length > 1
      Lich::Messaging.msg('bold', 'DRCT: Failed to find a matching room from unknown location.')
      return false
    end
    room = rooms.first
    return true if room_num == room.id

    if room.wayto[room_num.to_s]
      move room.wayto[room_num.to_s]
      return room_num == room.id
    end
    path = Map.findpath(room, Map[room_num])
    way = room.wayto[path.first.to_s]
    if way.is_a?(StringProc)
      way.call
    else
      move way
    end
    if retry_depth >= MAX_WALK_TO_RETRIES
      Lich::Messaging.msg('bold', "DRCT: Failed to navigate from unknown room after #{MAX_WALK_TO_RETRIES} retries, giving up.")
      return false
    end
    return walk_to(room_num, true, retry_depth: retry_depth + 1)
  end

  script_handle = start_script('go2', [room_num.to_s], force: true)

  timer = Time.now
  prev_room = XMLData.room_description + XMLData.room_title

  Flags.add('travel-closed-shop', 'The door is locked up tightly for the night', 'You smash your nose', '^A servant (blocks|stops)')
  Flags.add('travel-engaged', 'You are engaged')

  begin
    while Script.running.include?(script_handle)
      if Flags['travel-closed-shop']
        Flags.reset('travel-closed-shop')
        kill_script(script_handle)
        if /You open/ !~ DRC.bput('open door', 'It is locked', 'You .+', 'What were')
          restart_on_fail = false
          break
        end
        timer = Time.now
        script_handle = start_script('go2', [room_num.to_s])
      end
      if Flags['travel-engaged']
        Flags.reset('travel-engaged')
        kill_script(script_handle)
        DRC.retreat
        timer = Time.now
        script_handle = start_script('go2', [room_num.to_s])
      end
      if (Time.now - timer) > 90
        kill_script(script_handle)
        pause 0.5 while Script.running.include?(script_handle)
        break unless restart_on_fail

        timer = Time.now
        script_handle = start_script('go2', [room_num.to_s])
      end
      if Script.running?('escort') || Script.running?('bescort') || (XMLData.room_description + XMLData.room_title) != prev_room || XMLData.room_description =~ /The terrain constantly changes as you travel along on your journey/
        timer = Time.now
      end
      prev_room = XMLData.room_description + XMLData.room_title
      pause 0.5
    end
  ensure
    Flags.delete('travel-closed-shop')
    Flags.delete('travel-engaged')
  end

  if room_num != Room.current.id && restart_on_fail
    if retry_depth >= MAX_WALK_TO_RETRIES
      Lich::Messaging.msg('bold', "DRCT: Failed to navigate to room #{room_num} after #{MAX_WALK_TO_RETRIES} retries, giving up.")
      return false
    end
    Lich::Messaging.msg('bold', "DRCT: Failed to navigate to room #{room_num}, attempting again (#{retry_depth + 1}/#{MAX_WALK_TO_RETRIES}).")
    return walk_to(room_num, true, retry_depth: retry_depth + 1)
  end
  room_num == Room.current.id
end