MagicNStuff/game/src/game.gd
SchimmelSpreu83 94beee112e Some script improvements, clean-up and refactoring
- Refactored VibrationComponent (RumbleComponent)
-- Now supports multiple vibrations and adjustable curves.
-- Inspired by the RumblePak addon.

- Added a new startup scene.
2026-06-22 01:52:04 +02:00

193 lines
5.5 KiB
GDScript

@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()