From a76fade0c7636cd92ad5032224cc97c10154754a Mon Sep 17 00:00:00 2001 From: hz Date: Sun, 9 Nov 2025 13:12:40 -0600 Subject: [PATCH] Added water and bladder system --- scenes/Grid/grid_character_controller.gd | 135 ++++++++++++++++++++--- scenes/Grid/grid_field.tscn | 30 +++-- scenes/Grid/water_display.gd | 37 +++++++ scenes/Grid/water_display.gd.uid | 1 + 4 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 scenes/Grid/water_display.gd create mode 100644 scenes/Grid/water_display.gd.uid diff --git a/scenes/Grid/grid_character_controller.gd b/scenes/Grid/grid_character_controller.gd index 5def678..f723ed3 100644 --- a/scenes/Grid/grid_character_controller.gd +++ b/scenes/Grid/grid_character_controller.gd @@ -10,6 +10,12 @@ class_name GridCharacterController @export var home_marker_scene: PackedScene @export_range(1.0, 16.0, 0.5) var sleep_requirement_hours: float = 8.0 @export var day_night_controller_path: NodePath +@export_range(500.0, 5000.0, 50.0) var max_water_ml: float = 2000.0 +@export_range(0.0, 5.0, 0.1) var passive_water_usage_ml_per_minute: float = 1.0 +@export_range(0.0, 200.0, 0.5) var movement_water_usage_ml_per_meter: float = 2.0 +@export_range(50.0, 2000.0, 50.0) var low_water_threshold_ml: float = 400.0 +@export_range(100.0, 2000.0, 25.0) var bladder_capacity_ml: float = 750.0 +@export var vitals_display_path: NodePath var _grid_manager: GridPlaneManager = null var _camera: Camera3D = null @@ -24,6 +30,25 @@ var _sleep_accum_hours: float = 0.0 var _is_sleeping: bool = false var _sleep_pending_return: bool = false var _awake_duration_hours: float = 0.0 +var _water_level_ml: float = 0.0 +var _bladder_level_ml: float = 0.0 +var _needs_water: bool = false +var _needs_bathroom: bool = false +var _returning_home_for_needs: bool = false + +signal vitals_changed(water_level: float, bladder_level: float, needs_water: bool, needs_bathroom: bool) + +func get_water_level() -> float: + return _water_level_ml + +func get_bladder_level() -> float: + return _bladder_level_ml + +func is_water_low() -> bool: + return _needs_water + +func is_bladder_full() -> bool: + return _needs_bathroom func _ready() -> void: set_physics_process(true) @@ -33,6 +58,8 @@ func _ready() -> void: _grid_manager.grid_layout_changed.connect(_on_grid_layout_changed) _move_to_cell(start_cell, true) _spawn_home() + _water_level_ml = max_water_ml + _bladder_level_ml = 0.0 _update_awake_duration() if _day_night_controller: _last_time_hours = _day_night_controller.get_time_of_day_hours() @@ -40,6 +67,7 @@ func _ready() -> void: _last_time_hours = 0.0 _is_sleeping = false _sleep_pending_return = false + emit_signal("vitals_changed", _water_level_ml, _bladder_level_ml, _needs_water, _needs_bathroom) func _physics_process(delta: float) -> void: if not _is_moving: @@ -49,17 +77,17 @@ func _physics_process(delta: float) -> void: if distance <= 0.01: global_position = _target_position _is_moving = false - if _sleep_pending_return and _current_cell == start_cell: - _enter_sleep_state() + _handle_arrival_at_current_cell() return var step: float = move_speed * delta var move_vector: Vector3 = direction.normalized() * min(step, distance) global_position += move_vector + emit_signal("vitals_changed", _water_level_ml, _bladder_level_ml, _needs_water, _needs_bathroom) func _unhandled_input(event: InputEvent) -> void: if Engine.is_editor_hint(): return - if _is_sleeping or _sleep_pending_return: + if _should_ignore_player_input(): return if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: _move_to_mouse_click() @@ -67,7 +95,7 @@ func _unhandled_input(event: InputEvent) -> void: func _move_to_mouse_click() -> void: if not _grid_manager or not _camera: return - if _is_sleeping or _sleep_pending_return: + if _should_ignore_player_input(): return var viewport: Viewport = get_viewport() if viewport == null: @@ -85,11 +113,12 @@ func _move_to_cell(cell: Vector2i, instant: bool = false) -> void: var local_center: Vector3 = _grid_manager.cell_to_world(cell) var world_center: Vector3 = _grid_manager.to_global(local_center) _target_position = world_center + Vector3.UP * height_offset + var travel_distance := global_transform.origin.distance_to(_target_position) + _apply_movement_water_usage(travel_distance) if instant: global_position = _target_position _is_moving = false - if _sleep_pending_return and cell == start_cell: - _enter_sleep_state() + _handle_arrival_at_current_cell() else: _is_moving = true @@ -130,7 +159,9 @@ func _resolve_references() -> void: if day_night_controller_path != NodePath(): _day_night_controller = get_node_or_null(day_night_controller_path) as DayNightController else: - _day_night_controller = get_node_or_null("../DirectionalLight3D") as DayNightController + var parent := get_parent() + if parent: + _day_night_controller = parent.get_node_or_null("../DirectionalLight3D") as DayNightController _connect_day_night() func _spawn_home() -> void: @@ -165,13 +196,35 @@ func _update_home_position() -> void: var world_center := _grid_manager.to_global(local_center) _home_instance.global_position = world_center + Vector3.UP * 0.01 +func _should_ignore_player_input() -> bool: + return _is_sleeping or _sleep_pending_return or _needs_water or _needs_bathroom + +func _handle_arrival_at_current_cell() -> void: + if _current_cell == start_cell: + _on_arrived_home() + +func _on_arrived_home() -> void: + _is_moving = false + if _needs_bathroom: + _empty_bladder() + _needs_bathroom = false + if _needs_water: + _fill_water() + _needs_water = false + _returning_home_for_needs = false + if _sleep_pending_return: + _enter_sleep_state() + func _connect_day_night() -> void: - if _day_night_controller and not _day_night_controller.is_connected("time_changed", Callable(self, "_on_time_changed")): - _day_night_controller.time_changed.connect(_on_time_changed) - _update_awake_duration() + if not _day_night_controller: + return + var callback := Callable(self, "_on_time_changed") + if not _day_night_controller.time_changed.is_connected(callback): + _day_night_controller.time_changed.connect(callback) + _update_awake_duration() func _update_awake_duration() -> void: - var total_hours := sleep_requirement_hours + 1.0 + var total_hours := 24.0 if _day_night_controller: total_hours = _day_night_controller.get_day_length_hours() _awake_duration_hours = max(total_hours - sleep_requirement_hours, 0.1) @@ -181,6 +234,7 @@ func _on_time_changed(current_hours: float) -> void: return var delta := _compute_time_delta(current_hours) _last_time_hours = current_hours + _apply_passive_water_usage(delta) if _is_sleeping: _sleep_accum_hours += delta if _sleep_accum_hours >= sleep_requirement_hours: @@ -193,8 +247,8 @@ func _on_time_changed(current_hours: float) -> void: _begin_sleep_cycle() func _compute_time_delta(current_hours: float) -> float: - var day_length := _day_night_controller.get_day_length_hours() if _day_night_controller else 24.0 - var delta := current_hours - _last_time_hours + var day_length: float = _day_night_controller.get_day_length_hours() if _day_night_controller else 24.0 + var delta: float = current_hours - _last_time_hours if delta < 0.0: delta += day_length return delta @@ -210,9 +264,64 @@ func _enter_sleep_state() -> void: _sleep_pending_return = false _is_sleeping = true _sleep_accum_hours = 0.0 + _empty_bladder() func _exit_sleep_state() -> void: _is_sleeping = false _sleep_pending_return = false _sleep_accum_hours = 0.0 _awake_accum_hours = 0.0 + if _current_cell == start_cell: + if _needs_water: + _fill_water() + _needs_water = false + if _needs_bathroom: + _empty_bladder() + _needs_bathroom = false + +func _apply_passive_water_usage(delta_hours: float) -> void: + if delta_hours <= 0.0: + return + var minutes := delta_hours * 60.0 + var amount := minutes * passive_water_usage_ml_per_minute + _consume_water(amount) + +func _apply_movement_water_usage(distance: float) -> void: + if distance <= 0.0: + return + var usage := movement_water_usage_ml_per_meter * distance + _consume_water(usage) + +func _consume_water(amount: float) -> void: + if amount <= 0.0: + return + _water_level_ml = max(_water_level_ml - amount, 0.0) + _bladder_level_ml = min(_bladder_level_ml + amount, bladder_capacity_ml * 2.0) + if _bladder_level_ml >= bladder_capacity_ml: + _needs_bathroom = true + if not _returning_home_for_needs and not _sleep_pending_return: + _returning_home_for_needs = true + _request_home_return() + if _water_level_ml <= low_water_threshold_ml: + _needs_water = true + if not _returning_home_for_needs and not _sleep_pending_return: + _returning_home_for_needs = true + _request_home_return() + emit_signal("vitals_changed", _water_level_ml, _bladder_level_ml, _needs_water, _needs_bathroom) + +func _fill_water() -> void: + _water_level_ml = max_water_ml + emit_signal("vitals_changed", _water_level_ml, _bladder_level_ml, _needs_water, _needs_bathroom) + +func _empty_bladder() -> void: + _bladder_level_ml = 0.0 + _needs_bathroom = false + emit_signal("vitals_changed", _water_level_ml, _bladder_level_ml, _needs_water, _needs_bathroom) + +func _request_home_return() -> void: + if _is_sleeping: + return + if _current_cell == start_cell and not _is_moving: + _on_arrived_home() + return + _move_to_cell(start_cell) diff --git a/scenes/Grid/grid_field.tscn b/scenes/Grid/grid_field.tscn index dc268b8..7d2664c 100644 --- a/scenes/Grid/grid_field.tscn +++ b/scenes/Grid/grid_field.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=17 format=4 uid="uid://qd6xvgwrd00g"] +[gd_scene load_steps=18 format=4 uid="uid://qd6xvgwrd00g"] [ext_resource type="Script" uid="uid://3goclr4srt7k" path="res://scenes/Grid/grid_plane_manager.gd" id="1_72cxc"] [ext_resource type="Script" uid="uid://5p4mcxw7wann" path="res://scenes/Grid/grid_camera_controller.gd" id="2_o2iv9"] @@ -6,7 +6,8 @@ [ext_resource type="Script" uid="uid://oy8cl8dscrh2" path="res://scenes/Grid/day_night_controller.gd" id="4_u8bvi"] [ext_resource type="Script" uid="uid://6asq70b60kif" path="res://scenes/Grid/grid_character_controller.gd" id="5_n4d0h"] [ext_resource type="Script" uid="uid://24uoe7hanwp2" path="res://scenes/Grid/time_display.gd" id="6_8n3rl"] -[ext_resource type="PackedScene" path="res://scenes/Grid/grid_home_marker.tscn" id="7_1vmu7"] +[ext_resource type="PackedScene" uid="uid://r703rwsvxsx2" path="res://scenes/Grid/grid_home_marker.tscn" id="7_1vmu7"] +[ext_resource type="Script" uid="uid://25dqopfaaam0" path="res://scenes/Grid/water_display.gd" id="8_8k3xv"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ground"] albedo_color = Color(0.211765, 0.364706, 0.211765, 1) @@ -23,7 +24,7 @@ shading_mode = 0 vertex_color_use_as_albedo = true albedo_color = Color(0.811765, 0.917647, 1, 0.74902) -[sub_resource type="ArrayMesh" id="ArrayMesh_ot7o0"] +[sub_resource type="ArrayMesh" id="ArrayMesh_1wejn"] _surfaces = [{ "aabb": AABB(-5, 0.02, -5, 10, 1e-05, 10), "attribute_data": PackedByteArray("M7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6Uzsv+lM7L/pTOy/6U="), @@ -40,7 +41,7 @@ no_depth_test = true shading_mode = 0 albedo_color = Color(0.976471, 0.843137, 0.333333, 0.6) -[sub_resource type="PlaneMesh" id="PlaneMesh_1wejn"] +[sub_resource type="PlaneMesh" id="PlaneMesh_2l7eq"] size = Vector2(1, 1) [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_character"] @@ -75,13 +76,14 @@ shape = SubResource("BoxShape3D_ground") unique_name_in_owner = true material_override = SubResource("StandardMaterial3D_grid") cast_shadow = 0 -mesh = SubResource("ArrayMesh_ot7o0") +mesh = SubResource("ArrayMesh_1wejn") [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] unique_name_in_owner = true transform = Transform3D(1.0000002, 1.306986e-15, 5.713018e-23, 0, -4.3711392e-08, 1.0000001, 1.3069862e-15, -1.0000001, -4.3711392e-08, 5.165678e-06, 60, 9.1084837e-07) -light_color = Color(0.12, 0.18, 0.35, 1) -light_energy = 0.05 +light_color = Color(1, 0.96, 0.88, 1) +light_energy = 1.25 +shadow_enabled = true script = ExtResource("4_u8bvi") grid_manager_path = NodePath("..") day_length_hours = 24.0 @@ -102,7 +104,7 @@ unique_name_in_owner = true visible = false material_override = SubResource("StandardMaterial3D_highlight") cast_shadow = 0 -mesh = SubResource("PlaneMesh_1wejn") +mesh = SubResource("PlaneMesh_2l7eq") script = ExtResource("3_n2b7o") grid_manager_path = NodePath("..") camera_path = NodePath("../Camera3D") @@ -113,7 +115,6 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.5, 0.3, -4.5) script = ExtResource("5_n4d0h") grid_manager_path = NodePath("..") camera_path = NodePath("../Camera3D") -start_cell = Vector2i(0, 0) move_speed = 5.0 home_marker_scene = ExtResource("7_1vmu7") day_night_controller_path = NodePath("../DirectionalLight3D") @@ -139,3 +140,14 @@ horizontal_alignment = 2 vertical_alignment = 1 script = ExtResource("6_8n3rl") day_night_controller_path = NodePath("../../DirectionalLight3D") + +[node name="WaterLabel" type="Label" parent="HUD"] +offset_left = 20.0 +offset_top = 20.0 +offset_right = 420.0 +offset_bottom = 60.0 +grow_horizontal = 0 +theme_override_font_sizes/font_size = 22 +text = "Water: 0 ml | Bladder: 0 ml" +script = ExtResource("8_8k3xv") +character_path = NodePath("../../GridCharacter") diff --git a/scenes/Grid/water_display.gd b/scenes/Grid/water_display.gd new file mode 100644 index 0000000..2961bf2 --- /dev/null +++ b/scenes/Grid/water_display.gd @@ -0,0 +1,37 @@ +@tool +extends Label +class_name WaterDisplay + +@export var character_path: NodePath +@export var show_bladder: bool = true +@export var show_water: bool = true + +var _character: GridCharacterController = null + +func _ready() -> void: + set_process(true) + _resolve_character() + if _character: + _character.vitals_changed.connect(_on_vitals_changed) + _on_vitals_changed(_character.get_water_level(), _character.get_bladder_level(), _character.is_water_low(), _character.is_bladder_full()) + +func _process(_delta: float) -> void: + if Engine.is_editor_hint(): + _update_text(0.0, 0.0, false, false) + +func _resolve_character() -> void: + if character_path != NodePath(): + _character = get_node_or_null(character_path) as GridCharacterController + else: + _character = get_parent() as GridCharacterController + +func _on_vitals_changed(water_level: float, bladder_level: float, needs_water: bool, needs_bathroom: bool) -> void: + _update_text(water_level, bladder_level, needs_water, needs_bathroom) + +func _update_text(water_level: float, bladder_level: float, needs_water: bool, needs_bathroom: bool) -> void: + var parts: Array[String] = [] + if show_water: + parts.append("Water: %4d ml%s" % [int(water_level), " !" if needs_water else ""]) + if show_bladder: + parts.append("Bladder: %4d ml%s" % [int(bladder_level), " !" if needs_bathroom else ""]) + text = " | ".join(parts) diff --git a/scenes/Grid/water_display.gd.uid b/scenes/Grid/water_display.gd.uid new file mode 100644 index 0000000..0add41b --- /dev/null +++ b/scenes/Grid/water_display.gd.uid @@ -0,0 +1 @@ +uid://25dqope89amz