Module: Lich::DragonRealms::DRCA

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

Overview

DragonRealms Common Arcana: utility functions for spell preparation, casting, and magical item management. Provides the contract for spell-casting scripts in DragonRealms.

Constant Summary collapse

CYCLIC_RELEASE_SUCCESS_PATTERNS =

Patterns matched when successfully releasing a cyclic spell. Each regex corresponds to the end-of-spell message for a cyclic ability across Ranger, Empath, Bard, Warrior Mage, Cleric, Paladin, Moon Mage, Trader, and Necromancer spell lines.

Returns:

  • (Array<Regexp>)

    immutable array of release success patterns

[
  # Ranger spells
  /^The world seems to accelerate around you as the spirit of the cheetah escapes you/, # Cheetah Swiftness
  /^You feel distinctly frail and vulnerable as the spirit of the bear leaves you/, # Bear Strength
  /^The forces of nature that you roused are no longer with you/, # Awaken Forest
  # Empath spells
  /^Aesandry Darlaeth loses cohesion, returning your reaction time to normal/, # Aesandry Darlaeth
  /^You sense your hold on your Guardian Spirit weaken, then evaporate entirely/, # Guardian Spirit
  /^The signs of empathic atrocity escape to the deepest pits of your personality, your touch no longer deadly/, # Icutu Zaharenela
  /^The tingling across your body diminishes as you feel the motes of energy fade away/, # Regenerate
  # Bard spells
  /^You sing, purposely warbling some of the held notes for effec/, # Abandoned Heart (ABAN)
  /^The final tones of your enchante end with an abrupt flourish that leaves stark silence in its wake/, # Aether Wolves (AEWO)
  /^With a rising crescendo in your voice, you reprise the strong lines of the chorus of Albreda's Balm before bringing it to an abrupt conclusion/, # Albreda's Balm (ALB)
  /^The final, quiet notes of Blessing of the Fae stir the air gently, and die away/, # Blessing of the Fae (BOTF)
  /^The warm air swirling around you stills and begins to cool/, # Caress of the Sun (CARE)
  /^A few fleeting, soporific notes tarry in the air before your lullaby slowly dies down like the night receding at Anduwen/, # Damaris' Lullaby (DALU)
  /^You no longer feel the clarity of vision you had, as shadows creep across the area/, # Eye of Kertigen (EYE)
  /^You let your voice fade even as the pace of Faenella's Grace slows, winding down to a quiet conclusion/, # Faenella's Grace (FAE)
  /^The aethereal static subsides, returning your spellcasting abilities to normal/, # Glythtide's Joy (GJ)
  /^As your rendition of Hodierna's Lilt winds down to a close, you let each note linger on the air a moment, drawing out the final moment with a reluctance to let the soothing melody fade/, # Hodierna's Lilt (HODI)
  /^You build the final notes of Phoenix's Pyre with an upward scale that rises into a steep crescendo, and end with an abrupt silence/, # Phoenix's Pyre (PYRE)
  /^The dome of light extinguishes as the final notes of music die away/, # Sanctuary
  # Warrior Mage spells
  /^The dark mantle of aether surrounding you fades away/, # Aether Cloak (AC)
  /^You release your connection to the Elemental Plane of Electricity, allowing the static electricity to dissipate/, # Electrostatic Eddy (EE)
  /^Your link to the Fire Rain matrix has been severed/, # Fire Rain (FR)
  /^The chilling vapor surrounding you dissipates slowly/, # Rimefang (spell) (RIM)
  /^The frost-covered blade circling around you shatters into a fine icy mist/, # Rimefang (spell) (RIM)
  /^The jagged stone spears surrounding you at .* range tremble slightly, then crumble into a grey dust that is quickly reclaimed by the earth/, # Ring of Spears (ROS)
  # Cleric spells
  /^The deadening murk around you subsides/, # Hydra Hex (HYH)
  /^The dark patch of grime around you subsides/, # Hydra Hex (HYH)
  /^You sense the dark presence depart/, # Soul Attrition (SA)
  # Resurrection (REZZ) does not have messaging that makes it usable here.
  /^The heightened sense of spiritual awareness leaves you/, # Revelation (REV)
  /^The swirling fog dissipates from around you/, # Ghost Shroud (GHS)
  # Paladin spells
  /^The holy golden radiance of your soul subsides, retreating into your body/, # Holy Warrior (HOW)
  /^Truffenyi's Rally ends, leaving behind a momentary sensation of something stuck in your throat/, # Truffenyi's Rally (TR)
  # Moon Mage spells
  /^The web of shadows twitches one last time and then goes inert/, # Shadow Web (SHW)
  /^You release your mental hold on the lunar energy that sustains your moongate/, # Moongate (MG)
  /^The refractive field surrounding you fades away/, # Steps of Vuan (SOV)
  /^A .* sphere suddenly flares with a cold light and vaporizes/, # Starlight Sphere (SLS)
  # Trader spells
  /^Your calligraphy of light assailing/, # Arbiter's Stylus (ARS)
  /^The .* moonsmoke blows away from your face/, # Mask of the Moons (MOM)
  # Necromancer spells
  /^The Rite of Contrition matrix loses cohesion, leaving your aura naked/, # Rite of Contrition (ROC)
  /^The Rite of Forbearance matrix loses cohesion, leaving you to wallow in temptation/, # Rite of Forbearance (ROF)
  /^The Rite of Grace matrix loses cohesion, leaving your body exposed/, # Rite of Grace (ROG)
  /^The greenish hues about you vanish as the Universal Solvent matrix loses its cohesion/, # Universal Solvent (USOL)
  /^You sense your Call from Within spell weaken and disperse/ # Call from Within (CFW)
].freeze
INFUSE_OM_SUCCESS_PATTERNS =

Patterns matched when Infuse Osrel Meraud succeeds. Includes messages for reaching full capacity, sensing fullness, or environmental interference.

Returns:

  • (Array<String>)

    immutable array of success indicators

[
  'having reached its full capacity',
  'A sense of fullness',
  'Something in the area is interfering with your attempt to harness'
].freeze
INFUSE_OM_FAILURE_PATTERNS =

Patterns matched when Infuse Osrel Meraud fails. Includes messages for insufficient mana, hunger, and user error.

Returns:

  • (Array<String>)

    immutable array of failure indicators

[
  'as if it hungers for more',
  'Your infusion fails completely',
  "You don't have enough harnessed mana to infuse that much",
  'You have no harnessed'
].freeze
INFUSE_OM_MAX_RETRIES =
20
PREPARE_MAX_RETRIES =
3
CAST_MAX_RETRIES =
3
BARB_BUFF_MAX_RETRIES =
3
STOW_FOCUS_MAX_RETRIES =
3
WIELD_FOCUS_SUCCESS_PATTERNS =

Patterns matched when successfully wielding a spell focus.

Returns:

  • (Array<Regexp>)

    immutable array of wield success patterns

[
  /^You draw out/,
  /^You grab/,
  /^You slip/,
  /^You deftly remove/
].freeze
WIELD_FOCUS_FAILURE_PATTERNS =

Patterns matched when failing to wield a spell focus.

Returns:

  • (Array<Regexp>)

    immutable array of wield failure patterns

[
  /^What were you/,
  /^Wield what/,
  /^You need a free hand/
].freeze
SHEATHE_FOCUS_SUCCESS_PATTERNS =

Patterns matched when successfully sheathing a spell focus.

Returns:

  • (Array<Regexp>)

    immutable array of sheathe success patterns

[
  /^You sheathe/,
  /^Sheathing/,
  /^You secure/,
  /^You slip/,
  /^You hang/,
  /^You strap/,
  /^You easily strap/
].freeze
SHEATHE_FOCUS_FAILURE_PATTERNS =

Patterns matched when failing to sheathe a spell focus.

Returns:

  • (Array<Regexp>)

    immutable array of sheathe failure patterns

[
  /^Sheathe your .* where/,
  /^There's no room/
].freeze
STARLIGHT_MESSAGES =

Progression of Trader aura growth messages from lowest to highest starlight level. Used to determine aura level when perceiving aura.

Returns:

  • (Array<String>)

    immutable array of 10 aura level messages

[
  'The smallest hint of starlight flickers within your aura',
  'A bare flicker of starlight plays within your aura',
  'A faint amount of starlight illuminates your aura',
  'Your aura pulses slowly with starlight',
  'A steady pulse of starlight runs through your aura',
  'Starlight dances vividly across the confines of your aura',
  'Strong pulses of starlight flare within your aura',
  'Your aura seethes with brilliant starlight',
  'Your aura is blinding',
  'The power contained in your aura'
].freeze
CHARGE_LEVELS =

Patterns matched to determine a Warrior Mage's elemental charge level. Ranges from level 0 (no charge) to level 11 (maximum charge). Used by #check_elemental_charge to infer readiness for abilities like barrage.

Returns:

  • (Array<Regexp>)

    immutable array of 12 charge level patterns

See Also:

[
  /^You sense nothing out of the ordinary.  Only magic could detect the useless trace of .* still in your system.$/,
  /^A small charge lingers within your body, just above the threshold of perception.$/,
  /^A small charge lingers within your body.$/,
  /^A charge dances through your body.$/,
  /^A charge dances just below the threshold of discomfort.$/,
  /^A charge circulates through your body, causing a low hum to vibrate through your bones.$/,
  /^Elemental essence floats freely within your body, leaving little untouched.$/,
  /^Elemental essence has infused every inch of your body.  While you could contain more, you'd do so at the risk of your health.$/,
  /^Extraplanar power crackles within your body, leaving you feeling mildly feverish.$/,
  /^Extraplanar power crackles within your body, leaving you feeling acutely ill.$/,
  /^Your body sings and crackles with a barely contained charge, destroying what little cenesthesia you had left.$/,
  /^You have reached the limits of your body's capacity to store a charge.  The laws of the Elemental Plane of .* scream demands upon your physiology, threatening your life.$/
].freeze
PERC_MANA_START_PATTERN =

