feat: shared creature root, enemy definitions, defend duty, and player downing

This commit is contained in:
Rijad Zuzo
2026-08-12 00:16:48 +02:00
parent 6de6d25bf5
commit 661d53f31a
23 changed files with 653 additions and 76 deletions
+69 -17
View File
@@ -15,6 +15,8 @@ signal combatant_died(combatant_id: StringName)
signal raid_started(raider_ids: Array[StringName])
signal war_resolved(outcome: StringName)
signal war_aborted
signal player_damaged(amount: float)
signal player_downed
const NPC_MAX_HEALTH := 100.0
const STRONG_STRENGTH := 5.0
@@ -24,9 +26,7 @@ const WOLF_HUNT_RADIUS := 18.0
const RAID_SPAWN_OFFSET := Vector3(42.0, 0.0, 42.0)
const RAID_SPEED := 1.2
const RAID_SIZE := 4
const RAIDER_HEALTH := 60.0
const WOLF_HEALTH := 40.0
const RAIDER_REACH_FACTOR := 1.6
const PLAYER_COMBATANT_ID := &"player_combatant"
var combatants: Dictionary = {}
var factions: Dictionary = {}
@@ -52,6 +52,53 @@ func initialize_factions() -> void:
factions[SimulationIds.FACTION_TRIBE] = FactionStateRecord.create(
SimulationIds.FACTION_TRIBE, "Hill Tribe", 4.0, RAID_SIZE, 0.7, 0.7
)
_ensure_player_combatant()
func _ensure_player_combatant() -> void:
if combatants.has(PLAYER_COMBATANT_ID):
return
var sword := SimulationItems.get_weapon(SimulationIds.ITEM_SWORD)
combatants[PLAYER_COMBATANT_ID] = CombatantStateRecord.create(
PLAYER_COMBATANT_ID,
SimulationIds.COMBATANT_KIND_PLAYER,
SimulationIds.FACTION_VILLAGE,
"Player",
village_center,
100.0,
sword.item_id if sword != null else SimulationIds.ITEM_SWORD,
CombatantStateRecord.NO_NPC_ID
)
func set_player_combatant_position(position: Vector3) -> void:
var player_combatant := get_combatant(PLAYER_COMBATANT_ID)
if player_combatant == null:
_ensure_player_combatant()
player_combatant = get_combatant(PLAYER_COMBATANT_ID)
if player_combatant != null:
player_combatant.set_position(position)
func set_player_health(health: float, downed: bool) -> void:
var player_combatant := get_combatant(PLAYER_COMBATANT_ID)
if player_combatant == null:
_ensure_player_combatant()
player_combatant = get_combatant(PLAYER_COMBATANT_ID)
if player_combatant == null:
return
if not downed and player_combatant.is_alive():
var target := clampf(health, 0.0, player_combatant.get_max_health())
var current := player_combatant.get_health()
if target < current:
player_combatant.take_damage(current - target)
elif target > current:
player_combatant.data["health"] = target
func is_player_downed() -> bool:
var player_combatant := get_combatant(PLAYER_COMBATANT_ID)
return player_combatant != null and not player_combatant.is_alive()
func register_npc_combatants(npc_list: Array[SimNPC]) -> void:
@@ -100,7 +147,7 @@ func advance(simulation_tick: int) -> void:
func _advance_raider_positions() -> void:
if not _is_raiding():
if not is_raiding():
return
for combatant_id in _sorted_combatant_ids():
var combatant := combatants[combatant_id] as CombatantStateRecord
@@ -132,16 +179,17 @@ func player_attack(combatant_id: StringName) -> float:
func spawn_wolf(position: Vector3) -> StringName:
var definition := SimulationEnemies.get_enemy(&"enemy_wolf")
var wolf_id := StringName("wolf_%d" % next_wolf_id)
next_wolf_id += 1
var wolf := CombatantStateRecord.create(
wolf_id,
SimulationIds.COMBATANT_KIND_WOLF,
SimulationIds.FACTION_TRIBE,
"Wolf",
definition.display_name if definition != null else "Wolf",
position,
WOLF_HEALTH,
SimulationIds.ITEM_CLAW,
definition.health if definition != null else 40.0,
definition.weapon_id if definition != null else SimulationIds.ITEM_CLAW,
CombatantStateRecord.NO_NPC_ID,
true
)
@@ -165,6 +213,11 @@ func _apply_damage(target: CombatantStateRecord, damage: float, actor_id: int) -
target.get_weapon_id(),
applied
)
if target.get_kind() == SimulationIds.COMBATANT_KIND_PLAYER:
player_damaged.emit(applied)
if not target.is_alive():
player_downed.emit()
return applied
if not target.is_alive():
_kill_combatant(target, actor_id)
return applied
@@ -313,6 +366,7 @@ func village_defender_count() -> int:
func _start_raid(tribe: FactionStateRecord, simulation_tick: int) -> void:
var definition := SimulationEnemies.get_enemy(&"enemy_raider")
tribe.set_war_plan(SimulationIds.WAR_PLAN_RAIDING, simulation_tick)
tribe.set_stance(SimulationIds.STANCE_HOSTILE)
var raider_ids: Array[StringName] = []
@@ -324,10 +378,10 @@ func _start_raid(tribe: FactionStateRecord, simulation_tick: int) -> void:
raider_id,
SimulationIds.COMBATANT_KIND_RAIDER,
SimulationIds.FACTION_TRIBE,
"Raider",
definition.display_name if definition != null else "Raider",
village_center + RAID_SPAWN_OFFSET + offset,
RAIDER_HEALTH,
SimulationIds.ITEM_SWORD,
definition.health if definition != null else 60.0,
definition.weapon_id if definition != null else SimulationIds.ITEM_SWORD,
CombatantStateRecord.NO_NPC_ID,
true
)
@@ -347,7 +401,7 @@ func _start_raid(tribe: FactionStateRecord, simulation_tick: int) -> void:
func _run_battle() -> void:
var raid_active := _is_raiding()
var raid_active := is_raiding()
var living_hostiles: Array[CombatantStateRecord] = []
for combatant_id in _sorted_combatant_ids():
var combatant := combatants[combatant_id] as CombatantStateRecord
@@ -483,11 +537,7 @@ func _reach(combatant: CombatantStateRecord) -> float:
var weapon := SimulationItems.get_weapon(combatant.get_weapon_id())
if weapon == null:
return 1.5
return (
weapon.reach * RAIDER_REACH_FACTOR
if combatant.get_kind() == SimulationIds.COMBATANT_KIND_WOLF
else weapon.reach
)
return weapon.reach
func _actor_id_for(combatant: CombatantStateRecord) -> int:
@@ -506,7 +556,7 @@ func _living_village_npc_count() -> int:
return count
func _is_raiding() -> bool:
func is_raiding() -> bool:
var tribe := _get_faction(SimulationIds.FACTION_TRIBE)
return tribe != null and tribe.get_war_plan() == SimulationIds.WAR_PLAN_RAIDING
@@ -596,6 +646,8 @@ func _narrative_event_requested(
func append_state_records(record: SimulationStateRecord) -> void:
var combatant_keys: Array[String] = []
for combatant_id in combatants.keys():
if String(combatant_id) == String(PLAYER_COMBATANT_ID):
continue
combatant_keys.append(String(combatant_id))
combatant_keys.sort()
for combatant_key in combatant_keys: