extends Node class_name GlobalState #region configuration signal settings_changed var _settings_initialized var room: RoomTemplate var save_game: SaveGame signal environment_settings_changed # FIXME find a better way to switch fonts and maybe emit the theme_changed signal! signal theme_changed var current_main_theme:Theme = preload("res://logic-scenes/themes/handwriting.theme") func set_theme(new_theme:Theme): current_main_theme = new_theme theme_changed.emit(new_theme) @export_file var user_settings_path:String = "user://user_settings.json" @export_file var user_saves_path:String = "user://savegames" @export_group("Acessability") @export var reduce_motion: bool = false: set(value): reduce_motion = value if _settings_initialized: settings_changed.emit() @export var screen_reader_enabled:bool = false: set(value): screen_reader_enabled = value if _settings_initialized: settings_changed.emit() @export var rendering_disabled: bool = false: set(value): rendering_disabled = value if _settings_initialized: settings_changed.emit() @export var use_simplified_navigation:bool = false: set(value): use_simplified_navigation = value if _settings_initialized: settings_changed.emit() @export var enable_hyperacoustic_filter: bool = false: set(value): enable_hyperacoustic_filter = value if enable_hyperacoustic_filter: AudioServer.set_bus_effect_enabled(0, 0, enable_hyperacoustic_filter) if _settings_initialized: settings_changed.emit() @export var show_navigation_buttons: bool = false: set(value): show_navigation_buttons = value if _settings_initialized: settings_changed.emit() @export_enum("handwriting", "serif", "legible", "system") var font_style: int = 0: set(value): font_style = value if _settings_initialized: settings_changed.emit() @export_enum("disabled", "text", "cc") var subtitles: int = false: set(value): subtitles = value if _settings_initialized: settings_changed.emit() @export var ui_scaling: float = 1: set(value): ui_scaling = value ProjectSettings.set_setting("display/window/stretch/scale", value) get_tree().root.content_scale_factor = value @export var show_content_notes: bool = false: set(value): show_content_notes = value if _settings_initialized: settings_changed.emit() @export var provide_summaries: bool = false: set(value): provide_summaries = value if _settings_initialized: settings_changed.emit() @export var allow_skipping: bool = false: set(value): allow_skipping = value if _settings_initialized: settings_changed.emit() @export_group("AudioSettings") @export var main_volume:float = 1: set(volume): main_volume = volume AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Master"), linear_to_db(volume)) @export var sfx_muted:bool = false: set(mute): sfx_muted = mute AudioServer.set_bus_volume_db(AudioServer.get_bus_index("sfx"), linear_to_db(mute)) @export var sfx_volume:float = 1: set(volume): sfx_volume = volume AudioServer.set_bus_volume_db(AudioServer.get_bus_index("sfx"), linear_to_db(volume/2)) @export var music_muted:bool = false: set(mute): sfx_muted = mute AudioServer.set_bus_volume_db(AudioServer.get_bus_index("sfx"), linear_to_db(mute)) @export var music_volume:float = 1: set(volume): music_volume = volume AudioServer.set_bus_volume_db(AudioServer.get_bus_index("music"), linear_to_db(volume/2.5)) @export var speech_volume: float = 1: set(volume): speech_volume = volume AudioServer.set_bus_volume_db(AudioServer.get_bus_index("text"), linear_to_db(volume)) @export var force_stereo: bool = false: set(stereo): if stereo != force_stereo: force_stereo = stereo settings_changed.emit() @export var disconnect_steam:bool = false: set(disconnect): if disconnect and not disconnect_steam: Steam.steamShutdown() Steamworks.has_initialized = false elif not disconnect and disconnect_steam: Steamworks._ready() disconnect_steam = disconnect @export var obscure_logs:bool = true @export_enum("system_locale", "english", "german") var text_language: int = 0: set(value): text_language = value match text_language: 1: TranslationServer.set_locale("en") 2: TranslationServer.set_locale("de") _: TranslationServer.set_locale(OS.get_locale()) settings_changed.emit() @export_enum("system_locale", "english", "german") var speech_language: int = 0: set(value): speech_language = value @export_group("Gameplay Settings") @export var input_sensitivity: float = 1.0 @export var fov: float = 35.0 @export var inverty_y_axis: bool = false @export var inverty_y_mouse: bool = false @export_enum("off", "top_left", "top_right", "bottom_left", "bottom_right") var stream_overlay_position: int # for passing VFX settings not contained by project settings to scene environemnt var ssil_enable:bool = false: set(value): ssil_enable = value environment_settings_changed.emit() var sdfgi_enable:bool = false: set(value): sdfgi_enable = value environment_settings_changed.emit() func load_user_settings(): if FileAccess.file_exists(user_settings_path): var file := FileAccess.open(user_settings_path, FileAccess.READ) var raw_json := FileAccess.get_file_as_string(user_settings_path) file.close() var parsed: Dictionary = JSON.parse_string(raw_json) for kategory in parsed.values(): for key in kategory.keys(): if key in self: set(key, parsed[key]) else: if OS.has_feature("macos"): var out: Array OS.execute("defaults", ["read", "/Users/$loggedInUser/Library/Preferences/com.apple.universalaccess.plist", "reduceMotion"], out) if out[0] == "reduce": reduce_motion = true _settings_initialized = true settings_changed.emit() func save_settings(): var out_dict := { "accessability": { "reduce_motion:": reduce_motion, "screen_reader_enabled": screen_reader_enabled, "rendering_disabled": rendering_disabled, "use_simplified_navigation": use_simplified_navigation, "show_navigation_buttons": show_navigation_buttons, "subtitles": subtitles, "font_style": font_style, "ui_scaling": ui_scaling, "show_content_notes:": show_content_notes, "provide_summaries:": provide_summaries, "allow_skipping:": allow_skipping }, "audio": { "main_volume": main_volume, "sfx_muted": sfx_muted, "sfx_volume": sfx_volume, "music_muted": music_muted, "music_volume": music_volume, "speech_volume": speech_volume, "speech_language": speech_language, "text_language": text_language, "force_stereo": force_stereo }, "gameplay": { "input_sensitivity": input_sensitivity, "inverty_y_axis": inverty_y_axis, "inverty_y_mouse": inverty_y_mouse, "fov": fov, "stream_overlay_position": stream_overlay_position }, "privacy": { "disconnect_steam": disconnect_steam, "obscure_logs": obscure_logs } } var file := FileAccess.open(user_settings_path, FileAccess.WRITE) file.store_string(JSON.stringify(out_dict)) file.close() _settings_initialized = true settings_changed.emit() var last_mode := DisplayServer.WINDOW_MODE_WINDOWED func _unhandled_input(event: InputEvent) -> void: #FIXME this can be removed when state no longer needs to be a tool if not Engine.is_editor_hint(): if event.is_action_pressed("toggle_fullscreen"): # I have no idea why I wrote thit as conviluted, # but it works(TM) so I am not gonna change it :D if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN: DisplayServer.window_set_mode(last_mode) else: last_mode = DisplayServer.window_get_mode() DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) #endregion func _ready(): await get_tree().process_frame for child in get_tree().root.get_children(): if child is RoomTemplate and save_game == null: push_warning("Room initialised without a SaveGame. Creating proxy save.") #TODO: make this a bit more clean maybe? save_game = ResourceLoader.load("res://dev-util/debug_save.tres") if "has_stage" in child: take_stage(child) break music_volume = music_volume #region focus handling (called staging to avoid name colisions) # Stage list is a stack. Only the front (top) element "has" the stage. # The stage manager sets has_stage on actors when they take/leave stage. var stage_list: Array = [] var focus_locked: bool = false # Helper to safely set has_stage on an actor func _set_actor_stage(actor: Object, value: bool) -> void: if is_instance_valid(actor) and "has_stage" in actor: actor.has_stage = value # Actor takes the stage (pushes to front of stack) func take_stage(actor: Object) -> void: print_debug(">>> take_stage(", actor, ")") assert(not focus_locked, "Focus is locked, %s cannot take focus." % actor) assert(is_instance_valid(actor), "Cannot take stage with invalid actor") # Remove actor if already in list (to re-add at front) if actor in stage_list: stage_list.erase(actor) # Remove stage from current front if stage_list.size() > 0: _set_actor_stage(stage_list.front(), false) # Add new actor to front and give it stage stage_list.push_front(actor) _set_actor_stage(actor, true) # Actor leaves the stage (removes from stack) func leave_stage(actor: Object) -> void: print_debug("<<< leave_stage(", actor, ")") if not (actor in stage_list): push_warning("Actor %s not in stage list, ignoring leave_stage call." % actor) return var was_front = false if stage_list.size() > 0: if stage_list.front() == actor: was_front = true # Remove stage from actor and remove from list _set_actor_stage(actor, false) stage_list.erase(actor) # If actor was at front, give stage to new front if was_front: if stage_list.size() > 0: _set_actor_stage(stage_list.front(), true) # Pass stage to a new target (pushes target to front) func pass_stage_to(target: Object, force: bool = false) -> void: print_debug(">>> pass_stage_to(", target, ")") if not is_instance_valid(target): push_error("Cannot pass stage to invalid target") return if not "has_stage" in target: push_error(target, " has no has_stage property") return if (focus_locked or get_tree().paused) and not force: push_error(target, " requested focus while it was locked or tree is paused") return # If target is already at front, nothing to do if stage_list.size() > 0 and stage_list.front() == target: push_warning(target, " is already at front of stage. Ignoring.") return take_stage(target) # Queue an actor for stage at a specific position (does not give it stage yet) func queue_for_stage(target: Object, index: int = 1) -> void: print_debug(">>> queue_for_stage(", target, ") at index ", index) if target in stage_list: stage_list.erase(target) stage_list.insert(index, target) # Currently focused element loses stage but remains in stack func free_focus() -> void: if focus_locked: return if stage_list.size() > 0: _set_actor_stage(stage_list.front(), false) # Reset stack to only contain the bottom element (original/root) func reset_focus() -> void: # Remove stage from current front if stage_list.size() > 0: _set_actor_stage(stage_list.front(), false) # Keep only the last element if stage_list.size() > 0: var root = stage_list[-1] stage_list = [root] _set_actor_stage(root, true) # Transfer stage from current front to a new actor (removes current front) func transition_stage_to(target: Object, lock: bool = false) -> void: print_debug(">>> transition_stage_to(", target, ")") # Remove current front from stack entirely if stage_list.size() > 0: var old_front = stage_list.pop_front() _set_actor_stage(old_front, false) # Add new target stage_list.push_front(target) _set_actor_stage(target, true) focus_locked = lock #endregion #region play state enum rooms { NULL, YOUTH, TRANSITION, ADULTHOOD, ENDING } enum sequences { DRAEVEN, CHILDHOOD, VOICE, JUI_JUTSU, TRANS, AUTISM, UNI_START, VOLUNTARY, UNI_CONTINUE, UNI_ALT, THERAPY, BURNOUT } var current_room: rooms = rooms.NULL var onready_room: rooms = rooms.NULL