class_name Combatant extends NBattleObject ## An entity that participates in battle. ## Has stats, active effects, flags, and an active state. signal active_changed(active: bool) var _active: bool = false var _stats: Array # Array of Stat references var _effects: Array # Array of dictionaries: {effect: EffectBase, ctx: EffectCtx} var _flags: int = 0 func _init(context: BattleContext) -> void: super._init(context) _active = false _stats = [] _effects = [] _flags = 0 set_active(true) ## Resets all stat mods back to their base values. func reset() -> void: for stat in _stats: stat.reset() ## Returns true if this combatant is active in battle. func is_active() -> bool: return _active ## Sets the active state. Emits a CombatantUpdate event. func set_active(active: bool) -> void: if active != _active: _active = active active_changed.emit(_active) _get_context()._emit_event(CombatantUpdateEvent.new(get_id(), _active, _flags)) ## Gets a stat for this combatant. Lazily creates it on first access. ## Accepts a StatDef, stat name (String), or stat ID (int). func get_stat(stat_def: Variant) -> Stat: var resolved: StatDef = _resolve_stat_def(stat_def) if resolved == null: push_error("NBattle: Could not resolve StatDef in get_stat") return null for stat in _stats: if stat.get_def().get_id() == resolved.get_id(): return stat var new_stat: Stat = Stat.new(resolved) _stats.append(new_stat) return new_stat ## Gets all stats for this combatant. func get_stats() -> Array: return _stats ## Applies an effect. If potency <= 0, removes the effect. ## Accepts EffectDef, effect name (String), or effect ID (int) for effect_def. ## Source can be a Combatant, NBattleObject, or null. func set_effect(effect_def: Variant, potency: int, source: Variant = null) -> void: var resolved_def: EffectDef = _resolve_effect_def(effect_def) if resolved_def == null: push_error("NBattle: Could not resolve EffectDef in set_effect") return var resolved_source: NBattleObject = _resolve_source(source) _remove_effect(resolved_def) if potency <= 0: return _add_effect(resolved_def, potency, resolved_source) ## Returns true if this combatant has the given effect. func has_effect(effect_def: Variant) -> bool: var resolved_def: EffectDef = _resolve_effect_def(effect_def) if resolved_def == null: return false for ce in _effects: if ce["ctx"].def.get_id() == resolved_def.get_id(): return true return false ## Sets or clears a flag. func set_flag(flag: Variant, on: bool) -> void: var flag_value: int = _resolve_flag(flag) if flag_value < 0: return if on: _flags |= flag_value else: _flags = _flags & ~flag_value _get_context()._emit_event(CombatantUpdateEvent.new(get_id(), _active, _flags)) ## Returns true if the given flag is set. func has_flag(flag: Variant) -> bool: var flag_value: int = _resolve_flag(flag) if flag_value < 0: return false return (_flags & flag_value) != 0 ## Gets the raw flags value. func get_flags() -> int: return _flags ## Gets all active effects on this combatant. func get_effects() -> Array: return _effects ## Dispatches an event to all effects on this combatant. func handle_effect_event(evt: NBattleEvent) -> void: var snapshot: Array = _effects.duplicate() for ce in snapshot: var effect: EffectBase = ce["effect"] var effect_ctx: EffectCtx = ce["ctx"] if _get_context()._is_effect_in_stack(effect): continue _get_context()._push_effect_stack(effect) effect.on_event(effect_ctx, evt) _get_context()._pop_effect_stack(effect) ## Removes an effect by its definition. Calls on_remove and emits event. func _remove_effect(effect_def: EffectDef) -> void: for i in range(_effects.size()): if _effects[i]["ctx"].def.get_id() == effect_def.get_id(): var ce: Dictionary = _effects[i] var effect: EffectBase = ce["effect"] var effect_ctx: EffectCtx = ce["ctx"] _effects.remove_at(i) if not _get_context()._is_effect_in_stack(effect): _get_context()._push_effect_stack(effect) effect.on_remove(effect_ctx) _get_context()._pop_effect_stack(effect) _get_context()._emit_event(CombatantEffectEvent.new(get_id(), effect_def.get_id(), 0, 0)) return func _add_effect(effect_def: EffectDef, potency: int, source: NBattleObject) -> void: var effect: EffectBase = effect_def.create_instance() var effect_ctx: EffectCtx = EffectCtx.new( _get_context(), effect_def, potency, self, source ) var ce: Dictionary = {"effect": effect, "ctx": effect_ctx} _effects.append(ce) var source_id: int = 0 if source: source_id = source.get_id() _get_context()._push_effect_stack(effect) effect.on_add(effect_ctx) _get_context()._pop_effect_stack(effect) _get_context()._emit_event(CombatantEffectEvent.new(get_id(), effect_def.get_id(), potency, source_id)) func _resolve_stat_def(stat_def: Variant) -> StatDef: if stat_def is StatDef: return stat_def if stat_def is String: return _get_context().get_stat_def_by_name(stat_def) if stat_def is int: return _get_context().get_stat_def_by_id(stat_def) push_error("NBattle: Invalid stat_def type") return null func _resolve_effect_def(effect_def: Variant) -> EffectDef: if effect_def is EffectDef: return effect_def if effect_def is String: return _get_context().get_effect_def_by_name(effect_def) if effect_def is int: return _get_context().get_effect_def_by_id(effect_def) push_error("NBattle: Invalid effect_def type") return null func _resolve_source(source: Variant) -> NBattleObject: if source == null: return null if source is NBattleObject: return source push_error("NBattle: Invalid source type") return null func _resolve_flag(flag: Variant) -> int: if flag is String: return _get_context().get_flag_by_name(flag) if flag is int: return flag push_error("NBattle: Invalid flag type") return -1 func get_type() -> NBattleObject.Type: return NBattleObject.Type.COMBATANT