Module: Lich::DragonRealms

Includes:
Lich
Defined in:
documented/games.rb,
documented/dragonrealms/creature.rb,
documented/dragonrealms/drinfomon.rb,
documented/dragonrealms/commons/common.rb,
documented/dragonrealms/commons/slackbot.rb,
documented/dragonrealms/drinfomon/drdefs.rb,
documented/dragonrealms/drinfomon/drroom.rb,
documented/dragonrealms/drinfomon/events.rb,
documented/dragonrealms/drinfomon/drskill.rb,
documented/dragonrealms/drinfomon/drstats.rb,
documented/dragonrealms/drinfomon/startup.rb,
documented/dragonrealms/drinfomon/drparser.rb,
documented/dragonrealms/drinfomon/drspells.rb,
documented/dragonrealms/drinfomon/drbanking.rb,
documented/dragonrealms/commons/common-items.rb,
documented/dragonrealms/commons/common-money.rb,
documented/dragonrealms/commons/equipmanager.rb,
documented/dragonrealms/custom_substitutions.rb,
documented/dragonrealms/commons/common-arcana.rb,
documented/dragonrealms/commons/common-travel.rb,
documented/dragonrealms/drinfomon/drvariables.rb,
documented/dragonrealms/commons/common-healing.rb,
documented/dragonrealms/commons/common-theurgy.rb,
documented/dragonrealms/drinfomon/drexpmonitor.rb,
documented/dragonrealms/commons/common-crafting.rb,
documented/dragonrealms/commons/common-moonmage.rb,
documented/dragonrealms/commons/common-summoning.rb,
documented/dragonrealms/commons/common-money-data.rb,
documented/dragonrealms/commons/common-validation.rb,
documented/dragonrealms/dependency/settings_config.rb,
documented/dragonrealms/commons/common-healing-data.rb

Overview

Module containing configuration settings for DragonRealms.

This module holds the configuration constants used by the SettingsTransformer.

See Also:

  • SettingsConfig

Defined Under Namespace

Modules: Creature, CustomSubstitutions, DRBanking, DRC, DRCA, DRCC, DRCH, DRCI, DRCM, DRCMM, DRCS, DRCT, DRCTH, DRDefsPattern, DRExpMonitor, DRInfomon, DRParser, DRSpells, DRStats, SettingsConfig Classes: CharacterValidator, CreatureInstance, DRRoom, DRSkill, EquipmentManager, Flags, Game, GameInstance, SlackBot

Constant Summary collapse

DR_LEARNING_RATES =

Learning rate progression levels in DragonRealms, from slowest to fastest.

These levels are used to interpret the "learning" component of skill experience displays and are ordered from "clear" (no experience gain) to "mind lock" (maximum learning rate).

Returns:

  • (Array<String>)

    immutable array of learning rate names

[
  'clear',
  'dabbling',
  'perusing',
  'learning',
  'thoughtful',
  'thinking',
  'considering',
  'pondering',
  'ruminating',
  'concentrating',
  'attentive',
  'deliberative',
  'interested',
  'examining',
  'understanding',
  'absorbing',
  'intrigued',
  'scrutinizing',
  'analyzing',
  'studious',
  'focused',
  'very focused',
  'engaged',
  'very engaged',
  'cogitating',
  'fascinated',
  'captivated',
  'engrossed',
  'riveted',
  'very riveted',
  'rapt',
  'very rapt',
  'enthralled',
  'nearly locked',
  'mind lock'
].freeze
DR_LONGEST_LEARNING_RATE_LENGTH =

Length of the longest learning rate name, used for padding in exp display

DR_LEARNING_RATES.max_by(&:length).length
DR_BALANCE_VALUES =

Combat balance modifiers in DragonRealms, reflecting body control and stance.

These values describe how well-balanced a combatant is, independent of positional advantage against an opponent. Used to interpret combat status messages.

Returns:

  • (Array<String>)

    immutable array of balance descriptions

[
  'completely',
  'hopelessly',
  'extremely',
  'very badly',
  'badly',
  'somewhat off',
  'off',
  'slightly off',
  'solidly',
  'nimbly',
  'adeptly',
  'incredibly'
].freeze
DR_POSITION_VALUES =

Combat positioning relative to your opponent, captured from the balance status line (e.g. "[You're solidly balanced and in good position.]"). Stored as a signed magnitude: positive means you hold the advantage, negative means your opponent does, and 0 is an even contest. The scale is symmetric, and the two "overwhelming opponent" phrasings map to the same value.

