Class: Lich::DragonRealms::EquipmentManager

Inherits:
Object
  • Object
show all
Defined in:
documented/dragonrealms/commons/equipmanager.rb

Overview

Manages character equipment sets and gear swapping.

Handles wearing, removing, wielding, and tracking gear based on user configuration. Maintains state about equipment sets and provides methods for combat gear rotation.

Constant Summary collapse

STOW_RECOVERY_PATTERNS =

Recovery patterns handled by stow_helper's retry logic. These are automatically appended to every stow_helper call so callers only need to pass success/failure patterns.

See Also:

[
  /unload/,
  /close the fan/,
  /You are a little too busy/,
  /You don't seem to be able to move/,
  /is too small to hold that/,
  /Your wounds hinder your ability to do that/,
  /Sheath your .* where/
].freeze
STOW_HELPER_MAX_RETRIES =

Maximum retry attempts for stow_helper before giving up.

10
UNTIE_EXHAUSTED_PATTERNS =

Non-recoverable untie failure patterns that should return false immediately in #get_item_helper. Contains every entry from DRCI::UNTIE_ITEM_FAILURE_PATTERNS EXCEPT the "too busy" patterns which are recoverable (retreat / stop playing) and live in the :failures array instead.

For :worn and +:stowed+/+:transform+, exhausted is set directly to the full DRCI failure constant because their :failures entries don't overlap. :tied is the exception -- "too busy" appears in both DRCI failures and the recoverable :failures array, so this curated subset excludes them to prevent the exhausted branch from swallowing recovery.

If a new pattern is added to DRCI::UNTIE_ITEM_FAILURE_PATTERNS, it must be categorized here or in :failures -- the coverage spec enforces that no DRCI failure falls through to the timeout branch.

