Module: Lich::Common::CreatureBase::InstanceMethods

Defined in:
documented/common/creature/creature_base.rb

Overview

Per-instance transient status and classification tracking driven by the <crtrStatus> feed. The host class must call #initialize_status_tracking from its own initialize and expose @name/@id (used only for debug headers).

Instance Method Summary collapse

Instance Method Details

#add_status(status, duration = nil) ⇒ void

This method returns an undefined value.

Adds a status to the creature.

Normalizes to String so callers can pass symbols from message-based status parsing or strings from <crtrStatus>; both sources then land in the same status entries and match #has_status?.

Parameters:

  • status (String, Symbol) —

    canonical status name.

  • duration (Integer, nil) (defaults to: nil) —

    optional expiration duration in seconds; falls back to STATUS_DURATIONS when omitted.



452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'documented/common/creature/creature_base.rb', line 452

def add_status(status, duration = nil)
  # Canonicalize case at the boundary, not per producer: the crit
  # tables report position as "PRONE"/"KNEELING"/"SITTING" while
  # messaging and <crtrStatus> use lowercase, and @status membership
  # is exact - an uppercase add could never be removed or matched.
  status = status.to_s.downcase
  if @status.include?(status)
    # Re-adding with an explicit duration extends the lock: without
    # this, a 3s roundtime followed by an overlapping 20s one kept
    # the 3s expiry and reported the creature free ~19s early.
    # Never shorten - the longest known source wins.
    if duration
      expires = Time.now + duration
      current = @status_timestamps[status]
      @status_timestamps[status] = expires if current.nil? || expires > current
    end
    return
  end

  @status << status

  duration ||= STATUS_DURATIONS[status]
  if duration
    @status_timestamps[status] = Time.now + duration
    debug_log("+status: #{status} (expires in #{duration}s)")
  else
    debug_log("+status: #{status} (no auto-expiry)")
  end
end

#cleanup_expired_statuses ⇒ void

This method returns an undefined value.

Drops any timed statuses whose expiry has passed.



500
501
502
503
504
505
506
507
508
509
# File 'documented/common/creature/creature_base.rb', line 500

def cleanup_expired_statuses
  return unless @status_timestamps && !@status_timestamps.empty?

  now = Time.now
  @status_timestamps.select { |_status, expires_at| expires_at <= now }.keys.each do |expired_status|
    @status.delete(expired_status)
    @status_timestamps.delete(expired_status)
    debug_log("~status: #{expired_status} (auto-expired)")
  end
end

#crtr_flag?(key) ⇒ Boolean

Checks a classification flag captured from <crtrStatus>.

Unlike template tri-state facts, live XML flags are always-sent booleans, so an unknown or unseen flag is false, not nil.

Parameters:

  • key (String, Symbol) —

    classification key, e.g. :hostile.

Returns:

  • (Boolean)


569
570
571
# File 'documented/common/creature/creature_base.rb', line 569

def crtr_flag?(key)
  @crtr_flags[key.to_sym] || false
end

#crtr_flags? ⇒ Boolean

Whether any <crtrStatus> classification has been seen for this creature yet.

Callers filtering on a flag need to tell "the feed said this is not hostile" apart from "the feed has not told us anything" - an absent flag is unknown, not false. A ridden mount, for instance, carries a bold creature link but never a <crtrStatus> of its own.

Returns:

  • (Boolean)


582
583
584
# File 'documented/common/creature/creature_base.rb', line 582

def crtr_flags?
  !@crtr_flags.empty?
end

#flag_active?(key) ⇒ Boolean

Checks whether a status or classification flag is active.

The two vocabularies are intentionally disjoint, so callers can filter on any known flag name without caring which bucket stores it.

Parameters:

  • key (String, Symbol) —

    status or classification key.

Returns:

  • (Boolean)


593
594
595
# File 'documented/common/creature/creature_base.rb', line 593

def flag_active?(key)
  has_status?(key) || crtr_flag?(key)
end

#has_status?(status) ⇒ Boolean

Checks whether a specific status is active (after expiring stale ones).

Parameters:

  • status (String, Symbol) —

    canonical status name.

Returns:

  • (Boolean)


515
516
517
518
# File 'documented/common/creature/creature_base.rb', line 515

def has_status?(status)
  cleanup_expired_statuses
  @status.include?(status.to_s.downcase)
end

#initialize_status_tracking ⇒ void

This method returns an undefined value.

Initializes the tracking ivars. Call from the host class initialize.



436
437
438
439
440
# File 'documented/common/creature/creature_base.rb', line 436

def initialize_status_tracking
  @status = []
  @status_timestamps = {}
  @crtr_flags = {}
end

#remove_status(status) ⇒ void

This method returns an undefined value.

Removes a status from the creature.

Parameters:

  • status (String, Symbol) —

    canonical status name.



486
487
488
489
490
491
492
493
494
495
# File 'documented/common/creature/creature_base.rb', line 486

def remove_status(status)
  status = status.to_s.downcase
  @status.delete(status)
  @status_timestamps.delete(status)
  # A stun removal (message or <crtrStatus> snapshot) is authoritative
  # and retires any crit-table duration estimate with it, however much
  # time that estimate had left. GemStone-only hook.
  clear_stun_estimate if status == 'stunned' && respond_to?(:clear_stun_estimate)
  debug_log("-status: #{status}")
end

#statuses ⇒ Array<String>

Returns a copy of the currently-active statuses (after expiry).

Returns:



523
524
525
526
# File 'documented/common/creature/creature_base.rb', line 523

def statuses
  cleanup_expired_statuses
  @status.dup
end

#sync_crtr_status(attrs) ⇒ void

This method returns an undefined value.

Reconciles status and classification from a <crtrStatus> tag.

The tag is a full snapshot of what is currently active, not a delta. A missing flag, or a flag set to "0", means inactive even if it was active a moment ago, so absent known flags are cleared rather than ignored.

Parameters:



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'documented/common/creature/creature_base.rb', line 537

def sync_crtr_status(attrs)
  # Scoped to CRTR_STATUS_FLAGS on purpose: @status has two writers,
  # this feed and the combat parser reading messaging. The feed is a
  # full snapshot only of the states it reports, so it owns removal
  # for exactly those - and must not touch the rest. blind,
  # poisoned, natures_decay and the other message-only effects never
  # appear in the tag; reconciling them here would clear them on the
  # next one and make them impossible to model.
  CRTR_STATUS_FLAGS.each do |xml_name, status|
    if attrs[xml_name] == '1'
      add_status(status)
    elsif @status.include?(status)
      remove_status(status)
    end
  end

  CRTR_CLASSIFICATION_FLAGS.each do |xml_name, key|
    new_value = (attrs[xml_name] == '1')
    debug_log("~flag: #{key}=#{new_value}") if debug_level == :changes && @crtr_flags[key] != new_value
    @crtr_flags[key] = new_value
  end

  report_crtr_snapshot(attrs) if %i[all active].include?(debug_level)
end