@tool class_name Game extends Node ## TODO: sicherungskasten minigames (Kabel verbinden, oder draufhauen und geht wieder) const USER_SETTINGS_PATH: String = "user://settings.ini" const SAVE_DIR: String = "user://saves/%d/" # TODO "user://ch1/saves/%d/ # Chapter 1 const SAVE_PATH: String = SAVE_DIR + "%s" const INITIAL_SAVE_DATA: SaveData = preload("uid://b8ojagpq5pxr2") static var is_debug: bool = true @export_file("*.tscn", "*.scn") var initial_setup_path: String = "uid://8bxv3c5f8d2j" @export_file("*.tscn", "*.scn") var main_menu_path: String = "uid://7v62dybcabgw" var save_slot: int = 0 var save_data: SaveData = INITIAL_SAVE_DATA.duplicate() var world: World var main_menu: MainMenu var _os_dialog_window_result: int = -1 @onready var world_holder: Node = %WorldHolder @onready var loading_screen: LoadingScreen = %LoadingScreen static func _static_init() -> void: is_debug = OS.is_debug_build() or Engine.is_editor_hint() static func debug_draw(draw_shape: String, args: Array) -> void: const DD3D: StringName = &"DebugDraw3D" if is_debug and Engine.has_singleton(DD3D): Engine.get_singleton(DD3D).callv("draw_" + draw_shape, args) ## Turns res://example.tscn into example.tres (and .scn -> .res) and uid://1234567 into 1234567.tres. static func convert_path_to_resource_filename(path: String) -> String: path = ResourceUID.path_to_uid(path) if path.begins_with("res://"): return path.get_file().replace(".tscn", ".tres").replace(".scn", ".res") return path.replace("uid://", "") + ".tres" func _ready() -> void: if not Engine.is_editor_hint(): load_startup() register_commands() func load_game(_save_data: SaveData, _save_slot: int) -> void: if not await check_computer_hash(save_data.com_hash): return save_data = _save_data.duplicate() save_slot = _save_slot InputManager.capture_mouse() load_world(save_data.current_world, false) func save_game() -> Error: if not is_instance_valid(world): return ERR_DOES_NOT_EXIST var file_name: String = "save_data.tres" if is_instance_valid(world): save_data.current_world = ResourceUID.path_to_uid(world.scene_file_path) DirAccess.make_dir_recursive_absolute(SAVE_DIR % save_slot) return ResourceSaver.save(save_data, SAVE_PATH % [save_slot, file_name]) func load_world(world_path: String, save_previous: bool = true, load_from_save: bool = true) -> Error: if not ResourceLoader.exists(world_path, "PackedScene"): return ERR_FILE_NOT_FOUND await loading_screen.fade_in() unload_main_menu() unload_world(save_previous) var loader := InteractiveLoader.create_oneshot_and_bind(self) var loaded_scene: PackedScene = await loader.load_threaded(world_path) world = loaded_scene.instantiate() as World if not is_instance_valid(world): loading_screen.fade_out() push_error("Loaded world is not valid or not of type [World].") return ERR_INVALID_DATA if load_from_save: world.world_state = load_world_state(world_path) world.change_world_requested.connect(load_world) world.world_unload_requested.connect(_on_unload_world_request) world_holder.add_child.call_deferred(world) await world.loaded loading_screen.fade_out() return OK func unload_world(do_save: bool = true) -> void: if not is_instance_valid(world): return if do_save: save_world_state() world.process_mode = Node.PROCESS_MODE_DISABLED world.queue_free() func save_world_state() -> Error: if not is_instance_valid(world): return ERR_DOES_NOT_EXIST world.update_world_state() var world_state: WorldState = world.world_state var file_name: String = convert_path_to_resource_filename(world_state.world_path) print("File name: ", file_name) print(SAVE_PATH % [save_slot, file_name]) DirAccess.make_dir_recursive_absolute(SAVE_DIR % save_slot) save_game() return ResourceSaver.save(world_state, SAVE_PATH % [save_slot, file_name]) func load_world_state(world_path: String) -> WorldState: var file_name: String = convert_path_to_resource_filename(world_path) var file_path: String = SAVE_PATH % [save_slot, file_name] if not ResourceLoader.exists(file_path): return null return ResourceLoader.load(file_path) func load_main_menu() -> void: var loader := InteractiveLoader.create_oneshot_and_bind(self) var loaded_scene: PackedScene = await loader.load_threaded(main_menu_path) var _main_menu: MainMenu = loaded_scene.instantiate() _main_menu.load_game_request.connect(load_game) _main_menu.quit_request.connect(quit_game) main_menu = _main_menu world_holder.add_child(main_menu) func load_startup() -> void: var startup: StartupScene = load(startup_scene).instantiate() startup.startup_finished.connect(func() -> void: startup.queue_free() await loading_screen.fade_in(0.0) load_main_menu() loading_screen.fade_out() ) add_child(startup) func unload_main_menu() -> void: if is_instance_valid(main_menu): main_menu.queue_free() func quit_game() -> void: await loading_screen.fade_in() get_tree().quit() func register_commands() -> void: var load_save_func: Callable = func(save_index: int) -> void: var path: String = MainMenu.get_save_path(save_index) if ResourceLoader.exists(path): load_game(load(path), save_index) LimboConsole.register_command(load_save_func, "load_save", "Loads the save with the given save slot index.") LimboConsole.register_command(save_game, "save_game", "Saves the game based on the current save slot.") func _on_unload_world_request(do_save: bool) -> void: if do_save: save_game() if is_instance_valid(world): world.process_mode = Node.PROCESS_MODE_DISABLED await loading_screen.fade_in() unload_world(do_save) await load_main_menu() loading_screen.fade_out()