feat: add cold-season scarcity that halves yields and pauses regrowth
This commit is contained in:
@@ -68,6 +68,10 @@ var _population_view := SimulationPopulationView.new()
|
|||||||
var speed_index := 2
|
var speed_index := 2
|
||||||
const SPEED_LEVELS := [0.25, 0.5, 1.0, 2.0, 4.0, 10.0]
|
const SPEED_LEVELS := [0.25, 0.5, 1.0, 2.0, 4.0, 10.0]
|
||||||
const KNOWLEDGE_COMMUNICATION_RADIUS := 2.5
|
const KNOWLEDGE_COMMUNICATION_RADIUS := 2.5
|
||||||
|
const SEASON_DAYS := 4
|
||||||
|
const COLD_DAYS := 3
|
||||||
|
const SEASON_COLD := &"cold"
|
||||||
|
const SEASON_WARM := &"warm"
|
||||||
signal speed_changed(multiplier: float)
|
signal speed_changed(multiplier: float)
|
||||||
|
|
||||||
const NPC_NAMES := ["Amina", "Tarik", "Jasmin", "Elma", "Mirza", "Lejla"]
|
const NPC_NAMES := ["Amina", "Tarik", "Jasmin", "Elma", "Mirza", "Lejla"]
|
||||||
@@ -337,7 +341,9 @@ func _complete_resource_gather(npc: SimNPC, completed_task: StringName) -> void:
|
|||||||
release_npc_reservation(npc.id)
|
release_npc_reservation(npc.id)
|
||||||
return
|
return
|
||||||
|
|
||||||
var extracted := resource_state.extract()
|
var extracted := resource_state.extract(
|
||||||
|
resource_state.get_yield_per_action() * _gather_yield_multiplier()
|
||||||
|
)
|
||||||
if extracted > 0.0:
|
if extracted > 0.0:
|
||||||
var resource_id := resource_state.get_resource_id()
|
var resource_id := resource_state.get_resource_id()
|
||||||
npc.add_inventory(resource_id, extracted)
|
npc.add_inventory(resource_id, extracted)
|
||||||
@@ -1019,7 +1025,9 @@ func harvest_resource_node(node: ResourceNode) -> float:
|
|||||||
var resource_state := get_resource_state(node.node_id)
|
var resource_state := get_resource_state(node.node_id)
|
||||||
if resource_state == null or not resource_state.can_player_use_resource():
|
if resource_state == null or not resource_state.can_player_use_resource():
|
||||||
return 0.0
|
return 0.0
|
||||||
var extracted := resource_state.extract(resource_state.get_yield_per_action())
|
var extracted := resource_state.extract(
|
||||||
|
resource_state.get_yield_per_action() * _gather_yield_multiplier()
|
||||||
|
)
|
||||||
if extracted <= 0.0:
|
if extracted <= 0.0:
|
||||||
return 0.0
|
return 0.0
|
||||||
var resource_id := resource_state.get_resource_id()
|
var resource_id := resource_state.get_resource_id()
|
||||||
@@ -1066,7 +1074,23 @@ func advance_player_needs() -> void:
|
|||||||
player_system.advance_needs(village.food_modifier)
|
player_system.advance_needs(village.food_modifier)
|
||||||
|
|
||||||
|
|
||||||
|
func get_season() -> StringName:
|
||||||
|
var ticks_per_day := maxi(get_knowledge_review_interval(), 1)
|
||||||
|
var season_day := int(clock.elapsed_ticks / ticks_per_day)
|
||||||
|
return SEASON_COLD if season_day % SEASON_DAYS >= COLD_DAYS else SEASON_WARM
|
||||||
|
|
||||||
|
|
||||||
|
func is_cold_season() -> bool:
|
||||||
|
return get_season() == SEASON_COLD
|
||||||
|
|
||||||
|
|
||||||
|
func _gather_yield_multiplier() -> float:
|
||||||
|
return 0.5 if is_cold_season() else 1.0
|
||||||
|
|
||||||
|
|
||||||
func _advance_resource_regrowth() -> void:
|
func _advance_resource_regrowth() -> void:
|
||||||
|
if is_cold_season():
|
||||||
|
return
|
||||||
for resource_id in resource_states:
|
for resource_id in resource_states:
|
||||||
var resource_state := resource_states[resource_id] as ResourceStateRecord
|
var resource_state := resource_states[resource_id] as ResourceStateRecord
|
||||||
resource_state.regrow()
|
resource_state.regrow()
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var manager := _create_manager(971)
|
||||||
|
var ticks_per_day: int = manager.get_knowledge_review_interval()
|
||||||
|
|
||||||
|
var berry := ResourceNode.new()
|
||||||
|
berry.name = "SeasonBerry"
|
||||||
|
berry.node_id = &"season_berry"
|
||||||
|
berry.action_id = SimulationIds.ACTION_GATHER_FOOD
|
||||||
|
berry.resource_id = SimulationIds.RESOURCE_FOOD
|
||||||
|
berry.initial_amount = 2.0
|
||||||
|
berry.max_amount = 2.0
|
||||||
|
berry.regrow_rate = 0.1
|
||||||
|
berry.debug_label_enabled = false
|
||||||
|
var interaction_point := Marker3D.new()
|
||||||
|
interaction_point.name = "InteractionPoint"
|
||||||
|
interaction_point.position = Vector3.ZERO
|
||||||
|
berry.add_child(interaction_point)
|
||||||
|
root.add_child(berry)
|
||||||
|
await process_frame
|
||||||
|
_check(
|
||||||
|
manager.register_resource_node(berry), "Season test should bind a real finite ResourceNode"
|
||||||
|
)
|
||||||
|
var state: ResourceStateRecord = manager.get_resource_state(berry.node_id)
|
||||||
|
|
||||||
|
manager.clock.elapsed_ticks = 0 * ticks_per_day + 50
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
manager.get_season() == &"warm"
|
||||||
|
and is_equal_approx(manager._gather_yield_multiplier(), 1.0)
|
||||||
|
),
|
||||||
|
"The first season days should be warm with full yields"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
is_equal_approx(manager.harvest_resource_node(berry), 2.0),
|
||||||
|
"Warm-season gathering should extract the full configured yield"
|
||||||
|
)
|
||||||
|
|
||||||
|
state.set_amount_remaining(2.0)
|
||||||
|
manager.clock.elapsed_ticks = 3 * ticks_per_day + 50
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
manager.get_season() == &"cold"
|
||||||
|
and is_equal_approx(manager._gather_yield_multiplier(), 0.5)
|
||||||
|
),
|
||||||
|
"Later season days should be cold with reduced yields"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
is_equal_approx(manager.harvest_resource_node(berry), 1.0),
|
||||||
|
"Cold-season gathering should extract only half the configured yield"
|
||||||
|
)
|
||||||
|
|
||||||
|
state.set_amount_remaining(0.5)
|
||||||
|
manager.simulate_tick()
|
||||||
|
_check(
|
||||||
|
is_equal_approx(state.get_amount_remaining(), 0.5),
|
||||||
|
"Cold-season regrowth should pause until warmer days"
|
||||||
|
)
|
||||||
|
manager.clock.elapsed_ticks = 4 * ticks_per_day + 50
|
||||||
|
_check(manager.get_season() == &"warm", "The season should return warm after the cold spell")
|
||||||
|
manager.simulate_tick()
|
||||||
|
_check(
|
||||||
|
is_equal_approx(state.get_amount_remaining(), 0.6),
|
||||||
|
"Regrowth should resume once the warm season returns"
|
||||||
|
)
|
||||||
|
|
||||||
|
manager.clock.elapsed_ticks = 3 * ticks_per_day + 50
|
||||||
|
var saved_json: String = manager.serialize_state()
|
||||||
|
var restored := _create_manager(972)
|
||||||
|
_check(
|
||||||
|
restored.restore_state_from_json(saved_json),
|
||||||
|
"Seasonal clock state should survive save and restore"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
restored.get_state_checksum() == manager.get_state_checksum()
|
||||||
|
and restored.get_season() == &"cold"
|
||||||
|
and is_equal_approx(restored._gather_yield_multiplier(), 0.5)
|
||||||
|
),
|
||||||
|
"Restore should re-derive the same cold season and its reduced yields"
|
||||||
|
)
|
||||||
|
restored.free()
|
||||||
|
|
||||||
|
berry.queue_free()
|
||||||
|
manager.free()
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _create_manager(seed_value: int = 971) -> Node:
|
||||||
|
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||||
|
manager.simulation_seed = seed_value
|
||||||
|
manager.debug_logs = false
|
||||||
|
var home_positions: Array[Vector3] = [
|
||||||
|
Vector3(0.0, 0.0, 0.0),
|
||||||
|
Vector3(2.0, 0.0, 0.0),
|
||||||
|
Vector3(10.0, 0.0, 0.0),
|
||||||
|
Vector3(12.0, 0.0, 0.0),
|
||||||
|
Vector3(30.0, 0.0, 30.0),
|
||||||
|
Vector3(40.0, 0.0, 40.0),
|
||||||
|
]
|
||||||
|
manager.home_positions = home_positions
|
||||||
|
root.add_child(manager)
|
||||||
|
manager.set_process(false)
|
||||||
|
return manager
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures.is_empty():
|
||||||
|
print("[TEST] Seasonal rhythm passed: cold yields + paused regrowth")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
for failure in failures:
|
||||||
|
push_error("[TEST] " + failure)
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bu0iqfwmmcwsk
|
||||||
@@ -86,6 +86,7 @@ func _on_village_changed(village: SimVillage) -> void:
|
|||||||
if rates["wood_per_day"] > 0.01:
|
if rates["wood_per_day"] > 0.01:
|
||||||
wood_rate = " (−%.0f/d)" % rates["wood_per_day"]
|
wood_rate = " (−%.0f/d)" % rates["wood_per_day"]
|
||||||
var opportunity_text := _build_active_opportunity_display()
|
var opportunity_text := _build_active_opportunity_display()
|
||||||
|
var season_text := "Cold" if simulation_manager.is_cold_season() else "Warm"
|
||||||
village_stats_label.text = (
|
village_stats_label.text = (
|
||||||
"""
|
"""
|
||||||
Village [%s]
|
Village [%s]
|
||||||
@@ -95,6 +96,7 @@ func _on_village_changed(village: SimVillage) -> void:
|
|||||||
Safety: %s
|
Safety: %s
|
||||||
Knowledge: %s
|
Knowledge: %s
|
||||||
Starving: %s
|
Starving: %s
|
||||||
|
Season: %s
|
||||||
|
|
||||||
Food Mod: %.2f
|
Food Mod: %.2f
|
||||||
Safety Mod: %.2f
|
Safety Mod: %.2f
|
||||||
@@ -110,6 +112,7 @@ func _on_village_changed(village: SimVillage) -> void:
|
|||||||
round(village.safety),
|
round(village.safety),
|
||||||
round(village.knowledge),
|
round(village.knowledge),
|
||||||
simulation_manager.get_starving_count(),
|
simulation_manager.get_starving_count(),
|
||||||
|
season_text,
|
||||||
village.food_modifier,
|
village.food_modifier,
|
||||||
village.safety_modifier,
|
village.safety_modifier,
|
||||||
village.knowledge_modifier,
|
village.knowledge_modifier,
|
||||||
|
|||||||
Reference in New Issue
Block a user