Regex pattern that marks the start of "perc mana" output for Moon Mages. Matches lines containing mana stream descriptions.

Examples:

"streams of water mana" #=> matches

Returns:

  • (Regexp)

    pattern matching mana stream lines

See Also:

  • #perc_mana
/streams of .* mana/
PERC_MANA_END_PATTERN =

Regex pattern that marks the end of "perc mana" output. Matches the Roundtime line that terminates command output.

Returns:

  • (Regexp)

    pattern matching Roundtime

See Also:

  • #perc_mana
/Roundtime/
SYMBIOSIS_PATTERN =

Regex pattern that captures active symbiotic research type. Named capture group 'type' extracts the research school name.

Examples:

"combine the weaves of the restoration symbiosis" #=> type = "restoration"

Returns:

  • (Regexp)

    pattern with named capture group :type

See Also:

  • #perc_symbiotic_research
/combine the weaves of the (?<type>\w+) symbiosis/
DISCERN_SORCERY_PATTERN =

Regex pattern that captures minimum mana streams for sorcery spells. Named capture group 'min' extracts the minimum stream count.

Examples:

"requires at minimum 3 mana streams" #=> min = "3"

Returns:

  • (Regexp)

    pattern with named capture group :min

See Also:

  • #check_discern
/requires at minimum (?<min>\d+) mana streams/
DISCERN_FULL_PATTERN =

Regex pattern that captures minimum and additional mana streams for spells. Named capture groups 'min' (base) and 'more' (reinforcement) extract stream counts. Used when discerning spell mana requirements.

Examples:

"minimum 5 mana streams and you think you can reinforce it with 3 more" #=> min = "5", more = "3"

Returns:

  • (Regexp)

    pattern with named capture groups :min and :more

See Also:

  • #check_discern
/minimum (?<min>\d+) mana streams and you think you can reinforce it with (?<more>\d+) more/i
USELESS_RUNESTONE_PATTERNS =

Patterns matched when retrieving a useless runestone (exhausted charge).

Returns:

  • (Array<Regexp>)

    immutable array of useless runestone patterns

[
  /^You get a useless/
].freeze
GET_RUNESTONE_SUCCESS_PATTERNS =

Patterns matched when successfully retrieving a runestone from storage.

Returns:

  • (Array<Regexp>)

    immutable array of retrieval success patterns

[
  /^You get/,
  /^You pick up/
].freeze
GET_RUNESTONE_FAILURE_PATTERNS =

Patterns matched when failing to retrieve a runestone from storage.

Returns:

  • (Array<Regexp>)

    immutable array of retrieval failure patterns

[
  /^What were you referring to/,
  /^I could not find/
].freeze

Class Method Summary collapse

Class Method Details

.activate_barb_buff?(name, meditation_pause_timer = 20, sit_to_meditate = false, retries: BARB_BUFF_MAX_RETRIES) ⇒ Boolean

Activates a Barbarian ability, with automatic retry on failure conditions. Returns true if already active, or if successfully activated. Handles meditation pause timing and sitting requirement. Requires Power Meditation or Powermonger mastery for active abilities to be detected by DRSpells.

Parameters:

  • name (String)

    the ability name

  • meditation_pause_timer (Integer) (defaults to: 20)

    seconds to pause after meditation activation (default 20)

  • sit_to_meditate (Boolean) (defaults to: false)

    whether to sit before meditation abilities (default false)

  • retries (Integer) (defaults to: BARB_BUFF_MAX_RETRIES)

    maximum retry attempts (default BARB_BUFF_MAX_RETRIES)

Returns:

  • (Boolean)

    true if ability is now active, false if activation failed



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# File 'documented/dragonrealms/commons/common-arcana.rb', line 381

def activate_barb_buff?(name, meditation_pause_timer = 20, sit_to_meditate = false, retries: BARB_BUFF_MAX_RETRIES)
  # Note, you must know Power meditation or Powermonger mastery
  # for your active abilities to be detected by DRSpells.
  return true if DRSpells.active_spells[name]

  if retries <= 0
    Lich::Messaging.msg("bold", "DRCA: activate_barb_buff? exhausted #{BARB_BUFF_MAX_RETRIES} retries for '#{name}' - giving up")
    return false
  end

  activated = false
  ability_data = get_data('spells').barb_abilities[name]
  if ability_data['type'].eql?('meditation') && sit_to_meditate
    DRC.retreat
    DRC.bput('sit', 'You sit', 'You are already', 'You rise', 'While swimming?')
  end
  case DRC.bput(ability_data['start_command'], ability_data['activated_message'], 'You have not been trained', 'But you are already', 'Your inner fire lacks', 'find yourself lacking the inner fire', 'You should stand', 'You must be sitting', 'You must be unengaged', 'While swimming?')
  when 'You must be unengaged'
    DRC.retreat
    activated = activate_barb_buff?(name, meditation_pause_timer, sit_to_meditate, retries: retries - 1)
  when 'You must be sitting'
    DRC.retreat
    case DRC.bput('sit', 'You sit', 'You are already', 'You rise', 'While swimming?')
    when 'While swimming?'
      Lich::Messaging.msg("bold", "DRCA: cannot sit to activate '#{name}' - water is too deep")
      activated = false
    else
      activated = activate_barb_buff?(name, meditation_pause_timer, sit_to_meditate, retries: retries - 1)
    end
  when 'You should stand'
    DRC.fix_standing
    activated = activate_barb_buff?(name, meditation_pause_timer, sit_to_meditate, retries: retries - 1)
  when /#{ability_data['activated_message']}/
    # Pause at least for the preferred amount of time
    # to let the meditation take effect else it may fail.
    if ability_data['type'].eql?('meditation') && meditation_pause_timer
      pause meditation_pause_timer
    end
    # Wait for any remaining RT before proceeding
    waitrt?
    activated = true
  else
    activated = false
  end
  DRC.fix_standing
  activated
end

.activate_khri?(settings_kneel, ability) ⇒ Boolean

Needs to handle (based on current usage):

  • Hasten
  • Delay Hasten
  • Hasten Focus
  • Delay Hasten Focus
  • Khri Hasten
  • Khri Delay Hasten
  • Khri Delay Hasten Focus

Returns:

  • (Boolean)


321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'documented/dragonrealms/commons/common-arcana.rb', line 321

def activate_khri?(settings_kneel, ability)
  abilities = ability.split.map(&:capitalize)

  # Standardize for with/without 'Khri' on the front
  abilities = abilities.drop(1) if abilities.first.casecmp('khri') == 0

  # Handling for 'Delay'
  should_delay = abilities.first.casecmp('delay') == 0
  abilities = abilities.drop(1) if should_delay

  # Check each khri in the list against Active Spells, Drop any that are active
  needed_abilities = abilities.select { |ability_to_check| DRSpells.active_spells["Khri #{ability_to_check}"].nil? }
  return true if needed_abilities.empty?

  kneel = needed_abilities.any? { |ability_to_check| kneel_for_khri?(settings_kneel, ability_to_check) }
  DRC.retreat if kneel
  DRC.bput('kneel', 'You kneel', 'You are already', 'You rise', "While swimming?  Don't be silly") if kneel && !kneeling?

  result = DRC.bput("Khri #{should_delay ? 'Delay ' : ''}#{needed_abilities.join(' ')}", get_data('spells').khri_preps)
  waitrt?
  DRC.fix_standing

  return ['Your mind and body are willing', 'Your body is willing', 'You have not recovered'].none?(result)
end

.backfired?Boolean

Returns whether the most recent spell cast backfired.

Returns:

  • (Boolean)

    true if last cast backfired, false otherwise



619
620
621
# File 'documented/dragonrealms/commons/common-arcana.rb', line 619

def backfired?
  @backfired_status || false
end

.calculate_mana(min, more, discern_data, cyclic_or_ritual, settings) ⇒ void

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

This method returns an undefined value.

Calculates mana harness and cambrinth charge amounts from discern results. Applies prep_scaling_factor, distributes remaining mana across cambrinth items proportionally, and handles cases where total > available cambrinth capacity.

Parameters:

  • min (Integer)

    minimum mana streams required

  • more (Integer)

    additional streams that can be reinforced

  • discern_data (Hash)

    hash to populate with calculated 'mana' and 'cambrinth' arrays

  • cyclic_or_ritual (Boolean)

    whether spell is cyclic or ritual (disables cambrinth)

  • settings (OpenStruct)

    configuration (prep_scaling_factor, cambrinth_num_charges, cambrinth_items)



1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1241

def calculate_mana(min, more, discern_data, cyclic_or_ritual, settings)
  total = min + more
  total = (total * settings.prep_scaling_factor).floor
  discern_data['mana'] = [(total / 5.0).ceil, min].max
  remaining = total - discern_data['mana']
  normalize_cambrinth_items(settings)
  # Ignore cambrinth if charges to use is nil or 0
  settings.cambrinth_num_charges ||= 0
  settings.cambrinth_items = [] if settings.cambrinth_num_charges == 0
  total_cambrinth_cap = settings.cambrinth_items.map { |x| x['cap'] }.inject(&:+) || 0
  charges_count_floor = remaining >= settings.cambrinth_num_charges ? settings.cambrinth_num_charges : 1
  settings.cambrinth_items.each do |item|
    item['charges'] = ((item['cap'].to_f / total_cambrinth_cap) * charges_count_floor).ceil
  end
  total_cambrinth_charges = settings.cambrinth_items.map { |x| x['charges'] }.inject(&:+) || 0
  if remaining > total_cambrinth_cap
    discern_data['mana'] = discern_data['mana'] + (remaining - total_cambrinth_cap)
    remaining = total - discern_data['mana']
  end
  if cyclic_or_ritual || total_cambrinth_charges == 0
    discern_data['cambrinth'] = nil
    discern_data['mana'] = discern_data['mana'] + remaining
  elsif remaining > 0
    total_cambrinth_mana = [remaining, total_cambrinth_cap].min
    settings.cambrinth_items.each_with_index do |item, index|
      discern_data['cambrinth'] ||= []
      charge_amount = (total_cambrinth_mana / total_cambrinth_charges) * item['charges']
      discern_data['cambrinth'][index] = []
      charge_amount.times do |i|
        discern_data['cambrinth'][index][i % item['charges']] += 1
      end
    end
  else
    discern_data['cambrinth'] = nil
  end
