Module: Lich::Gemstone::Combat::Tracker

Defined in:
documented/gemstone/combat/tracker.rb

Overview

Combat tracking system

Main interface for the combat tracking system. Integrates with Lich's downstream hooks to process game output and track combat events.

Features:

  • Damage tracking and HP estimation
  • Wound/injury tracking by body part
  • Status effect tracking with auto-expiration
  • UCS (Unarmed Combat System) support
  • Async processing for performance
  • Automatic creature registry cleanup

Examples:

Enable tracking

Combat::Tracker.enable!
Combat::Tracker.configure(track_wounds: true, track_statuses: true)

Get statistics

stats = Combat::Tracker.stats
respond "Active threads: #{stats[:active]}"

Constant Summary collapse

DEFAULT_SETTINGS =

Default settings for combat tracking

{
  enabled: false,           # Disabled by default, user must enable
  track_damage: true,
  track_wounds: true,
  track_statuses: true,
  track_ucs: true,          # Track UCS (position, tierup, smite)
  emit_attacks: false,      # Emit whole parsed events (:attack blob) for recorder-class subscribers
  max_threads: 2,           # Keep threading for performance
  debug: false,
  buffer_size: 200,         # Increase for large combat chunks
  fallback_max_hp: 350,     # Default max HP when template unavailable
  cleanup_interval: 100,    # Cleanup creature registry every N chunks
  cleanup_max_age: 600      # Remove creatures older than N seconds (10 minutes)
}.freeze
DEBUG_LEVELS =

Debug levels, in the order they were introduced. true is kept as an alias for :verbose so settings saved before levels existed still resolve.

%i[summary verbose].freeze
COMBAT_RELEVANT_PATTERN =

Single compiled filter for combat-relevant content. One regex scan replaces ~11 include? calls plus a regex per line; alternation of literals compiles to an efficient multi-substring search.

Regexp.union(
  'points of damage',
  ' damage!', # roll-based short form ("... 6 damage!")
  # has no "points of" - chunks holding
  # only these were dropped entirely
  '<pushBold/>',              # Creatures
  '**',                       # Flares
  'AS:',                      # Attack rolls
  'swing', 'thrust', 'cast', 'gesture',
  'positioning against',      # UCS position
  'vulnerable to a followup', # UCS tierup
  'crimson mist'              # UCS smite
).freeze
COMBAT_RESOLUTION_PATTERN =

Pattern matching combat resolution outcomes: hit, miss, parry, block, or dodge.

Case-insensitive word-boundary match used to detect attack resolution lines in game output when the full COMBAT_RELEVANT_PATTERN is too broad.

Examples:

"You hit the goblin." #=> match
"Your swing misses." #=> match

See Also:

/\b(?:hit|miss|parr|block|dodge)\b/i.freeze

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.buffer ⇒ Object (readonly)

Returns the value of attribute buffer.



68
69
70
# File 'documented/gemstone/combat/tracker.rb', line 68

def buffer
  @buffer
end

.settings ⇒ Object (readonly)

Returns the value of attribute settings.



68
69
70
# File 'documented/gemstone/combat/tracker.rb', line 68

def settings
  @settings
end

Class Method Details

.combat_relevant?(line) ⇒ Boolean

Check if line contains combat-relevant content

Quick filter to avoid processing non-combat lines.

Parameters:

  • line (String) —

    Game line to check

Returns:

  • (Boolean) —

    true if line may contain combat events



277
278
279
# File 'documented/gemstone/combat/tracker.rb', line 277

def combat_relevant?(line)
  COMBAT_RELEVANT_PATTERN.match?(line) || COMBAT_RESOLUTION_PATTERN.match?(line)
end

.configure(new_settings = {}) ⇒ void

This method returns an undefined value.

Update tracker settings

Merges new settings with existing ones and persists to Lich settings. Reinitializes processor if thread count changes.

Parameters:

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

    Settings to update

