feat: shared creature root, enemy definitions, defend duty, and player downing
This commit is contained in:
@@ -33,6 +33,7 @@ signal combatant_died(combatant_id: StringName)
|
||||
signal raid_started(raider_ids: Array[StringName])
|
||||
signal war_resolved(outcome: StringName)
|
||||
signal war_aborted
|
||||
signal player_downed
|
||||
|
||||
var village := SimVillage.new()
|
||||
var player_system := PlayerCitizenSystem.new()
|
||||
@@ -98,6 +99,8 @@ func _ready() -> void:
|
||||
conflict_system.raid_started.connect(func(raider_ids): raid_started.emit(raider_ids))
|
||||
conflict_system.war_resolved.connect(func(outcome): war_resolved.emit(outcome))
|
||||
conflict_system.war_aborted.connect(func(): war_aborted.emit())
|
||||
conflict_system.player_damaged.connect(_on_player_damaged)
|
||||
conflict_system.player_downed.connect(func(): player_downed.emit())
|
||||
action_selector.relationship_system = relationship_system
|
||||
var definition_errors := SimulationDefinitions.validate()
|
||||
if not definition_errors.is_empty():
|
||||
@@ -258,8 +261,16 @@ func _select_action_if_idle(npc: SimNPC, previous_state: StringName) -> void:
|
||||
record_narrative_event(SimulationIds.EVENT_VILLAGER_WEAK, npc.id)
|
||||
var helper := get_active_opportunity_helper()
|
||||
var animal_feed_available := animal_care.has_available_feed_target(npc.id)
|
||||
var war_raid_active := conflict_system.is_raiding()
|
||||
var selection := action_selector.select_action(
|
||||
npc, village, clock.time_of_day(), npcs, helper, _population_view, animal_feed_available
|
||||
npc,
|
||||
village,
|
||||
clock.time_of_day(),
|
||||
npcs,
|
||||
helper,
|
||||
_population_view,
|
||||
animal_feed_available,
|
||||
war_raid_active
|
||||
)
|
||||
if selection == null:
|
||||
return
|
||||
@@ -390,6 +401,8 @@ func _apply_action_completion(
|
||||
pass
|
||||
SimulationIds.ACTION_PATROL, SimulationIds.ACTION_STUDY, SimulationIds.ACTION_REST, SimulationIds.ACTION_WANDER:
|
||||
village.apply_npc_task(npc)
|
||||
SimulationIds.ACTION_DEFEND:
|
||||
village.apply_metric_delta(&"safety", 0.5)
|
||||
if debug_logs and completed_task == SimulationIds.ACTION_SLEEP:
|
||||
print("[SimulationManager] ", npc.npc_name, " completed sleep at home")
|
||||
|
||||
@@ -1138,6 +1151,31 @@ func harvest_resource_node(node: ResourceNode) -> float:
|
||||
|
||||
func advance_player_needs() -> void:
|
||||
player_system.advance_needs(village.food_modifier)
|
||||
player_system.player_state.advance_health()
|
||||
_sync_player_combatant()
|
||||
|
||||
|
||||
func _sync_player_combatant() -> void:
|
||||
conflict_system.set_player_health(
|
||||
player_system.player_state.get_health(), player_system.player_state.is_downed()
|
||||
)
|
||||
|
||||
|
||||
func _on_player_damaged(amount: float) -> void:
|
||||
player_system.player_state.take_damage(amount)
|
||||
_sync_player_combatant()
|
||||
|
||||
|
||||
func update_player_combatant(position: Vector3) -> void:
|
||||
conflict_system.set_player_combatant_position(position)
|
||||
|
||||
|
||||
func player_is_downed() -> bool:
|
||||
return player_system.player_state.is_downed()
|
||||
|
||||
|
||||
func get_player_health() -> float:
|
||||
return player_system.player_state.get_health()
|
||||
|
||||
|
||||
func get_season() -> StringName:
|
||||
|
||||
@@ -24,7 +24,8 @@ func select_action(
|
||||
all_npcs: Array = [],
|
||||
opportunity_helper: OpportunityHelperResult = null,
|
||||
population_view: SimulationPopulationView = null,
|
||||
animal_feed_available: bool = false
|
||||
animal_feed_available: bool = false,
|
||||
war_raid_active: bool = false
|
||||
) -> ActionSelectionResult:
|
||||
if npc.is_dead:
|
||||
return null
|
||||
@@ -115,6 +116,10 @@ func select_action(
|
||||
-1.0,
|
||||
"Responding to village need: %s" % opportunity_helper.reason
|
||||
)
|
||||
if war_raid_active and period == SchedulePeriod.WORK and _is_eligible_defender(npc):
|
||||
return ActionSelectionResult.new(
|
||||
SimulationIds.ACTION_DEFEND, -1.0, "Rallying to defend the village"
|
||||
)
|
||||
if npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) > 0.0:
|
||||
return ActionSelectionResult.new(
|
||||
SimulationIds.ACTION_DEPOSIT_FOOD, -1.0, "Carrying food for storage"
|
||||
@@ -297,6 +302,10 @@ func _calculate_score(
|
||||
return score
|
||||
|
||||
|
||||
static func _is_eligible_defender(npc: SimNPC) -> bool:
|
||||
return npc.strength >= 5.0 or npc.profession == SimulationIds.PROFESSION_GUARD
|
||||
|
||||
|
||||
static func _get_period(time_of_day: float) -> int:
|
||||
if _in_wrap_range(time_of_day, SLEEP_BEGIN, SLEEP_END):
|
||||
return SchedulePeriod.SLEEP
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
class_name EnemyDefinition
|
||||
extends Resource
|
||||
|
||||
@export var enemy_id: StringName
|
||||
@export var display_name: String
|
||||
@export var kind: StringName = SimulationIds.COMBATANT_KIND_RAIDER
|
||||
@export var weapon_id: StringName = SimulationIds.ITEM_SWORD
|
||||
@export var health := 60.0
|
||||
@export var move_speed := 3.5
|
||||
@export var body_color := Color(0.4, 0.13, 0.1, 1.0)
|
||||
@export var accent_color := Color(0.55, 0.55, 0.58, 1.0)
|
||||
@export var visual_scale := Vector3.ONE
|
||||
|
||||
|
||||
func validate() -> Array[String]:
|
||||
var errors: Array[String] = []
|
||||
if enemy_id.is_empty():
|
||||
errors.append("enemy_id is empty")
|
||||
if display_name.is_empty():
|
||||
errors.append("display_name is empty for '%s'" % enemy_id)
|
||||
if (
|
||||
kind
|
||||
not in [
|
||||
SimulationIds.COMBATANT_KIND_RAIDER,
|
||||
SimulationIds.COMBATANT_KIND_WOLF,
|
||||
]
|
||||
):
|
||||
errors.append("kind '%s' is invalid for '%s'" % [kind, enemy_id])
|
||||
if health <= 0.0:
|
||||
errors.append("health must be positive for '%s'" % enemy_id)
|
||||
return errors
|
||||
@@ -0,0 +1 @@
|
||||
uid://5tfgb1oww0e0
|
||||
@@ -13,7 +13,8 @@ const ACTION_PATHS := [
|
||||
"res://simulation/definitions/actions/deposit_food.tres",
|
||||
"res://simulation/definitions/actions/withdraw_food.tres",
|
||||
"res://simulation/definitions/actions/sleep.tres",
|
||||
"res://simulation/definitions/actions/deposit_wood.tres"
|
||||
"res://simulation/definitions/actions/deposit_wood.tres",
|
||||
"res://simulation/definitions/actions/defend.tres"
|
||||
]
|
||||
|
||||
const PROFESSION_PATHS := [
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
class_name SimulationEnemies
|
||||
extends RefCounted
|
||||
|
||||
static var _enemies: Dictionary = {}
|
||||
static var _cache_valid := false
|
||||
|
||||
|
||||
static func _ensure_cache() -> void:
|
||||
if _cache_valid:
|
||||
return
|
||||
_enemies = {}
|
||||
var raider := EnemyDefinition.new()
|
||||
raider.enemy_id = &"enemy_raider"
|
||||
raider.display_name = "Raider"
|
||||
raider.kind = SimulationIds.COMBATANT_KIND_RAIDER
|
||||
raider.weapon_id = SimulationIds.ITEM_SWORD
|
||||
raider.health = 60.0
|
||||
raider.move_speed = 3.5
|
||||
raider.body_color = Color(0.4, 0.13, 0.1, 1.0)
|
||||
raider.accent_color = Color(0.55, 0.55, 0.58, 1.0)
|
||||
_enemies[raider.enemy_id] = raider
|
||||
|
||||
var wolf := EnemyDefinition.new()
|
||||
wolf.enemy_id = &"enemy_wolf"
|
||||
wolf.display_name = "Wolf"
|
||||
wolf.kind = SimulationIds.COMBATANT_KIND_WOLF
|
||||
wolf.weapon_id = SimulationIds.ITEM_CLAW
|
||||
wolf.health = 40.0
|
||||
wolf.move_speed = 4.2
|
||||
wolf.body_color = Color(0.32, 0.32, 0.34, 1.0)
|
||||
wolf.accent_color = Color(0.95, 0.5, 0.12, 1.0)
|
||||
wolf.visual_scale = Vector3(0.8, 0.8, 0.8)
|
||||
_enemies[wolf.enemy_id] = wolf
|
||||
_cache_valid = true
|
||||
|
||||
|
||||
static func invalidate_cache() -> void:
|
||||
_cache_valid = false
|
||||
|
||||
|
||||
static func get_enemy(enemy_id: StringName) -> EnemyDefinition:
|
||||
_ensure_cache()
|
||||
return _enemies.get(enemy_id) as EnemyDefinition
|
||||
|
||||
|
||||
static func get_all() -> Array[EnemyDefinition]:
|
||||
_ensure_cache()
|
||||
var values: Array[EnemyDefinition] = []
|
||||
for enemy in _enemies.values():
|
||||
values.append(enemy)
|
||||
return values
|
||||
@@ -0,0 +1 @@
|
||||
uid://daurjkbl4hcd8
|
||||
@@ -18,6 +18,7 @@ const ACTION_WANDER := &"wander"
|
||||
const ACTION_DEPOSIT_FOOD := &"deposit_food"
|
||||
const ACTION_WITHDRAW_FOOD := &"withdraw_food"
|
||||
const ACTION_DEPOSIT_WOOD := &"deposit_wood"
|
||||
const ACTION_DEFEND := &"defend"
|
||||
|
||||
const PROFESSION_FARMER := &"farmer"
|
||||
const PROFESSION_WOODCUTTER := &"woodcutter"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="ActionDefinition" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://simulation/definitions/ActionDefinition.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
action_id = &"defend"
|
||||
display_name = "Defend"
|
||||
default_duration = 5.0
|
||||
preferred_profession_id = &"guard"
|
||||
target_type = &"activity"
|
||||
@@ -1,7 +1,9 @@
|
||||
class_name PlayerStateRecord
|
||||
extends RefCounted
|
||||
|
||||
const SCHEMA_VERSION := 1
|
||||
const SCHEMA_VERSION := 2
|
||||
const LEGACY_SCHEMA_VERSION := 1
|
||||
const MAX_HEALTH := 100.0
|
||||
|
||||
var data: Dictionary
|
||||
|
||||
@@ -18,6 +20,10 @@ static func create_default() -> PlayerStateRecord:
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"hunger": 40.0,
|
||||
"energy": 100.0,
|
||||
"health": MAX_HEALTH,
|
||||
"max_health": MAX_HEALTH,
|
||||
"downed": false,
|
||||
"downed_ticks": 0,
|
||||
"inventory": {},
|
||||
}
|
||||
)
|
||||
@@ -25,15 +31,27 @@ static func create_default() -> PlayerStateRecord:
|
||||
|
||||
|
||||
static func from_dictionary(record_data: Dictionary) -> PlayerStateRecord:
|
||||
if int(record_data.get("schema_version", -1)) != SCHEMA_VERSION:
|
||||
var version := int(record_data.get("schema_version", -1))
|
||||
if version == LEGACY_SCHEMA_VERSION:
|
||||
record_data = _migrate_v1(record_data)
|
||||
elif version != SCHEMA_VERSION:
|
||||
return null
|
||||
if not record_data.has_all(["hunger", "energy", "inventory"]):
|
||||
if not record_data.has_all(
|
||||
["hunger", "energy", "health", "max_health", "downed", "downed_ticks", "inventory"]
|
||||
):
|
||||
return null
|
||||
if not record_data["inventory"] is Dictionary:
|
||||
return null
|
||||
var hunger := float(record_data["hunger"])
|
||||
var energy := float(record_data["energy"])
|
||||
if not is_finite(hunger) or not is_finite(energy):
|
||||
var health := float(record_data["health"])
|
||||
var max_health := float(record_data["max_health"])
|
||||
if (
|
||||
not is_finite(hunger)
|
||||
or not is_finite(energy)
|
||||
or not is_finite(health)
|
||||
or not is_finite(max_health)
|
||||
):
|
||||
return null
|
||||
var normalized_inventory := {}
|
||||
for raw_item_id in record_data["inventory"]:
|
||||
@@ -54,12 +72,26 @@ static func from_dictionary(record_data: Dictionary) -> PlayerStateRecord:
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"hunger": clampf(hunger, 0.0, 100.0),
|
||||
"energy": clampf(energy, 0.0, 100.0),
|
||||
"health": clampf(health, 0.0, max_health),
|
||||
"max_health": max_health,
|
||||
"downed": bool(record_data["downed"]),
|
||||
"downed_ticks": int(record_data["downed_ticks"]),
|
||||
"inventory": normalized_inventory,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
static func _migrate_v1(legacy_data: Dictionary) -> Dictionary:
|
||||
var migrated := legacy_data.duplicate(true)
|
||||
migrated["schema_version"] = SCHEMA_VERSION
|
||||
migrated["health"] = MAX_HEALTH
|
||||
migrated["max_health"] = MAX_HEALTH
|
||||
migrated["downed"] = false
|
||||
migrated["downed_ticks"] = 0
|
||||
return migrated
|
||||
|
||||
|
||||
func get_hunger() -> float:
|
||||
return clampf(float(data["hunger"]), 0.0, 100.0)
|
||||
|
||||
@@ -68,6 +100,22 @@ func get_energy() -> float:
|
||||
return clampf(float(data["energy"]), 0.0, 100.0)
|
||||
|
||||
|
||||
func get_health() -> float:
|
||||
return clampf(float(data["health"]), 0.0, get_max_health())
|
||||
|
||||
|
||||
func get_max_health() -> float:
|
||||
return maxf(float(data["max_health"]), 1.0)
|
||||
|
||||
|
||||
func is_downed() -> bool:
|
||||
return bool(data["downed"])
|
||||
|
||||
|
||||
func get_downed_ticks() -> int:
|
||||
return int(data["downed_ticks"])
|
||||
|
||||
|
||||
func set_hunger(amount: float) -> void:
|
||||
data["hunger"] = clampf(amount, 0.0, 100.0)
|
||||
|
||||
@@ -76,6 +124,28 @@ func set_energy(amount: float) -> void:
|
||||
data["energy"] = clampf(amount, 0.0, 100.0)
|
||||
|
||||
|
||||
func take_damage(amount: float) -> float:
|
||||
if not is_finite(amount) or amount <= 0.0 or is_downed():
|
||||
return 0.0
|
||||
var previous := get_health()
|
||||
data["health"] = clampf(previous - amount, 0.0, get_max_health())
|
||||
if get_health() <= 0.0:
|
||||
data["downed"] = true
|
||||
data["downed_ticks"] = 20
|
||||
return previous - get_health()
|
||||
|
||||
|
||||
func advance_health() -> void:
|
||||
if is_downed():
|
||||
data["downed_ticks"] = maxi(get_downed_ticks() - 1, 0)
|
||||
if get_downed_ticks() <= 0:
|
||||
data["downed"] = false
|
||||
data["health"] = get_max_health() * 0.5
|
||||
return
|
||||
if get_health() < get_max_health():
|
||||
data["health"] = clampf(get_health() + 0.25, 0.0, get_max_health())
|
||||
|
||||
|
||||
func get_inventory_amount(item_id: StringName) -> float:
|
||||
return float(data["inventory"].get(String(item_id), 0.0))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user