end

.cast?(cast_command = 'cast', symbiosis = false, before = [], after = [], retries: CAST_MAX_RETRIES) ⇒ Boolean

Casts a prepared spell, handling failure modes, barrage fallback, and cyclic/prep wait. Sets up and clears flags for unknown commands, barrage failure, spell failure, and backfiring. Executes before/after action blocks. Retries on cyclic-too-recent or full-prep-required. Cleans up spell and mana on certain failures.

Parameters:

  • cast_command (String) (defaults to: 'cast')

    the cast command, often 'cast' or 'barrage ...' (default 'cast')

  • symbiosis (Boolean) (defaults to: false)

    whether symbiotic research was active (default false)

  • before (Array<Hash>) (defaults to: [])

    actions to perform before casting, each with 'message' and 'matches' keys (default [])

  • after (Array<Hash>) (defaults to: [])

    actions to perform after casting, each with 'message' and 'matches' keys (default [])

  • retries (Integer) (defaults to: CAST_MAX_RETRIES)

    maximum retry attempts (default CAST_MAX_RETRIES)

Returns:

  • (Boolean)

    true if spell cast without 'spell-fail' flag, false otherwise



635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
# File 'documented/dragonrealms/commons/common-arcana.rb', line 635

def cast?(cast_command = 'cast', symbiosis = false, before = [], after = [], retries: CAST_MAX_RETRIES)
  before.each { |action| DRC.bput(action['message'], action['matches']) }

  Flags.add('unknown-command', "Please rephrase that command")
  Flags.add('barrage-fail', "That was an invalid attack choice.", "Wouldn't it be better if you used a melee weapon?", "You'll need to be using a weapon to BARRAGE your target", "You must have a fully developed target matrix to make a barrage attack", "You are unable to muster the energy to do that", "You do not know how to manipulate that pathway.", "You cannot BARRAGE with that spell.")
  Flags.add('spell-fail', 'Currently lacking the skill to complete the pattern', "You don't have a spell prepared!", /^Your spell .*backfires/, 'Something is interfering with the spell', 'There is nothing else to face', 'You strain, but are too mentally fatigued', 'The spell pattern resists the influx of unfocused mana', 'Your target pattern dissipates because')
  Flags.add('cyclic-too-recent', 'The mental strain of initiating a cyclic spell so recently prevents you from formulating the spell pattern')
  Flags.add('spell-full-prep', /^This pattern may only be cast with full preparation/)
  Flags.add('spell-backfired', /^Your spell .*backfires/)

  case DRC.bput(cast_command || 'cast', get_data('spells').cast_messages)
  when /^Your target pattern dissipates/, /^You can't cast that at yourself/, /^You need to specify a body part to consume/, /^There is nothing else to face/
    DRC.bput('release spell', 'You let your concentration lapse', "You aren't preparing a spell")
    DRC.bput('release mana', 'You release all', "You aren't harnessing any mana")
  when /You gesture/
    pause 0.25
  end
  waitrt?

  # Warrior Mage failed to use (or doesn't know) barrage ability. Do regular cast instead.
  if cast_command =~ /\b(barrage)\b/i && (Flags['unknown-command'] || Flags['barrage-fail'])
    return cast?('cast', symbiosis, [], after, retries: retries - 1) if retries > 0

    Lich::Messaging.msg("bold", "DRCA: cast? barrage fallback exhausted retries - giving up")
    return false
  end

  if Flags['cyclic-too-recent'] || Flags['spell-full-prep']
    if retries <= 0
      Lich::Messaging.msg("bold", "DRCA: cast? exhausted #{CAST_MAX_RETRIES} retries waiting for cyclic/full-prep - giving up")
      return false
    end
    pause 1
    Flags.delete('spell-full-prep')
    return cast?(cast_command, symbiosis, [], after, retries: retries - 1)
  end

  after.each { |action| DRC.bput(action['message'], action['matches']) }

  if symbiosis && Flags['spell-fail']
    DRC.bput('release mana', 'You release all', "You aren't harnessing any mana")
    DRC.bput('release symbiosis', 'You release', 'But you haven\'t prepared')
  elsif Flags['spell-fail']
    DRC.bput('release mana', 'You release all', "You aren't harnessing any mana")
  end

  @backfired_status = Flags['spell-backfired']

  !Flags['spell-fail']
end

.cast_spell(data, settings, force_cambrinth = false, cast_lifecycle_lambda = nil) ⇒ Boolean?

Casts a single spell from preparation through invoke and cast. Handles astral data updates, Clear Vision prerequisite, rituals, runestones, segue casting, and lifecycle callbacks. Returns nil on early exit.

Parameters:

  • data (Hash)

    spell data (abbrev, mana, focus, worn_focus, tied_focus, sheathed_focus, cast, symbiosis, before, after, prep_time, ritual, runestone_name, tattoo_tm, runestone_tm, cyclic, cambrinth, prep, prep_type)

  • settings (OpenStruct)

    configuration

  • force_cambrinth (Boolean) (defaults to: false)

    whether to force cambrinth use over harnessing (default false)

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

    optional lambda called with (phase, data, settings) phases: 'pre-prep', 'post-prep', 'pre-cast', 'post-cast' (default nil)

Returns:

  • (Boolean, nil)

    true if spell cast, false if cast failed, nil if early return (e.g., prep failed)



1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
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
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1105

def cast_spell(data, settings, force_cambrinth = false, cast_lifecycle_lambda = nil)
  return unless data
  return unless settings

  data = DRCMM.update_astral_data(data, settings)
  return unless data # DRCMM.update_astral_data returns nil on failure

  if (data['abbrev'] =~ /locat/i) && !DRSpells.active_spells['Clear Vision']
    cast_spell({ 'abbrev' => 'cv', 'mana' => 1, 'prep_time' => 5 }, settings)
  end

  if data['ritual']
    ritual(data, settings)
    return
  end

  if data['runestone_name']
    return unless prepare_to_cast_runestone?(data, settings)
  end

  cast_lifecycle_lambda&.call('pre-prep', data, settings)

  command = 'prep'
  command = data['prep'] if data['prep']
  command = data['prep_type'] if data['prep_type']

  if command == 'segue'
    return if segue?(data['abbrev'], data['mana'])

    command = 'prep'
  end

  release_cyclics if data['cyclic']
  DRC.bput('release spell', 'You let your concentration lapse', "You aren't preparing a spell") unless checkprep == 'None'
  DRC.bput('release mana', 'You release all', "You aren't harnessing any mana")

  return unless prepare?(data['abbrev'], data['mana'], data['symbiosis'], command, data['tattoo_tm'], data['runestone_name'], data['runestone_tm'], settings['custom_spell_prep'])

  DRCI.put_away_item?(data['runestone_name'], settings.runestone_storage) if DRCI.in_hands?(data['runestone_name'])
  prepare_time = Time.now

  normalize_cambrinth_items(settings)
  if check_to_harness(settings.use_harness_when_arcana_locked) && !force_cambrinth
    harness_mana(data['cambrinth'].flatten)
  else
    charge_cambrinth_items(data, settings)
  end

  cast_lifecycle_lambda&.call('post-prep', data, settings)

  if data['prep_time']
    pause until Time.now - prepare_time >= data['prep_time']
  else
    waitcastrt?
  end

  cast_lifecycle_lambda&.call('pre-cast', data, settings)
  spell_cast = cast?(data['cast'], data['symbiosis'], data['before'], data['after'])
  cast_lifecycle_lambda&.call('post-cast', data, settings)

  spell_cast
end

.cast_spell?(data, settings, force_cambrinth = false, cast_lifecycle_lambda = nil) ⇒ Boolean

Convenience wrapper that casts a single spell and returns a boolean result.

Parameters:

  • data (Hash)

    spell data

  • settings (OpenStruct)

    configuration

  • force_cambrinth (Boolean) (defaults to: false)

    whether to force cambrinth use (default false)

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

    optional lifecycle lambda (default nil)

Returns:

  • (Boolean)

    true if spell cast successfully, false otherwise



1091
1092
1093
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1091

def cast_spell?(data, settings, force_cambrinth = false, cast_lifecycle_lambda = nil)
  !!cast_spell(data, settings, force_cambrinth, cast_lifecycle_lambda)
end

.cast_spells(spells, settings, force_cambrinth = false, cast_lifecycle_lambda = nil) ⇒ void

This method returns an undefined value.

Casts a collection of spells, infusing OM first if configured. Skips spells that are already active (unless non-cyclic with nil recast). Waits on mana and concentration thresholds.

Parameters:

  • spells (Hash)

    spell name => data hash

  • settings (OpenStruct)

    configuration (osrel_no_harness, osrel_amount, waggle_spells_mana_threshold, waggle_spells_concentration_threshold)

  • force_cambrinth (Boolean) (defaults to: false)

    whether to force cambrinth use over harnessing (default false)

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

    optional lambda called with (phase, data, settings) for 'pre-prep', 'post-prep', 'pre-cast', 'post-cast' (default nil)



1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1070