Options Hash (new_settings):

  • :enabled (Boolean) —

    Enable/disable tracking

  • :track_damage (Boolean) —

    Track damage

  • :track_wounds (Boolean) —

    Track wounds/injuries

  • :track_statuses (Boolean) —

    Track status effects

  • :track_ucs (Boolean) —

    Track UCS data

  • :max_threads (Integer) —

    Thread pool size

  • :debug (Boolean) —

    Enable debug logging



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'documented/gemstone/combat/tracker.rb', line 295

def configure(new_settings = {})
  initialize! unless @initialized
  @settings.merge!(new_settings)

  # Save to Lich settings system for persistence
  save_settings

  # Reinitialize processor if thread count changed
  if new_settings.key?(:max_threads)
    shutdown_processor
    initialize_processor
  end

  respond "[Combat] Settings updated: #{@settings}" if debug?
end

.debug?(level = nil) ⇒ Symbol, ...

Check if debug mode is enabled, or whether a specific level is.

Called bare it returns the active level (truthy), which keeps every existing if Tracker.debug? guard working unchanged.

Parameters:

  • level (Symbol, nil) (defaults to: nil) —

    :verbose or :summary to test one level

Returns:

  • (Symbol, Boolean, nil) —

    active level, or whether level is on



151
152
153
154
155
156
157
# File 'documented/gemstone/combat/tracker.rb', line 151

def debug?(level = nil)
  current = @settings[:debug] || $combat_debug
  current = :verbose if current == true
  return current unless level

  current == level
end

.disable! ⇒ void

This method returns an undefined value.

Disable combat tracking

Shuts down the processor, removes hooks, and persists disabled state.



126
127
128
129
130
131
132
133
134
135
136
137
# File 'documented/gemstone/combat/tracker.rb', line 126

def disable!
  return unless @enabled

  initialize! unless @initialized
  @enabled = false
  @settings[:enabled] = false
  save_settings # Persist disabled state
  remove_downstream_hook
  shutdown_processor

  respond "[Combat] Combat tracking disabled" if debug?
end

.disable_debug! ⇒ void

This method returns an undefined value.

Disable debug logging

Restores the thread count debug mode took over.



189
190
191
192
193
194
195
# File 'documented/gemstone/combat/tracker.rb', line 189

def disable_debug!
  initialize! unless @initialized
  restore = @pre_debug_threads || DEFAULT_SETTINGS[:max_threads]
  @pre_debug_threads = nil
  configure(debug: false, max_threads: restore)
  respond "[Combat] Debug mode disabled (max_threads: #{restore})"
end

.enable! ⇒ void

This method returns an undefined value.

Enable combat tracking

Initializes the processor, loads settings, and adds downstream hook. Persists enabled state to DB.



108
109
110
111
112
113
114
115
116
117
118
119
# File 'documented/gemstone/combat/tracker.rb', line 108

def enable!
  return if @enabled

  initialize! unless @initialized
  @enabled = true
  @settings[:enabled] = true # Force enabled in settings
  save_settings # Persist enabled state
  initialize_processor
  add_downstream_hook

  respond "[Combat] Combat tracking enabled" if debug?
end

.enable_debug!(level = :verbose) ⇒ void

This method returns an undefined value.

Enable debug logging

Forces inline processing (max_threads: 0) for the duration, so debug output appears in true order relative to the game text instead of interleaving from the async worker.

Parameters:

  • level (Symbol) (defaults to: :verbose) —

    :verbose for the line-by-line parse trace, :summary for one line per persisted event



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'documented/gemstone/combat/tracker.rb', line 168

