Module: Lich::DragonRealms::DRC

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

Overview

DragonRealms common library: shared utilities for item interaction, command execution, combat, music, and character state management.

Methods in this module are called as functions: DRC.bput(...), DRC.forage?(...), etc. They wrap low-level Lich APIs (fput, get?, GameObj) and game-specific knowledge into higher-level abstractions for scripting convenience.

Defined Under Namespace

Classes: Item

Constant Summary collapse

XML_TAG_PATTERN =

Pattern for XML tags

/<[^>]+>/.freeze
WAIT_RESPONSE_PATTERN =

Pattern for game wait/roundtime responses in bput

/(?:\.\.\.wait |Wait |\.\.\. wait )(?<seconds>[0-9]+)/.freeze
COLLECT_MESSAGES =

Collect command response messages

[
  'As you rummage around',
  'believe you would probably have better luck trying to find a dragon',
  'if you had a bit more luck',
  'The room is too cluttered',
  'one hand free to properly collect',
  'You are sure you knew',
  'You begin to forage around,',
  'You begin scanning the area before you',
  'You begin exploring the area, searching for',
  'You find something dead and lifeless',
  'You cannot collect anything',
  'you fail to find anything',
  'You forage around but are unable to find anything',
  'You manage to collect a pile',
  'You survey the area and realize that any collecting efforts would be futile',
  'You wander around and poke your fingers',
  'You forage around for a while and manage to stir up a small mound of fire ants!'
].freeze
RETREAT_ESCAPE_MESSAGES =

Retreat command response patterns

[
  /You are already as far away as you can get/,
  /You retreat from combat/,
  /You sneak back out of combat/,
  /Retreat to where/,
  /There's no place to retreat to/
].freeze
RETREAT_MESSAGES =

Response patterns that indicate a retreat or movement blocking event. Matched during retreat loop to detect when the character has broken combat, tried to retreat, or hit obstacles.

Examples:

bput("retreat", *DRC::RETREAT_MESSAGES)

Returns:

  • (Array<Regexp>) —

    patterns including /retreat/, /sneak/, /grip/, /You must stand first/, and /You are already/

See Also:

[
  /retreat/,
  /sneak/,
  /grip on you/,
  /grip remains solid/,
  /You try to back/,
  /You must stand first/,
  /You stop advancing/,
  /You are already/
].freeze
ASSESS_TEACH_TEACHER_PATTERN =

Assess teach parsing patterns

/(?<teacher>.*) is teaching a class on (?<skill>.*) which is still open to new students/.freeze
ASSESS_TEACH_SKILL_FILTER_PATTERN =

Regex to extract filtered skill names from assess teach output. Matches the portion after "(compared to what you already know)" in a teacher's skill line.

Examples:

line = "Combat (compared to what you already know) Melee"
line.match(ASSESS_TEACH_SKILL_FILTER_PATTERN)[:filtered_skill] #=> "Melee"

Returns:

  • (Regexp) —

    pattern with named capture group filtered_skill

See Also:

/.* \(compared to what you already know\) (?<filtered_skill>.*)/.freeze
COMMON_RANGED_WEAPONS_PATTERN =

Common ranged weapon nouns