def cast_spells(spells, settings, force_cambrinth = false, cast_lifecycle_lambda = nil)
  infuse_om(!settings.osrel_no_harness, settings.osrel_amount)
  spells.each do |name, data|
    next if DRSpells.active_spells[name] && (data['recast'].nil? || DRSpells.active_spells[name].to_i > data['recast'])

    while DRStats.mana < settings.waggle_spells_mana_threshold || DRStats.concentration < settings.waggle_spells_concentration_threshold
      Lich::Messaging.msg("plain", "DRCA: waiting on mana over #{settings.waggle_spells_mana_threshold} or concentration over #{settings.waggle_spells_concentration_threshold}...")
      pause 15
    end
    cast_spell(data, settings, force_cambrinth, cast_lifecycle_lambda)
  end
end

.charge?(cambrinth, mana) ⇒ 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.

Charges cambrinth with the specified mana amount. Handles worn/held cambrinth and skill checks. Falls back to harnessing if body condition prevents charging. Retries find_cambrinth if cambrinth is missing.

Parameters:

  • cambrinth (String)

    cambrinth item noun

  • mana (Integer)

    mana amount to charge

Returns:

  • (Boolean)

    true if "absorbs all" message received, false otherwise



893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
# File 'documented/dragonrealms/commons/common-arcana.rb', line 893

def charge?(cambrinth, mana)
  charged = false
  result = DRC.bput("charge my #{cambrinth} #{mana}", get_data('spells').charge_messages, 'I could not find')
  pause
  waitrt?
  case result
  when /You are in no condition to do that/
    charged = harness?(mana)
  when /You'll have to hold it/
    # You're not wearing nor holding your cambrinth item, go find it again.
    # Likely it's configured in your yaml that you wear it but it's stowed for some reason.
    # Try to find the cambrinth and get it to your hands.
    Lich::Messaging.msg("bold", "DRCA: where did your cambrinth go?")
    retry_find_cambrinth = true
  when /you find it too clumsy/
    Lich::Messaging.msg("bold", "DRCA: your arcana skill is too low to charge your cambrinth while worn")
    retry_find_cambrinth = true
  else
    charged = result =~ /absorbs? all of the energy/
  end
  if retry_find_cambrinth
    # If the cambrinth is in your hands and you can't charge it, nothing else to do.
    unless DRCI.in_hands?(cambrinth)
      # Otherwise, try to find the cambrinth and get it to your hands.
      find_cambrinth(cambrinth, false, 999)
      # If you were able to get the cambrinth into a hand then retry charging it.
      # You might not have been able to if your hands were full.
      if DRCI.in_hands?(cambrinth)
        charged = charge?(cambrinth, mana)
        stow_cambrinth(cambrinth, false, 999)
      end
    end
  end
  charged
end

.charge_and_invoke(cambrinth, dedicated_camb_use, charges, invoke_exact_amount = nil) ⇒ void

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

This method returns an undefined value.

Charges cambrinth with each amount in sequence, then invokes it. Returns early if charges is nil or empty.

Parameters:

  • cambrinth (String)

    cambrinth item noun

  • dedicated_camb_use (String)

    dedicated use modifier (e.g., 'attunement')

  • charges (Array<Integer>, nil)

    mana amounts to charge in sequence

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

    exact mana to invoke; if nil, invokes the sum of charges (default nil)



841
842
843
844
845
846
847
848
849
850
851
# File 'documented/dragonrealms/commons/common-arcana.rb', line 841

def charge_and_invoke(cambrinth, dedicated_camb_use, charges, invoke_exact_amount = nil)
  return unless charges&.any?

  charges.each do |mana|
    break unless charge?(cambrinth, mana)
  end

  invoke_amount = invoke_exact_amount ? charges.inject(0, :+) : nil

  invoke(cambrinth, dedicated_camb_use, invoke_amount)
end

.charge_avtalia(cambrinth, charge_amount) ⇒ void

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

This method returns an undefined value.

Charges Avtalia cambrinth if avtalia script is running. Updates tracking with charge success or assumed reserve (with 10% falloff per 5 minutes).

Parameters:

  • cambrinth (String)

    cambrinth item noun

  • charge_amount (Integer)

    mana to charge



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

def charge_avtalia(cambrinth, charge_amount)
  return unless cambrinth
  return unless Script.running?('avtalia')

  if !charge?(cambrinth, charge_amount)
    UserVars.avtalia[cambrinth]['mana'] = UserVars.avtalia[cambrinth]['cap']
  else
    # Experiments show very roughly 10% falloff regardless of cap every 10 minutes
    # Assume 10% every 5 minutes.  No falloff in starlight, but that's not tracked ATM.
    time_diff = Time.now - UserVars.avtalia[cambrinth]['time_seen']
    time_mod = (time_diff / 300.0).floor
    time_adjust = 1 - [time_mod * 0.10, 1.0].min
    assumed_reserve = (UserVars.avtalia[cambrinth]['mana'] * time_adjust).floor + charge_amount
    UserVars.avtalia[cambrinth]['mana'] = [assumed_reserve, UserVars.avtalia[cambrinth]['cap']].min
  end
  UserVars.avtalia[cambrinth]['time_seen'] = Time.now
end

.charge_cambrinth_items(data, settings) ⇒ void

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

This method returns an undefined value.

Charges and invokes each cambrinth item in the configured list. Handles both array (per-item charges) and flat (shared charges) formats.

Parameters:

  • data (Hash)

    spell data with 'cambrinth' key (Array<Array> or Array)

  • settings (OpenStruct)

    configuration (cambrinth_items, dedicated_camb_use, cambrinth_invoke_exact_amount)



1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1516

def charge_cambrinth_items(data, settings)
  settings.cambrinth_items.each_with_index do |item, index|
    case data['cambrinth'].first
    when Array
      find_charge_invoke_stow(item['name'], item['stored'], item['cap'], settings.dedicated_camb_use, data['cambrinth'][index], settings.cambrinth_invoke_exact_amount)
    when Integer
      find_charge_invoke_stow(item['name'], item['stored'], item['cap'], settings.dedicated_camb_use, data['cambrinth'], settings.cambrinth_invoke_exact_amount)
    end
  end
end

.check_discern(data, settings, spell_is_sorcery = false, more_override = nil) ⇒ Hash

Discerns a spell to determine mana and cambrinth requirements. Caches results per spell abbreviation in UserVars.discerns. For sorcery or symbiosis, caches only min streams. For others, rechecks periodically or if overridden. Updates spell data with mana and cambrinth arrays.

Parameters:

  • data (Hash)

    spell data with abbrev, symbiosis, mana keys

  • settings (OpenStruct)

    configuration (check_discern_timer_in_hours, prep_scaling_factor, cambrinth_num_charges, cambrinth_items)

  • spell_is_sorcery (Boolean) (defaults to: false)

    whether this is a sorcery spell (default false)

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

    force this as the "more" value for sorcery (default nil)

Returns:

  • (Hash)

    updated spell data



1195
1196
1197
1198
1199
1200
1201
1202
1203
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
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1195

def check_discern(data, settings, spell_is_sorcery = false, more_override = nil)
  UserVars.discerns = {} unless UserVars.discerns
  discern_data = UserVars.discerns[data['abbrev']] || {}
  if data['symbiosis'] || spell_is_sorcery
    if discern_data.empty? || discern_data['min'].nil? || more_override
      DRC.retreat
      discern_result = DRC.bput("discern #{data['abbrev']}", 'requires at minimum \d+ mana streams')
      match = discern_result.match(DISCERN_SORCERY_PATTERN)
      if match
        discern_data['mana'] = match[:min].to_i
        discern_data['cambrinth'] = nil
        discern_data['min'] = match[:min].to_i
        discern_data['more'] = (more_override || 0)
      end
    end
    calculate_mana(discern_data['min'], discern_data['more'], discern_data, false, settings)
  elsif discern_data.empty? || discern_data['time_stamp'].nil? || Time.now - discern_data['time_stamp'] > settings.check_discern_timer_in_hours * 60 * 60 || !discern_data['more'].nil?
    discern_data['time_stamp'] = Time.now
    DRC.retreat
    case discern = DRC.bput("discern #{data['abbrev']}", 'The spell requires at minimum \d+ mana streams and you think you can reinforce it with \d+ more', 'You don\'t think you are able to cast this spell', 'You have no idea how to cast that spell', 'You don\'t seem to be able to move to do that')
    when /you don't think you are able/i, 'You have no idea how to cast that spell', 'You don\'t seem to be able to move to do that'
      discern_data['mana'] = 1
      discern_data['cambrinth'] = nil
    else
      match = discern.match(DISCERN_FULL_PATTERN)
      calculate_mana(match[:min].to_i, match[:more].to_i, discern_data, data['cyclic'] || data['ritual'], settings) if match
    end
  end
  waitrt?
  UserVars.discerns[data['abbrev']] = discern_data
  data['mana'] = discern_data['mana']
  data['cambrinth'] = discern_data['cambrinth']
  data
end

.check_elemental_chargeObject

Determine the numerical range of a Warrior Mage's elemental charge. This can be used to know if the mage is ready to perform certain abilities, like barrage. Returns a number between 0 (no charge) and 11 (max charge). https://elanthipedia.play.net/Summoning_skill#Charge_levels



1473
1474
1475
1476
1477
1478
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1473

def check_elemental_charge
  return 0 unless DRStats.warrior_mage?

  result = DRC.bput("pathway sense", *CHARGE_LEVELS)
  CHARGE_LEVELS.find_index { |pattern| pattern =~ result }
end

.check_to_harness(should_harness) ⇒ 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.

Determines whether to use harnessing instead of cambrinth charging. Returns true only if harnessing is enabled and Attunement XP <= Arcana XP.

Parameters:

  • should_harness (Boolean)

    whether harnessing is configured

