Module: Lich::Gemstone::Combat::Observers

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

Overview

Subscription feed for parsed combat events: damage, wounds, status changes, and creature state transitions.

The Creature registry is the read model for current state; Observers emit the transient events that state queries cannot provide: edges (transitions like stunned→unstunned), per-event ledger details (individual damage instances), and session-only facts before they are swept.

Subscribers must be cheap, non-blocking, and may never send game commands (fput, Spell#cast, PSMS.use) - queue work for your own script thread. Errors are isolated, logged, and never break other subscribers.

Class Method Summary collapse

Class Method Details

.any_for?(type) ⇒ Boolean

Returns true if there are any subscribers for the given event type or :any.

Allows the processor to avoid constructing event payloads when nobody is listening.

Examples:

Combat::Tracker.any_for?(:damage) #=> true

Parameters:

  • type (Symbol) —

    the event type to check (e.g., :damage, :status)

Returns:

  • (Boolean) —

    true if one or more handlers are registered



137
138
139
# File 'documented/gemstone/combat/observers.rb', line 137

def any_for?(type)
  @mutex.synchronize { !@subscribers[type].empty? || !@subscribers[:any].empty? }
end

.clear! ⇒ void

This method returns an undefined value.

Removes all subscribers and named registrations.

Used during processor teardown and testing.



146
147
148
149
150
151
# File 'documented/gemstone/combat/observers.rb', line 146

def clear!
  @mutex.synchronize do
    @subscribers.clear
    @named.clear
  end
end

.emit(type, data) ⇒ Object

Emit an event to type + :any subscribers. Subscriber errors are isolated and logged, never raised to the caller (the processor).



117
118
119
120
121
122
123
124
125
126
127
# File 'documented/gemstone/combat/observers.rb', line 117

def emit(type, data)
  handlers = @mutex.synchronize { @subscribers[type].dup + @subscribers[:any].dup }
  handlers.each do |handler|
    begin
      handler.call(type, data)
    rescue StandardError => e
      Lich.log "error: Combat::Observers subscriber (#{type}): #{e.message}\n\t#{e.backtrace&.first}"
    end
  end
  nil
end

.off(handler_or_name) ⇒ Object

Remove a handler - pass the Proc returned by on, or the name it was registered under.



102
103
104
105
106
107
108
109
110
111
112
113
# File 'documented/gemstone/combat/observers.rb', line 102

def off(handler_or_name)
  @mutex.synchronize do
    handler = if handler_or_name.is_a?(Proc)
                handler_or_name
              else
                @named.delete(handler_or_name.to_s)
              end
    @named.delete_if { |_, h| h == handler }
    @subscribers.each_value { |list| list.delete(handler) } if handler
  end
  nil
end

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

Subscribe to one or more event types (or :any for everything). Returns the block; keep it to unsubscribe via .off.

With name:, registration is idempotent (DownstreamHook.add semantics): re-registering the same name replaces the previous handler instead of stacking - safe for script restarts and interactive ;e testing.

Raises:

  • (ArgumentError)


85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'documented/gemstone/combat/observers.rb', line 85

def on(*types, name: nil, &block)
  raise ArgumentError, 'block required' unless block

  types = [:any] if types.empty?
  @mutex.synchronize do
    if name
      old = @named.delete(name.to_s)
      @subscribers.each_value { |list| list.delete(old) } if old
      @named[name.to_s] = block
    end
    types.each { |t| @subscribers[t.to_sym] << block }
  end
  block
end