def enable_debug!(level = :verbose)
  level = :verbose if level == true
  unless DEBUG_LEVELS.include?(level)
    respond "[Combat] Unknown debug level #{level.inspect} (expected #{DEBUG_LEVELS.map(&:inspect).join(' or ')})"
    return
  end

  initialize! unless @initialized
  # Only capture on the first enable - a second call while already
  # in debug would otherwise record the forced 0 as the value to
  # restore, stranding the character inline.
  @pre_debug_threads = @settings[:max_threads] unless debug?
  configure(debug: level, enabled: true, max_threads: 0)
  respond "[Combat] Debug mode enabled (#{level}, inline processing)"
end

.enabled? ⇒ Boolean

Check if combat tracking is enabled

Lazily initializes on first check if not already initialized.

Returns:

  • (Boolean) —

    true if tracking is active



92
93
94
95
96
97
98
99
100
# File 'documented/gemstone/combat/tracker.rb', line 92

def enabled?
  # Before login data is available we can't load per-character
  # settings; report disabled instead of sleeping on the caller's
  # thread (the background init thread completes setup once ready).
  return false unless @initialized || xmldata_ready?

  initialize! unless @initialized
  @enabled && @settings[:enabled]
end

.fallback_hp ⇒ Integer

Returns the configured fallback max HP for creatures without templates.

Returns:

  • (Integer) —

    the fallback max HP value



209
210
211
212
# File 'documented/gemstone/combat/tracker.rb', line 209

def fallback_hp
  initialize! unless @initialized
  @settings[:fallback_max_hp]
end

.off(handler_or_name) ⇒ Object

Unsubscribe a handler returned by on, or by its name:.



83
84
85
# File 'documented/gemstone/combat/tracker.rb', line 83

def off(handler_or_name)
  Observers.off(handler_or_name)
end

.on(*types, name: nil, &block) ⇒ Proc

Subscribe to parsed combat events (see Combat::Observers for event types, payloads, and the subscriber contract - callbacks may run on worker threads; never send game commands from one).

Examples:

Combat::Tracker.on(:damage) { |type, data| queue << data }
Combat::Tracker.on(:damage, name: 'mybar') { ... } # idempotent

Returns:

  • (Proc) —

    handler; pass to off to unsubscribe



78
79
80
# File 'documented/gemstone/combat/tracker.rb', line 78

def on(*types, name: nil, &block)
  Observers.on(*types, name: name, &block)
end

.process(chunk) ⇒ void

This method returns an undefined value.

Process a chunk of game lines

Filters for combat-relevant lines and processes them. Triggers periodic cleanup of old creature instances.

Parameters:

  • chunk (Array<String>) —

    Game lines to process



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'documented/gemstone/combat/tracker.rb', line 221

def process(chunk)
  return unless enabled?
  return if chunk.empty?

  # Quick filter - only process if combat-related content present
  return unless chunk.any? { |line| combat_relevant?(line) }

  if @async_processor
    @async_processor.process_async(chunk)
  else
    Processor.process(chunk)
  end

  # Periodic cleanup of old creature instances
  @chunks_processed += 1
  if @chunks_processed >= @settings[:cleanup_interval]
    cleanup_creatures
    @chunks_processed = 0
  end
end

.set_fallback_hp(hp_value) ⇒ void

This method returns an undefined value.

Set fallback HP value for creatures without templates

Parameters:

  • hp_value (Integer) —

    Default max HP value



201
202
203
204
# File 'documented/gemstone/combat/tracker.rb', line 201

def set_fallback_hp(hp_value)
  configure(fallback_max_hp: hp_value.to_i)
  respond "[Combat] Fallback max HP set to #{hp_value}"
end

.stats ⇒ Hash

Get processing statistics

Returns:

  • (Hash) —

    Stats including :enabled, :buffer_size, :settings, :active, :total



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'documented/gemstone/combat/tracker.rb', line 314

def stats
  return { enabled: false } unless enabled?

  base_stats = {
    enabled: true,
    buffer_size: @buffer.size,
    settings: @settings
  }

  if @async_processor
    base_stats.merge(@async_processor.stats)
  else
    base_stats.merge(active: 0, total: 0)
  end
end