Returns:

  • (Boolean)

    true if harnessing should be used, false otherwise



1284
1285
1286
1287
1288
1289
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1284

def check_to_harness(should_harness)
  return false unless should_harness
  return false if DRSkill.getxp('Attunement') > DRSkill.getxp('Arcana')

  true
end

.choose_avtalia(charge_needed, mana_percentage) ⇒ Hash?

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

Selects the best Avtalia cambrinth by mana availability and freshness. Filters for items with time_seen within 10 minutes, mana >= mana_percentage, and mana > charge_needed / 10.

Parameters:

  • charge_needed (Integer)

    total mana to be charged

  • mana_percentage (Integer)

    minimum percent of capacity (0-100)

Returns:

  • (Hash, nil)

    the [cambrinth_name, data] entry with highest mana, or nil if none qualify



1461
1462
1463
1464
1465
1466
1467
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1461

def choose_avtalia(charge_needed, mana_percentage)
  UserVars.avtalia.select { |_camb, data| data['time_seen'] && data['cap'] && data['mana'] }
                  .select { |_camb, data| Time.now - data['time_seen'] < 600.0 }
                  .select { |_camb, data| (data['mana'].to_f / data['cap'].to_f) * 100 >= mana_percentage }
                  .select { |_camb, data| data['mana'] > charge_needed / 10 }
                  .max_by { |_camb, data| data['mana'] }
end

.crafting_cast_spell(data, settings) ⇒ 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.

Casts a prepared spell during crafting, handling cambrinth or harnessing. Does not handle preparation; assume spell is already prepared.

Parameters:

  • data (Hash)

    spell data (cast, symbiosis, before, after, cambrinth)

  • settings (OpenStruct)

    configuration

Returns:

  • (Boolean)

    true if spell cast, false otherwise



1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1298

def crafting_cast_spell(data, settings)
  return unless data
  return unless settings

  normalize_cambrinth_items(settings)
  if check_to_harness(settings.use_harness_when_arcana_locked)
    harness_mana(data['cambrinth'].flatten)
  else
    charge_cambrinth_items(data, settings)
  end

  cast?(data['cast'], data['symbiosis'], data['before'], data['after'])
end

.crafting_magic_routine(settings) ⇒ void

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

This method returns an undefined value.

Conducts spell-based training during crafting: casts queued spells if ready, then prepares low-XP training spells (Warding, Utility, Augmentation, optionally Sorcery). Returns early if no training spells, low mana, or casting in progress.

Parameters:

  • settings (OpenStruct)

    configuration (crafting_training_spells, waggle_spells_mana_threshold, crafting_training_spells_enable_sorcery, crafting_training_spells_enable_sorcery_forging)



1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1344

def crafting_magic_routine(settings)
  training_spells = settings.crafting_training_spells

  return if training_spells.empty?
  return if DRStats.mana <= settings.waggle_spells_mana_threshold

  if checkcastrt > 0
    return
  elsif !XMLData.prepared_spell.eql?('None') && checkcastrt == 0
    spell = XMLData.prepared_spell
    data = training_spells.find { |_skill, info| info['name'] == spell }.last
    crafting_cast_spell(data, settings)
  end

  return if checkcastrt > 0

  needs_training = %w[Warding Utility Augmentation]
  needs_training.append("Sorcery") if (settings.crafting_training_spells_enable_sorcery && !Script.running?('forge')) ||
                                      (settings.crafting_training_spells_enable_sorcery && settings.crafting_training_spells_enable_sorcery_forging)
  needs_training = needs_training.select { |skill| training_spells[skill] }
                                 .select { |skill| DRSkill.getxp(skill) < 31 }
                                 .sort_by { |skill| [DRSkill.getxp(skill), DRSkill.getrank(skill)] }
                                 .first

  return unless needs_training

  crafting_prepare_spell(training_spells[needs_training], settings)
end

.crafting_prepare_spell(data, settings) ⇒ 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.

Prepares a spell during crafting, handling lunar/moon data and cyclic cleanup. Does not handle casting.

Parameters:

  • data (Hash)

    spell data (abbrev, mana, symbiosis, cyclic, prep, prep_type, tattoo_tm, runestone_name, runestone_tm)

  • settings (OpenStruct)

    configuration (custom_spell_prep)

Returns:

  • (String, Boolean, nil)

    prep message on success, false on failure, nil if moon data unavailable



1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1319

def crafting_prepare_spell(data, settings)
  return unless data
  return unless settings

  # Skip preparing lunar spell if no moons available
  return unless DRCMM.set_moon_data(data)

  release_cyclics if data['cyclic']
  DRC.bput('release spell', 'You let your concentration lapse', "You aren't preparing a spell") unless checkprep == 'None'
  DRC.bput('release mana', 'You release all', "You aren't harnessing any mana")

  command = 'prep'
  command = data['prep'] if data['prep']
  command = data['prep_type'] if data['prep_type']

  prepare?(data['abbrev'], data['mana'], data['symbiosis'], command, data['tattoo_tm'], data['runestone_name'], data['runestone_tm'], settings['custom_spell_prep'])
end

.do_buffs(settings, set_name) ⇒ void

This method returns an undefined value.

Activates all buffs in a named waggle set. For Barbarians, activates abilities; for Thieves, activates khris; for other guilds, filters by day/night and casts spells.

Parameters:

  • settings (OpenStruct)

    configuration (waggle_sets)

  • set_name (String)

    name of the buff set to activate



1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1381

def do_buffs(settings, set_name)
  return unless settings.waggle_sets[set_name]

  spells = settings.waggle_sets[set_name]

  if DRStats.barbarian?
    start_barb_abilities(spells, settings)
  elsif DRStats.thief?
    start_khris(spells, settings)
  else
    spells
      .select! { |_name, data| data['night'] ? UserVars.sun['night'] : true }
      .select! { |_name, data| data['day'] ? UserVars.sun['day'] : true }

    spells.values
          .select { |spell| spell['use_auto_mana'] }
          .each { |spell| check_discern(spell, settings) }

    cast_spells(spells, settings, settings.waggle_force_cambrinth)
  end
end

.find_cambrinth(cambrinth, stored_cambrinth, cambrinth_cap) ⇒ Boolean, void

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

Ensures cambrinth is in hand (either held or worn) based on configuration and skill. If kept stowed, retrieves it. If not skilled to charge while worn, removes from wear or retrieves. Otherwise assumes it is being worn without verification.

Parameters:

  • cambrinth (String)

    cambrinth item noun

  • stored_cambrinth (Boolean)

    whether cambrinth is kept stowed

  • cambrinth_cap (Integer)

    mana capacity (used for skill check)

Returns:

  • (Boolean, void)

    result of the last item operation or true if no action needed



773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
# File 'documented/dragonrealms/commons/common-arcana.rb', line 773

def find_cambrinth(cambrinth, stored_cambrinth, cambrinth_cap)
  if stored_cambrinth
    # Your config says you keep your cambrinth stowed.
    # If item not in your hands, maybe you're wearing it by accident?
    DRCI.get_item_if_not_held?(cambrinth) || DRCI.remove_item?(cambrinth)
  elsif !skilled_to_charge_while_worn?(cambrinth_cap)
    # Your config says you wear your cambrinth.
    # But you're not skilled to charge it while worn.
    # If item not in your hands, maybe you're wearing it or stowed it by accident?
    DRCI.in_hands?(cambrinth) || DRCI.remove_item?(cambrinth) || DRCI.get_item?(cambrinth)
  else
    # Your config says you wear your cambrinth
    # and you're skilled to charge it while worn.
    # Let's hope you're wearing or holding it :)
    # To verify that would require more commands
    # and more time, and be more spammy.
    # For now, no validation or recovery for this scenario.
    true
  end
end

.find_charge_invoke_stow(cambrinth, stored_cambrinth, cambrinth_cap, dedicated_camb_use, charges, invoke_exact_amount = nil) ⇒ void

This method returns an undefined value.

Wrapper that finds cambrinth, charges and invokes it, then stows it. Returns early if charges are nil or empty.

Parameters:

  • cambrinth (String)

    cambrinth item noun

  • stored_cambrinth (Boolean)

    whether cambrinth is kept stowed

  • cambrinth_cap (Integer)

    mana capacity of cambrinth

  • dedicated_camb_use (String)

    dedicated cambrinth use modifier (e.g., 'attunement')

  • charges (Array<Integer>)

    amounts of mana to charge in sequence

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

    exact mana to invoke, or nil to use total of charges (default nil)



697
698
699
700
701
702
703
# File 'documented/dragonrealms/commons/common-arcana.rb', line 697

def find_charge_invoke_stow(cambrinth, stored_cambrinth, cambrinth_cap, dedicated_camb_use, charges, invoke_exact_amount = nil)
  return unless charges

  find_cambrinth(cambrinth, stored_cambrinth, cambrinth_cap)
  charge_and_invoke(cambrinth, dedicated_camb_use, charges, invoke_exact_amount)
  stow_cambrinth(cambrinth, stored_cambrinth, cambrinth_cap)
end

.find_focus(focus, worn, tied, sheathed) ⇒ void

This method returns an undefined value.

Locates and retrieves a spell focus based on its storage location. Does nothing if focus is nil.

Parameters:

  • focus (String, nil)

    focus item noun

  • worn (Boolean)

    whether focus is worn on the body

  • tied (String)

    noun of the location focus is tied to, or false if not tied

  • sheathed (Boolean)

    whether focus is sheathed (requires wielding)



714
715
716
717
718
719
720
721
722
723
724
725
726
727
# File 'documented/dragonrealms/commons/common-arcana.rb', line 714

