extends Node ## Player-facing game options (autoload `Settings`), persisted to user://settings.cfg. ## Distinct from DP (debug tunables) and Controls (keybinds): this is the small set of ## comfort/accessibility toggles the options menu exposes to the player. signal changed(key: String, value: Variant) const SAVE_PATH := "user://settings.cfg" const SECTION := "options" ## Master screen-shake toggle — gates every camera_iso.trigger_hit (hits and ability FX). var screen_shake: bool = true ## Gore toggle — when off, no blood spawns (impact spurt + ongoing spear-wound bleed). var gore: bool = true ## Audio volumes stored as linear 0–1. Applied to AudioServer buses on load/change. var volume_master: float = 1.0 var volume_ambient: float = 1.0 var volume_effects: float = 1.0 var mute_all: bool = false func _ready() -> void: _ensure_buses() load_saved() ## Ensure Ambient and Effects buses exist as children of Master. func _ensure_buses() -> void: for bus_name: String in ["Ambient", "Effects"]: if AudioServer.get_bus_index(bus_name) == -1: AudioServer.add_bus() var idx := AudioServer.bus_count - 1 AudioServer.set_bus_name(idx, bus_name) AudioServer.set_bus_send(idx, &"Master") func _apply_audio() -> void: AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), _vol_db(volume_master)) AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Ambient"), _vol_db(volume_ambient)) AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Effects"), _vol_db(volume_effects)) AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), mute_all) func _vol_db(linear: float) -> float: return linear_to_db(maxf(linear, 0.0001)) func set_screen_shake(value: bool) -> void: if screen_shake == value: return screen_shake = value changed.emit("screen_shake", value) save() func set_gore(value: bool) -> void: if gore == value: return gore = value changed.emit("gore", value) save() func set_volume_master(value: float) -> void: volume_master = clampf(value, 0.0, 1.0) AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), _vol_db(volume_master)) changed.emit("volume_master", volume_master) save() func set_volume_ambient(value: float) -> void: volume_ambient = clampf(value, 0.0, 1.0) AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Ambient"), _vol_db(volume_ambient)) changed.emit("volume_ambient", volume_ambient) save() func set_volume_effects(value: float) -> void: volume_effects = clampf(value, 0.0, 1.0) AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Effects"), _vol_db(volume_effects)) changed.emit("volume_effects", volume_effects) save() func set_mute_all(value: bool) -> void: if mute_all == value: return mute_all = value AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), mute_all) changed.emit("mute_all", value) save() func save() -> void: var cfg := ConfigFile.new() cfg.set_value(SECTION, "screen_shake", screen_shake) cfg.set_value(SECTION, "gore", gore) cfg.set_value(SECTION, "volume_master", volume_master) cfg.set_value(SECTION, "volume_ambient", volume_ambient) cfg.set_value(SECTION, "volume_effects", volume_effects) cfg.set_value(SECTION, "mute_all", mute_all) cfg.save(SAVE_PATH) func load_saved() -> void: var cfg := ConfigFile.new() if cfg.load(SAVE_PATH) != OK: _apply_audio() return screen_shake = cfg.get_value(SECTION, "screen_shake", true) as bool gore = cfg.get_value(SECTION, "gore", true) as bool volume_master = cfg.get_value(SECTION, "volume_master", 1.0) as float volume_ambient = cfg.get_value(SECTION, "volume_ambient", 1.0) as float volume_effects = cfg.get_value(SECTION, "volume_effects", 1.0) as float mute_all = cfg.get_value(SECTION, "mute_all", false) as bool _apply_audio()