[
  /^You don't seem to be able to move/,
  /^You fumble with the ties/,
  /^Untie what/,
  /^What were you referring/
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(settings = nil) ⇒ EquipmentManager

Creates a new EquipmentManager and loads gear configuration.

Parameters:

  • settings (OpenStruct, nil) (defaults to: nil)

    user settings from get_settings, or nil to load automatically



42
43
44
# File 'documented/dragonrealms/commons/equipmanager.rb', line 42

def initialize(settings = nil)
  items(settings)
end

Instance Method Details

#desc_to_items(descs) ⇒ Array<DRC::Item>

Converts an array of description strings to matching DRC::Item objects.

Parameters:

  • descs (Array<String>)

    item descriptions to look up

Returns:

  • (Array<DRC::Item>)

    matching items (unmatched descriptions are excluded)



129
130
131
# File 'documented/dragonrealms/commons/equipmanager.rb', line 129

def desc_to_items(descs)
  descs.map { |description| item_by_desc(description) }.compact
end

#empty_handsvoid

This method returns an undefined value.

Empties both hands by returning held gear or falling back to DRCI.stow_hands.



468
469
470
# File 'documented/dragonrealms/commons/equipmanager.rb', line 468

def empty_hands
  return_held_gear || DRCI.stow_hands
end

#get_combat_itemsArray<String>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Retrieves the list of currently worn combat equipment via the INV COMBAT command.

Returns:

  • (Array<String>)

    combat item description strings



207
208
209
210
211
212
# File 'documented/dragonrealms/commons/equipmanager.rb', line 207

def get_combat_items
  snapshot = Lich::Util.issue_command("inv combat", /All of your worn combat|You aren't wearing anything like that/, /Use INVENTORY HELP for more options/, usexml: false, include_end: false)
  return [] unless snapshot

  snapshot.map(&:strip) - ["All of your worn combat equipment:", "You aren't wearing anything like that."]
end

#get_item?(item) ⇒ Boolean

Retrieves an item from wherever it is stored (worn, tied, sheathed, container, or stowed).

Checks hands first, then tries wield, transform, tie-to, worn, container, and general stow locations in order.

Parameters:

Returns:

  • (Boolean)

    true if item is now in hand



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'documented/dragonrealms/commons/equipmanager.rb', line 397

def get_item?(item)
  return true if DRCI.in_hands?(item)

  if item.wield
    case DRC.bput("wield my #{item.short_name}", *DRCI::WIELD_ITEM_SUCCESS_PATTERNS, *DRCI::WIELD_ITEM_FAILURE_PATTERNS)
    when *DRCI::WIELD_ITEM_SUCCESS_PATTERNS
      return true
    else
      Lich::Messaging.msg("bold", "EquipmentManager: Unable to wield #{item.short_name}")
      return false
    end
  elsif item.transforms_to
    transform_item = item_by_desc(item.transforms_to)
    unless transform_item
      Lich::Messaging.msg("bold", "EquipmentManager: Could not find transformed item matching '#{item.transforms_to}' in gear list")
      return false
    end
    unless transform_item.worn ? get_item_helper(transform_item, :worn) : get_item_helper(transform_item, :stowed)
      Lich::Messaging.msg("bold", "EquipmentManager: Unable to retrieve #{transform_item.short_name} for transform")
      return false
    end
    get_item_helper(transform_item, :transform)
  elsif (item.tie_to && get_item_helper(item, :tied)) || (item.worn && get_item_helper(item, :worn)) || (item.container && DRCI.get_item(item.short_name, item.container)) || get_item_helper(item, :stowed)
    true
  else
    Lich::Messaging.msg("bold", "EquipmentManager: Could not find #{item.short_name} anywhere")
    false
  end
end

#get_item_helper(item, type) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Attempts to retrieve an item using the verb configuration for the given type.

Issues the appropriate game command (remove, untie, get, or transform verb) and handles failures with recovery procs.

Parameters:

  • item (DRC::Item, nil)

    item to retrieve

  • type (Symbol)

    retrieval type (:worn, :tied, :stowed, :transform)

Returns:

  • (Boolean)

    true if item was successfully retrieved into hand



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
# File 'documented/dragonrealms/commons/equipmanager.rb', line 609

def get_item_helper(item, type)
  return false unless item

  data = verb_data(item)[type]
  snapshot = [DRC.left_hand, DRC.right_hand]
  waitrt?
  response = DRC.bput("#{data[:verb]} my #{item.short_name}", *data[:matches])
  waitrt?

  # Handle empty/nil response (bput timeout) as failure
  if response.nil? || response.empty?
    Lich::Messaging.msg("bold", "EquipmentManager: No response from game for '#{data[:verb]} my #{item.short_name}' - command may have been lost")
    return false
  end

  # For non-transform types, verify success via the XML game-object
  # feed rather than trusting bput's text match. This prevents false
  # positives where an unrelated game message (e.g., "You get the
  # feeling...") matches /^You get/ in GET_ITEM_SUCCESS_PATTERNS,
  # causing bput to return "You get" which then triggers the failure
  # recovery proc and stows the item that was just retrieved.
  # See elanthia-online/lich-5#1286 for the same approach in
  # DRCI.get_item_unsafe.
  # Transform is excluded because the item changes identity (e.g.,
  # orb -> armor) so noun verification against the original item
  # would fail.
  if type != :transform
    noun = DRC.get_noun(item.short_name)
    10.times do
      break if item_noun_in_hands?(noun)
      pause 0.05
    end
    return true if item_noun_in_hands?(noun)
  end

  case response
  when 'You are already holding'
    return true
  when *data[:exhausted]
    return false
  when *data[:failures]
    data[:failure_recovery].call(item.name, item, response)
    # Check if hands changed from pre-command snapshot, consistent with
    # the success (else) branch. Using in_hands?(item) here would fail
    # for :transform where the item changes identity (e.g., orb -> armor).
    return snapshot != [DRC.left_hand, DRC.right_hand]
  else
    # Wait for hands to change with a timeout to prevent infinite loop
    timeout = Time.now + 5
    pause 0.05 while snapshot == [DRC.left_hand, DRC.right_hand] && Time.now < timeout
    if snapshot == [DRC.left_hand, DRC.right_hand]
      Lich::Messaging.msg("bold", "EquipmentManager: Hands did not change after '#{data[:verb]} my #{item.short_name}' - item may not have been retrieved")
      return false
    end
    return true
  end
end

#item_by_desc(description) ⇒ DRC::Item?

Finds a gear item matching the given description.

Parameters:

  • description (String)

    item description to match against gear list

Returns:

  • (DRC::Item, nil)

    matching item or nil if not found



137
138
139
# File 'documented/dragonrealms/commons/equipmanager.rb', line 137

def item_by_desc(description)
  items.find { |item| item.short_regex =~ description }
end

#item_noun_in_hands?(noun) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Checks whether the given noun is in either hand via GameObj XML feed.

Uses DRC.left_hand_noun / DRC.right_hand_noun which read GameObj.noun directly, bypassing fix_dr_bullshit name truncation that can drop interior words from multi-word item names (e.g., "steel foil with a sandalwood hilt" becomes "steel hilt", losing the "foil" noun entirely).

Parameters:

  • noun (String)

    item noun to look for (e.g., "foil", "sword")

Returns:

  • (Boolean)

    true if noun matches either hand's GameObj noun



678
679
680
# File 'documented/dragonrealms/commons/equipmanager.rb', line 678

def item_noun_in_hands?(noun)
  [DRC.left_hand_noun, DRC.right_hand_noun].compact.include?(noun)
end

#items(settings = nil) ⇒ Array<DRC::Item>

Returns the list of gear items, loading from settings on first call.

Parses the user's gear configuration into DRC::Item objects and caches the result. Also initializes gear sets from settings.

Parameters:

  • settings (OpenStruct, nil) (defaults to: nil)

    user settings from get_settings, or nil to load automatically

Returns:

  • (Array<DRC::Item>)

    configured gear items



53
54
55
56
57
58
59
60
61
# File 'documented/dragonrealms/commons/equipmanager.rb', line 53

def items(settings = nil)
  return @items if @items

  settings ||= get_settings
  @gear_sets = {}
  settings.gear_sets.each { |set_name, gear_list| @gear_sets[set_name] = gear_list.flatten.uniq }
  @sort_head = settings.sort_auto_head
  @items = settings.gear.map { |item| DRC::Item.new(name: item[:name], leather: item[:is_leather], hinders_locks: item[:hinders_lockpicking], worn: item[:is_worn], swappable: item[:swappable], tie_to: item[:tie_to], adjective: item[:adjective], bound: item[:bound], wield: item[:wield], transforms_to: item[:transforms_to], transform_verb: item[:transform_verb], transform_text: item[:transform_text], lodges: item[:lodges], ranged: item[:ranged], needs_unloading: item[:needs_unloading], skip_repair: item[:skip_repair], container: item[:container]) }
end

#listed_item?(desc) ⇒ DRC::Item? Also known as: is_listed_item?

Checks whether a description matches a configured gear item.

Parameters:

  • desc (String)

    item description to check

Returns:

  • (DRC::Item, nil)

    matching item or nil if not in gear list



431
432
433
# File 'documented/dragonrealms/commons/equipmanager.rb', line 431

def listed_item?(desc)
  items.find { |item| item.short_regex =~ desc }
end

#matching_combat_items(list) ⇒ Array<DRC::Item> Also known as: worn_items

Returns the subset of currently worn combat items that match the given description list.

Parameters:

  • list (Array<String>)

    item descriptions to filter by

Returns:

  • (Array<DRC::Item>)

    matching worn combat items



218
219
220
221
222
# File 'documented/dragonrealms/commons/equipmanager.rb', line 218

def matching_combat_items(list)
  filter_gear = desc_to_items(list)
  gear = desc_to_items(get_combat_items)
  gear.select { |x| filter_gear.include?(x) }
end

#notify_missing(lost_items) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Alerts the user about equipment items that could not be found.

Parameters:

  • lost_items (Array<DRC::Item>, nil)

    items that were not located



146
147
148
149
150
151
152
153
154
# File 'documented/dragonrealms/commons/equipmanager.rb', line 146

def notify_missing(lost_items)
  return unless lost_items && !lost_items.empty?

  DRC.beep
  Lich::Messaging.msg("bold", "EquipmentManager: MISSING EQUIPMENT - Please verify these items are in a closed container and not lost:")
  Lich::Messaging.msg("bold", "EquipmentManager: #{lost_items.map(&:short_name).join(', ')}")
  pause
  DRC.beep
end

#remove_gear_by {|DRC::Item| ... } ⇒ Array<DRC::Item>

Removes currently worn combat items that match the given block condition.

Yields each DRC::Item to the block and removes those for which the block returns true.

Examples:

Remove items that hinder lockpicking

removed = @equipment_manager.remove_gear_by(&:hinders_lockpicking)

Yields:

  • (DRC::Item)

    each combat item to evaluate

Yield Returns:

  • (Boolean)

    true to remove the item

Returns:

  • (Array<DRC::Item>)

    items that were removed



74
75
76
77
78
79
# File 'documented/dragonrealms/commons/equipmanager.rb', line 74

def remove_gear_by(&_block)
  combat_items = get_combat_items
  gear = desc_to_items(combat_items).select { |item| yield(item) }
  gear.each { |item| remove_item(item) }
  gear
end

#remove_item(item, retries: 2) ⇒ Boolean?

Removes an item from the character and stows it in its configured location.

Handles transform items (e.g., exoskeletal armor becoming an orb), tie-to items, sheathed weapons, container-specific items, and general stow. Empties hands if needed for two-handed removal.

Parameters:

  • item (DRC::Item)

    item to remove

  • retries (Integer) (defaults to: 2)

    remaining retry attempts for hand-emptying recovery

Returns:

  • (Boolean, nil)

    false if removal failed, nil otherwise



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
# File 'documented/dragonrealms/commons/equipmanager.rb', line 236

def remove_item(item, retries: 2)
  if retries <= 0
    Lich::Messaging.msg("bold", "EquipmentManager: remove_item exceeded max retries for #{item.short_name}")
    return false
  end

  result = DRC.bput("remove my #{item.short_name}", *DRCI::REMOVE_ITEM_SUCCESS_PATTERNS, *DRCI::REMOVE_ITEM_FAILURE_PATTERNS, "then constricts tighter around your")
  waitrt?
  case result
  when /then constricts tighter around your/
    # Items that auto-repair, like exoskeletal armor,
    # may have a timer on them that prevents you removing them.
    Lich::Messaging.msg("bold", "EquipmentManager: The #{item.short_name} is not ready to be removed yet. Try again later.")
    return false
  when *DRCI::REMOVE_ITEM_FAILURE_PATTERNS
    # We may need to empty our hands to remove the item.
    # For example, exoskeletal armor requires two hands.
    temp_left_item = DRC.left_hand
    temp_right_item = DRC.right_hand
    # Lower the items because that preserves loaded bows.
    # Stowing them in a container would require unloading.
    did_lower = [temp_left_item, temp_right_item].compact.all? { |item_in_hand| DRCI.lower_item?(item_in_hand) }
    if did_lower
      remove_item(item, retries: retries - 1)
    else
      Lich::Messaging.msg("bold", "EquipmentManager: Unable to empty your hands to remove #{item.short_name}")
    end
    # Pick up the items in reverse order you lowered them
    # so that they end up in the correct hands again.
    DRCI.get_item_if_not_held?(temp_right_item) if temp_right_item
    DRCI.get_item_if_not_held?(temp_left_item) if temp_left_item
    # In case they end up in different hands, swap.
    if DRC.left_hand != temp_left_item || DRC.right_hand != temp_right_item
      swap_result = DRC.bput('swap', *DRCI::SWAP_HANDS_SUCCESS_PATTERNS, *DRCI::SWAP_HANDS_FAILURE_PATTERNS)
      unless DRCI::SWAP_HANDS_SUCCESS_PATTERNS.any? { |p| p.match?(swap_result) }
        Lich::Messaging.msg("bold", "EquipmentManager: Unable to restore hand order after removing #{item.short_name}")
      end
    end
  when *DRCI::REMOVE_ITEM_SUCCESS_PATTERNS
    # If removing item transforms it (e.g. exoskeletal armor => orb) then continue with the transformed item.
    if item.transforms_to && DRCI.in_hands?(item.transforms_to)
      transform_desc = item.transforms_to
      item = item_by_desc(transform_desc)
      unless item
        Lich::Messaging.msg("bold", "EquipmentManager: Could not find transformed item matching '#{transform_desc}' in gear list")
        return false
      end
    end
    if item.tie_to || item.wield || item.container
      stow_by_type(item)
    elsif /more room|too long to fit/ =~ DRC.bput("stow my #{item.short_name}", *DRCI::PUT_AWAY_ITEM_SUCCESS_PATTERNS, 'There isn\'t any more room', 'straps have all been used', 'is too long to fit')
      wear_item?(item)
    end
  end
  waitrt?
end

#remove_unmatched_items(combat_items, target_items) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Removes currently worn combat items that are not in the target gear set.

Parameters:

  • combat_items (Array<String>)

    currently worn combat item descriptions

  • target_items (Array<DRC::Item>)

    desired gear set items



190
191
192
193
194
195
196
197
198
199
200
201
# File 'documented/dragonrealms/commons/equipmanager.rb', line 190

def remove_unmatched_items(combat_items, target_items)
  if UserVars.equipmanager_debug
    Lich::Messaging.msg("plain", "EquipmentManager: removing unmatched items between these two sets")
    Lich::Messaging.msg("plain", "EquipmentManager: combat: #{combat_items.join(',')}")
    Lich::Messaging.msg("plain", "EquipmentManager: target: #{target_items.map(&:short_name).join(',')}")
  end
  combat_items
    .reject { |description| target_items.find { |item| item.short_regex =~ description } }
    .map { |description| items.find { |item| item.short_regex =~ description } }
    .compact
    .each { |item| remove_item(item) }
end

#return_held_gear(gear_set = 'standard') ⇒ Boolean?

Stows whatever is currently held in hands back to the appropriate location.

For items in the specified gear set, wears them. For other known items, ties, sheathes, or stows them based on their configuration.

Parameters:

  • gear_set (String) (defaults to: 'standard')

    gear set name to check for wear-back items

Returns:

  • (Boolean, nil)

    true if all held items were stowed, nil if hands are empty



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'documented/dragonrealms/commons/equipmanager.rb', line 445

def return_held_gear(gear_set = 'standard')
  return unless DRC.right_hand || DRC.left_hand

  todo = [DRC.left_hand, DRC.right_hand].compact

  gear_set_items = desc_to_items(@gear_sets[gear_set] || [])

  todo.all? do |held_item|
    if (info = gear_set_items.find { |item| item.short_regex =~ held_item })
      unload_weapon(info.short_name) if info.needs_unloading
      stow_helper("wear my #{info.short_name}", info.short_name, *DRCI::WEAR_ITEM_SUCCESS_PATTERNS, failure_patterns: DRCI::WEAR_ITEM_FAILURE_PATTERNS)
    elsif (info = items.find { |item| item.short_regex =~ held_item })
      unload_weapon(info.short_name) if info.needs_unloading
      stow_by_type(info)
    else
      false
    end
  end
end

#stow_by_type(item) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Executes a stow action with automatic recovery on common failure conditions.

Handles unload prompts, fan close, combat busy, movement lock, container size, and wound/sheath failures by retrying with corrective actions.

Stows an item based on its configured storage type (tie, sheath, container, or default stow).

Handles the common tie/wield/container/stow decision shared by #remove_item, #return_held_gear, and #stow_weapon.

Parameters:

  • action (String)

    game command to execute (e.g., "sheath my sword")

  • weapon_name (String)

    weapon noun for recovery commands

  • accept_strings (Array<Regexp, String>)

    success patterns to match

  • failure_patterns (Array<Regexp>)

    failure patterns that indicate unrecoverable stow failure

  • retries (Integer)

    remaining retry attempts

  • item (DRC::Item)

    item to stow

Returns:

  • (Boolean)

    true if stow succeeded, false if retries exhausted or failure pattern matched

  • (Boolean)

    true if stow succeeded, false otherwise

See Also:



896
897
898
899
900
901
902
903
904
905
906
# File 'documented/dragonrealms/commons/equipmanager.rb', line 896

def stow_by_type(item)
  if item.tie_to
    stow_helper("tie my #{item.short_name} to my #{item.tie_to}", item.short_name, *DRCI::TIE_ITEM_SUCCESS_PATTERNS, failure_patterns: DRCI::TIE_ITEM_FAILURE_PATTERNS)
  elsif item.wield
    stow_helper("sheath my #{item.short_name}", item.short_name, *DRCI::SHEATH_ITEM_SUCCESS_PATTERNS, failure_patterns: DRCI::SHEATH_ITEM_FAILURE_PATTERNS)
  elsif item.container
    stow_helper("put my #{item.short_name} in my #{item.container}", item.short_name, *DRCI::PUT_AWAY_ITEM_SUCCESS_PATTERNS, failure_patterns: DRCI::PUT_AWAY_ITEM_FAILURE_PATTERNS)
  else
    stow_helper("stow my #{item.short_name}", item.short_name, *DRCI::PUT_AWAY_ITEM_SUCCESS_PATTERNS, failure_patterns: DRCI::PUT_AWAY_ITEM_FAILURE_PATTERNS)
  end
end

#stow_helper(action, weapon_name, *accept_strings, failure_patterns: [], retries: STOW_HELPER_MAX_RETRIES) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
# File 'documented/dragonrealms/commons/equipmanager.rb', line 911

def stow_helper(action, weapon_name, *accept_strings, failure_patterns: [], retries: STOW_HELPER_MAX_RETRIES)
  if retries <= 0
    Lich::Messaging.msg("bold", "EquipmentManager: stow_helper exceeded max retries for '#{action}'")
    return false
  end

  result = DRC.bput(action, *accept_strings, *failure_patterns, *STOW_RECOVERY_PATTERNS)
  if result.nil? || result.empty?
    Lich::Messaging.msg("bold", "EquipmentManager: stow_helper got no response for '#{action}'")
    return false
  end

  case result
  when /unload/
    unload_weapon(weapon_name)
    return stow_helper(action, weapon_name, *accept_strings, failure_patterns: failure_patterns, retries: retries - 1)
  when /close the fan/
    fput("close my #{weapon_name}")
    return stow_helper(action, weapon_name, *accept_strings, failure_patterns: failure_patterns, retries: retries - 1)
  when /You are a little too busy/
    DRC.retreat
    return stow_helper(action, weapon_name, *accept_strings, failure_patterns: failure_patterns, retries: retries - 1)
  when /You don't seem to be able to move/
    pause 1
    return stow_helper(action, weapon_name, *accept_strings, failure_patterns: failure_patterns, retries: retries - 1)
  when /is too small to hold that/
    fput("swap my #{weapon_name}")
    return stow_helper(action, weapon_name, *accept_strings, failure_patterns: failure_patterns, retries: retries - 1)
  when /Your wounds hinder your ability to do that/, /Sheath your .* where/
    return stow_helper("stow my #{weapon_name}", weapon_name, *DRCI::PUT_AWAY_ITEM_SUCCESS_PATTERNS, failure_patterns: DRCI::PUT_AWAY_ITEM_FAILURE_PATTERNS, retries: retries - 1)
  when *STOW_RECOVERY_PATTERNS
    # Catch-all for any recovery pattern not explicitly handled above
    Lich::Messaging.msg("bold", "EquipmentManager: stow_helper unhandled recovery for '#{action}': #{result}")
    return false
  end
  # Check if the result matched an explicit failure pattern
  if failure_patterns.any? { |p| p.match?(result) }
    Lich::Messaging.msg("bold", "EquipmentManager: stow_helper failed for '#{action}': #{result}")
    return false
  end
  true
end

#stow_weapon(description = nil, transform_depth: 3) ⇒ void

This method returns an undefined value.

Stows a weapon in its configured location (sheath, wear, tie, container, or general stow).

When called without a description, stows whatever is in both hands. Unloads ranged weapons before stowing if configured.

Parameters:

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

    weapon description to match, or nil to stow both hands

  • transform_depth (Integer) (defaults to: 3)

    remaining transform recursion depth



843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
# File 'documented/dragonrealms/commons/equipmanager.rb', line 843

def stow_weapon(description = nil, transform_depth: 3)
  unless description
    return unless DRC.right_hand || DRC.left_hand

    stow_weapon(DRC.right_hand) if DRC.right_hand
    stow_weapon(DRC.left_hand)  if DRC.left_hand
    return
  end
  weapon = item_by_desc(description)
  return unless weapon

  # Is this a weapon that needs to be unloaded before it is put away?
  # This is an optimization attempt so that the script
  # isn't trying to unload every weapon that gets put away.
  # Would be silly to try "unload my scimitar" wouldn't it? :grins:
  unload_weapon(weapon.short_name) if weapon.needs_unloading
  if weapon.worn
    stow_helper("wear my #{weapon.short_name}", weapon.short_name, *DRCI::WEAR_ITEM_SUCCESS_PATTERNS, failure_patterns: DRCI::WEAR_ITEM_FAILURE_PATTERNS)
  elsif weapon.transforms_to
    if transform_depth <= 0
      Lich::Messaging.msg("bold", "EquipmentManager: stow_weapon exceeded max transform depth for #{weapon.short_name}")
      return
    end
    stow_helper("#{weapon.transform_verb} my #{weapon.short_name}", weapon.short_name, weapon.transform_text)
    stow_weapon(weapon.transforms_to, transform_depth: transform_depth - 1)
  else
    stow_by_type(weapon)
  end
end

#swap_to_skill?(noun, skill) ⇒ Boolean

Swaps a weapon to be used for a different weapon skill.

Handles multi-skill weapons such as bastard swords, bar maces, and ristes. For fans, opens or closes them based on skill type.

Parameters:

  • noun (String)

    weapon noun to swap

  • skill (String)

    target weapon skill (e.g., "Heavy Edged", "Two-Handed Blunt", "Staves")

Returns:

  • (Boolean)

    true if weapon is now set to the desired skill



708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
# File 'documented/dragonrealms/commons/equipmanager.rb', line 708

def swap_to_skill?(noun, skill)
  if noun =~ /\bfan\b/i
    command = skill =~ /edged/i ? 'open' : 'close'
    DRC.bput("#{command} my fan", 'you snap', 'already')
    return true
  end
  proper_skill = case skill
                 when /^he$|heavy edge|large edge|one-handed/i
                   'heavy edged'
                 when /^2he$|^the$|twohanded edge|two-handed edge/i
                   'two-handed edged'
                 when /^hb$|heavy blunt|large blunt/i
                   'heavy blunt'
                 when /^2hb$|^thb$|twohanded blunt|two-handed blunt/i
                   'two-handed blunt'
                 when /^se$|small edged|light edge|medium edge/i
                   '(light edged|medium edged)'
                 when /^sb$|small blunt|light blunt|medium blunt/i
                   '(light blunt|medium blunt)'
                 when /^lt$|light thrown/i
                   'light thrown'
                 when /^ht$|heavy thrown/i
                   'heavy thrown'
                 when /stave/i
                   '(short|quarter) staff'
                 when /polearms/i
                   '(halberd|pike)'
                 when /^ow$|offhand weapon/i
                   return true # just use weapon in your left hand
                 else
                   Lich::Messaging.msg("bold", "EquipmentManager: Unsupported weapon swap: #{noun} to #{skill}. Please report this to https://github.com/elanthia-online/lich-5/issues")
                   return false
                 end
  # All possible weapon skills to swap into.
  weapon_skills = [
    'light edged',
    'medium edged',
    'heavy edged',
    'two-handed edged',
    'light blunt',
    'medium blunt',
    'heavy blunt',
    'two-handed blunt',
    'light thrown',
    'heavy thrown',
    'short staff',
    'quarter staff',
    'halberd',
    'pike'
  ]
  failure_matches = [
    /You have nothing to swap/,
    /Your (left|right) hand is too injured/,
    /Will alone cannot conquer the paralysis that has wracked your body/,
    /^You move a .* to your (left|right) hand/
  ]
  # The spaces in the regex are deliberate
  skill_match = / #{proper_skill} /i
  swapped_count = 0
  loop do
    pause 0.25
    # Avoid infinite loop where weapon can't swap to desired skill.
    return false if swapped_count > weapon_skills.length

    swapped_count += 1

    # Try to swap weapon to desired skill.
    case DRC.bput("swap my #{noun}", skill_match, /\b#{noun}\b.*(#{weapon_skills.join('|')})/, "You must have two free hands", *failure_matches)
    when /You must have two free hands/
      DRCI.stow_hand('left') if DRC.left_hand && DRC.left_hand !~ /#{noun}/i
      DRCI.stow_hand('right') if DRC.right_hand && DRC.right_hand !~ /#{noun}/i
      hands_free = [DRC.left_hand, DRC.right_hand].compact.all? { |h| h =~ /#{noun}/i }
      unless hands_free
        Lich::Messaging.msg("bold", "EquipmentManager: Unable to free hands for weapon swap")
        return false
      end
    when *failure_matches
      return false
    when skill_match
      return true
    end
  end
end

#turn_to_weapon?(old_noun, new_noun) ⇒ Boolean

Turns a multi-form weapon to a different weapon form (e.g., Damaris weapons).

Parameters:

  • old_noun (String)

    current weapon noun

  • new_noun (String)

    desired weapon noun to turn to

Returns:

  • (Boolean)

    true if weapon shifted to the new form



687
688
689
690
691
692
693
694
695
696
697
698
# File 'documented/dragonrealms/commons/equipmanager.rb', line 687

def turn_to_weapon?(old_noun, new_noun)
  return true if old_noun == new_noun

  result = DRC.bput("turn my #{old_noun} to #{new_noun}", /^Turn what?/i, /^Which weapon did you want to pull out/i, /^Your .*\b#{old_noun}.* shifts .*/i)
  waitrt? # turning may incur roundtime
  case result
  when /^Your .*\b#{old_noun}.* shifts .* before resolving itself into .*\b#{new_noun}/i
    true
  else
    false
  end
end

#unload_weapon(name) ⇒ void

This method returns an undefined value.

Unloads a ranged weapon (bow, crossbow) and stows the ammo.



799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
# File 'documented/dragonrealms/commons/equipmanager.rb', line 799

def unload_weapon(name)
  result = DRC.bput("unload my #{name}", *DRCI::UNLOAD_WEAPON_SUCCESS_PATTERNS, *DRCI::UNLOAD_WEAPON_FAILURE_PATTERNS)
  waitrt? # wait out the unload roundtime so the ammo/hand state has settled before we act on it

  ammo_match = result&.match(/^(?:Your .*?\b(?<ammo>[\w]+)\b fall.* from your .* to your feet\.)$/)
  ground_match = result&.match?(/As you release the string/) ? result.match(/the (?<ammo>\w+) tumbles/) : nil

  if ammo_match || ground_match
    # Ammo ended up on the GROUND (hands were full, or it tumbled). Lower the
    # weapon, stow the ammo from your feet, then pick the weapon back up.
    ammo = (ammo_match || ground_match)[:ammo]
    unless DRCI.lower_item?(name)
      Lich::Messaging.msg("bold", "EquipmentManager: Unable to lower #{name} to pick up ammo")
      return
    end
    DRCI.put_away_item?(ammo)
    unless DRCI.get_item?(name)
      Lich::Messaging.msg("bold", "EquipmentManager: Unable to pick #{name} back up after unloading")
    end
  elsif result && DRCI::UNLOAD_WEAPON_SUCCESS_PATTERNS.any? { |pattern| pattern.match?(result) }
    # Unload succeeded with the ammo now in a hand. Stow whichever hand is NOT
    # the weapon, comparing by NOUN against the actual hand contents -- robust to
    # the <dialogData ...AimTimer...> tag the game prepends (which the old
    # ^-anchored /^(?:You unload|...)/ branch missed) and to ammo nouns that
    # contain the weapon noun (e.g. "crossbow bolt"). Guarded on a positive
    # success match so an unload failure or a bput timeout can't stow an
    # unrelated off-hand item.
    weapon_noun = DRC.get_noun(name)
    [['left', DRC.left_hand], ['right', DRC.right_hand]].each do |side, held|
      next if held.nil? || DRC.get_noun(held) == weapon_noun

      Lich::Messaging.msg("bold", "EquipmentManager: Unable to stow ammo from #{side} hand") unless DRCI.stow_hand(side)
    end
  end
end

#verb_data(item) ⇒ Hash{Symbol => Hash}

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds a hash of verb configurations for retrieving an item by type.

Each type (+:worn+, :tied, :stowed, :transform) maps to a hash with the game verb, match patterns, failure patterns, and recovery procs. Match patterns reference DRCI constants so that new game messages added to DRCI are automatically picked up here.

The matches array is passed to bput and must include success, failure, and exhausted patterns so bput returns promptly. get_item_helper then triages the response:

  • exhausted: non-recoverable failure -- return false immediately
  • failures: recoverable error -- run failure_recovery proc
  • everything else: success -- wait for hand contents to change

Parameters:

  • item (DRC::Item)

    item to build verb data for

Returns:

  • (Hash{Symbol => Hash})

    verb configuration keyed by retrieval type

See Also:



517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# File 'documented/dragonrealms/commons/equipmanager.rb', line 517

def verb_data(item)
  {
    worn: {
      verb: 'remove',
      matches: [
        /^You .*#{item.short_regex}/,
        /^You (get|sling|pull|work|loosen|slide|remove|yank|unbuckle).*#{item.name}/,
        *DRCI::REMOVE_ITEM_SUCCESS_PATTERNS,
        *DRCI::REMOVE_ITEM_FAILURE_PATTERNS
      ],
      failures: [/^You (get|sling|pull|work|slide|remove|yank|unbuckle) $/],
      failure_recovery: proc { |noun| DRC.bput("wear my #{noun}", '^You ') },
      exhausted: DRCI::REMOVE_ITEM_FAILURE_PATTERNS
    },
    tied: {
      verb: 'untie',
      matches: [
        /^You .*#{item.short_regex}/,
        /^You remove.*#{item.name}/,
        /^.*you untie your .*#{item.short_regex} from it./,
        *DRCI::UNTIE_ITEM_SUCCESS_PATTERNS,
        *DRCI::UNTIE_ITEM_FAILURE_PATTERNS
      ],
      # NOTE: /^You remove$/ (with end anchor) prevents matching successful
      # untie responses like "You remove a sword from your belt" -- only
      # matches the bare "You remove" edge case.
      failures: [/^You remove$/, /^You are a little too busy/, /^You are a bit too busy/],
      # NOTE: response is accepted as a single String (not *splat) so that
      # case/when uses Regexp#=== for proper pattern matching. The original
      # *matches splat wrapped the response in an Array, making Regexp-based
      # when clauses silently fall through to else.
      failure_recovery: proc { |_noun, item_to_recover, response|
                          case response
                          when /You are a little too busy/
                            DRC.retreat
                            get_item?(item_to_recover)
                          when /You are a bit too busy/
                            DRC.stop_playing
                            get_item?(item_to_recover)
                          else
                            stow_weapon
                          end
                        },
      exhausted: UNTIE_EXHAUSTED_PATTERNS
    },
    stowed: {
      verb: 'get',
      matches: [
        /^You .*#{item.short_regex}/,
        /^You .*#{item.name}/,
        *DRCI::GET_ITEM_SUCCESS_PATTERNS,
        *DRCI::GET_ITEM_FAILURE_PATTERNS,
        /^The.* slides easily out/,
        /But that is already/
      ],
      failures: [/^You get$/, /But that is already/],
      failure_recovery: proc { |noun| DRC.bput("stow my #{noun}", 'You put', 'But that is already in') },
      exhausted: DRCI::GET_ITEM_FAILURE_PATTERNS
    },
    transform: {
      verb: item.transform_verb,
      matches: [
        item.transform_text,
        /You'll need a free hand to do that!/,
        /You don't seem to be holding/,
        *DRCI::GET_ITEM_FAILURE_PATTERNS
      ],
      failures: [/You'll need a free hand to do that!/, /You don't seem to be holding/],
      failure_recovery: proc do |noun|
                          DRCI.stow_hand('left') if DRC.left_hand && DRC.left_hand !~ /#{noun}/i
                          DRCI.stow_hand('right') if DRC.right_hand && DRC.right_hand !~ /#{noun}/i
                          if (DRC.left_hand && DRC.left_hand !~ /#{noun}/i) || (DRC.right_hand && DRC.right_hand !~ /#{noun}/i)
                            Lich::Messaging.msg("bold", "EquipmentManager: Unable to free hands for transform")
                            next
                          end
                          item.worn ? DRC.bput("remove my #{noun}", '^You') : DRC.bput("get my #{noun}", '^You')
                          DRC.bput("#{item.transform_verb} my #{item.short_name}", *verb_data(item)[:transform][:matches])
                        end,
      exhausted: DRCI::GET_ITEM_FAILURE_PATTERNS
    }
  }
end

#wear_equipment_set?(set_name) ⇒ Boolean

Switches to a named gear set, removing unneeded items and wearing missing ones.

Compares currently worn combat items against the target gear set, removes items not in the set, and wears any missing items. Notifies the user about items that could not be found.

Examples:

@equipment_manager.wear_equipment_set?("standard")

Parameters:

  • set_name (String, nil)

    gear set name from settings (e.g., "standard", "swimming")

Returns:

  • (Boolean)

    true if all items in the set are now worn



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'documented/dragonrealms/commons/equipmanager.rb', line 102

def wear_equipment_set?(set_name)
  return false unless set_name

  unless @gear_sets[set_name]
    Lich::Messaging.msg("bold", "EquipmentManager: Could not find gear set '#{set_name}'")
    return false
  end

  gear_set_items = desc_to_items(@gear_sets[set_name])
  Lich::Messaging.msg("plain", "EquipmentManager: expected worn items: #{gear_set_items.map(&:short_name).join(',')}") if UserVars.equipmanager_debug

  combat_items = get_combat_items

  remove_unmatched_items(combat_items, gear_set_items)

  lost_items = wear_missing_items(gear_set_items, combat_items)
  notify_missing(lost_items)

  DRC.bput('sort auto head', /^Your inventory is now arranged/) if @sort_head

  lost_items.empty?
end

#wear_item?(item) ⇒ Boolean

Retrieves an item and wears it.

Parameters:

Returns:

  • (Boolean)

    true if item was retrieved and worn successfully



297
298
299
300
301
302
303
304
305
306
# File 'documented/dragonrealms/commons/equipmanager.rb', line 297

def wear_item?(item)
  if item.nil?
    Lich::Messaging.msg("bold", "EquipmentManager: Failed to match an item, try turning on debugging with #{$clean_lich_char}e UserVars.equipmanager_debug = true")
    return false
  end
  if get_item?(item)
    return DRCI.wear_item?(item.short_name)
  end
  return false
end

#wear_items(items_list) ⇒ void

This method returns an undefined value.

Wears a list of items and optionally sorts inventory head position.

Parameters:

  • items_list (Array<DRC::Item>)

    items to wear



85
86
87
88
89
# File 'documented/dragonrealms/commons/equipmanager.rb', line 85

def wear_items(items_list)
  items_list.each { |item| wear_item?(item) }

  DRC.bput('sort auto head', /^Your inventory is now arranged/) if @sort_head
end

#wear_missing_items(target_items, combat_items) ⇒ Array<DRC::Item>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Wears items from the target set that are not currently in the combat inventory.

Stows any target items found in hands before wearing. Returns items that could not be worn (missing from containers).

Parameters:

  • target_items (Array<DRC::Item>)

    desired gear set items

  • combat_items (Array<String>)

    currently worn combat item descriptions

Returns:

  • (Array<DRC::Item>)

    items that could not be worn



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'documented/dragonrealms/commons/equipmanager.rb', line 165

def wear_missing_items(target_items, combat_items)
  if UserVars.equipmanager_debug
    Lich::Messaging.msg("plain", "EquipmentManager: wearing missing items between these two sets")
    Lich::Messaging.msg("plain", "EquipmentManager: combat: #{combat_items.join(',')}")
    Lich::Messaging.msg("plain", "EquipmentManager: target: #{target_items.map(&:short_name).join(',')}")
  end

  missing_items = target_items
                  .reject { |item| combat_items.find { |c_item| item.short_regex =~ c_item } }
                  .reject { |item| [DRC.right_hand, DRC.left_hand].grep(item.short_regex).any? ? (stow_weapon(item.short_name) || true) : false }

  Lich::Messaging.msg("plain", "EquipmentManager: wear missing items #{missing_items}") if !missing_items.empty? && UserVars.equipmanager_debug
  missing_items.reject do |item|
    item_copy = item.dup
    item_copy.instance_variable_set(:@worn, false)
    wear_item?(item_copy)
  end
end

#wield_weapon?(description, skill = nil) ⇒ Boolean? Also known as: wield_weapon

Wields a weapon into the right hand, or left hand if skill is "Offhand Weapon".

Stows any currently held instance of the weapon first, then retrieves and optionally swaps it to the desired skill.

Parameters:

  • description (String)

    weapon description to match in gear list

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

    weapon skill to swap to (e.g., "Heavy Edged", "Offhand Weapon")

Returns:

  • (Boolean, nil)

    true if wielded successfully, false on failure, nil if description is blank



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'documented/dragonrealms/commons/equipmanager.rb', line 355

def wield_weapon?(description, skill = nil)
  return unless description && !description.empty?

  offhand = skill == 'Offhand Weapon'
  weapon = item_by_desc(description)
  unless weapon
    Lich::Messaging.msg("bold", "EquipmentManager: Failed to match a weapon for #{description}:#{skill}")
    return false
  end

  if [DRC.left_hand, DRC.right_hand].grep(weapon.short_regex).any?
    stow_weapon
  end

  if get_item?(weapon)
    swap_to_skill?(weapon.name, skill) if skill && weapon.swappable

    if offhand && DRCI.in_right_hand?(weapon)
      case DRC.bput('swap', *DRCI::SWAP_HANDS_SUCCESS_PATTERNS, *DRCI::SWAP_HANDS_FAILURE_PATTERNS)
      when *DRCI::SWAP_HANDS_SUCCESS_PATTERNS
        return true
      else
        return false
      end
    end

    return true
  end

  return false
end

#wield_weapon_offhand?(description, skill = nil) ⇒ Boolean? Also known as: wield_weapon_offhand

Wields a weapon into the left (off) hand.

Retrieves the weapon, optionally swaps it to the desired skill, then swaps it from the right hand to the left hand.

Parameters:

  • description (String)

    weapon description to match in gear list

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

    weapon skill to swap to (e.g., "Heavy Edged", "Offhand Weapon")

Returns:

  • (Boolean, nil)

    true if wielded successfully, false on failure, nil if description is blank



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'documented/dragonrealms/commons/equipmanager.rb', line 316

def wield_weapon_offhand?(description, skill = nil)
  return unless description && !description.empty?

  weapon = item_by_desc(description)
  unless weapon
    Lich::Messaging.msg("bold", "EquipmentManager: Failed to match a weapon for #{description}:#{skill}")
    return false
  end

  return false unless get_item?(weapon)

  swap_to_skill?(weapon.name, skill) if skill && weapon.swappable

  # We want the weapon in the LEFT (off) hand. If it landed in the right hand
  # (e.g. get placed it there), swap it over; if it's already in the left
  # hand, we're done -- previously this returned false in that case.
  if DRCI.in_right_hand?(weapon)
    case DRC.bput('swap', *DRCI::SWAP_HANDS_SUCCESS_PATTERNS, *DRCI::SWAP_HANDS_FAILURE_PATTERNS)
    when *DRCI::SWAP_HANDS_SUCCESS_PATTERNS
      return true
    else
      return false
    end
  end

  DRCI.in_left_hand?(weapon)
end