feat: Implement nausea symptom system and related features
Build / Export windows (push) Successful in 10m56s
Build / Export linux (push) Successful in 6m10s

- Added SymptomManager autoload for managing symptom intensities.
- Introduced nausea tilt effect for camera roll based on nausea intensity.
- Created color desaturation shader and overlay for visual feedback.
- Developed TreatmentManager to handle nausea ramping during IV treatment.
- Added hospital room scene with first-person controller and interactions.
- Implemented sit-down interaction for the treatment chair.
- Created IV insertion sequence with animations and completion signal.
- Added player controller with movement and interaction capabilities.
- Integrated nausea effects into the player experience with smooth transitions.
This commit is contained in:
2026-05-30 14:14:38 +02:00
parent 2eb7f9b2cb
commit 8462a2fde7
16 changed files with 867 additions and 3 deletions
+52
View File
@@ -0,0 +1,52 @@
extends Node
## Autoload singleton managing symptom states.
##
## Intensities are floats in the range [0, 100]. Nausea is the MVP symptom but
## the API is generic so future symptoms (fatigue, taste distortion, etc.) can
## reuse it. Register this script as an autoload named `SymptomManager`.
## Emitted whenever any symptom intensity changes.
## `symptom_id` is the changed symptom, `value` its new clamped intensity.
signal symptom_intensity_changed(symptom_id: StringName, value: float)
## Convenience signal specific to nausea (MVP visual effects subscribe to this).
signal nausea_intensity_changed(value: float)
const MIN_INTENSITY: float = 0.0
const MAX_INTENSITY: float = 100.0
## Well-known symptom ids.
const NAUSEA: StringName = &"nausea"
var _intensities: Dictionary = {}
## Set a symptom's intensity to an absolute value (clamped to [0, 100]).
func trigger_symptom(symptom_id: StringName, intensity: float) -> void:
_set_intensity(symptom_id, intensity)
## Add (or subtract, with a negative delta) to a symptom's current intensity.
func add_intensity(symptom_id: StringName, delta: float) -> void:
_set_intensity(symptom_id, get_intensity(symptom_id) + delta)
## Return a symptom's current intensity, or 0.0 if never set.
func get_intensity(symptom_id: StringName) -> float:
return _intensities.get(symptom_id, 0.0)
## Reset all symptoms to 0 (e.g. between treatments / new game).
func reset_all() -> void:
for symptom_id in _intensities.keys():
_set_intensity(symptom_id, 0.0)
func _set_intensity(symptom_id: StringName, value: float) -> void:
var clamped := clampf(value, MIN_INTENSITY, MAX_INTENSITY)
if is_equal_approx(_intensities.get(symptom_id, 0.0), clamped):
return
_intensities[symptom_id] = clamped
symptom_intensity_changed.emit(symptom_id, clamped)
if symptom_id == NAUSEA:
nausea_intensity_changed.emit(clamped)
+54
View File
@@ -0,0 +1,54 @@
class_name TreatmentManager
extends Node
## Drives nausea during an active treatment.
##
## When `start_ramp()` is called (wired to IV `iv_completed`), nausea ramps from
## its current value up to `ramp_target` over `ramp_duration` seconds, then holds.
## The SymptomManager autoload propagates changes to the tilt + desaturation
## effects, so this node only needs to push intensity values.
signal treatment_started
signal ramp_complete
## Target nausea intensity at the end of the initial ramp.
@export var ramp_target: float = 40.0
## How long (seconds) the initial ramp takes.
@export var ramp_duration: float = 30.0
var _ramping: bool = false
var _elapsed: float = 0.0
var _start_value: float = 0.0
var _sm: Node = null
func _ready() -> void:
if is_inside_tree():
_sm = get_tree().root.get_node_or_null("SymptomManager")
if _sm == null:
_sm = SymptomManager
set_process(false)
## Begin the nausea ramp. Safe to wire directly to IVInsertion.iv_completed.
func start_ramp() -> void:
if _sm == null:
push_warning("TreatmentManager: SymptomManager not available.")
return
_start_value = _sm.get_intensity(_sm.NAUSEA)
_elapsed = 0.0
_ramping = true
set_process(true)
treatment_started.emit()
func _process(delta: float) -> void:
if not _ramping:
return
_elapsed += delta
var t := clampf(_elapsed / ramp_duration, 0.0, 1.0)
var value := lerpf(_start_value, ramp_target, t)
_sm.trigger_symptom(_sm.NAUSEA, value)
if t >= 1.0:
_ramping = false
set_process(false)
ramp_complete.emit()