{
  'opponent overwhelming you'        => -9,
  'opponent dominating'              => -8,
  'opponent in excellent position'   => -7,
  'opponent in superior position'    => -6,
  'opponent in very strong position' => -5,
  'opponent in strong position'      => -4,
  'opponent in good position'        => -3,
  'opponent in better position'      => -2,
  'opponent has slight advantage'    => -1,
  'no advantage'                     => 0,
  'have slight advantage'            => 1,
  'in better position'               => 2,
  'in good position'                 => 3,
  'in strong position'               => 4,
  'in very strong position'          => 5,
  'in superior position'             => 6,
  'in excellent position'            => 7,
  'in dominating position'           => 8,
  'overwhelming opponent'            => 9,
  'overwhelming your opponent'       => 9
}.freeze
DR_SKILLS_DATA =

Canonical skill hierarchy and guild-specific skill aliases for DragonRealms.

Contains two top-level keys:

  • skillsets: a [Hash] mapping skill categories ("Armor", "Lore", "Weapon", "Magic", "Survival") to arrays of individual skill names
  • guild_skill_aliases: a [Hash] mapping guild names to their primary magic overrides (e.g., "Cleric" maps "Primary Magic" to "Holy Magic")

Returns:

  • (Hash)

    immutable nested structure with symbolized and stringified keys

{
  skillsets: {
    'Armor'    => [
      'Shield Usage',
      'Light Armor',
      'Chain Armor',
      'Brigandine',
      'Plate Armor',
      'Defending',
      'Conviction'
    ].freeze,
    'Lore'     => [
      'Alchemy',
      'Appraisal',
      'Enchanting',
      'Engineering',
      'Forging',
      'Outfitting',
      'Performance',
      'Scholarship',
      'Tactics',
      'Empathy',
      'Bardic Lore',
      'Trading',
      'Mechanical Lore'
    ].freeze,
    'Weapon'   => [
      'Parry Ability',
      'Small Edged',
      'Large Edged',
      'Twohanded Edged',
      'Small Blunt',
      'Large Blunt',
      'Twohanded Blunt',
      'Slings',
      'Bow',
      'Crossbow',
      'Staves',
      'Polearms',
      'Light Thrown',
      'Heavy Thrown',
      'Brawling',
      'Offhand Weapon',
      'Melee Mastery',
      'Missile Mastery',
      'Expertise'
    ].freeze,
    'Magic'    => [
      'Primary Magic',
      'Arcana',
      'Attunement',
      'Augmentation',
      'Debilitation',
      'Targeted Magic',
      'Utility',
      'Warding',
      'Sorcery',
      'Astrology',
      'Summoning',
      'Theurgy',
      'Inner Magic',
      'Inner Fire',
      'Lunar Magic',
      'Elemental Magic',
      'Holy Magic',
      'Life Magic',
      'Arcane Magic'
    ].freeze,
    'Survival' => [
      'Evasion',
      'Athletics',
      'Perception',
      'Stealth',
      'Locksmithing',
      'Thievery',
      'First Aid',
      'Outdoorsmanship',
      'Skinning',
      'Instinct',
      'Backstab',
      'Thanatology'
    ].freeze
  }.freeze,
  guild_skill_aliases: {
    'Cleric'       => { 'Primary Magic' => 'Holy Magic' }.freeze,
    'Necromancer'  => { 'Primary Magic' => 'Arcane Magic' }.freeze,
    'Warrior Mage' => { 'Primary Magic' => 'Elemental Magic' }.freeze,
    'Thief'        => { 'Primary Magic' => 'Inner Magic' }.freeze,
    'Barbarian'    => { 'Primary Magic' => 'Inner Fire' }.freeze,
    'Ranger'       => { 'Primary Magic' => 'Life Magic' }.freeze,
    'Bard'         => { 'Primary Magic' => 'Elemental Magic' }.freeze,
    'Paladin'      => { 'Primary Magic' => 'Holy Magic' }.freeze,
    'Empath'       => { 'Primary Magic' => 'Life Magic' }.freeze,
    'Trader'       => { 'Primary Magic' => 'Lunar Magic' }.freeze,
    'Moon Mage'    => { 'Primary Magic' => 'Lunar Magic' }.freeze
  }.freeze
}.freeze
KRONAR_BANKS =

Towns in DragonRealms where Kronars (the primary currency) can be exchanged.

Returns:

  • (Array<String>)

    immutable array of town names