def find_focus(focus, worn, tied, sheathed)
  return unless focus

  if worn
    DRCI.remove_item?(focus)
  elsif tied
    DRCI.untie_item?(focus, tied)
  elsif sheathed
    result = DRC.bput("wield my #{focus}", WIELD_FOCUS_SUCCESS_PATTERNS, WIELD_FOCUS_FAILURE_PATTERNS)
    WIELD_FOCUS_FAILURE_PATTERNS.none? { |pattern| pattern =~ result }
  else
    DRCI.get_item?(focus)
  end
end

.get_runestone?(runestone, settings) ⇒ 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.

Retrieves a runestone from configured storage or hands (if already held). Returns false and disposes a useless (exhausted) runestone if found.

Parameters:

  • runestone (String)

    the runestone item noun

  • settings (OpenStruct)

    configuration with .runestone_storage attribute

Returns:

  • (Boolean)

    true if runestone is now in hand, false on error or no storage



597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'documented/dragonrealms/commons/common-arcana.rb', line 597

def get_runestone?(runestone, settings)
  return true if DRCI.in_hands?(runestone)

  result = DRC.bput(
    "get my #{runestone} from my #{settings.runestone_storage}",
    USELESS_RUNESTONE_PATTERNS, GET_RUNESTONE_SUCCESS_PATTERNS, GET_RUNESTONE_FAILURE_PATTERNS
  )
  if USELESS_RUNESTONE_PATTERNS.any? { |pat| pat.match?(result) }
    DRCI.dispose_trash(runestone)
    Lich::Messaging.msg("bold", "DRCA: got a useless #{runestone} - disposing and giving up")
    return false
  elsif GET_RUNESTONE_FAILURE_PATTERNS.any? { |pat| pat.match?(result) }
    Lich::Messaging.msg("bold", "DRCA: could not find #{runestone} in #{settings.runestone_storage}")
    return false
  end
  true
end

.harness?(mana) ⇒ 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.

Harnesses the specified amount of mana and returns whether it succeeded.

Parameters:

  • mana (Integer)

    amount of mana to harness

Returns:

  • (Boolean)

    true if "You tap into" message received, false otherwise



282
283
284
285
286
287
# File 'documented/dragonrealms/commons/common-arcana.rb', line 282

def harness?(mana)
  result = DRC.bput("harness #{mana}", 'You tap into', 'Strain though you may')
  pause 0.5
  waitrt?
  return result =~ /You tap into/
end

.harness_mana(amounts) ⇒ void

This method returns an undefined value.

Harnesses each amount of mana in sequence, stopping at the first failure.

Parameters:

  • amounts (Array<Integer>)

    mana amounts to harness in order



294
295
296
297
298
# File 'documented/dragonrealms/commons/common-arcana.rb', line 294

def harness_mana(amounts)
  amounts.each do |mana|
    break unless harness?(mana)
  end
end

.infuse_om(harness, amount) ⇒ void

This method returns an undefined value.

Infuses mana into Osrel Meraud (OM) if it is active and below 90 percent. Harnesses mana beforehand if requested. Retries up to INFUSE_OM_MAX_RETRIES times before giving up.

Parameters:

  • harness (Boolean)

    whether to harness mana before infusing

  • amount (Integer, nil)

    amount of mana to infuse; returns early if nil



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'documented/dragonrealms/commons/common-arcana.rb', line 256

def infuse_om(harness, amount)
  return unless DRSpells.active_spells['Osrel Meraud'] && DRSpells.active_spells['Osrel Meraud'] < 90
  return unless amount

  retries = 0
  loop do
    if retries >= INFUSE_OM_MAX_RETRIES
      Lich::Messaging.msg("bold", "DRCA: infuse_om exhausted #{INFUSE_OM_MAX_RETRIES} retries - giving up")
      break
    end
    retries += 1

    pause 5 while DRStats.mana <= 40
    harness_mana([amount]) if harness
    break if INFUSE_OM_SUCCESS_PATTERNS.include?(DRC.bput("infuse om #{amount}", INFUSE_OM_SUCCESS_PATTERNS, INFUSE_OM_FAILURE_PATTERNS))

    pause 0.5
    waitrt?
  end
end

.invoke(cambrinth, dedicated_camb_use, invoke_amount) ⇒ void

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

This method returns an undefined value.

Invokes cambrinth, releasing stored mana. Returns early if cambrinth is nil. If invoke fails due to low Arcana ("too clumsy"), attempts to move cambrinth to hand and retry. Logs a warning if Arcana is too low to invoke while worn.

Parameters:

  • cambrinth (String)

    cambrinth item noun

  • dedicated_camb_use (String)

    dedicated use modifier (e.g., 'attunement')

  • invoke_amount (Integer, nil)

    amount of mana to invoke (optional suffix)



862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
# File 'documented/dragonrealms/commons/common-arcana.rb', line 862

def invoke(cambrinth, dedicated_camb_use, invoke_amount)
  return unless cambrinth

  result = DRC.bput("invoke my #{cambrinth} #{invoke_amount} #{dedicated_camb_use}".strip, get_data('spells').invoke_messages, 'Invoke what?')
  pause
  waitrt?
  case result
  when /you find it too clumsy/
    Lich::Messaging.msg("bold", "DRCA: your arcana skill is too low to invoke your cambrinth while worn")
    # If the cambrinth is in your hands and you can't invoke it, nothing else to do.
    unless DRCI.in_hands?(cambrinth)
      # Otherwise, try to find the cambrinth and get it to your hands.
      find_cambrinth(cambrinth, false, 999)
      # If you were able to get the cambrinth into a hand then retry invoking it.
      # You might not have been able to if your hands were full.
      if DRCI.in_hands?(cambrinth)
        invoke(cambrinth, dedicated_camb_use, invoke_amount)
        stow_cambrinth(cambrinth, false, 999)
      end
    end
  end
end

.invoke_avtalia(cambrinth, dedicated_camb_use, invoke_amount) ⇒ void

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

This method returns an undefined value.

Invokes Avtalia cambrinth if the avtalia script is running. Updates UserVars.avtalia mana tracking.

Parameters:

  • cambrinth (String)

    cambrinth item noun

  • dedicated_camb_use (String)

    dedicated use modifier

  • invoke_amount (Integer)

    mana to invoke



1420
1421
1422
1423
1424
1425
1426
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1420

def invoke_avtalia(cambrinth, dedicated_camb_use, invoke_amount)
  return unless cambrinth
  return unless Script.running?('avtalia')

  invoke(cambrinth, dedicated_camb_use, invoke_amount)
  UserVars.avtalia[cambrinth]['mana'] -= [DRStats.mana, invoke_amount].min
end

.kneel_for_khri?(kneel, ability) ⇒ 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.

Determines whether to kneel before activating a specific khri ability.

Parameters:

  • kneel (Boolean, Array<String>)

    true for all khris, false for none, or list of khri names

  • ability (String)

    the khri ability name (with or without 'Khri' prefix)

Returns:

  • (Boolean)

    true if kneeling is required for this ability



352
353
354
355
356
357
358
# File 'documented/dragonrealms/commons/common-arcana.rb', line 352

def kneel_for_khri?(kneel, ability)
  if kneel.is_a? Array
    kneel.map(&:downcase).include? ability.downcase.sub('khri ', '')
  else
    kneel
  end
end

.need_buffs?(buff_list) ⇒ Boolean

Checks whether any buffs in the list are missing or need recasting. Behavior varies by guild: Barbarians check ability names directly; Thieves extract khri names and check by "Khri name"; other guilds check duration against recast timer.

Parameters:

  • buff_list (Array, Hash, nil)

    buffs to check (array for Barb/Thief, hash for others)

Returns:

  • (Boolean)

    true if any buff is missing or needs recasting, false otherwise



437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'documented/dragonrealms/commons/common-arcana.rb', line 437

def need_buffs?(buff_list)
  # no need to iterate if list of buffs is already empty
  return false if buff_list.nil? || buff_list.size == 0

  # commoner's don't really have a way to check buffs so assume always yes
  return true if DRStats.guild == "Commoner"

  # no need to iterate if no buffs active and buff_list is not empty
  return true if DRSpells.active_spells.empty?

  case DRStats.guild
  when 'Barbarian'
    # barbarian buffs are an array of strings so we can just iterate over it and check if any are not in the active spells hash
    buff_list.each { |buff| return true if !DRSpells.active_spells.key?("#{buff}") }
  when 'Thief'
    # thieves need special handling to convert buff_list to an array of JUST khri names due to the different options supported
    buff_list.map { |buff| buff.sub(/khri /i, '') }
             .map { |buff| buff.sub(/delay /i, '') }
             .join(' ')
             .split(' ')
             .each { |khri| return true if !DRSpells.active_spells.key?("Khri #{khri}") }
  else
    # for MUs check list of required buffs against list of active spells and their durations/recast
    buff_list.each do |spell, data|
      # ignore spells which don't recast (ignite, etf, etc.) but not cyclics
      next if data['recast'] < 0 && !data['cyclic']
      # if any buff is not in the active spells, or whose duration is less than recast, need to buff
      return true if !DRSpells.active_spells.key?(spell) || DRSpells.active_spells[spell] <= (data['recast'] ? data['recast'] : 1)
    end
  end

  # all buffs were in active spells with longer durations than recast, no need to buff
  return false
end

.normalize_cambrinth_items(settings) ⇒ void

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

This method returns an undefined value.

Converts legacy single-cambrinth settings into the multi-item format. If settings.cambrinth_items has no 'name' key, wraps legacy fields into a single-item array.

Parameters:

  • settings (OpenStruct)

    configuration to normalize



1499
1500
1501
1502
1503
1504
1505
1506
1507
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1499

def normalize_cambrinth_items(settings)
  return if settings.cambrinth_items[0]['name']

  settings.cambrinth_items = [{
    'name'   => settings.cambrinth,
    'cap'    => settings.cambrinth_cap,
    'stored' => settings.stored_cambrinth
  }]