/^(bow|shortbow|longbow|crossbow|stonebow|latchbow|slurbow|lockbow|pelletbow|arbalest|sling|slingshot|blowgun)$/i.freeze
RACIAL_RANGED_WEAPONS_PATTERN =
/^(jranoki|uku'uan|uku'uanstaho|chunenguti|hhr'ibu|guti|mahil|taisgwelduan|chyeb|sverfil|tangara|alaer|kari|wami|usus|srigos|href|vrope|falocisana|stof|dzelt)$/i.freeze
FLAVOR_TEXT_PATTERN =

Flavor text pattern for item descriptions https://regex101.com/r/4lGY6u/13

/\s?\b(?:(?:colorfully and )?(?:artfully|artistically|attractively|beautifully|bl?ack-|cleverly|clumsily|crudely|deeply|delicately|edged|elaborately|faintly|flamboyantly|front-|fully|gracefully|heavily|held|intricately|lavishly|masterfully|plentifully|prominantly|roughly|securely|sewn|shabbily|shadow-|simply|somberly|skillfully|sloppily|starkly|stitched|tied and|tightly|well-)\s?)?(?:accented|accentuated|acid-etched|adorned|affixed|appliqued|assembled|attached|augmented|awash|backed|back-laced|balanced|banded|batiked|beaded|bearded|bearing|bedazzled|bedecked|bejeweled|beset|bestrewn|blazoned|bordered|bound|braided|branded|brocaded|bristling|brushed|buckled|burned|buttoned|caked|camouflaged|capped|carved|caught|centered|chased|chiseled|cinched|circled|clasped|cloaked|closed|coated|cobbled together|coiled|colored|composed|concealed|connected|constructed|countoured|covered|crafted|crested|crisscrossed|crowded|crowned|cuffed|cut|dangling|dappled|decked|decorated|deformed|depicting|designed|detailed|discolored|displaying|divided|done|dotted|draped|drawn|dressed|drizzled|dusted|edged|elaborately|embedded|embell?ished|emblazed|emblazoned|embossed|embroidered(?: all over| painstakingly)?|enameled(?: across)?|encircled|encrusted|engraved|engulfed|enhanced|entwined|equipped|etched|fashioned(?: so)?|fastened|feathered|featuring|festooned|fettered|filed|filled|firestained|fit|fitted|fixed|flecked|fletched|forged|formed|framed|fringed|frosted|full|gathered|gleaming|glimmering|glittering|goldworked|growing|gypsy-set|hafted|hand-tooled|hanging|heavily(?:-beaded| covered)?|held fast|hemmed|hewn|hideously|highlighted|hilted|honed|hung|impressed|incised|ingeniously repurposed|inscribed|inlaid|inset|interlaced|interspersed|interwoven|jeweled|joined|laced(?: up)?|lacquered|laden|layered|limned|lined|linked|looped|knotted|made|marbled|marked|marred|meshed|mosaicked|mottled|mounted|oiled|oozing|outlined|ornamented|overlai(?:d|n)|padded|painted|paired|patched|pattern-welded|patterned|pinned|plumed|polished|printed|reinforced|reminiscent|rendered|revealing|riddled|ridged|rimed|ringed|riveted|sashed|scarred|scattered|scorched|sculpted|sealed|seamed|secured|securely|set|sewn|shaped|shimmering|shod|shot|shrouded|side-laced|slashed|slung|smeared|smudged|spangled|speckled|spiraled|splatter-dyed|splattered|spotted|sprinkled|stacked|surmounted|surrounded|suspended|stained|stamped|starred|stenciled|stippled|stitched(?: together)?|strapped|streaked|strengthened|strewn|striated|striped|strung|studded|swathed|swirled|tailored|tangled|tapered|tethered|textured|threaded|tied|tightly|tinged|tinted|tipped|tooled|topped|traced|trimmed|twined|veined|vivified|washed|webbed|weighted|whorled|worked|worn|woven|wrapped|wreathed|wrought)?\b ["]?\b(?:a hand-tooled|across|along|an|around|atop|bearing|belted|bright streaks|dangling|designed|detailing|down (?:each leg|one side)|dyed (?:a|and|deep|of|in|night|rust|shimmering|the|to|with)|engravings|entitled|errant pieces|featuring|flaunting|frescoed|from|Gnomish Pride|(?:encased |quartered )?in(?: the)?|into|labeled|leading|like|lining|matching|(?<!stick|slice|chunk|flask|hunk|series|set|pair|piece) of|on|out|overlayed gleaming silver|resembling|shades of color|sporting|surrounding|that|the|through|tinged somber black|titled|to|upon|WAR MONGER|with|within|\b(?:at|bearing|(?:accented |held |secured )?by|carrying|clutching|colored|cradling|dangling|depicting|(?:prominently )?displaying|embossed|etched|featuring|for(?:ming)?|holding|(?<!slice |chunk |flask |hunk |series |set |pair |piece )of|over|patterned|striped|suspending|textured|that)\b \b(?:a (?:band|beaded|brass|cascade|cluster|coral|crown|dead|.+ (?:ingot|boulder|stone|rock|nugget)|fierce|fanged|fringe|glowing|golden|grinning|howling|large|lotus|mosaic|pair|poorly|rainbow|roaring|row|silver(?:y|weave)?|small|snarling|spray|tailored|thick|tiny|trio|turquoise|yellowed)|(?:squared )?agonite (?:links|decorated)|alternating|an|(?:purple |blue )?and|ash|beaded fringe|blackened (?:steel(?: accents| bearing| with|$)|ironwood)|blue (?:gold|steel)|burnished golden|cascading layers|carved ivory|chain-lined|chitinous|(?:deep red|dull black|pale blue) cloth|cloudberry blossoms|colorful tightly|cotton candy|crimson steel|crisscrossed|curious design|curved|crystaline charm|dark (?:blue|green|grey|metals|windsteel) (?:and|exuding|glaes|hues|khor'vela|muracite|pennon|with)|dark supple|deepest|deeply blending|delicate|dusky (?:dreamweave|green-grey)|ebonwood$|emblazoned|enamel?led (?:steel|bronze)|etched|fine(?:-grained| black| crushed)|finely wrought|flame-kissed|forest|fused-together|fuzzy grey|gauze atop|gilded steel|glass eyeballs|glistening green|golden oak|grey fur|hammered|haralun|has|heavy (?:grey|pearl|silver)|horn|Ilithi cedar|inky black|interlocking silver|interwoven|iridescent|jagged interlocking plates|(?:soft dark|supple|thick|woven) (?:bolts|leather)|lightweight|long swaths|lustrous|kertig ravens|made|metal cogs|mirror-finished|mottled|multiple woods|naphtha|oak|oblong sanguine|one|onyx buttons|opposing images|overlapping|pale cerulean|pallid links|pastel-hued|pins|pitted (?:black iron|steel)|plush velvet|polished (?:bronze|hemlock|steel)|raccoon tails|ram's horns|rat pelts|raw|red and blue|rich (?:purple|golden)|riveted bindings|roughened|rowan|sanguine thornweave|scattered star|scorch marks|sculpted|shadows|shark cartilage|shifting (?:celadon|shades)|shipboard|(?:braided |cobalt |deep black |desert-tan |dusky red Taisidon |ebony |exquisite spider|fine leaf-green |flowing night|glimmering ebony |heavy |marigold |pale gold marquisette and virid |rich copper |spiral-braided |steel|unadorned black Musparan )?silk(?:cress)?|(?:coiled |shimmering )?silver(?:steel| and |y)?|sirese blue spun glitter|six crossed|slender|small bones|smoothly interlocking|snow leopard|soft brushed|somber black|sprawled|sun-bleached|steel links|stones|strips of|sunny yellow|teardrop plates|telothian|the|tiny (?:golden|indurium|scales|skull)|tightly braided|tomiek|torn|twists|two|undyed|vibrant multicolored|viscous|waves of|weighted|well-cured|white ironwood|windstorm gossamer|wintry faeweave|woven diamondwood))\b.*/.freeze
CANNOT_STAND_PATTERN =

Game responses to STAND that mean standing cannot currently succeed, so fix_standing must stop instead of looping forever (issue #3668). These are already in the STAND match list, but matching one does not change posture, so without this guard the loop spams STAND endlessly (e.g. while unconscious, plummeting, held, or overburdened).

/unconscious|plummeting to your death|prevents you from standing|don't seem to be able to move|overburdened and cannot|weight of all your possessions|no room to do much of anything/.freeze
DEFAULT_BOX_SUBSTITUTIONS =

Post-match box name rewrites (applied via gsub after a box is matched). "ironwood" -> "iron" because the game parser wants the shortened noun. Players extend this via the custom_box_substitutions setting; see box_list_to_adj_and_noun.

Returns:

  • (Array<Array(String, String)>) —

    ordered [from, to] literal pairs

[%w[ironwood iron]].freeze
DEFAULT_SCROLL_SUBSTITUTIONS_PRE =

Item-specific scroll rewrites applied before SCROLL_KEYWORD_COLLAPSE. These full game descriptions contain keywords the collapse would otherwise mangle (e.g. "icy blue vellum scroll" -> "icy scroll", not "icy blue vellum"), or must be caught before the collapse can run. Order matters and is preserved. Players extend this list via the custom_scroll_substitutions setting; see scroll_list_to_adj_and_noun.

Returns:

  • (Array<Array(String, String)>) —

    ordered [from, to] literal pairs

[
  ['large midnight-blue scale torn with symbols', 'midnight-blue scale'],
  ['icy blue vellum scroll', 'icy scroll'],
  ['green vellum scroll', 'green scroll'],
  ['fetid antelope vellum', 'antelope vellum'],
  ['papyrus roll', 'papyrus.roll'],
  ['pallid red scroll', 'pallid scroll']
].freeze
DEFAULT_SCROLL_SUBSTITUTIONS_POST =

Adjective-pair scroll rewrites applied after SCROLL_KEYWORD_COLLAPSE, reducing already-collapsed forms (e.g. "stormy grey" -> "stormy"). Order matters and is preserved. Not player-extensible (these operate on the collapsed noun, not the raw description).

Returns:

  • (Array<Array(String, String)>) —

    ordered [from, to] literal pairs

[
  ['crumpled paper', 'crumpled'],
  ['pale ricepaper', 'pale'],
  ['stormy grey', 'stormy'],
  ['mossy green', 'mossy'],
  ['dark purple', 'dark'],
  ['vibrant red', 'vibrant'],
  ['bright green', 'bright'],
  ['icy blue', 'blue'],
  ['pearl-white silk', 'silk'],
  ['ghostly white', 'white'],
  ['crinkled violet', 'crinkled'],
  ['drawing paper', 'drawing']
].freeze
SCROLL_KEYWORD_COLLAPSE =

Structural collapse: reduce " <flavor...>" to " " by keeping the noun keyword and dropping trailing flavor. Sandwiched between the pre and post substitution passes.

Returns:

  • (Regexp)
/\s(bark|leaf|ostracon|papyrus|parchment|roll|scroll|tablet|vellum|manuscript)\s.*/.freeze

Class Method Summary collapse

Class Method Details

.assess_teach ⇒ Hash<String, String>

Runs an "assess teach" command and parses the output into a teacher=>skill hash. Returns an empty hash if no one is teaching or if the character is teaching a class. Waits for roundtime after completion.

Examples:

DRC.assess_teach #=> {"Master Trainer" => "Magic"}

Returns:

  • (Hash<String, String>) —

    mapping of teacher names to skill names (e.g. Trainer" => "Utility")

See Also:

  • #parse_assess_teach_lines
  • #listen?


790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
# File 'documented/dragonrealms/commons/common.rb', line 790

def assess_teach
  lines = Lich::Util.issue_command(
    'assess teach',
    /is teaching a class|No one seems to be teaching|You are teaching a class/,
    /Roundtime/,
    usexml: false,
    quiet: true,
    include_end: false
  )
  waitrt?
  return {} if lines.nil?
  return {} if lines.any? { |l| l.match?(/No one seems to be teaching|You are teaching a class/) }

  parse_assess_teach_lines(lines.map(&:strip).reject(&:empty?))
end

.atmo(text, make_bold = true) ⇒ Object

Sends a message to the atmospherics window. By default the message is bold. DEPRECATED in favor of log_window



1495
1496
1497
# File 'documented/dragonrealms/commons/common.rb', line 1495

def atmo(text, make_bold = true)
  log_window(text, "atmospherics", make_bold)
end

.beep ⇒ Object

windows only I believe.



727
728
729
# File 'documented/dragonrealms/commons/common.rb', line 727

def beep
  echo("\a")
end

.bold(text) ⇒ Object

Helper function to wrap text in the necessary markup to make it render as bold in a frontend client. Used by atmo and log_window methods.



1520
1521
1522
1523
1524
# File 'documented/dragonrealms/commons/common.rb', line 1520

def bold(text)
  prefix = Frontend.supports_gsl? ? "\034GSL\r\n " : "<pushBold\/>"
  suffix = Frontend.supports_gsl? ? "\034GSM\r\n " : "<popBold\/>"
  "#{prefix}#{text}#{suffix}"
end

.box_list_to_adj_and_noun(list) ⇒ Array<String>

Take a game formatted list of boxes "a reinforced wooden strongbox and a plain ironwood crate" and return ["wooden strongbox", "iron crate"].

The recognized wood and container words are BOX_WOODS and BOX_CONTAINERS merged with the player's custom_box_woods / custom_box_containers settings, so a player can teach Lich about a box material or container it does not yet know without a Lich release. The global $box_regex (built from the same defaults) is left untouched for third-party scripts. Post-match rewrites come from DEFAULT_BOX_SUBSTITUTIONS merged with custom_box_substitutions.

Examples:

box_list_to_adj_and_noun('an ironwood crate') #=> ['iron crate']

Parameters:

  • list (String) —

    game-formatted box list (e.g. from rummage /B)

Returns:

  • (Array<String>) —

    gettable box adjective+noun names

See Also:



474
475
476
477
478
479
480
481
482
483
484
# File 'documented/dragonrealms/commons/common.rb', line 474

def box_list_to_adj_and_noun(list)
  woods = CustomSubstitutions.resolve(:custom_box_woods, BOX_WOODS, type: :names)
  containers = CustomSubstitutions.resolve(:custom_box_containers, BOX_CONTAINERS, type: :names)
  substitutions = CustomSubstitutions.resolve(:custom_box_substitutions, DEFAULT_BOX_SUBSTITUTIONS, type: :pairs)
  box_regex = /((?:#{woods.map { |wood| Regexp.escape(wood) }.join('|')}) (?:#{containers.map { |container| Regexp.escape(container) }.join('|')}))/
  list.strip
      .split(box_regex)
      .reject(&:empty?)
      .select { |item| item =~ box_regex }
      .map { |box| substitutions.reduce(box) { |current, (from, to)| current.gsub(from, to) } }
end

.bput(message, *matches) ⇒ Object

Like fput but better because will wait for RT before performing command and do smart retries. Will wait for matching text up to 15 seconds then timeout. Also recovers from some limited failures wherein we want to simply fix the issue and retry the bput, like when we're prone and need to be standing. Complex handling should be done within the calling script.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'documented/dragonrealms/commons/common.rb', line 130

def bput(message, *matches)
  options = (matches.shift if matches.first.is_a?(Hash)) || {}
  options['timeout'] ||= 15
  options['ignore_rt'] ||= false

  timeout = options['timeout']
  ignore_rt = options['ignore_rt']
  suppress = options['suppress_no_match']

  if options['debug']
    echo "bput.message=#{message}"
    echo "bput.options=#{options}"
    echo "bput.matches=#{matches}"
  end

  waitrt? unless ignore_rt
  log = []
  matches.flatten!
  matches.map! { |item| item.is_a?(Regexp) ? item : /#{item}/i }
  clear
  put message
  timer = Time.now
  while (response = get?) || (Time.now - timer < timeout)

    if response.nil?
      pause 0.1
      next
    end

    log += [response]

    case response
    when /^For some strange reason you are unable to do that\.  The world somehow seems frozen in place/
      # Zadraes - 13:32 It's a "You're in an area actively being updated" message
      pause 1
      put message
      timer = Time.now
      next
    when WAIT_RESPONSE_PATTERN
      unless ignore_rt
        wait_match = response.match(WAIT_RESPONSE_PATTERN)
        pause(wait_match[:seconds].to_i - 0.5)
        waitrt?
        put message
        timer = Time.now
      end
      next
    when /Sorry, you may only type ahead/
      pause 1
      put message
      timer = Time.now
      next
    when /^You can't do that while you are asleep./
      put 'wake'
      put message
      timer = Time.now
      next
    when /^You are a bit too busy performing to do that/, /^You should stop playing before you do that/
      put 'stop play'
      put message
      timer = Time.now
      next
    when /would give away your hiding place/
      release_invisibility
      put 'unhide'
      put message
      timer = Time.now
      next
    when /^You don't seem to be able to move to do that/
      next unless matches.include?(response)
    when /^You are still stunned/
      pause 0.5 while stunned?
      pause 0.5
      put message
      timer = Time.now
      next
    when /^You can't do that while entangled in a web/
      pause 0.5 while webbed?
      pause 0.5
      put message
      timer = Time.now
      next
    when /^You must be standing/, /^You should stand up first/, /^You'll need to stand up first/, /^You can't do that while (sitting|kneeling|lying)/, /^You should be sitting up/, /^You really should be standing to play/, /^After failing to draw a breath for what feels like forever/
      fix_standing
      waitrt?
      put message
      timer = Time.now
      next
    end

    matches.each do |match|
      if (result = response.match(match))
        return result.to_a.first
      end
    end
  end

  unless suppress
    Lich::Messaging.msg("bold", "DRC: No match was found after #{timeout} seconds for command '#{message}'")
    Lich::Messaging.msg("bold", "DRC: Messages seen: #{log.length}")
    log.reverse.each { |logged_response| Lich::Messaging.msg("bold", "DRC: > #{logged_response}") }
    Lich::Messaging.msg("bold", "DRC: Checked against: #{matches}")
  end

  ''
end

.can_see_sky? ⇒ Boolean

Returns true if the character can see the sky (outdoors or indoors with windows). Issues a "weather" command and examines the response.

Examples:

DRC.can_see_sky? #=> true (if in an open area or by a window)

Returns:

  • (Boolean) —

    true if outdoors or indoors with skylight/window, false if fully enclosed



287
288
289
290
291
292
293
294
295
296
# File 'documented/dragonrealms/commons/common.rb', line 287

def can_see_sky?
  # If you are indoors and not able to see the sky.
  inside_no_sky = "That's a bit hard to do while inside."
  # If you are indoors but able to see the sky (e.g. a window or skylight).
  inside_yes_sky = "You glance outside"
  # If you are outdoors.
  outside = "You glance up at the sky"
  # Can we see the sky?
  bput("weather", inside_no_sky, inside_yes_sky, outside) != inside_no_sky
end

.check_encumbrance(refresh = true) ⇒ Integer, String

Returns the character's encumbrance level as a numeric constant or string. Optionally queries the game with an "encumbrance" command to refresh the status before looking it up in the encumbrance map.

Examples:

DRC.check_encumbrance #=> 5 (if refreshed and heavily encumbered)
DRC.check_encumbrance(false) #=> 3 (uses cached DRStats.encumbrance)

Parameters:

  • refresh (Boolean) (defaults to: true) —

    whether to issue an encumbrance command first (default true)

Returns:

  • (Integer, String) —

    the mapped encumbrance value from $ENC_MAP

See Also:



948
949
950
951
952
953
954
955
956
957
# File 'documented/dragonrealms/commons/common.rb', line 948

def check_encumbrance(refresh = true)
  encumbrance = DRStats.encumbrance
  if refresh
    encumbrance_pattern = /(?:Encumbrance)\s:\s(?<encumbrance>.*)/
    result = bput('encumbrance', encumbrance_pattern)
    enc_match = result.match(encumbrance_pattern)
    encumbrance = enc_match[:encumbrance] if enc_match
  end
  $ENC_MAP[encumbrance]
end

.clean_instrument(settings, worn = true) ⇒ Boolean

Cleans and dries an instrument using a cleaning cloth (chamois). Removes the instrument if worn, gets it if not. Wipes and wrings the cloth until dry, then cleans the instrument until it needs no more cleaning. Re-wears if originally worn. Returns false if hands are not empty, cloth/instrument cannot be retrieved, or standing is needed.

Examples:

settings = OpenStruct.new(cleaning_cloth: 'chamois', worn_instrument: 'lute')
DRC.clean_instrument(settings) #=> true (if cleaned successfully)

Parameters:

  • settings (OpenStruct) —

    outfit settings with cleaning_cloth and worn_instrument/instrument keys

  • worn (Boolean) (defaults to: true) —

    whether the instrument is worn (vs in inventory) (default true)

Returns:

  • (Boolean) —

    true if successfully cleaned, false if preconditions fail

See Also:

  • #tune_instrument
  • #play_song?


1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
# File 'documented/dragonrealms/commons/common.rb', line 1137

def clean_instrument(settings, worn = true)
  cloth = settings.cleaning_cloth
  instrument = worn ? settings.worn_instrument : settings.instrument

  unless DRCI.get_item?(cloth)
    Lich::Messaging.msg("bold", "DRC: You have no chamois cloth -- this could cause problems with playing an instrument!")
    DRC.beep
    return false
  end
  DRC.stop_playing

  if worn
    unless DRCI.remove_item?(instrument)
      Lich::Messaging.msg("bold", "DRC: Could not remove #{instrument}, putting away cloth, and not trying to clean.")
      DRCI.stow_item?(cloth)
      DRC.beep
      return false
    end
  else
    unless DRCI.get_item?(instrument)
      Lich::Messaging.msg("bold", "DRC: Could not get #{instrument}, putting away cloth, and not trying to clean.")
      DRCI.stow_item?(cloth)
      DRC.beep
      return false
    end
  end

  loop do
    case DRC.bput("wipe my #{instrument} with my #{cloth}", 'Roundtime', 'not in need of drying', 'You should be sitting up')
    when 'not in need of drying'
      break
    when 'You should be sitting up'
      DRC.fix_standing
      next
    end
    pause 1
    waitrt?

    until /you wring a dry/i =~ DRC.bput("wring my #{cloth}", 'You wring a dry', 'You wring out')
      pause 1
      waitrt?
    end
  end

  until /not in need of cleaning/i =~ DRC.bput("clean my #{instrument} with my #{cloth}", 'Roundtime', 'not in need of cleaning')
    pause 1
    waitrt?
  end

  DRCI.wear_item?(instrument) if worn
  DRCI.stow_item?(cloth)
  true
end

.collect(item, practice = true) ⇒ void

This method returns an undefined value.

Collects an item, optionally marking it as practice to gain skill. Retries if the room is too cluttered after kicking the pile. Waits for roundtime after completion.

Examples:

DRC.collect('herbs')
DRC.collect('herbs', false)  # collect without practice flag

Parameters:

  • item (String) —

    the item to collect, e.g. "herbs"

  • practice (Boolean) (defaults to: true) —

    whether to add "practice" to the collect command (default true)

See Also:

  • #forage?
  • #kick_pile?


342
343
344
345
346
347
348
349
350
351
352
# File 'documented/dragonrealms/commons/common.rb', line 342

def collect(item, practice = true)
  practicing = "practice" if practice

  case bput("collect #{item} #{practicing}", COLLECT_MESSAGES)
  when 'The room is too cluttered'
    return unless kick_pile?

    collect(item)
  end
  waitrt?
end

.do_tune(instrument, tuning = "") ⇒ Boolean

Recursively tunes an instrument, adjusting for sharp/flat responses. Issues a "tune my " command, then recursively adjusts with "sharp" or "flat" if needed. Fixes standing if required. Returns false if the instrument is not in hands.

Examples:

DRC.do_tune('lute') #=> true (if tuned)

Parameters:

  • instrument (String) —

    the instrument noun

  • tuning (String) (defaults to: "") —

    tuning directive to append ("sharp", "flat", or empty for initial attempt) (default "")

Returns:

  • (Boolean) —

    true if tuned successfully, false if instrument not in hands

See Also:

  • #tune_instrument


1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
# File 'documented/dragonrealms/commons/common.rb', line 1249

def do_tune(instrument, tuning = "")
  unless instrument && DRCI.in_hands?(instrument)
    Lich::Messaging.msg("bold", "DRC: No instrument found in hands. Not trying to tune.")
    DRC.beep
    return false
  end

  case DRC.bput("tune my #{instrument} #{tuning}",
                /^You should be sitting up/,
                /After a moment, you .* flat/,
                /After a moment, you .* sharp/,
                /After a moment, you .* tune/)
  when /After a moment, you .* tune/
    Lich::Messaging.msg("plain", "DRC: Instrument tuned.")
    return true
  when /After a moment, you .* flat/
    DRC.do_tune(instrument, "sharp")
  when /After a moment, you .* sharp/
    DRC.do_tune(instrument, "flat")
  when /^You should be sitting up/
    DRC.fix_standing
    DRC.do_tune(instrument)
  end
end

.fix_dr_bullshit(string) ⇒ 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.

Simplifies multi-word item names by extracting just first and last words. Removes " and chain" substring if present. Returns original string if fewer than 3 words. Used internally to normalize item names from GameObj (which can contain verbose descriptions).

Examples:

DRC.fix_dr_bullshit('a shiny broadsword of elanthia') #=> "a elanthia"
DRC.fix_dr_bullshit('a ball and chain') #=> "a chain"

Parameters:

  • string (String) —

    the item name to simplify

Returns:

  • (String) —

    simplified name with first and last words, or original if too short



858
859
860
861
862
863
864
865
866
867
# File 'documented/dragonrealms/commons/common.rb', line 858

def fix_dr_bullshit(string)
  return string if string.split.length <= 2

  string = string.sub(' and chain', '') if string =~ /ball and chain/

  match = string.match(/(?<first>\S+) .* (?<last>\S+)/)
  return string unless match

  "#{match[:first]} #{match[:last]}"
end

.fix_standing ⇒ void

This method returns an undefined value.

Issues STAND until the character is standing, giving up when the game reports a state from which standing cannot currently succeed. Without the CANNOT_STAND_PATTERN guard these states loop forever spamming STAND, because matching the message never makes standing? true (issue #3668: safe-room spamming STAND while unconscious).



737
738
739
740
741
742
743
744
# File 'documented/dragonrealms/commons/common.rb', line 737

def fix_standing
  loop do
    break if standing?

    result = bput('stand', 'You stand', 'You are so unbalanced', 'As you stand', 'You are already', 'weight of all your possessions', 'You are overburdened and cannot', 'You\'re unconscious', 'You swim back up into a vertical position', "You don't seem to be able to move to do that", 'prevents you from standing', 'You\'re plummeting to your death', 'There\'s no room to do much of anything here')
    break if result =~ CANNOT_STAND_PATTERN
  end
end

.forage?(item, tries = 5) ⇒ Boolean

Forages for an item up to a maximum number of tries, returning true if successful. Stops early if the room is too cluttered, foraging is futile, or hands are full. Attempts to free the right hand if needed. Waits for roundtime between attempts.

Examples:

DRC.forage?('apple', 3) #=> true if an apple was found within 3 tries

Parameters:

  • item (String) —

    the item to forage for, e.g. "herb"

  • tries (Integer) (defaults to: 5) —

    maximum number of forage attempts (default 5)

Returns:

  • (Boolean) —

    true if something was found (inventory changed), false otherwise

See Also:

  • #collect
  • #kick_pile?


309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'documented/dragonrealms/commons/common.rb', line 309

def forage?(item, tries = 5)
  snapshot = "#{right_hand}#{left_hand}"
  while snapshot == "#{right_hand}#{left_hand}"
    tries > 0 ? tries -= 1 : (return false)
    case bput("forage #{item}", 'Roundtime', 'The room is too cluttered to find anything here', 'You really need to have at least one hand free to forage properly', 'You survey the area and realize that any foraging efforts would be futile')
    when 'The room is too cluttered to find anything here'
      return false unless kick_pile?
    when 'You survey the area and realize that any foraging efforts would be futile'
      return false
    when 'You really need to have at least one hand free to forage properly'
      Lich::Messaging.msg("bold", "DRC: Hands not emptied properly. Stowing right hand...")
      unless DRCI.stow_hand('right')
        Lich::Messaging.msg("bold", "DRC: Failed to stow right hand, cannot forage.")
        return false
      end
    end
    waitrt?
  end
  true
end

.get_gems(container) ⇒ Array<String>

Rummages for gems in the given container.

Examples:

DRC.get_gems('backpack') #=> ["diamond", "ruby"]

Parameters:

  • container (String) —

    the container noun

Returns:

  • (Array<String>) —

    list of gem item nouns

See Also:

  • #rummage


427
428
429
# File 'documented/dragonrealms/commons/common.rb', line 427

def get_gems(container)
  rummage('G', container)
end

.get_materials(container) ⇒ Array<String>

Rummages for materials in the given container.

Examples:

DRC.get_materials('backpack') #=> ["ingot", "silk"]

Parameters:

  • container (String) —

    the container noun

Returns:

  • (Array<String>) —

    list of material item nouns

See Also:

  • #rummage


438
439
440
# File 'documented/dragonrealms/commons/common.rb', line 438

def get_materials(container)
  rummage('M', container)
end

.get_noun(long_name) ⇒ String?

Extracts the gettable noun from an item long name by stripping flavor text and returning the rightmost word.

Examples:

DRC.get_noun('a sword adorned with rubies') #=> "sword"
DRC.get_noun('silver coins') #=> "coins"

Parameters:

  • long_name (String) —

    the full item name, e.g. "a sword adorned with rubies"

Returns:

  • (String, nil) —

    the noun, e.g. "sword", or nil if no noun found

See Also:

  • #remove_flavor_text


576
577
578
# File 'documented/dragonrealms/commons/common.rb', line 576

def get_noun(long_name)
  remove_flavor_text(long_name).strip.scan(/[a-z\-']+$/i).first
end

.get_skins(container) ⇒ Array<String>

Rummages for skins in the given container.

Examples:

DRC.get_skins('backpack') #=> ["fur", "scale"]

Parameters:

  • container (String) —

    the container noun

Returns:

  • (Array<String>) —

    list of skin item nouns

See Also:

  • #rummage


416
417
418
# File 'documented/dragonrealms/commons/common.rb', line 416

def get_skins(container)
  rummage('S', container)
end

.get_town_name(text) ⇒ Object

Looks up the canonical name of the town based on the given text. Utility to help identify the canonical town name based on arbitrary text. For example, "Theren" for "Therenborough" and "Haven" for "Riverhaven". It also handles missing apostrophes and the occasional space between names like "merkresh" or "Mer'Kresh" or "ainghazal" or "Ain Ghazal". Returns nil if unable to find a match.



716
717
718
719
720
721
722
723
724
# File 'documented/dragonrealms/commons/common.rb', line 716

def get_town_name(text)
  towns = $HOMETOWN_REGEX_MAP.select { |_town, regex| regex =~ text }.keys
  if towns.length > 1
    Lich::Messaging.msg("bold", "DRC: Found multiple towns that match '#{text}': #{towns}")
    Lich::Messaging.msg("bold", "DRC: Using first town that matched: #{towns.first}")
    Lich::Messaging.msg("bold", "DRC: To avoid ambiguity, please use the town's full name: https://elanthipedia.play.net/Category:Cities")
  end
  towns.first
end

.hide?(hide_type = 'hide') ⇒ Boolean

Attempts to hide or stalk until the character is successfully hiding. Recovers from common failures: stops playing music if too busy, stops stalking if already stalking, waits if not enough time has passed. Returns final hiding status.

Examples:

DRC.hide? #=> true (if successfully hidden)
DRC.hide?('stalk') #=> true (if stalking)

Parameters:

  • hide_type (String) (defaults to: 'hide') —

    the hide command to use: 'hide' or 'stalk' (default 'hide')

Returns:

  • (Boolean) —

    true if now hiding, false if hiding failed



829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
# File 'documented/dragonrealms/commons/common.rb', line 829

def hide?(hide_type = 'hide')
  unless hiding?
    case bput(hide_type, 'Roundtime', 'too busy performing', 'can\'t see any place to hide yourself', 'Stalk what', 'You\'re already stalking', 'Stalking is an inherently stealthy', 'You haven\'t had enough time', 'You search but find no place to hide')
    when 'too busy performing'
      bput('stop play', 'You stop playing', 'In the name of')
      return hide?(hide_type)
    when "You're already stalking"
      put 'stop stalk'
      return hide?(hide_type)
    when 'You haven\'t had enough time'
      pause 1
      return hide?(hide_type)
    end
    pause
    waitrt?
  end
  hiding?
end

.kick_pile?(item = 'pile') ⇒ Boolean

Attempts to kick a pile in the room, returning true if successfully knocked back. First ensures the character is standing, then checks if a matching room object exists. Returns false if no piles match or the kick does not succeed.

Examples:

DRC.kick_pile? #=> true if pile was kicked back
DRC.kick_pile?('rubble') #=> true if rubble was kicked back

Parameters:

  • item (String) (defaults to: 'pile') —

    the item name to kick (default 'pile')

Returns:

  • (Boolean) —

    true if kicked back successfully, false if not found or not kicked

See Also:

  • #collect
  • #forage?


365
366
367
368
369
# File 'documented/dragonrealms/commons/common.rb', line 365

def kick_pile?(item = 'pile')
  fix_standing
  return unless DRRoom.room_objs.any? { |room_obj| room_obj.match?(/pile/) }
  bput("kick #{item}", 'I could not find', 'take a step back and run up to', 'Now what did the .* ever do to you', 'You lean back and kick your feet,') == 'take a step back and run up to'
end

.left_hand ⇒ String?

Returns the cleaned-up name of the item in the left hand, or nil if empty. Applies fix_dr_bullshit to normalize verbose descriptions from GameObj.

Examples:

DRC.left_hand #=> "sword" (or nil if hand is empty)

Returns:

  • (String, nil) —

    the left-hand item name, or nil if hand is empty

See Also:

  • #right_hand
  • #left_hand_noun


877
878
879
# File 'documented/dragonrealms/commons/common.rb', line 877

def left_hand
  GameObj.left_hand.name == 'Empty' ? nil : fix_dr_bullshit(GameObj.left_hand.name)
end

.left_hand_noun ⇒ String?

Returns the noun (gettable name) of the item in the left hand, or nil if empty. Returns the raw noun from GameObj without cleanup.

Examples:

DRC.left_hand_noun #=> "sword"

Returns:

  • (String, nil) —

    the left-hand item noun, or nil if hand is empty

See Also:

  • #left_hand
  • #right_hand_noun


901
902
903
# File 'documented/dragonrealms/commons/common.rb', line 901

def left_hand_noun
  GameObj.left_hand == 'Empty' ? nil : GameObj.left_hand.noun
end

.list_to_array(list) ⇒ Object

Take a game formatted list "an arrow, silver coins and a deobar strongbox" And return an array ["an arrow", "silver coins", "a deobar strongbox"] is this ever useful compared to the list_to_nouns?



445
446
447
# File 'documented/dragonrealms/commons/common.rb', line 445

def list_to_array(list)
  list.strip.split(/(?:,|(?:, |\s)?and\s?)(?:\s?<pushBold\/>\s?)?(?=\s\ba\b|\s\ban\b|\s\bsome\b|\s\bthe\b)/i).reject(&:empty?)
end

.list_to_nouns(list) ⇒ Object

Take a game formatted list "an arrow, silver coins and a deobar strongbox" And return an array of nouns ["arrow", "coins", "strongbox"]



560
561
562
563
564
565
# File 'documented/dragonrealms/commons/common.rb', line 560

def list_to_nouns(list)
  list_to_array(list)
    .map { |long_name| get_noun(long_name) }
    .compact
    .reject { |noun| noun == '' }
end

.listen?(teacher, observe_flag = false) ⇒ Boolean

Attempts to listen to a teacher for skill training, handling class restrictions. Stops listening if the skill is forbidden for the character's class (e.g., Thief cannot learn Sorcery). Returns true if successfully listening or already listening to an allowed skill.

Examples:

DRC.listen?('Mage Trainer') #=> true (if allowed for your class)
DRC.listen?('invalid teacher') #=> false

Parameters:

  • teacher (String, nil) —

    the NPC teacher name

  • observe_flag (Boolean) (defaults to: false) —

    whether to use the "observe" flag to listen without joining (default false)

Returns:

  • (Boolean) —

    true if now listening to an allowed skill, false if teacher not found, skill forbidden, or in combat

See Also:

  • #assess_teach


757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
# File 'documented/dragonrealms/commons/common.rb', line 757

def listen?(teacher, observe_flag = false)
  return false if teacher.nil?
  return false if teacher.empty?

  bad_classes = %w[Thievery Sorcery]
  bad_classes += ['Life Magic', 'Holy Magic', 'Lunar Magic', 'Elemental Magic', 'Arcane Magic', 'Targeted Magic', 'Arcana', 'Attunement'] if DRStats.barbarian? || DRStats.thief?
  bad_classes += ['Utility'] if DRStats.barbarian?

  observe = observe_flag ? 'observe' : ''

  result = bput("listen to #{teacher} #{observe}", 'begin to listen to \w+ teach the .* skill', 'already listening', 'could not find who', 'You have no idea', 'isn\'t teaching a class', 'don\'t have the appropriate training', 'Your teacher appears to have left', 'isn\'t teaching you anymore', 'experience differs too much from your own', 'but you don\'t see any harm in listening', 'invitation if you wish to join this class', 'You cannot concentrate to listen to .* while in combat')
  if (skill_match = result.match(/begin to listen to \w+ teach the (?<skill>.*) skill/))
    return true if bad_classes.grep(/#{skill_match[:skill]}/i).empty?

    bput('stop listening', 'You stop listening')
  elsif result == 'already listening'
    return true
  elsif result == "but you don't see any harm in listening"
    bput('stop listening', 'You stop listening')
  end

  false
end

.log_window(text, window_name, make_bold = true, create_window = false, pre_clear_window = false) ⇒ Object

Sends a message to the specified window. Replaces the deprecated atmo method. By default the message is bold. Creates window upon request. Pre-clears window upon request.



1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
# File 'documented/dragonrealms/commons/common.rb', line 1501

def log_window(text, window_name, make_bold = true, create_window = false, pre_clear_window = false)
  if create_window
    _respond("<streamWindow id=\"#{window_name}\" title=\"#{window_name}\" location=\"center\" save=\"true\" />")
    _respond("<exposeStream id=\"#{window_name}\"/>")
  end

  if pre_clear_window
    _respond("<clearStream id=\"#{window_name}\"/>\r\n")
  end

  _respond(
    "<pushStream id=\"#{window_name}\"/>" + (make_bold ? bold(text) : text),
    "<popStream id=\"#{window_name}\" /><prompt time=\"#{XMLData.server_time.to_i}\">&gt;</prompt>"
  )
end

.message(text, make_bold = true) ⇒ Object

Sends a message to the game window. By default the message is bold. Delegates to Lich::Messaging.msg for consistent multi-frontend support.



1529
1530
1531
# File 'documented/dragonrealms/commons/common.rb', line 1529

def message(text, make_bold = true)
  Lich::Messaging.msg(make_bold ? "bold" : "plain", text, encode: false)
end

.parse_assess_teach_lines(lines) ⇒ Object

Pure parsing method for assess teach output (unit-testable)



807
808
809
810
811
812
813
814
815
816
817
818
# File 'documented/dragonrealms/commons/common.rb', line 807

def parse_assess_teach_lines(lines)
  lines.each_with_object({}) do |line, hash|
    match = line.match(ASSESS_TEACH_TEACHER_PATTERN)
    next unless match

    teacher = match[:teacher]
    skill = match[:skill]
    skill_filter = skill.match(ASSESS_TEACH_SKILL_FILTER_PATTERN)
    skill = skill_filter[:filtered_skill] if skill_filter
    hash[teacher] = skill
  end
end

.pause_all ⇒ 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.

Pauses all running scripts except the current one, using a global lock. Scripts already paused before this call are marked and not unpaused by unpause_all. Returns false if the lock cannot be acquired. Pauses for 1 second after pausing scripts.

Examples:

DRC.pause_all #=> true (if paused; false if already held)

Returns:

  • (Boolean) —

    true if successfully acquired lock and paused scripts, false if lock already held

See Also:

  • #unpause_all
  • #smart_pause_all
  • #safe_pause_list


1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
# File 'documented/dragonrealms/commons/common.rb', line 1285

def pause_all
  return false unless $pause_all_lock.try_lock

  @pause_all_no_unpause = []

  Script.running.find_all(&:paused?).each do |script|
    @pause_all_no_unpause << script
  end

  Script.running.find_all do |script|
    !script.paused? &&
      !script.no_pause_all &&
      script != Script.current
  end
        .each(&:pause)

  pause 1
  true
end

.play_song?(settings, song_list, worn = true, skip_clean = false, climbing = false, skip_tuning = false) ⇒ Boolean

Plays a song from a list, cycling to the next song if the current one fails. Tracks the current song and climbing song separately in UserVars. Resets tracking when instrument changes. Handles common failures: missing/wrong instrument, out-of-tune, dirty instrument (cleans if Performance rank < 20), stunned, playing already, etc. Returns false if playing fails due to environment (in water, not the right time).

Examples:

settings = OpenStruct.new(worn_instrument: 'lute', cleaning_cloth: 'chamois')
DRC.play_song?(settings, ['ballad', 'march', 'waltz'])
# => true if any song plays successfully

Parameters:

  • settings (OpenStruct) —

    outfit settings with worn_instrument, instrument, cleaning_cloth keys

  • song_list (Array, Hash) —

    list/hash of songs to cycle through

  • worn (Boolean) (defaults to: true) —

    whether instrument is worn (remove/wear) vs in inventory (get/stow) (default true)

  • skip_clean (Boolean) (defaults to: false) —

    whether to skip cleaning the instrument (default false)

  • climbing (Boolean) (defaults to: false) —

    whether to use climbing_song tracking instead of song (default false)

  • skip_tuning (Boolean) (defaults to: false) —

    whether to skip tuning if out of tune (default false)

Returns:

  • (Boolean) —

    true if song was played or is already playing, false if environment prevents playing

See Also:

  • #stop_playing
  • #clean_instrument
  • #tune_instrument


1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
# File 'documented/dragonrealms/commons/common.rb', line 1034

def play_song?(settings, song_list, worn = true, skip_clean = false, climbing = false, skip_tuning = false)
  instrument = worn ? settings.worn_instrument : settings.instrument

  if UserVars.instrument.nil?
    Lich::Messaging.msg("plain", "DRC: No previous instrument setting detected. Cleaning stored song data.")
    UserVars.song = nil
    UserVars.climbing_song = nil
    UserVars.instrument = instrument
  elsif UserVars.instrument != instrument
    Lich::Messaging.msg("plain", "DRC: New instrument #{instrument} detected; old instrument: #{UserVars.instrument}. Resetting stored song data.")
    UserVars.song = nil
    UserVars.climbing_song = nil
    UserVars.instrument = instrument
  end
  UserVars.song = song_list.first.first unless UserVars.song
  UserVars.climbing_song = song_list.first.first unless UserVars.climbing_song
  song_to_play = climbing ? UserVars.climbing_song : UserVars.song
  play_command = "play #{song_to_play}"
  if instrument
    play_command = play_command + " on my #{instrument}"
  end
  fput('release ecry') if DRSpells.active_spells["Eillie's Cry"].to_i > 0
  result = bput(play_command, 'too damaged to play', 'dirtiness may affect your performance', 'slightest hint of difficulty', 'fumble slightly', /Your .+ is submerged in the water/, 'You begin a', 'You struggle to begin', 'You\'re already playing a song', 'You effortlessly begin', 'You begin some', 'You cannot play', 'Play on what instrument', 'Are you sure that\'s the right instrument', 'now isn\'t the best time to be playing', 'Perhaps you should find somewhere drier before trying to play', 'You should stop practicing', /^You really need to drain/, /Your .* tuning is off, and may hinder your performance/)
  case result
  when 'Play on what instrument', 'Are you sure that\'s the right instrument'
    unless DRCI.get_item?(instrument)
      Lich::Messaging.msg("bold", "DRC: Failed to get #{instrument}.")
      return false
    end
    if worn && !DRCI.wear_item?(instrument)
      Lich::Messaging.msg("bold", "DRC: Failed to wear #{instrument}.")
      return false
    end
    play_song?(settings, song_list, worn, skip_clean, climbing, skip_tuning)
  when 'now isn\'t the best time to be playing', 'Perhaps you should find somewhere drier before trying to play', 'You should stop practicing'
    false
  when 'You\'re already playing a song'
    fput('stop play')
    play_song?(settings, song_list, worn, skip_clean, climbing, skip_tuning)
  when 'You cannot play'
    wait_for_script_to_complete('safe-room')
  when /Your .* tuning is off, and may hinder your performance/
    Lich::Messaging.msg("bold", "DRC: Instrument out of tune. Attempting to tune it.")
    return true if DRSkill.getrank('Performance') < 20
    return true if skip_tuning
    return true unless DRC.tune_instrument(settings)

    play_song?(settings, song_list, worn, skip_clean, climbing, skip_tuning)
  when 'dirtiness may affect your performance', /^You really need to drain/
    return true if DRSkill.getrank('Performance') < 20
    return true if skip_clean
    return true unless clean_instrument(settings, worn)

    play_song?(settings, song_list, worn, skip_clean, climbing, skip_tuning)
  when 'slightest hint of difficulty', 'fumble slightly'
    true
  when 'You begin a', 'You effortlessly begin', 'You begin some'
    return true if song_to_play == song_list.to_a.last.last
    # Ignore difficulty messages if we have an offset
    return true if climbing && UserVars.climbing_song_offset

    stop_playing
    UserVars.climbing_song = song_list[UserVars.climbing_song] || song_list.first.first if climbing
    UserVars.song = song_list[UserVars.song] || song_list.first.first unless climbing
    play_song?(settings, song_list, worn, skip_clean, climbing, skip_tuning)
  when 'You struggle to begin'
    return true if song_to_play == song_list.first.first
    # Ignore difficulty messages if we have an offset
    return true if climbing && UserVars.climbing_song_offset

    stop_playing
    UserVars.climbing_song = song_list.first.first if climbing
    UserVars.song = song_list.first.first unless climbing
    play_song?(settings, song_list, worn, skip_clean, climbing, skip_tuning)
  else
    false
  end
end

.release_invisibility ⇒ void

This method returns an undefined value.

Releases all active invisibility spells and khri abilities to become visible. Queries spell data for spells with the invisibility flag, then releases each active one. Also handles Khri Silence and Khri Vanish (thief-only) if active.

Examples:

DRC.release_invisibility  # ends Invisibility, Khri Silence, Khri Vanish

See Also:



925
926
927
928
929
930
931
932
933
934
935
936
# File 'documented/dragonrealms/commons/common.rb', line 925

def release_invisibility
  get_data('spells')
    .spell_data
    .select { |_name, properties| properties['invisibility'] }
    .select { |name, _properties| DRSpells.active_spells.keys.include?(name) }
    .map { |_name, properties| properties['abbrev'] }
    .each { |abbrev| fput("release #{abbrev}") }

  # handle khri silence as it's not part of base-spells data, and method of ending it differs from spells
  bput('khri stop silence', 'You attempt to relax') if DRSpells.active_spells.keys.include?('Khri Silence')
  bput('khri stop vanish', /^You would need to start Vanish/, /^Your control over the limited subversion of reality falters/, /^You are not trained in the Vanish meditation/) if (DRStats.guild == "Thief" && invisible?)
end

.remove_flavor_text(item) ⇒ String

Strips descriptive flavor text ("... adorned with ...") from an item name, leaving the gettable noun phrase.

Applies the built-in FLAVOR_TEXT_PATTERN first, then any player-defined custom_flavor_text_patterns (regular expressions) for flavor the built-in pattern misses -- letting a player strip a new flavor phrasing without a Lich release. User patterns are compiled with a per-pattern timeout and validated/guarded by CustomSubstitutions; an invalid or runaway pattern is reported and skipped, never raising here.

Examples:

remove_flavor_text('a sword adorned with rubies of deep crimson') #=> 'a sword'

Parameters:

  • item (String) —

    the item long name

Returns:

  • (String) —

    the item name with flavor text removed

See Also:



596
597
598
599
# File 'documented/dragonrealms/commons/common.rb', line 596

def remove_flavor_text(item)
  custom_patterns = CustomSubstitutions.resolve(:custom_flavor_text_patterns, [], type: :regexes)
  CustomSubstitutions.apply_regexes(item.sub(FLAVOR_TEXT_PATTERN, ''), custom_patterns)
end

.retreat(ignored_npcs = []) ⇒ Boolean, void

Attempts to retreat from combat, ignoring a list of NPCs (summoned allies or unavoidable enemies). Returns early if no hostile NPCs are present after filtering. Loops until retreat succeeds or the room becomes safe, fixing standing if needed between attempts.

Examples:

DRC.retreat #=> true (if in combat and retreat succeeds)
DRC.retreat(['summoned_mage']) #=> true (ignores the summoned ally)

Parameters:

  • ignored_npcs (Array<String>) (defaults to: []) —

    NPC names to ignore (e.g. summoned creatures)

Returns:

  • (Boolean, void) —

    true if successfully retreated, nil if no combat

See Also:



970
971
972
973
974
975
976
977
978
979
980
981
# File 'documented/dragonrealms/commons/common.rb', line 970

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

  loop do
    case DRC.bput("retreat", *RETREAT_ESCAPE_MESSAGES, *RETREAT_MESSAGES)
    when *RETREAT_ESCAPE_MESSAGES
      return true
    else
      DRC.fix_standing
    end
  end
end

.right_hand ⇒ String?

Returns the cleaned-up name of the item in the right hand, or nil if empty. Applies fix_dr_bullshit to normalize verbose descriptions from GameObj.

Examples:

DRC.right_hand #=> "sword" (or nil if hand is empty)

Returns:

  • (String, nil) —

    the right-hand item name, or nil if hand is empty

See Also:

  • #left_hand
  • #right_hand_noun


889
890
891
# File 'documented/dragonrealms/commons/common.rb', line 889

def right_hand
  GameObj.right_hand.name == 'Empty' ? nil : fix_dr_bullshit(GameObj.right_hand.name)
end

.right_hand_noun ⇒ String?

Returns the noun (gettable name) of the item in the right hand, or nil if empty. Returns the raw noun from GameObj without cleanup.

Examples:

DRC.right_hand_noun #=> "sword"

Returns:

  • (String, nil) —

    the right-hand item noun, or nil if hand is empty

See Also:

  • #right_hand
  • #left_hand_noun


913
914
915
# File 'documented/dragonrealms/commons/common.rb', line 913

def right_hand_noun
  GameObj.right_hand == 'Empty' ? nil : GameObj.right_hand.noun
end

.rummage(parameter, container) ⇒ Array<String>

Rummages through a container with a search parameter, returning matching items as nouns. Handles closed containers, invisibility, and missing items. Parses the game response to extract a list of items, then applies type-specific list conversion: 'B' for boxes, 'SC' for scrolls, other parameters for generic items.

Examples:

DRC.rummage('G', 'backpack') #=> ["diamond", "emerald"]
DRC.rummage('B', 'crate') #=> ["oak crate", "iron box"]

Parameters:

  • parameter (String) —

    the rummage filter: 'B' for boxes, 'G' for gems, 'M' for materials, 'S' for skins, 'SC' for scrolls

  • container (String) —

    the container noun, e.g. "backpack" (used in "my ")

Returns:

  • (Array<String>) —

    list of matching item nouns or adjective+noun pairs for boxes/scrolls; empty array if nothing found or container is closed

See Also:

  • #get_gems
  • #get_skins
  • #get_materials
  • #box_list_to_adj_and_noun
  • #scroll_list_to_adj_and_noun


387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'documented/dragonrealms/commons/common.rb', line 387

def rummage(parameter, container)
  result = DRC.bput("rummage /#{parameter} my #{container}", 'but there is nothing in there like that\.', 'looking for .* and see .*', 'While it\'s closed', 'I don\'t know what you are referring to', 'You feel about', 'That would accomplish nothing')

  case result
  when 'You feel about'
    release_invisibility
    return rummage(parameter, container)
  when 'but there is nothing in there like that.', 'While it\'s closed', 'I don\'t know what you are referring to', 'That would accomplish nothing'
    return []
  end

  text = result.match(/looking for .* and see (.*)\.$/).to_a[1]
  case parameter
  when 'B'
    box_list_to_adj_and_noun(text)
  when 'SC'
    scroll_list_to_adj_and_noun(text)
  else
    list_to_nouns(text)
  end
end

.safe_pause_list ⇒ Array<OpenStruct>, 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.

Pauses all running scripts except the current one using a global lock. Makes the lock holder immune to pause while it holds the lock, preventing deadlock from pause_script while holding the lock. Returns the actual paused Script objects (not names) for use with safe_unpause_list, ensuring exact restoration even if scripts have come and gone from Script.running. Returns false if the lock cannot be acquired.

Examples:

paused = DRC.safe_pause_list
# => [<Script name='hunt'>, <Script name='loot'>]
# ... do exclusive work ...
DRC.safe_unpause_list(paused)

Returns:

  • (Array<OpenStruct>, Boolean) —

    array of paused Script objects, or false if lock already held

See Also:

  • #safe_unpause_list


1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
# File 'documented/dragonrealms/commons/common.rb', line 1388

def safe_pause_list
  return false unless $safe_pause_lock.try_lock

  # Pausing is cooperative: a paused script is a live thread that keeps
  # every mutex it holds (Ruby frees a mutex on thread death, not on
  # pause). So a script that gets pause_script'd while holding
  # $safe_pause_lock never releases it, and every peer spins forever on
  # try_lock -> false. Make the lock-holder immune to pause for as long as
  # it owns the lock, so this deadlock cannot form. Save the prior value
  # (and the holder itself) so we restore exactly what was there for a
  # caller that was already ignoring pauses, e.g. mid-travel.
  @safe_pause_holder = Script.self
  @safe_pause_prev_ignore_pause = @safe_pause_holder&.ignore_pause
  @safe_pause_holder&.ignore_pause = true

  # Capture the exact Script objects we pause -- not their names. The
  # matching safe_unpause_list restores from this list directly instead of
  # re-scanning Script.running by name. A name + live-rescan restore
  # silently drops any script that has left Script.running between pause
  # and unpause (gone hidden, or mid start/teardown) or whose pause flags
  # changed, stranding it paused forever while the log still claims it was
  # unpaused. Holding the objects makes the restore undo exactly what the
  # pause did.
  paused_scripts = []
  Script.running.find_all { |s| !s.paused? && !s.no_pause_all && s.name != Script.self.name }.each do |s|
    s.pause
    paused_scripts << s
  end
  Lich::Messaging.msg("plain", "DRC: Pausing #{paused_scripts.map(&:name)} to run #{Script.self.name}")
  return paused_scripts
end

.safe_unpause_list(paused_scripts) ⇒ 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.

Unpauses Script objects that were paused by safe_pause_list, restores the lock holder's previous pause immunity state, and releases the global lock. Skips any scripts already unpaused by a peer to avoid spurious noise. Returns false if the lock is not held.

Examples:

paused = DRC.safe_pause_list
# ... do exclusive work ...
DRC.safe_unpause_list(paused) #=> true

Parameters:

  • paused_scripts (Array<OpenStruct>) —

    Script objects returned from safe_pause_list

Returns:

  • (Boolean) —

    true if successfully unpaused and lock released, false if lock not held

See Also:

  • #safe_pause_list


1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
# File 'documented/dragonrealms/commons/common.rb', line 1433

def safe_unpause_list(paused_scripts)
  return false unless $safe_pause_lock.owned?

  if paused_scripts.empty?
    Lich::Messaging.msg("plain", "DRC: #{Script.self.name} has finished.")
  else
    Lich::Messaging.msg("plain", "DRC: Unpausing #{paused_scripts.map(&:name)}, #{Script.self.name} has finished.")
    # Unpause exactly the objects we paused, regardless of their current
    # Script.running visibility or flags. Skip any a peer already unpaused
    # so we neither emit spurious "is not paused" noise nor fight another
    # coordinator that has since taken ownership of the pause.
    paused_scripts.each { |s| s.unpause if s.paused? }
  end
  # Restore the holder's pre-lock pause immunity before releasing the lock,
  # so we never leave a script permanently unpausable.
  @safe_pause_holder&.ignore_pause = @safe_pause_prev_ignore_pause
  @safe_pause_holder = nil
  @safe_pause_prev_ignore_pause = nil
  $safe_pause_lock.unlock
end

.scroll_list_to_adj_and_noun(list) ⇒ Array<String>

Converts a game rummage scroll list into gettable adjective+noun forms.

Pipeline per entry: strip the leading article, strip "labeled with...", apply the pre-collapse literal substitutions (DEFAULT_SCROLL_SUBSTITUTIONS_PRE merged with the player's custom_scroll_substitutions), apply the SCROLL_KEYWORD_COLLAPSE, then apply the post-collapse substitutions (DEFAULT_SCROLL_SUBSTITUTIONS_POST).

Examples:

scroll_list_to_adj_and_noun(' an icy blue parchment') #=> ['blue parchment']

Parameters:

  • list (String) —

    game-formatted scroll list (e.g. from rummage /SC)

Returns:

  • (Array<String>) —

    gettable scroll names

See Also:



546
547
548
549
550
551
552
553
554
555
556
# File 'documented/dragonrealms/commons/common.rb', line 546

def scroll_list_to_adj_and_noun(list)
  pre_substitutions = CustomSubstitutions.resolve(:custom_scroll_substitutions, DEFAULT_SCROLL_SUBSTITUTIONS_PRE, type: :pairs)
  list_to_array(list).map do |entry|
    without_article = entry
                      .sub(/(an|some|a(?: piece of)?)\s/, '')
                      .sub(/\slabeled with.*/, '')
    with_pre = pre_substitutions.reduce(without_article) { |text, (from, to)| text.sub(from, to) }
    collapsed = with_pre.sub(SCROLL_KEYWORD_COLLAPSE, ' \1')
    DEFAULT_SCROLL_SUBSTITUTIONS_POST.reduce(collapsed) { |text, (from, to)| text.sub(from, to) }.strip
  end
end

.set_stance(skill) ⇒ void

This method returns an undefined value.

Sets the character's defensive stance based on a skill name and computed defending ranks. Stance distribution depends on guild: Paladins use 50x divisor, Barbarians/Rangers/Traders/Commoners use 60x, others use 70x. Excess points beyond 100 are distributed to secondary/tertiary.

Examples:

DRC.set_stance('evasion') #=> issues "stance set 100 X Y"
DRC.set_stance('parry')   #=> issues "stance set X 100 Y"

Parameters:

  • skill (String) —

    the skill name: 'evasion', 'parry', or 'shield' (case-insensitive)

See Also:



1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
# File 'documented/dragonrealms/commons/common.rb', line 1465

def set_stance(skill)
  div = if DRStats.guild == 'Paladin'
          50
        elsif %w[Barbarian Ranger Trader Commoner].include?(DRStats.guild)
          60
        else
          70
        end

  points = 80 + DRSkill.getrank('Defending') / div
  secondary = points > 100 ? 100 : points
  tertiary = points > 100 ? points - 100 : 0

  stance = case skill.downcase
           when 'evasion'
             "100 #{secondary} #{tertiary}"
           when 'parry'
             "#{secondary} 100 #{tertiary}"
           when 'shield'
             "#{secondary} #{tertiary} 100"
           else
             "100 #{secondary} #{tertiary}"
           end

  DRC.bput("stance set #{stance}", /Setting your/)
end

.smart_pause_all ⇒ Array<String>

Pauses all running scripts except the current one and logs which were paused. Unlike pause_all, does not use a global lock and does not restore paused state. Returns an array of paused script names for later restoration.

Examples:

paused = DRC.smart_pause_all
# => ["hunt", "loot"]
# ... do exclusive work ...
DRC.unpause_all_list(paused)

Returns:

  • (Array<String>) —

    names of scripts that were paused

See Also:

  • #unpause_all_list


1344
1345
1346
1347
1348
1349
1350
1351
1352
# File 'documented/dragonrealms/commons/common.rb', line 1344

def smart_pause_all
  paused_script_list = []
  Script.running.find_all { |s| !s.paused? && !s.no_pause_all && s.name != Script.self.name }.each do |s|
    s.pause
    paused_script_list << s.name
  end
  Lich::Messaging.msg("plain", "DRC: Pausing #{paused_script_list} to run #{Script.self.name}")
  return paused_script_list
end

.stop_playing ⇒ void

This method returns an undefined value.

Stops playing the current song by issuing the "stop play" command. Waits for roundtime after stopping.

Examples:

DRC.stop_playing

See Also:

  • #play_song?


1120
1121
1122
# File 'documented/dragonrealms/commons/common.rb', line 1120

def stop_playing
  bput('stop play', 'You stop playing your song', 'In the name of', "But you're not performing")
end

.strip_xml(lines) ⇒ Array<String>

Strips XML tags and decodes common HTML entities from game output lines.

Parameters:

  • lines (Array<String>) —

    Array of raw game output lines

Returns:

  • (Array<String>) —

    Array of non-empty, trimmed strings with XML removed



118
119
120
121
# File 'documented/dragonrealms/commons/common.rb', line 118

def strip_xml(lines)
  lines.map { |line| line.gsub(XML_TAG_PATTERN, '').gsub('&gt;', '>').gsub('&lt;', '<').strip }
       .reject(&:empty?)
end

.text2num(text_num) ⇒ Integer?

Converts English text numbers (e.g. "forty-two") to integers using a global $NUM_MAP. Handles multipliers like "hundred" and "thousand". Returns nil if an unknown number word is found.

Examples:

DRC.text2num('forty-two') #=> 42
DRC.text2num('one hundred') #=> 100
DRC.text2num('unknownword') #=> nil (logs error, returns nil)

Parameters:

  • text_num (String) —

    the text number, e.g. "one hundred twenty-three"

Returns:

  • (Integer, nil) —

    the numeric value, or nil if an unknown word is encountered



992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
# File 'documented/dragonrealms/commons/common.rb', line 992

def text2num(text_num)
  text_num = text_num.tr('-', ' ')
  split_words = text_num.split(' ')
  g = 0

  split_words.each do |word|
    x = $NUM_MAP.fetch(word, nil)
    if word.eql?('hundred') && (g != 0)
      g *= 100
    elsif word.eql?('thousand') && (g != 0)
      g *= 1000
    elsif x.nil?
      Lich::Messaging.msg("bold", "DRC: Unknown number word '#{word}' in '#{text_num}'")
      return nil
    else
      g += x
    end
  end

  g
end

.tune_instrument(settings) ⇒ Boolean

Tunes an instrument to correct pitch, removing/getting it as needed. Requires two free hands. Handles worn vs inventory instruments. Calls do_tune recursively to adjust sharp/flat. Re-wears the instrument after tuning if it was originally worn. Returns false if no instrument is configured, hands are full, or instrument cannot be retrieved.

Examples:

settings = OpenStruct.new(worn_instrument: 'lute')
DRC.tune_instrument(settings) #=> true (if tuned successfully)

Parameters:

  • settings (OpenStruct) —

    outfit settings with worn_instrument and/or instrument keys

Returns:

  • (Boolean) —

    true if successfully tuned, false if preconditions fail

See Also:

  • #do_tune
  • #clean_instrument
  • #play_song?


1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
# File 'documented/dragonrealms/commons/common.rb', line 1204

def tune_instrument(settings)
  instrument = settings.worn_instrument || settings.instrument

  unless instrument
    Lich::Messaging.msg("bold", "DRC: Neither worn_instrument nor instrument set. Doing nothing.")
    return false
  end

  DRC.stop_playing

  unless (DRC.left_hand.nil? && DRC.right_hand.nil?) || DRCI.in_hands?(instrument)
    Lich::Messaging.msg("bold", "DRC: Need two free hands. Not tuning now.")
    return false
  end

  if settings.worn_instrument
    unless DRCI.remove_item?(instrument) || DRCI.in_hands?(instrument)
      Lich::Messaging.msg("bold", "DRC: Could not remove #{instrument}. Not trying to tune.")
      DRC.beep
      return false
    end
  else
    unless DRCI.get_item?(instrument) || DRCI.in_hands?(instrument)
      Lich::Messaging.msg("bold", "DRC: Could not get #{instrument}. Not trying to tune.")
      DRC.beep
      return false
    end
  end
  DRC.do_tune(instrument)
  waitrt?
  pause 1
  DRCI.wear_item?(instrument) if settings.worn_instrument
  true
end

.unpause_all ⇒ 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.

Unpauses scripts that were paused by pause_all, but not those that were already paused. Clears the tracking list and releases the global lock. Returns false if the lock is not held by the current script.

Examples:

DRC.pause_all
# ... do work ...
DRC.unpause_all #=> true

Returns:

  • (Boolean) —

    true if successfully unpaused and released lock, false if lock not held

See Also:

  • #pause_all
  • #smart_pause_all
  • #safe_unpause_list


1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
# File 'documented/dragonrealms/commons/common.rb', line 1318

def unpause_all
  return false unless $pause_all_lock.owned?

  Script.running.find_all do |script|
    script.paused? &&
      !@pause_all_no_unpause.include?(script)
  end
        .each(&:unpause)

  @pause_all_no_unpause = []
  $pause_all_lock.unlock

  true
end

.unpause_all_list(scripts_to_unpause) ⇒ void

This method returns an undefined value.

Unpauses a specific list of scripts by name, logging the completion. Returns silently if the list is empty.

Examples:

paused = DRC.smart_pause_all
# ... do work ...
DRC.unpause_all_list(paused)

Parameters:

  • scripts_to_unpause (Array<String>) —

    names of scripts to unpause

See Also:

  • #smart_pause_all


1364
1365
1366
1367
1368
1369
1370
1371
# File 'documented/dragonrealms/commons/common.rb', line 1364

def unpause_all_list(scripts_to_unpause)
  if scripts_to_unpause.empty?
    Lich::Messaging.msg("plain", "DRC: #{Script.self.name} has finished.")
  else
    Lich::Messaging.msg("plain", "DRC: Unpausing #{scripts_to_unpause}, #{Script.self.name} has finished.")
    Script.running.find_all { |s| s.paused? && !s.no_pause_all && scripts_to_unpause.include?(s.name) }.each(&:unpause)
  end
end

.verify_script(script_names) ⇒ Boolean

Checks that one or more scripts exist in the current game environment. Logs a bold message for each missing script and returns false if any are not found.

Examples:

DRC.verify_script('safe-room') #=> true
DRC.verify_script(['hunt', 'loot', 'missing-script'])
# => logs "DRC: Failed to find a script named 'missing-script'", returns false

Parameters:

  • script_names (String, Array<String>) —

    script name(s) to verify

Returns:

  • (Boolean) —

    true if all scripts exist, false if any are missing

See Also:

  • #wait_for_script_to_complete


247
248
249
250
251
252
253
254
255
256
257
# File 'documented/dragonrealms/commons/common.rb', line 247

def verify_script(script_names)
  script_names = [script_names] unless script_names.is_a?(Array)
  state = true
  script_names
    .reject { |name| Script.exists?(name) }
    .each do |name|
      Lich::Messaging.msg("bold", "DRC: Failed to find a script named '#{name}'")
      state = false
    end
  state
end

.wait_for_script_to_complete(name, args = [], flags = {}) ⇒ OpenStruct?

Starts a script and blocks until it finishes running, then returns its handle. Verifies the script exists before starting. Pauses 2 seconds after start, then polls Script.running every 0.5 seconds until the handle disappears.

Examples:

DRC.wait_for_script_to_complete('forage', ['apple'])
DRC.wait_for_script_to_complete('sell', ['item'], {quiet: true})

Parameters:

  • name (String) —

    the script name

  • args (Array) (defaults to: []) —

    command-line arguments; quoted if they contain whitespace

  • flags (Hash) (defaults to: {}) —

    flags passed to start_script (e.g. true)

Returns:

  • (OpenStruct, nil) —

    the script handle, or nil if script does not exist

See Also:

  • #verify_script


271
272
273
274
275
276
277
278
279
# File 'documented/dragonrealms/commons/common.rb', line 271

def wait_for_script_to_complete(name, args = [], flags = {})
  verify_script(name)
  script_handle = start_script(name, args.map { |arg| arg.to_s =~ /\s/ ? "\"#{arg}\"" : arg }, flags)
  if script_handle
    pause 2
    pause 0.5 while Script.running.include?(script_handle)
  end
  script_handle
end