['Crossings', 'Dirge', 'Ilaya Taipa', 'Leth Deriel'].freeze
LIRUM_BANKS =

Towns in DragonRealms where Lirums (the eastern currency) can be exchanged.

Returns:

  • (Array<String>)

    immutable array of town names

["Aesry Surlaenis'a", "Hara'jaal", "Mer'Kresh", "Muspar'i", 'Ratha', 'Riverhaven', "Rossman's Landing", 'Therenborough', 'Throne City'].freeze
DOKORA_BANKS =

Towns in DragonRealms where Dokoras (the southern currency) can be exchanged.

Returns:

  • (Array<String>)

    immutable array of town names

['Ain Ghazal', 'Boar Clan', "Chyolvea Tayeu'a", 'Hibarnhvidar', 'Fang Cove', "Raven's Point", 'Shard'].freeze
BANK_TITLES =

Room titles of bank deposit windows in DragonRealms towns, indexed by town name.

Maps each town to an array of full room titles (e.g., "[[Provincial Bank, Teller]]"). Used to locate and identify bank deposit windows when depositing or checking balances.

Returns:

  • (Hash{String => Array<String>})

    immutable mapping from town name to room title array

{
  "Aesry Surlaenis'a" => ['[[Tona Kertigen, Deposit Window]]'].freeze,
  'Ain Ghazal'        => ['[[Ain Ghazal, Private Depository]]'].freeze,
  'Boar Clan'         => ['[[Ranger Guild, Bank]]'].freeze,
  "Chyolvea Tayeu'a"  => ["[[Chyolvea Tayeu'a, Teller]]"].freeze,
  'Crossings'         => ['[[Provincial Bank, Teller]]'].freeze,
  'Dirge'             => ["[[Dirge, Traveller's Bank]]"].freeze,
  'Fang Cove'         => ['[[First Council Banking, Vault]]'].freeze,
  "Hara'jaal"         => ["[[Baron's Forset, Teller]]"].freeze,
  'Hibarnhvidar'      => ['[[Second Provincial Bank of Hibarnhvidar, Teller]]', '[[Hibarnhvidar, Teller Windows]]', '[[First Arachnid Bank, Lobby]]'].freeze,
  'Ilaya Taipa'       => ['[[Ilaya Taipa, Trader Outpost Bank]]'].freeze,
  'Leth Deriel'       => ['[[Imperial Depository, Domestic Branch]]'].freeze,
  "Mer'Kresh"         => ["[[Harti Clemois Bank, Teller's Window]]"].freeze,
  "Muspar'i"          => ["[[Old Lata'arna Keep, Teller Windows]]"].freeze,
  'Ratha'             => ['[[Lower Bank of Ratha, Cashier]]', '[[Sshoi-sson Palace, Grand Provincial Bank, Bursarium]]'].freeze,
  "Raven's Point"     => ["[[Bank of Raven's Point, Depository]]"].freeze,
  'Riverhaven'        => ['[[Bank of Riverhaven, Teller]]'].freeze,
  "Rossman's Landing" => ["[[Traders' Guild Outpost, Depository]]"].freeze,
  'Shard'             => ["[[First Bank of Ilithi, Teller's Windows]]"].freeze,
  'Therenborough'     => ['[[Bank of Therenborough, Teller]]'].freeze,
  'Throne City'       => ['[[Faldesu Exchequer, Teller]]'].freeze
}.freeze
VAULT_TITLES =

Room titles of vault carousel chambers in DragonRealms towns, indexed by town name.

Maps each town with a vault carousel to its full room title (e.g., "[[Crossing, Carousel Chamber]]"). Used to locate and identify vault chambers when managing long-term item storage.

Returns:

  • (Hash{String => Array<String>})

    immutable mapping from town name to room title array

{
  'Crossings'     => ['[[Crossing, Carousel Chamber]]'].freeze,
  'Fang Cove'     => ['[[Fang Cove, Carousel Chamber]]'].freeze,
  'Leth Deriel'   => ['[[Leth Deriel, Carousel Chamber]]'].freeze,
  "Mer'Kresh"     => ["[[Mer'Kresh, Carousel Square]]"].freeze,
  "Muspar'i"      => ["[[Muspar'i, Carousel Square]]"].freeze,
  'Ratha'         => ['[[Ratha, Carousel Square]]'].freeze,
  'Riverhaven'    => ['[[Riverhaven, Carousel Chamber]]'].freeze,
  'Shard'         => ['[[Shard, Carousel Chamber]]'].freeze,
  'Therenborough' => ['[[Therenborough, Carousel Chamber]]'].freeze
}.freeze
UNKNOWN_DURATION =

