## Demo scene that exercises all NBattle features. ## Attach to a Node (e.g., Node2D) in Godot to run. ## Uses print() to show test results in the Godot Output panel. extends Node var tests_run: int = 0 var tests_passed: int = 0 func _ready() -> void: _run_all_tests() get_tree().quit() func _run_all_tests() -> void: _test_stat_system() _test_poison_effect() _test_regenerate_effect() _test_poison_and_regenerate() _test_shield_effect() _test_attack_effect() _test_reflect_effect() _test_defend_effect() _test_counter_effect() _test_self_heal_effect() _test_copy_stat_effect() _test_buff_effect() _test_trigger_effect() _test_multiple_effects() _test_effect_removal() _test_flags() print("\n=== RESULTS: %d/%d tests passed ===" % [tests_passed, tests_run]) func _assert(condition: bool, message: String) -> void: tests_run += 1 if condition: tests_passed += 1 print(" PASS: %s" % message) else: print(" FAIL: %s" % message) func _test_stat_system() -> void: print("\n--- Stat System ---") var ctx: BattleContext = BattleContext.new() var hp: StatDef = ctx.new_stat_def("hp", 0, 100) var c: Combatant = ctx.new_combatant() var stat: Stat = c.get_stat(hp) stat.set_base(50) _assert(stat.get_base() == 50, "base set to 50") _assert(stat.get_value() == 50, "value is 50") stat.set_mod("test_source", 10) _assert(stat.get_value() == 60, "value with +10 mod is 60") stat.set_mod("test_source", 0) _assert(stat.get_value() == 50, "value after removing mod is 50") stat.set_base(150) _assert(stat.get_value() == 100, "clamped to max 100") stat.set_base(-10) _assert(stat.get_value() == 0, "clamped to min 0") func _test_poison_effect() -> void: print("\n--- Poison Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 99) var poison_def: EffectDef = ctx.new_effect_def("poison", _poison_factory) var target: Combatant = ctx.new_combatant() var hp: Stat = target.get_stat("hp") hp.set_base(50) var attacker: Combatant = ctx.new_combatant() target.set_effect(poison_def, 1, attacker) _assert(hp.get_value() == 50, "hp stays 50 before tick") ctx.tick() _assert(hp.get_value() == 48, "hp is 48 after 1 tick") ctx.tick() _assert(hp.get_value() == 46, "hp is 46 after 2 ticks") func _test_regenerate_effect() -> void: print("\n--- Regenerate Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 99) var regen_def: EffectDef = ctx.new_effect_def("regenerate", _regenerate_factory) var target: Combatant = ctx.new_combatant() var hp: Stat = target.get_stat("hp") hp.set_base(10) target.set_effect(regen_def, 1, null) ctx.tick() _assert(hp.get_value() == 13, "hp is 13 after 1 regen tick") ctx.tick() _assert(hp.get_value() == 16, "hp is 16 after 2 regen ticks") func _test_poison_and_regenerate() -> void: print("\n--- Poison + Regenerate ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 200) var poison_def: EffectDef = ctx.new_effect_def("poison", _poison_factory) var regen_def: EffectDef = ctx.new_effect_def("regenerate", _regenerate_factory) var target: Combatant = ctx.new_combatant() var hp: Stat = target.get_stat("hp") hp.set_base(100) var attacker: Combatant = ctx.new_combatant() target.set_effect(poison_def, 1, attacker) target.set_effect(regen_def, 1, target) ctx.tick() _assert(hp.get_value() == 101, "hp is 101 (100 - 2 + 3)") ctx.tick() _assert(hp.get_value() == 102, "hp is 102 after 2 ticks") func _test_shield_effect() -> void: print("\n--- Shield Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 200) var shield_def: EffectDef = ctx.new_effect_def("shield", _shield_factory) var target: Combatant = ctx.new_combatant() target.set_effect(shield_def, 1, null) var hp: Stat = target.get_stat("hp") hp.set_base(10) _assert(hp.get_value() == 20, "hp is 20 (doubled)") hp.set_base(5) _assert(hp.get_value() == 10, "hp is 10 (5*2)") func _test_attack_effect() -> void: print("\n--- Attack Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 200) ctx.new_stat_def("str", 0, 99) ctx.new_stat_def("def", 0, 99) var attack_def: EffectDef = ctx.new_effect_def("attack", _attack_factory) var target: Combatant = ctx.new_combatant() var t_hp: Stat = target.get_stat("hp") t_hp.set_base(50) var t_def: Stat = target.get_stat("def") t_def.set_base(5) var attacker: Combatant = ctx.new_combatant() var a_str: Stat = attacker.get_stat("str") a_str.set_base(20) target.set_effect(attack_def, 1, attacker) _assert(t_hp.get_value() == 35, "hp is 35 (50 - 15 damage)") _assert(not target.has_effect(attack_def), "attack self-removed") func _test_reflect_effect() -> void: print("\n--- Reflect Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 200) var reflect_def: EffectDef = ctx.new_effect_def("reflect", _reflect_factory) var target: Combatant = ctx.new_combatant() var t_hp: Stat = target.get_stat("hp") t_hp.set_base(30) var attacker: Combatant = ctx.new_combatant() var a_hp: Stat = attacker.get_stat("hp") a_hp.set_base(30) target.set_effect(reflect_def, 1, attacker) t_hp.set_base(20) _assert(t_hp.get_value() == 20, "target hp is 20") _assert(a_hp.get_value() == 20, "attacker hp reflected to 20") func _test_defend_effect() -> void: print("\n--- Defend Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 200) var defend_def: EffectDef = ctx.new_effect_def("defend", _defend_factory) var target: Combatant = ctx.new_combatant() var t_hp: Stat = target.get_stat("hp") t_hp.set_base(30) var attacker: Combatant = ctx.new_combatant() target.set_effect(defend_def, 1, attacker) t_hp.set_base(20) _assert(t_hp.get_value() == 25, "hp is 25 (half damage)") func _test_counter_effect() -> void: print("\n--- Counter Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 200) var counter_def: EffectDef = ctx.new_effect_def("counter", _counter_factory) var target: Combatant = ctx.new_combatant() var t_hp: Stat = target.get_stat("hp") t_hp.set_base(30) var attacker: Combatant = ctx.new_combatant() var a_hp: Stat = attacker.get_stat("hp") a_hp.set_base(30) target.set_effect(counter_def, 1, attacker) t_hp.set_base(25) _assert(t_hp.get_value() == 25, "target hp is 25") _assert(a_hp.get_value() == 25, "attacker took counter damage to 25") func _test_self_heal_effect() -> void: print("\n--- SelfHeal Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 99) var heal_def: EffectDef = ctx.new_effect_def("self_heal", _self_heal_factory) var target: Combatant = ctx.new_combatant() var hp: Stat = target.get_stat("hp") hp.set_base(20) target.set_effect(heal_def, 1, null) hp.set_base(15) _assert(hp.get_value() == 16, "hp is 16 (healed 1 from 15)") hp.set_base(20) _assert(hp.get_value() == 20, "no heal when hp not decreasing") func _test_copy_stat_effect() -> void: print("\n--- CopyStat Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 99) ctx.new_stat_def("str", 0, 99) var copy_def: EffectDef = ctx.new_effect_def("copy_stat", _copy_stat_factory) var source: Combatant = ctx.new_combatant() source.get_stat("hp").set_base(30) source.get_stat("str").set_base(12) var target: Combatant = ctx.new_combatant() target.get_stat("hp").set_base(20) target.get_stat("str").set_base(3) target.set_effect(copy_def, 1, source) _assert(target.get_stat("str").get_value() == 12, "str copied to 12") _assert(target.get_stat("hp").get_value() == 20, "hp unchanged at 20") func _test_buff_effect() -> void: print("\n--- Buff Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("str", 0, 99) var buff_def: EffectDef = ctx.new_effect_def("buff", _buff_factory) var target: Combatant = ctx.new_combatant() var str: Stat = target.get_stat("str") str.set_base(5) target.set_effect(buff_def, 1, null) _assert(str.get_value() == 15, "str is 15 (5 + 10)") _assert(not target.has_effect(buff_def), "buff self-removed") func _test_trigger_effect() -> void: print("\n--- Trigger Effect ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 99) ctx.new_stat_def("str", 0, 99) ctx.new_trigger_def("on_hp_zero") var trigger_def: EffectDef = ctx.new_effect_def("trigger_effect", _trigger_factory) var buff_def: EffectDef = ctx.new_effect_def("buff_on_trigger", _buff_on_trigger_factory) var target: Combatant = ctx.new_combatant() target.get_stat("hp").set_base(30) target.get_stat("str").set_base(5) target.set_effect(trigger_def, 1, null) target.set_effect(buff_def, 1, null) target.get_stat("hp").set_base(0) _assert(target.get_stat("str").get_value() == 15, "str buffed to 15 via trigger") func _test_multiple_effects() -> void: print("\n--- Multiple Effects ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 99) var poison_def: EffectDef = ctx.new_effect_def("poison", _poison_factory) var regen_def: EffectDef = ctx.new_effect_def("regenerate", _regenerate_factory) var shield_def: EffectDef = ctx.new_effect_def("shield", _shield_factory) var target: Combatant = ctx.new_combatant() target.set_effect(shield_def, 1, null) target.set_effect(poison_def, 1, null) target.set_effect(regen_def, 1, null) var hp: Stat = target.get_stat("hp") hp.set_base(10) ctx.tick() _assert(hp.get_value() == 78, "hp is 78 with shield+poison+regen") func _test_effect_removal() -> void: print("\n--- Effect Removal ---") var ctx: BattleContext = BattleContext.new() ctx.new_stat_def("hp", 0, 99) var regen_def: EffectDef = ctx.new_effect_def("regenerate", _regenerate_factory) var target: Combatant = ctx.new_combatant() var hp: Stat = target.get_stat("hp") hp.set_base(10) target.set_effect(regen_def, 1, null) ctx.tick() _assert(hp.get_value() == 13, "hp is 13 after regen") target.set_effect(regen_def, 0, null) ctx.tick() _assert(hp.get_value() == 13, "hp stays 13 after removal") func _test_flags() -> void: print("\n--- Flags ---") var ctx: BattleContext = BattleContext.new() var poisoned_flag: int = ctx.new_flag("poisoned") var burning_flag: int = ctx.new_flag("burning") var c: Combatant = ctx.new_combatant() c.set_flag("poisoned", true) _assert(c.has_flag("poisoned"), "poisoned flag set") _assert(not c.has_flag("burning"), "burning flag not set") _assert(c.get_flags() == poisoned_flag, "flags value matches") c.set_flag("poisoned", false) _assert(not c.has_flag("poisoned"), "poisoned flag cleared") ## Factory functions for effects (mimic the Go lambda pattern). func _poison_factory() -> EffectBase: return preload("res://examples/effects/poison.gd").new() func _regenerate_factory() -> EffectBase: return preload("res://examples/effects/regenerate.gd").new() func _shield_factory() -> EffectBase: return preload("res://examples/effects/shield.gd").new() func _attack_factory() -> EffectBase: return preload("res://examples/effects/attack.gd").new() func _reflect_factory() -> EffectBase: return preload("res://examples/effects/reflect.gd").new() func _defend_factory() -> EffectBase: return preload("res://examples/effects/defend.gd").new() func _counter_factory() -> EffectBase: return preload("res://examples/effects/counter.gd").new() func _self_heal_factory() -> EffectBase: return preload("res://examples/effects/self_heal.gd").new() func _copy_stat_factory() -> EffectBase: return preload("res://examples/effects/copy_stat.gd").new() func _buff_factory() -> EffectBase: return preload("res://examples/effects/buff.gd").new() func _trigger_factory() -> EffectBase: return preload("res://examples/effects/trigger_effect.gd").new() ## Buff effect that responds to the "on_hp_zero" trigger. func _buff_on_trigger_factory() -> EffectBase: return BuffOnTrigger.new() class BuffOnTrigger extends EffectBase: func on_trigger(ctx: EffectCtx, evt: TriggerEvent) -> void: var trigger: TriggerDef = ctx.ctx.get_trigger_def_by_id(evt.trigger_def_id) if trigger and trigger.get_name() == "on_hp_zero": var str: Stat = ctx.target.get_stat("str") str.add_base(10) ctx.target.set_effect(ctx.def, 0, null)