From 7aa5c1c9799d06f6ef29b53f6a788f80a3a445f3 Mon Sep 17 00:00:00 2001 From: Rijad Zuzo Date: Wed, 8 Jul 2026 18:44:03 +0200 Subject: [PATCH] feat: starvation balance rework and Ghibli wind-stroke visuals - Reduce hunger rate to 0.125/tick (~25/day) for ~6-7 day death timeline - Slow energy drain: idle 0.125, travel 0.5, work 0.5, complete 0.0625/tick - Use exact binary fractions for all rates to preserve float precision in save/load - Starving/critically-hungry NPCs always override sleep to seek food first - Extend starvation death threshold from 20 to 600 ticks (~3 days of starvation) - Add calligraphy-style wind stroke particles (WindStrokes scene + shader) - Two wind stroke emitters in valley and ridge for Ghibli atmosphere - Rework terrain macro-variation colors for vibrant warm-green palette - Enhance environment: stronger glow/bloom, higher saturation, warmer sky/fog/ambient --- simulation/SimNPC.gd | 2 +- simulation/actions/ActionExecutionSystem.gd | 16 ++++--- simulation/actions/ActionSelectionSystem.gd | 25 ++++++++++ world/jajce/JajceWorld.tscn | 51 +++++++++++---------- world/jajce/WindStrokes.tscn | 39 ++++++++++++++++ world/jajce/materials/wind_stroke.gdshader | 14 ++++++ 6 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 world/jajce/WindStrokes.tscn create mode 100644 world/jajce/materials/wind_stroke.gdshader diff --git a/simulation/SimNPC.gd b/simulation/SimNPC.gd index c5fe5b9..39aa8aa 100644 --- a/simulation/SimNPC.gd +++ b/simulation/SimNPC.gd @@ -20,7 +20,7 @@ var home_position: Vector3 var is_starving := false var starvation_ticks := 0 -var starvation_death_threshold := 20 +var starvation_death_threshold := 600 var is_dead := false var task_duration := 0.0 diff --git a/simulation/actions/ActionExecutionSystem.gd b/simulation/actions/ActionExecutionSystem.gd index 5a0a24a..6439b2d 100644 --- a/simulation/actions/ActionExecutionSystem.gd +++ b/simulation/actions/ActionExecutionSystem.gd @@ -15,19 +15,21 @@ func advance_npc(npc: SimNPC, village: SimVillage) -> void: func _update_needs(npc: SimNPC, village: SimVillage) -> void: - npc.hunger += 3.0 * village.food_modifier + npc.hunger += 0.125 * village.food_modifier match npc.task_state: SimNPC.TASK_STATE_IDLE: - npc.energy -= 0.5 + npc.energy -= 0.125 SimNPC.TASK_STATE_TRAVELING: - npc.energy -= 2.0 + npc.energy -= 0.5 SimNPC.TASK_STATE_WORKING: if npc.current_task == SimulationIds.ACTION_SLEEP: - npc.energy = minf(npc.energy + 5.0, 100.0) - elif npc.current_task != SimulationIds.ACTION_REST: - npc.energy -= 3.0 + npc.energy = minf(npc.energy + 1.0, 100.0) + elif npc.current_task == SimulationIds.ACTION_REST: + pass + else: + npc.energy -= 0.5 SimNPC.TASK_STATE_COMPLETE: - npc.energy -= 0.25 + npc.energy -= 0.0625 npc.hunger = clamp(npc.hunger, 0.0, 100.0) npc.energy = clamp(npc.energy, 0.0, 100.0) npc.is_starving = npc.hunger >= 90.0 diff --git a/simulation/actions/ActionSelectionSystem.gd b/simulation/actions/ActionSelectionSystem.gd index 6f477e9..dc59365 100644 --- a/simulation/actions/ActionSelectionSystem.gd +++ b/simulation/actions/ActionSelectionSystem.gd @@ -22,6 +22,31 @@ func select_action(npc: SimNPC, village: SimVillage, time_of_day: float = 0.5) - if npc.is_dead: return null + if npc.is_starving: + if npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) >= 1.0: + return ActionSelectionResult.new( + SimulationIds.ACTION_EAT, 1.0, "Starving and carrying food" + ) + if village.food > 0: + return ActionSelectionResult.new( + SimulationIds.ACTION_WITHDRAW_FOOD, 1.0, "Starving; pantry has food" + ) + return ActionSelectionResult.new( + SimulationIds.ACTION_GATHER_FOOD, -1.0, "Starving; must find food" + ) + if npc.hunger > 80.0: + if npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) >= 1.0: + return ActionSelectionResult.new( + SimulationIds.ACTION_EAT, -1.0, "Critically hungry and carrying food" + ) + if village.food > 0: + return ActionSelectionResult.new( + SimulationIds.ACTION_WITHDRAW_FOOD, -1.0, "Critically hungry; pantry has food" + ) + return ActionSelectionResult.new( + SimulationIds.ACTION_GATHER_FOOD, -1.0, "Critically hungry; must find food" + ) + var period := _get_period(time_of_day) if period == SchedulePeriod.SLEEP: diff --git a/world/jajce/JajceWorld.tscn b/world/jajce/JajceWorld.tscn index 797b710..65d4e5e 100644 --- a/world/jajce/JajceWorld.tscn +++ b/world/jajce/JajceWorld.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=39 format=3] +[gd_scene load_steps=40 format=3] [ext_resource type="Terrain3DAssets" path="res://terrain/jajce/assets.tres" id="1_assets"] [ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"] @@ -16,17 +16,18 @@ [ext_resource type="PackedScene" path="res://world/storage/StorageNode.tscn" id="14_storage"] [ext_resource type="Script" path="res://world/activity/ActivitySite.gd" id="15_activity"] [ext_resource type="NavigationMesh" path="res://world/jajce/JajceNavigationMesh.tres" id="16_nav"] +[ext_resource type="PackedScene" path="res://world/jajce/WindStrokes.tscn" id="19_windstrokes"] [sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"] _shader_parameters = { "auto_base_texture": 1, "auto_overlay_texture": 0, -"auto_slope": 0.72, -"blend_sharpness": 0.82, +"auto_slope": 0.68, +"blend_sharpness": 0.78, "enable_macro_variation": true, -"macro_variation1": Color(0.9, 0.94, 0.82, 1), -"macro_variation2": Color(0.76, 0.81, 0.7, 1), -"macro_variation_slope": 0.38 +"macro_variation1": Color(0.84, 0.92, 0.55, 1), +"macro_variation2": Color(0.62, 0.78, 0.48, 1), +"macro_variation_slope": 0.42 } world_background = 0 auto_shader = true @@ -96,10 +97,10 @@ material = SubResource("Material_site_canvas") size = Vector3(2.9, 0.08, 1.4) [sub_resource type="ProceduralSkyMaterial" id="SkyMaterial_jajce"] -sky_top_color = Color(0.23, 0.43, 0.62, 1) -sky_horizon_color = Color(0.82, 0.68, 0.5, 1) -ground_bottom_color = Color(0.12, 0.17, 0.12, 1) -ground_horizon_color = Color(0.62, 0.55, 0.42, 1) +sky_top_color = Color(0.28, 0.52, 0.72, 1) +sky_horizon_color = Color(0.88, 0.74, 0.52, 1) +ground_bottom_color = Color(0.18, 0.24, 0.14, 1) +ground_horizon_color = Color(0.68, 0.6, 0.44, 1) sun_angle_max = 18.0 [sub_resource type="Sky" id="Sky_jajce"] @@ -110,18 +111,18 @@ background_mode = 2 sky = SubResource("Sky_jajce") background_energy_multiplier = 0.9 ambient_light_source = 3 -ambient_light_color = Color(0.91, 0.85, 0.72, 1) -ambient_light_energy = 0.7 +ambient_light_color = Color(0.95, 0.9, 0.76, 1) +ambient_light_energy = 0.75 reflected_light_source = 2 tonemap_mode = 2 glow_enabled = true glow_normalized = true -glow_intensity = 0.34 -glow_strength = 0.75 -glow_bloom = 0.08 +glow_intensity = 0.5 +glow_strength = 0.9 +glow_bloom = 0.12 fog_enabled = true -fog_light_color = Color(0.82, 0.78, 0.66, 1) -fog_light_energy = 0.72 +fog_light_color = Color(0.88, 0.84, 0.7, 1) +fog_light_energy = 0.78 fog_density = 0.002 fog_height = -2.0 fog_height_density = 0.08 @@ -129,15 +130,15 @@ fog_aerial_perspective = 0.35 fog_sky_affect = 0.28 volumetric_fog_enabled = true volumetric_fog_density = 0.006 -volumetric_fog_albedo = Color(0.9, 0.84, 0.72, 1) -volumetric_fog_emission = Color(0.08, 0.065, 0.04, 1) -volumetric_fog_emission_energy = 0.18 +volumetric_fog_albedo = Color(0.94, 0.88, 0.75, 1) +volumetric_fog_emission = Color(0.1, 0.07, 0.04, 1) +volumetric_fog_emission_energy = 0.22 volumetric_fog_length = 96.0 volumetric_fog_detail_spread = 1.6 adjustment_enabled = true -adjustment_brightness = 1.04 -adjustment_contrast = 1.03 -adjustment_saturation = 1.1 +adjustment_brightness = 1.08 +adjustment_contrast = 1.04 +adjustment_saturation = 1.25 [sub_resource type="StandardMaterial3D" id="Material_guard"] albedo_color = Color(0.55, 0.45, 0.35, 1) @@ -601,6 +602,10 @@ light_energy = 1.45 shadow_enabled = true directional_shadow_max_distance = 180.0 +[node name="WindStrokes_Valley" parent="." instance=ExtResource("19_windstrokes")] + +[node name="WindStrokes_Ridge" parent="." instance=ExtResource("19_windstrokes")] + [node name="WorldEnvironment" type="WorldEnvironment" parent="."] environment = SubResource("Environment_greybox") diff --git a/world/jajce/WindStrokes.tscn b/world/jajce/WindStrokes.tscn new file mode 100644 index 0000000..30d2231 --- /dev/null +++ b/world/jajce/WindStrokes.tscn @@ -0,0 +1,39 @@ +[gd_scene load_steps=5 format=3] + +[ext_resource type="Shader" path="res://world/jajce/materials/wind_stroke.gdshader" id="1_shader"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_stroke"] +shader = ExtResource("1_shader") + +[sub_resource type="QuadMesh" id="Mesh_stroke"] +material = SubResource("ShaderMaterial_stroke") +size = Vector2(1.8, 0.18) + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_wind"] +lifetime_randomness = 0.5 +spread = 160.0 +gravity = Vector3(0, 0.15, 0) +initial_velocity_min = 0.6 +initial_velocity_max = 2.5 +angular_velocity_min = -0.6 +angular_velocity_max = 0.6 +radial_accel_min = -0.2 +radial_accel_max = 0.2 +tangential_accel_min = -0.3 +tangential_accel_max = 0.3 +scale_min = 0.5 +scale_max = 1.5 +color = Color(1, 1, 1, 0.5) +emission_shape = 1 +emission_box_extents = Vector3(35.0, 10.0, 35.0) + +[node name="WindStrokes" type="GPUParticles3D"] +emitting = true +amount = 18 +lifetime = 4.0 +one_shot = false +preprocess = 2.0 +speed_scale = 1.0 +local_coords = false +process_material = SubResource("ParticleProcessMaterial_wind") +draw_pass_1 = SubResource("Mesh_stroke") diff --git a/world/jajce/materials/wind_stroke.gdshader b/world/jajce/materials/wind_stroke.gdshader new file mode 100644 index 0000000..93df93b --- /dev/null +++ b/world/jajce/materials/wind_stroke.gdshader @@ -0,0 +1,14 @@ +shader_type spatial; +render_mode blend_mix, depth_draw_opaque, cull_disabled, unshaded; + +uniform float softness : hint_range(0.0, 1.0) = 0.45; +uniform vec3 tint : source_color = vec3(0.95, 0.93, 0.88); + +void fragment() { + float edge_dist = abs(UV.y - 0.5) * 2.0; + float alpha = 1.0 - smoothstep(0.0, 1.0, edge_dist); + alpha *= smoothstep(0.0, 0.2, UV.x) * smoothstep(0.0, 0.2, 1.0 - UV.x); + alpha *= softness * 0.35; + ALBEDO = tint; + ALPHA = alpha; +}