Some spells may last for an unknown duration, such as cyclic spells that last as long as the caster can harness mana for it. Or, barbarian abilities when the character doesn't have Power Monger mastery to see true durations but only vague guestimates. In those situations, we set use this value.

1000
HOMETOWN_REGEX_MAP =

Case-insensitive regex patterns for matching DragonRealms hometown abbreviations and aliases.

Maps each canonical hometown name to a pattern that matches common abbreviations and full names. Patterns are anchored to word boundaries and support apostrophe/optional-character variations in names. For example, 'Therenborough' matches /^(theren(borough)?)$/i to accept both "theren" and "therenborough".

Returns:

  • (Hash{String => Regexp})

    immutable mapping from canonical town name to regex pattern

See Also:

{
  'Arthe Dale'        => /^(arthe( dale)?)$/i,
  'Crossing'          => /^(cross(ing)?)$/i,
  'Darkling Wood'     => /^(darkling( wood)?)$/i,
  'Dirge'             => /^(dirge)$/i,
  "Fayrin's Rest"     => /^(fayrin'?s?( rest)?)$/i,
  'Leth Deriel'       => /^(leth( deriel)?)$/i,
  'Shard'             => /^(shard)$/i,
  'Steelclaw Clan'    => /^(steel( )?claw( clan)?|SCC)$/i,
  'Stone Clan'        => /^(stone( clan)?)$/i,
  'Tiger Clan'        => /^(tiger( clan)?)$/i,
  'Wolf Clan'         => /^(wolf( clan)?)$/i,
  'Riverhaven'        => /^(river|haven|riverhaven)$/i,
  "Rossman's Landing" => /^(rossman'?s?( landing)?)$/i,
  'Therenborough'     => /^(theren(borough)?)$/i,
  'Langenfirth'       => /^(lang(enfirth)?)$/i,
  'Fornsted'          => /^(fornsted)$/i,
  'Hvaral'            => /^(hvaral)$/i,
  'Ratha'             => /^(ratha)$/i,
  'Aesry'             => /^(aesry)$/i,
  "Mer'Kresh"         => /^(mer'?kresh)$/i,
  'Throne City'       => /^(throne( city)?)$/i,
  'Hibarnhvidar'      => /^(hib(arnhvidar)?)$/i,
  "Raven's Point"     => /^(raven'?s?( point)?)$/i,
  'Boar Clan'         => /^(boar( clan)?)$/i,
  'Fang Cove'         => /^(fang( cove)?)$/i,
  "Muspar'i"          => /^(muspar'?i)$/i,
  'Ain Ghazal'        => /^(ain( )?ghazal)$/i
}.freeze
HOMETOWN_LIST =

List of canonical town names, like 'Therenborough' and 'Langenfirth'.

HOMETOWN_REGEX_MAP.keys.freeze
HOMETOWN_REGEX =

Union of regular expressions that match town names, like /^(theren(borough)?)$/i

Regexp.union(HOMETOWN_REGEX_MAP.values)
ORDINALS =

English ordinal words from "first" through "twentieth".

Used to parse and convert written ordinal expressions ("first", "third", "tenth") in game text.

Returns:

  • (Array<String>)

    immutable array of ordinal words

%w[first second third fourth fifth sixth seventh eighth ninth tenth eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth eighteenth nineteenth twentieth].freeze
CURRENCIES =

The three playable currencies in DragonRealms.

Kronars are primary in western towns; Lirums in the east; Dokoras in the south.

Returns:

  • (Array<String>)

    immutable array of currency names

%w[Kronars Lirums Dokoras].freeze
ENC_MAP =

Encumbrance levels in DragonRealms, mapped to numeric burden values.

Maps descriptive encumbrance states ("None", "Light Burden", "Overburdened", etc.) to a numeric scale from 0 (no burden) to 11 (maximum burden). Used to interpret encumbrance status messages and assess character mobility.

Returns:

  • (Hash{String => Integer})

    immutable mapping from encumbrance description to numeric level

{
  'None'                              => 0,
  'Light Burden'                      => 1,
  'Somewhat Burdened'                 => 2,
  'Burdened'                          => 3,
  'Heavy Burden'                      => 4,
  'Very Heavy Burden'                 => 5,
  'Overburdened'                      => 6,
  'Very Overburdened'                 => 7,
  'Extremely Overburdened'            => 8,
  'Tottering Under Burden'            => 9,
  'Are you even able to move?'        => 10,
  "It's amazing you aren't squashed!" => 11
}.freeze
NUM_MAP =

English number words mapped to their integer values.

Covers cardinal numbers from "zero" through "ninety" (including teens and common tens). Used to parse written numbers in game text and convert them to integers.

Returns:

  • (Hash{String => Integer})

    immutable mapping from word to integer value

{
  'zero'      => 0,
  'one'       => 1,
  'two'       => 2,
  'three'     => 3,
  'four'      => 4,
  'five'      => 5,
  'six'       => 6,
  'seven'     => 7,
  'eight'     => 8,
  'nine'      => 9,
  'ten'       => 10,
  'eleven'    => 11,
  'twelve'    => 12,
  'thirteen'  => 13,
  'fourteen'  => 14,
  'fifteen'   => 15,
  'sixteen'   => 16,
  'seventeen' => 17,
  'eighteen'  => 18,
  'nineteen'  => 19,
  'twenty'    => 20,
  'thirty'    => 30,
  'forty'     => 40,
  'fifty'     => 50,
  'sixty'     => 60,
  'seventy'   => 70,
  'eighty'    => 80,
  'ninety'    => 90
}.freeze
BOX_WOODS =

Box wood/material adjectives recognized in rummaged box lists. Players extend this via the custom_box_woods setting; see DRC.box_list_to_adj_and_noun.

%w[brass copper deobar driftwood iron ironwood mahogany oaken pine steel wooden].freeze
BOX_CONTAINERS =

Box container nouns recognized in rummaged box lists. Players extend this via the custom_box_containers setting.

%w[box caddy casket chest coffer crate skippet strongbox trunk].freeze
BOX_REGEX =

Recognizes " " box descriptions. Built from BOX_WOODS and BOX_CONTAINERS so both remain a single source of truth; kept as a global ($box_regex) for third-party scripts.

/((?:#{BOX_WOODS.join('|')}) (?:#{BOX_CONTAINERS.join('|')}))/.freeze
MANA_MAP =

Mana adjectives grouped by mana development tier in DragonRealms.

Maps each development level ("weak", "developing", "improving", "good") to an array of adjectives that indicate mana at that tier (e.g., "weak" mana may appear "dim", "glowing", or "bright"). Used to parse spell descriptions and estimate mana control.

Returns:

  • (Hash{String => Array<String>})

    immutable mapping from tier to array of adjectives

{
  'weak'       => %w[dim glowing bright].freeze,
  'developing' => %w[faint muted glowing luminous bright].freeze,
  'improving'  => %w[faint hazy flickering shimmering glowing lambent shining fulgent glaring].freeze,
  'good'       => %w[faint dim hazy dull muted dusky pale flickering shimmering pulsating glowing lambent shining luminous radiant fulgent brilliant flaring glaring blazing blinding].freeze
}.freeze
PRIMARY_SIGILS_PATTERN =

Pattern matching primary (tier 1) sigil names in spell descriptions.

Matches the five primary sigils: abolition, congruence, induction, permutation, rarefaction.

Examples:

"abolition sigil" =~ PRIMARY_SIGILS_PATTERN #=> 0
"congruence sigil" =~ PRIMARY_SIGILS_PATTERN #=> 0

Returns:

  • (Regexp)

    immutable regex matching a primary sigil word

See Also:

/\b(?:abolition|congruence|induction|permutation|rarefaction) sigil\b/.freeze
SECONDARY_SIGILS_PATTERN =

Pattern matching secondary (tier 2) sigil names in spell descriptions.

Matches the ten secondary sigils: antipode, ascension, clarification, decay, evolution, integration, metamorphosis, nurture, paradox, unity.

Examples:

"antipode sigil" =~ SECONDARY_SIGILS_PATTERN #=> 0
"unity sigil" =~ SECONDARY_SIGILS_PATTERN #=> 0

Returns:

  • (Regexp)

    immutable regex matching a secondary sigil word

See Also:

/\b(?:antipode|ascension|clarification|decay|evolution|integration|metamorphosis|nurture|paradox|unity) sigil\b/.freeze
VOL_MAP =

Container volume categories in DragonRealms, mapped to numeric capacity values.

Maps descriptive size adjectives ("enormous", "tiny", etc.) to numeric volume units. Used to estimate storage capacity of containers based on their described size.

Returns:

  • (Hash{String => Integer})

    immutable mapping from volume adjective to capacity value

{
  'enormous' => 20,
  'massive'  => 10,
  'huge'     => 5,
  'large'    => 4,
  'medium'   => 3,
  'small'    => 2,
  'tiny'     => 1
}.freeze

Constants included from Lich

DEFAULT_SQLITE_BUSY_TIMEOUT_MS, MAX_DEBUG_LOGS_DEFAULT, MAX_DEBUG_LOGS_MINIMUM, ROOMID_LOCATIONS

Instance Method Summary collapse

Methods included from Lich

db_mutex, mutex_lock, mutex_unlock

Instance Method Details

#add_ordinals_to_duplicates(npc_list) ⇒ Array<String>

Adds ordinal prefixes to duplicate NPCs (e.g., "second goblin").

Parameters:

  • npc_list (Array<String>)

    Array of NPC names

Returns:

  • (Array<String>)

    Array with ordinals added to duplicates



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 252

def add_ordinals_to_duplicates(npc_list)
  flat_npcs = []

  npc_list.uniq.each do |npc|
    # Count how many times this NPC appears
    count = npc_list.count(npc)

    # Create entries with ordinals for duplicates
    count.times do |index|
      if index.zero?
        flat_npcs << npc
      else
        # Use ordinal from $ORDINALS if available, otherwise generate one
        ordinal = $ORDINALS[index] || "#{index + 1}th"
        flat_npcs << "#{ordinal} #{npc}"
      end
    end
  end

  flat_npcs
end

#check_exp_modsArray<String>?

Issues the 'exp mods' command and parses skill modifiers.

Returns:

  • (Array<String>, nil)

    The captured output lines or nil on timeout



88
89
90
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 88

def check_exp_mods
  Lich::Util.issue_command("exp mods", /The following skills are currently under the influence of a modifier/, /^<output class=""/, quiet: true, include_end: false, usexml: false)
end

#clean_and_split(room_objs) ⇒ Array<String>

Cleans room objects string and splits into array. Removes "You also see" prefix and mount descriptions.

Parameters:

  • room_objs (String)

    Raw room objects string from game output

Returns:

  • (Array<String>)

    Array of individual object strings



111
112
113
114
115
116
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 111

def clean_and_split(room_objs)
  room_objs.sub(DRDefsPattern::YOU_ALSO_SEE, '')
           .sub(DRDefsPattern::MOUNT_DESCRIPTION, '')
           .strip
           .split(DRDefsPattern::COMMA_OR_AND)
end

#clean_npc_string(npc_string) ⇒ Array<String>

Cleans and normalizes NPC strings to creature names. Handles XML tag removal, name normalization, and ordinal numbering.

Parameters:

  • npc_string (Array<String>)

    Array of raw NPC strings

Returns:

  • (Array<String>)

    Array of cleaned creature names with ordinals



182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 182

def clean_npc_string(npc_string)
  # Normalize NPC names
  normalized_npcs = npc_string
                    .map { |obj| normalize_creature_names(obj) }
                    .map { |obj| remove_html_tags(obj) }
                    .map { |obj| extract_last_creature(obj) }
                    .map { |obj| extract_final_name(obj) }
                    .compact
                    .sort

  # Count occurrences and add ordinals
  add_ordinals_to_duplicates(normalized_npcs)
end

#convert2copper(amt, denomination) ⇒ Integer

Converts an amount and denomination to copper.

Parameters:

  • amt (Integer, String)

    The amount to convert

  • denomination (String)

    The denomination (platinum, gold, silver, bronze, copper)

Returns:

  • (Integer)

    The value in copper



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 72

def convert2copper(amt, denomination)
  if denomination =~ /platinum/
    (amt.to_i * 10_000)
  elsif denomination =~ /gold/
    (amt.to_i * 1000)
  elsif denomination =~ /silver/
    (amt.to_i * 100)
  elsif denomination =~ /bronze/
    (amt.to_i * 10)
  else
    amt
  end
end

#convert2plats(copper) ⇒ String

Converts copper to a readable currency string with denominations.

Parameters:

  • copper (Integer)

    Amount in copper

Returns:

  • (String)

    Formatted string like "5 platinum, 3 gold, 2 silver"



95
96
97
98
99
100
101
102
103
104
105
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 95

def convert2plats(copper)
  denominations = [[10_000, 'platinum'], [1000, 'gold'], [100, 'silver'], [10, 'bronze'], [1, 'copper']]
  denominations.inject([copper, []]) do |result, denomination|
    remaining = result.first
    display = result.last
    if remaining / denomination.first > 0
      display << "#{remaining / denomination.first} #{denomination.last}"
    end
    [remaining % denomination.first, display]
  end.last.join(', ')
end

#extract_final_name(text) ⇒ String?

Extracts just the creature name (letters, hyphens, apostrophes).

Parameters:

  • text (String)

    Text containing creature name

Returns:

  • (String, nil)

    The final creature name or nil



244
245
246
247
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 244

def extract_final_name(text)
  # Extract just the creature name (letters, hyphens, apostrophes)
  text.strip.scan(DRDefsPattern::CREATURE_NAME).first
end

#extract_last_creature(text) ⇒ String

Extracts the last creature from an "X and Y" string. Removes modifiers like "glowing with a holy aura".

Parameters:

  • text (String)

    Text potentially containing multiple creatures

Returns:

  • (String)

    The last creature name portion



236
237
238
239
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 236

def extract_last_creature(text)
  # Get the last creature name after "and", removing modifiers like "glowing with"
  text.split(/\sand\s/).last.sub(DRDefsPattern::GLOWING_WITH, '')
end

#extract_npcs(room_objs, select_dead: false) ⇒ Array<String>

Extract NPCs from room objects

Parameters:

  • room_objs (String)

    The room objects string from game output

  • select_dead (Boolean) (defaults to: false)

    If true, select only dead NPCs; if false, reject dead NPCs

Returns:

  • (Array<String>)

    List of NPC names



278
279
280
281
282
283
284
285
286
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 278

def extract_npcs(room_objs, select_dead: false)
  all_npcs = find_all_npcs(room_objs)
  filtered = if select_dead
               all_npcs.select { |obj| DRDefsPattern::DEAD_NPC.match?(obj) }
             else
               all_npcs.reject { |obj| DRDefsPattern::DEAD_NPC.match?(obj) }
             end
  clean_npc_string(filtered)
end

#extract_pcs(room_players, filter_pattern: nil, status_pattern: DRDefsPattern::PLAYER_STATUS) ⇒ Array<String>

Extract player names from room player string

Parameters:

  • room_players (String)

    The room players string from game output

  • filter_pattern (Regexp, nil) (defaults to: nil)

    Optional pattern to filter players (e.g., LYING_DOWN, SITTING)

  • status_pattern (Regexp) (defaults to: DRDefsPattern::PLAYER_STATUS)

    Pattern to remove status text (defaults to PLAYER_STATUS)

Returns:

  • (Array<String>)

    List of player names



135
136
137
138
139
140
141
142
143
144
145
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 135

def extract_pcs(room_players, filter_pattern: nil, status_pattern: DRDefsPattern::PLAYER_STATUS)
  return [] if room_players.nil? || room_players.empty?

  players = normalize_trailing_and(room_players).split(', ')
  players = players.select { |obj| filter_pattern.match?(obj) } if filter_pattern

  players
    .map { |obj| obj.sub(status_pattern, '').sub(DRDefsPattern::PARENTHETICAL, '') }
    .map { |obj| obj.strip.scan(DRDefsPattern::PLAYER_NAME).first }
    .compact
end

#find_all_npcs(room_objs) ⇒ Array<String>

Finds all NPC strings (both living and dead) in room objects.

Parameters:

  • room_objs (String)

    Raw room objects string from game output

Returns:

  • (Array<String>)

    Array of raw NPC strings with XML tags



171
172
173
174
175
176
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 171

def find_all_npcs(room_objs)
  room_objs.sub(DRDefsPattern::YOU_ALSO_SEE, '')
           .sub(DRDefsPattern::MOUNT_DESCRIPTION, '')
           .strip
           .scan(DRDefsPattern::NPC_SCAN)
end

#find_dead_npcs(room_objs) ⇒ Array<String>

Finds dead NPCs in the room.

Parameters:

  • room_objs (String)

    Raw room objects string from game output

Returns:

  • (Array<String>)

    Array of dead NPC names



298
299
300
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 298

def find_dead_npcs(room_objs)
  extract_npcs(room_objs, select_dead: true)
end

#find_npcs(room_objs) ⇒ Array<String>

Finds living NPCs in the room.

Parameters:

  • room_objs (String)

    Raw room objects string from game output

Returns:

  • (Array<String>)

    Array of living NPC names



291
292
293
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 291

def find_npcs(room_objs)
  extract_npcs(room_objs, select_dead: false)
end

#find_objects(room_objs) ⇒ Array<String>

Finds non-NPC objects in the room (items, furniture, etc.).

Parameters:

  • room_objs (String)

    Raw room objects string from game output

Returns:

  • (Array<String>)

    Array of object names



305
306
307
308
309
310
311
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 305

def find_objects(room_objs)
  # Use sub instead of sub! to avoid mutating frozen strings
  processed_objs = room_objs.sub(DRDefsPattern::GELAPOD, DRDefsPattern::GELAPOD_REPLACEMENT)
  clean_and_split(processed_objs)
    .reject { |obj| DRDefsPattern::PUSH_BOLD.match?(obj) }
    .map { |obj| obj.sub(DRDefsPattern::TRAILING_PERIOD, '').strip.sub(DRDefsPattern::LEADING_ARTICLE, '').strip }
end

#find_pcs(room_players) ⇒ Array<String>

Finds all player names in the room.

Parameters:

  • room_players (String)

    The "Also here:" room players string

Returns:

  • (Array<String>)

    List of player names



150
151
152
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 150

def find_pcs(room_players)
  extract_pcs(room_players)
end

#find_pcs_prone(room_players) ⇒ Array<String>

Finds players who are lying down in the room.

Parameters:

  • room_players (String)

    The "Also here:" room players string

Returns:

  • (Array<String>)

    List of prone player names



157
158
159
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 157

def find_pcs_prone(room_players)
  extract_pcs(room_players, filter_pattern: DRDefsPattern::LYING_DOWN, status_pattern: DRDefsPattern::WHO_STATUS)
end

#find_pcs_sitting(room_players) ⇒ Array<String>

Finds players who are sitting in the room.

Parameters:

  • room_players (String)

    The "Also here:" room players string

Returns:

  • (Array<String>)

    List of sitting player names



164
165
166
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 164

def find_pcs_sitting(room_players)
  extract_pcs(room_players, filter_pattern: DRDefsPattern::SITTING, status_pattern: DRDefsPattern::WHO_STATUS)
end

#normalize_creature_names(text) ⇒ String

Normalizes creature names that have variant descriptions, collapsing any string that contains a canonical name to just that name (e.g. "a sinewy leopard that is prowling" -> "sinewy leopard").

The canonical list is Lich::DragonRealms::DRDefsPattern::CREATURE_NORMALIZATIONS merged with the player's custom_creature_normalizations setting, so a player can teach Lich about their own problematic creature/pet descriptions without a Lich release. The merged list is memoized; see CustomSubstitutions.

This runs on the hot NPC-parsing path (#find_npcs fires on every room update), so it uses a plain substring check rather than compiling a regex per name per call.

Examples:

normalize_creature_names('a massive sinewy leopard') #=> 'sinewy leopard'

Parameters:

  • text (String)

    text containing a creature description

Returns:

  • (String)

    the canonical name if text contains one, else text

See Also:



215
216
217
218
219
220
221
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 215

def normalize_creature_names(text)
  names = CustomSubstitutions.resolve(:custom_creature_normalizations, DRDefsPattern::CREATURE_NORMALIZATIONS, type: :names)
  # Longest match against the ORIGINAL text, not a fold over the accumulator:
  # folding would let a later name that is a substring of an earlier match's
  # replacement (e.g. 'war yak' after 'grumpy war yak') re-collapse it.
  names.select { |name| text.include?(name) }.max_by(&:length) || text
end

#normalize_trailing_and(text) ⇒ String

Normalizes "X and Y" to "X, Y" for consistent splitting. Used to handle the lack of Oxford comma in game output.

Parameters:

  • text (String)

    Text that may end with "and X"

Returns:

  • (String)

    Text with trailing "and X" replaced by ", X"



122
123
124
125
126
127
128
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 122

def normalize_trailing_and(text)
  if (match = text.match(DRDefsPattern::TRAILING_AND))
    text.sub(DRDefsPattern::TRAILING_AND, ", #{match[:last]}")
  else
    text
  end
end

#remove_html_tags(text) ⇒ String

Removes pushBold/popBold XML tags from text.

Parameters:

  • text (String)

    Text with potential XML tags

Returns:

  • (String)

    Text with tags removed



226
227
228
229
230
# File 'documented/dragonrealms/drinfomon/drdefs.rb', line 226

def remove_html_tags(text)
  text
    .sub('<pushBold/>', '')
    .sub(%r{<popBold/>.*}, '')
end