end

.parse_mana_message(mana_msg) ⇒ Integer

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.

Parses a mana perception message to extract and return the mana level as an integer (1-4). Uses the global $MANA_MAP to categorize weakness, development, improvement, or goodness.

Parameters:

  • mana_msg (String)

    perception message containing 'weak', 'developing', 'improving', or other

Returns:

  • (Integer)

    mana level 1–4 based on message content



983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
# File 'documented/dragonrealms/commons/common-arcana.rb', line 983

def parse_mana_message(mana_msg)
  manalevels = if mana_msg.include? 'weak'
                 $MANA_MAP['weak']
               elsif mana_msg.include? 'developing'
                 $MANA_MAP['developing']
               elsif mana_msg.include? 'improving'
                 $MANA_MAP['improving']
               else
                 $MANA_MAP['good']
               end

  adj = mana_msg.split(' ')[-1]

  manalevels.index(adj).to_i + 1
end

.parse_regaliaArray<String>?

Parses worn combat regalia and returns nouns of crystal items found. Returns nil if not a Trader.

Returns:

  • (Array<String>, nil)

    nouns of worn rough-cut, faceted, or resplendent crystals; nil if not Trader



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

def parse_regalia
  return unless DRStats.trader?

  snapshot = Lich::Util.issue_command("inv combat", /All of your worn combat|You aren't wearing anything like that/, /Use INVENTORY HELP for more options/, usexml: false, include_end: false)
                       .map(&:strip)
  regalia_items = snapshot - ["All of your worn combat equipment:", "You aren't wearing anything like that."]
  regalia_items.select { |item| item.include?('rough-cut crystal') || item.include?('faceted crystal') || item.include?('resplendent crystal') }
               .map { |item| DRC.get_noun(item) }
end

.perc_auraHash?

Perceives Trader aura and returns level, saturation, and growth status. Returns nil if not a Trader.

Returns:

  • (Hash, nil)

    hash with keys: level (0-9), capped (Boolean), growing (Boolean); nil if not Trader



1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1043

def perc_aura
  return unless DRStats.trader?

  Flags.add('aura-level', Regexp.union(STARLIGHT_MESSAGES))
  Flags.add('aura-capped?', 'Your aura contains as much starlight as you can safely handle')
  Flags.add('aura-growing?', 'Local conditions permit optimal growth of your aura', 'Local conditions are hindering the growth of your aura')
  aura = {}
  DRC.bput('perceive aura', 'Roundtime')
  aura['level'] = Flags['aura-level'] ? STARLIGHT_MESSAGES.index(Flags['aura-level'][0]) : 0
  aura['capped'] = Flags['aura-capped?'] ? true : false
  aura['growing'] = Flags['aura-growing?'] ? true : false
  Flags.delete('aura-level')
  Flags.delete('aura-capped?')
  Flags.delete('aura-growing?')
  aura
end

.perc_manaHash, ...

Perceives mana streams and returns level information. For Moon Mages, returns a hash with mana levels for each school. For other guilds, returns a single integer (1–4). Returns nil for Barbarians, Thieves, Traders, Commoners, and on command failure.

Returns:

  • (Hash, Integer, nil)

    for Moon Mages, a hash with keys enlightened_geometry, moonlight_manipulation, perception, psychic_projection (each 1-4); for other guilds, an integer 1-4; nil if unavailable



1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1006

def perc_mana
  return nil if DRStats.barbarian? || DRStats.thief? || DRStats.trader? || DRStats.commoner?

  if DRStats.moon_mage?
    lines = Lich::Util.issue_command(
      'perc mana',
      PERC_MANA_START_PATTERN,
      PERC_MANA_END_PATTERN,
      usexml: false,
      include_end: false,
      quiet: true,
      timeout: 5
    )
    return nil if lines.nil?

    mana_msgs = lines.map(&:strip).select { |line| line.include?('streams') }[0..3]
    return nil if mana_msgs.length < 4

    mana_msgs.collect! { |mana_msg| mana_msg.split(' streams')[0] }

    {
      'enlightened_geometry'   => parse_mana_message(mana_msgs[0]),
      'moonlight_manipulation' => parse_mana_message(mana_msgs[1]),
      'perception'             => parse_mana_message(mana_msgs[2]),
      'psychic_projection'     => parse_mana_message(mana_msgs[3])
    }
  else
    mana_msg = DRC.bput('perc', '^You reach out with your .* and (see|hear) \w+')
    parse_mana_message(mana_msg)
  end
end

.perc_symbiotic_researchObject

check which symbiotic research is active



1481
1482
1483
1484
1485
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1481

def perc_symbiotic_research
  result = DRC.bput('perceive', SYMBIOSIS_PATTERN, /Roundtime/)
  match = result.match(SYMBIOSIS_PATTERN)
  match ? match[:type] : nil
end

.prepare?(abbrev, mana, symbiosis = false, command = 'prepare', tattoo_tm = false, runestone_name = nil, runestone_tm = false, custom_prep = nil, retries: PREPARE_MAX_RETRIES) ⇒ String, false

Prepares a spell for casting, handling symbiosis setup and various failure modes. Retries on false preparation (mind and body unwilling) up to PREPARE_MAX_RETRIES times.

Parameters:

  • abbrev (String)

    spell abbreviation (e.g., "oly", "ebp")

  • mana (Integer)

    mana to use in preparation

  • symbiosis (Boolean) (defaults to: false)

    whether to prepare symbiotic research first (default false)

  • command (String) (defaults to: 'prepare')

    preparation command, usually 'prepare' or custom command (default 'prepare')

  • tattoo_tm (Boolean) (defaults to: false)

    whether to target using a spell tattoo (default false)

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

    name of runestone to invoke instead of abbrev (default nil)

  • runestone_tm (Boolean) (defaults to: false)

    whether to target using a runestone (default false)

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

    additional prep message to match (default nil)

  • retries (Integer) (defaults to: PREPARE_MAX_RETRIES)

    maximum retry attempts (default PREPARE_MAX_RETRIES)

Returns:

  • (String, false)

    matched prep message on success, false on failure



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
# File 'documented/dragonrealms/commons/common-arcana.rb', line 486

def prepare?(abbrev, mana, symbiosis = false, command = 'prepare', tattoo_tm = false, runestone_name = nil, runestone_tm = false, custom_prep = nil, retries: PREPARE_MAX_RETRIES)
  return false unless abbrev
  spell_prep_messages = !custom_prep ? get_data('spells').prep_messages : (get_data('spells').prep_messages + [custom_prep])

  DRC.bput('prepare symbiosis', 'You recall the exact details of the', 'But you\'ve already prepared', 'Please don\'t do that here') if symbiosis
  if runestone_name.nil?
    match = DRC.bput("#{command} #{abbrev} #{mana}", spell_prep_messages)
  else
    match = DRC.bput("#{command} my #{runestone_name}", get_data('spells').invoke_messages)
  end
  case match
  when 'Your desire to prepare this offensive spell suddenly slips away'
    if retries <= 0
      Lich::Messaging.msg("bold", "DRCA: prepare? exhausted #{PREPARE_MAX_RETRIES} retries for '#{abbrev}' - giving up")
      return false
    end
    pause 1
    return prepare?(abbrev, mana, symbiosis, command, tattoo_tm, runestone_name, runestone_tm, custom_prep, retries: retries - 1)
  when 'Something in the area interferes with your spell preparations', 'You shouldn\'t disrupt the area right now', 'You have no idea how to cast that spell', 'You have yet to receive any training in the magical arts', 'Please don\'t do that here', 'You cannot use the tattoo while maintaining the effort to stay hidden'
    DRC.bput('release symbiosis', 'You release the', 'But you haven\'t') if symbiosis
    return false
  when 'Well, that was fun'
    DRCI.dispose_trash(runestone_name)
    return false
  when 'You\'ll have to hold it'
    return false
  end

  DRC.bput("target", spell_prep_messages) if tattoo_tm || runestone_tm

  match
end

.prepare_to_cast_runestone?(spell, settings) ⇒ Boolean

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

Checks if a runestone is available in storage and retrieves it. Returns false if the runestone is out of stock or retrieval fails.

Parameters:

  • spell (Hash)

    spell data with key runestone_name

  • settings (OpenStruct)

    configuration with .runestone_storage attribute

Returns:

  • (Boolean)

    true if runestone is now in hand, false otherwise



580
581
582
583
584
585
586
587
588
# File 'documented/dragonrealms/commons/common-arcana.rb', line 580

def prepare_to_cast_runestone?(spell, settings)
  if DRCI.inside?("#{spell['runestone_name']}", settings.runestone_storage)
    return false unless get_runestone?(spell['runestone_name'], settings)
  else
    Lich::Messaging.msg("bold", "DRCA: out of #{spell['runestone_name']}!")
    return false
  end
  true
end

.release_cyclics(cyclic_no_release = []) ⇒ void

This method returns an undefined value.

Releases all active cyclic spells except those in the exclusion list.

Parameters:

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

    spell names to keep active (default [])



934
935
936
937
938
939
940
941
942
# File 'documented/dragonrealms/commons/common-arcana.rb', line 934

def release_cyclics(cyclic_no_release = [])
  get_data('spells')
    .spell_data
    .select { |_name, properties| properties['cyclic'] }
    .select { |name, _properties| DRSpells.active_spells.keys.include?(name) }
    .reject { |name| cyclic_no_release.include?(name) }
    .map { |_name, properties| properties['abbrev'] }
    .each { |abbrev| DRC.bput("release #{abbrev}", CYCLIC_RELEASE_SUCCESS_PATTERNS, 'Release what?') }
end

.release_magical_researchObject

release symbiotic research



1488
1489
1490
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1488

def release_magical_research
  2.times { DRC.bput("release symbiosis", "Are you sure", "You intentionally wipe", "But you haven't") }
end

.ritual(data, settings) ⇒ void

This method returns an undefined value.

Performs a complete spell ritual: prepare, find focus, invoke, stow focus, and cast. Handles positioning, stance, and visibility. Does not cast if preparation fails.

Parameters:

  • data (Hash)

    spell data with keys: abbrev, mana, symbiosis, command, focus, worn_focus, tied_focus, sheathed_focus, cast, before, after, prep_time, ritual, skip_retreat, tattoo_tm, runestone_name, runestone_tm

  • settings (OpenStruct)

    configuration (ignored_npcs, custom_spell_prep)



544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
# File 'documented/dragonrealms/commons/common-arcana.rb', line 544

def ritual(data, settings)
  DRC.retreat(settings.ignored_npcs) unless data['skip_retreat']
  DRC.release_invisibility
  DRC.set_stance('shield') unless data['skip_retreat']

  command = 'prepare'
  command = data['prep'] if data['prep']
  command = data['prep_type'] if data['prep_type']

  return unless prepare?(data['abbrev'], data['mana'], data['symbiosis'], command, data['tattoo_tm'], data['runestone_name'], data['runestone_tm'], settings['custom_spell_prep'])

  prepare_time = Time.now
  find_focus(data['focus'], data['worn_focus'], data['tied_focus'], data['sheathed_focus'])

  invoke(data['focus'], nil, nil)
  stow_focus(data['focus'], data['worn_focus'], data['tied_focus'], data['sheathed_focus'])
  DRC.retreat(settings.ignored_npcs) unless data['skip_retreat']

  if data['prep_time']
    pause until Time.now - prepare_time >= data['prep_time']
  else
    waitcastrt?
  end

  return unless cast?(data['cast'], data['symbiosis'], data['before'], data['after'])

  DRC.retreat(settings.ignored_npcs) unless data['skip_retreat']
end

.segue?(abbrev, mana) ⇒ Boolean

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

Attempts to segue into a new cyclic spell from an active cyclic. Returns false on failure conditions: not performing a cyclic, too soon to segue, or lacking bardic flair.

Parameters:

  • abbrev (String)

    spell abbreviation to segue to

  • mana (Integer)

    mana to use

Returns:

  • (Boolean)

    true if segue succeeded, false on failure



1176
1177
1178
1179
1180
1181
1182
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1176

def segue?(abbrev, mana)
  case DRC.bput("segue #{abbrev} #{mana}", get_data('spells').segue_messages)
  when 'You must be performing a cyclic spell to segue from', 'It is too soon to segue', 'You are lacking the bardic flair'
    return false
  end
  true
end

.shatter_regalia?(worn_regalia = nil) ⇒ Boolean

Shatters (removes) worn crystal regalia, converting them to motes. Parses regalia if not provided. Returns false if not a Trader or regalia is empty.

Parameters:

  • worn_regalia (Array<String>, nil) (defaults to: nil)

    nouns of crystals to shatter (default nil, auto-parse)

Returns:

  • (Boolean)

    true if any regalia were shattered, false otherwise



965
966
967
968
969
970
971
972
973
974
975
# File 'documented/dragonrealms/commons/common-arcana.rb', line 965

def shatter_regalia?(worn_regalia = nil)
  return false unless DRStats.trader?

  worn_regalia ||= parse_regalia
  return false if worn_regalia.empty?

  worn_regalia.each do |item|
    DRC.bput("remove my #{item}", 'into motes of silvery', 'Remove what?', "You .*#{item}")
  end
  true
end

.skilled_to_charge_while_worn?(cambrinth_cap) ⇒ 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.

Determines if Arcana skill is high enough to charge cambrinth while wearing it. Requires Arcana rank >= (capacity * 2) + 100.

Parameters:

  • cambrinth_cap (Integer)

    mana capacity of the cambrinth

Returns:

  • (Boolean)

    true if skilled, false otherwise



828
829
830
# File 'documented/dragonrealms/commons/common-arcana.rb', line 828

def skilled_to_charge_while_worn?(cambrinth_cap)
  DRSkill.getrank('Arcana').to_i >= ((cambrinth_cap.to_i * 2) + 100)
end

.spell_prepared?Boolean

Returns true if you're prepared to cast your spell. Infers this if you're preparing a spell and there's no more prep time to wait.

Returns:

  • (Boolean)


526
527
528
# File 'documented/dragonrealms/commons/common-arcana.rb', line 526

def spell_prepared?
  spell_preparing? && checkcastrt <= 0
end

.spell_preparingObject

Returns name of the spell being prepared, or nil if not preparing one.



531
532
533
534
535
# File 'documented/dragonrealms/commons/common-arcana.rb', line 531

def spell_preparing
  name = XMLData.prepared_spell
  name = nil if name.empty? || name.eql?('None')
  name
end

.spell_preparing?Boolean

Returns true if preparing a spell, false otherwise.

Returns:

  • (Boolean)


520
521
522
# File 'documented/dragonrealms/commons/common-arcana.rb', line 520

def spell_preparing?
  !spell_preparing.nil?
end

.start_barb_abilities(abilities, settings) ⇒ void

This method returns an undefined value.

Activates each Barbarian ability in the list.

Parameters:

  • abilities (Array<String>)

    ability names to activate

  • settings (OpenStruct)

    configuration object



366
367
368
# File 'documented/dragonrealms/commons/common-arcana.rb', line 366

def start_barb_abilities(abilities, settings)
  abilities.each { |name| activate_barb_buff?(name, settings.meditation_pause_timer, settings.sit_to_meditate) }
end

.start_khris(khris, settings) ⇒ void

This method returns an undefined value.

Activates each khri set in the list, kneeling if configured to do so.

Parameters:

  • khris (Array<String>)

    khri ability names or variants to activate

  • settings (OpenStruct)

    configuration with .kneel_khri attribute



306
307
308
309
310
311
# File 'documented/dragonrealms/commons/common-arcana.rb', line 306

def start_khris(khris, settings)
  khris
    .each do |khri_set|
      activate_khri?(settings.kneel_khri, khri_set)
    end
end

.stow_cambrinth(cambrinth, stored_cambrinth, _cambrinth_cap) ⇒ Boolean, void

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

Returns cambrinth to its configured storage location (stowed or worn). Handles cases where it is in hand or worn unexpectedly.

Parameters:

  • cambrinth (String)

    cambrinth item noun

  • stored_cambrinth (Boolean)

    whether cambrinth is kept stowed

  • _cambrinth_cap (Integer)

    unused; kept for signature compatibility

Returns:

  • (Boolean, void)

    result of storage operation or true if no action needed



802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
# File 'documented/dragonrealms/commons/common-arcana.rb', line 802

def stow_cambrinth(cambrinth, stored_cambrinth, _cambrinth_cap)
  if stored_cambrinth
    # Your config says you keep your cambrinth stowed.
    # If item not in your hands and not stowed, maybe you're wearing it by accident?
    DRCI.get_item_if_not_held?(cambrinth) || DRCI.remove_item?(cambrinth)
    DRCI.stow_item?(cambrinth)
  elsif DRCI.in_hands?(cambrinth)
    # Your config says you wear your cambrinth.
    # For some reason it's currently in your hands.
    # If can't wear item for some reason then stow it.
    DRCI.wear_item?(cambrinth) || DRCI.stow_item?(cambrinth)
  else
    # Your config says you wear your cambrinth
    # and you're not currently holding it so
    # we'll assume you're wearing it.
    # No further action needed.
    true
  end
end

.stow_focus(focus, worn, tied, sheathed, retries: STOW_FOCUS_MAX_RETRIES) ⇒ Boolean, void

Stows a spell focus back to its configured location. Retries tie attempts up to STOW_FOCUS_MAX_RETRIES times with retreat. Does nothing if focus is nil.

Parameters:

  • focus (String, nil)

    focus item noun

  • worn (Boolean)

    whether to wear the focus back on body

  • tied (String)

    noun of the location to tie focus to, or false if not tied

  • sheathed (Boolean)

    whether to sheathe the focus

  • retries (Integer) (defaults to: STOW_FOCUS_MAX_RETRIES)

    maximum retry attempts for tie operations (default STOW_FOCUS_MAX_RETRIES)

Returns:

  • (Boolean, void)

    false only on tie retry exhaustion; otherwise void or boolean from wear/stow



740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
# File 'documented/dragonrealms/commons/common-arcana.rb', line 740

def stow_focus(focus, worn, tied, sheathed, retries: STOW_FOCUS_MAX_RETRIES)
  return unless focus

  if worn
    DRCI.wear_item?(focus)
  elsif tied
    result = DRCI.tie_item?(focus, tied)
    unless result
      if retries <= 0
        Lich::Messaging.msg("bold", "DRCA: stow_focus exhausted #{STOW_FOCUS_MAX_RETRIES} retries tying #{focus} - giving up")
        return false
      end
      DRC.retreat
      return stow_focus(focus, worn, tied, sheathed, retries: retries - 1)
    end
    result
  elsif sheathed
    result = DRC.bput("sheathe my #{focus}", SHEATHE_FOCUS_SUCCESS_PATTERNS, SHEATHE_FOCUS_FAILURE_PATTERNS)
    SHEATHE_FOCUS_FAILURE_PATTERNS.none? { |pattern| pattern =~ result }
  else
    DRCI.stow_item?(focus)
  end
end

.update_avtaliavoid

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

This method returns an undefined value.

Updates Avtalia cambrinth focus state by sensing mana charge.



1407
1408
1409
1410
# File 'documented/dragonrealms/commons/common-arcana.rb', line 1407

def update_avtalia
  DRC.bput("focus cambrinth", /^The .+ pulses? .+ (\d+)/, 'dim, almost magically null', '^You let your magical senses wander')
  waitrt?
end