@ -0,0 +1,311 @@ | |||||
## Performs the managing of saves by saving, loading, and returning a list of saves[br][br] | |||||
## | |||||
## Below is a basic definition of various components and resources for reference:[br] | |||||
## * SaveLevelDataComponent - The root save component that provides the Save Settings[br] | |||||
## * SaveDataComponent - Attached to a node that has properties that need to be saved[br] | |||||
## * Node Type Resources - Each type of node that requires saving, such as a TileMapLayer, needs a resource created | |||||
extends Node | |||||
signal apply_complete | |||||
signal apply_save ## Apply the loaded data to the tree. Note: This should happen AFTER load_save() | |||||
signal autosave_complete ## Autosave has completed | |||||
signal autosave_start ## Autosave has started | |||||
signal create_autosave | |||||
signal create_save(save_name: String) | |||||
signal delete_save(filename: String) | |||||
signal disable_autosaves | |||||
signal enable_autosaves | |||||
signal error(error_message: String) | |||||
signal load_complete | |||||
signal load_save(filename: String) ## Loads the save into memory. Does NOT apply the load as this allows for other actions (such as resetting the levle/world).[br]Don't forget to run `apply_save` after loading | |||||
signal save_complete | |||||
signal start_autosave | |||||
signal stop_autosave | |||||
signal quick_save | |||||
signal quick_load | |||||
signal toggle_save_icon_generation(toggled: bool) ## Enable/Disable the generation of a screenshot during save for the save icon. | |||||
var _autosaves_enabled: bool = true | |||||
var _autosave_timer: Timer = Timer.new() | |||||
var _enable_save_icon_generation: bool = true | |||||
var _loaded_save_resource: SaveGameDataResource = SaveGameDataResource.new() | |||||
var _save_game_settings: SaveGameSettings ## Contains the save paths, filename prepends, and other save settings | |||||
var _save_icon_size: Vector2i = Vector2i(896, 504) ## If Vector2.ZERO, uses the user's resolution | |||||
func _ready() -> void: | |||||
apply_save.connect(_on_apply_save) | |||||
create_save.connect(_on_save_game_as_resource) | |||||
delete_save.connect(_on_delete_save) | |||||
disable_autosaves.connect(_on_disable_autosaves) | |||||
enable_autosaves.connect(_on_enable_autosaves) | |||||
load_save.connect(_on_load_game_save) | |||||
quick_load.connect(_on_quick_load) | |||||
quick_save.connect(_on_quick_save) | |||||
start_autosave.connect(_on_start_autosave) | |||||
stop_autosave.connect(_on_stop_autosave) | |||||
toggle_save_icon_generation.connect(_on_toggle_save_icon_generation) | |||||
_autosave_timer.name = "AutosaveTimer" | |||||
_autosave_timer.one_shot = false | |||||
_autosave_timer.timeout.connect(_on_autosave_timer_timeout) | |||||
add_child(_autosave_timer) | |||||
func _unhandled_input(event: InputEvent) -> void: | |||||
if event.is_action_pressed("quick_save"): | |||||
quick_save.emit() | |||||
if event.is_action_pressed("quick_load"): | |||||
quick_load.emit() | |||||
func list_saves(include_quick_saves: bool = true, include_autosaves: bool = true) -> Array[SaveFileDetailsResource]: | |||||
var save_files: Array[SaveFileDetailsResource] = [] | |||||
if not _load_save_settings(): | |||||
return save_files | |||||
var save_game_data_path: String = _save_game_settings.save_game_data_path | |||||
if !DirAccess.dir_exists_absolute(save_game_data_path): | |||||
return save_files | |||||
for filename: String in ResourceLoader.list_directory(save_game_data_path): | |||||
# TODO: Rework so the settings determine the file_name using prepends | |||||
if filename.begins_with(_save_game_settings.quicksave_file_name_prepend) and not include_quick_saves: | |||||
continue | |||||
elif filename.begins_with(_save_game_settings.autosave_file_name_prepend) and not include_autosaves: | |||||
continue | |||||
elif !filename.begins_with(_save_game_settings.save_file_name_prepend) and !filename.begins_with(_save_game_settings.quicksave_file_name_prepend) and !filename.begins_with(_save_game_settings.autosave_file_name_prepend): | |||||
continue | |||||
var _save_path: String = save_game_data_path + filename | |||||
var _save_icon: String = filename.replace(".tres", ".png") | |||||
var _save_resource: SaveFileDetailsResource = SaveFileDetailsResource.new() | |||||
# TODO: Reconsider loading the save to get the name | |||||
# This could be a large save making opening each one slow | |||||
# Should work out a better method for getting the save name (filename != save name) | |||||
var _save_file: SaveGameDataResource = ResourceLoader.load(_save_path) | |||||
_save_resource.save_name = _save_file.save_name | |||||
_save_resource.save_date = _save_file.save_date | |||||
_save_resource.filename = filename | |||||
## In 4.5, this can probably be replaced with FileAccess.get_size(_save_path) | |||||
## This should reduce the need for opening the file here | |||||
var _loaded_file: FileAccess = FileAccess.open(_save_path, FileAccess.READ) | |||||
_save_resource.filesize = _loaded_file.get_length() | |||||
_save_resource.save_icon_texture = _generate_save_icon_texture(save_game_data_path + _save_icon) | |||||
save_files.append(_save_resource) | |||||
save_files.sort_custom(_custom_save_file_sort) | |||||
return save_files | |||||
func _create_autosave() -> void: | |||||
if not _load_save_settings(): return | |||||
_autosave_timer.stop() | |||||
autosave_start.emit() | |||||
_rotate_autosaves() | |||||
var autosave_filename: String = _save_game_settings.autosave_file_name_prepend + "01.tres" | |||||
_save_game_as_resource("Auto Save", autosave_filename) | |||||
autosave_complete.emit() | |||||
_autosave_timer.start(_save_game_settings.autosave_duration) | |||||
## Sort the save files list by date created, descending | |||||
func _custom_save_file_sort(a: SaveFileDetailsResource, b: SaveFileDetailsResource) -> bool: | |||||
return a.save_date > b.save_date | |||||
## Save the properties defined on the SaveDataComponents attached to various nodes (such as Block) | |||||
func _generate_save_game_resource() -> SaveGameDataResource: | |||||
var nodes: Array = get_tree().get_nodes_in_group("save_data_component") | |||||
if nodes == null: return | |||||
var _resource: SaveGameDataResource = SaveGameDataResource.new() | |||||
for node: Node in nodes: | |||||
if node is SaveDataComponent: | |||||
@warning_ignore("unsafe_method_access") | |||||
var save_data_resource: NodeDataResource = node._save_data() | |||||
var save_final_resource: NodeDataResource = save_data_resource.duplicate() | |||||
_resource.save_data_nodes.append(save_final_resource) | |||||
return _resource | |||||
## Generate the texture for use in the save file listing | |||||
func _generate_save_icon_texture(save_icon: String) -> Texture2D: | |||||
var _icon_texture: Texture2D = ImageTexture.new() | |||||
if save_icon != null and !FileAccess.file_exists(save_icon): | |||||
_icon_texture = _save_game_settings.default_save_icon_resource | |||||
elif save_icon == null: | |||||
_icon_texture = _save_game_settings.default_save_icon_resource | |||||
else: | |||||
var _icon_image: Image = Image.new() | |||||
_icon_image.load(save_icon) | |||||
@warning_ignore("unsafe_method_access") | |||||
_icon_texture.set_image(_icon_image) | |||||
return _icon_texture | |||||
func _load_game_resource(resource_filename: String) -> void: | |||||
if not _load_save_settings(): return | |||||
var save_game_file_path: String = _save_game_settings.save_game_data_path + resource_filename | |||||
if !FileAccess.file_exists(save_game_file_path): | |||||
error.emit("Failed to load save. File does not exist: %s" % save_game_file_path) | |||||
return | |||||
_loaded_save_resource = ResourceLoader.load(save_game_file_path) | |||||
if _loaded_save_resource == null: | |||||
error.emit("Failed to load save. Unknown format? %s" % save_game_file_path) | |||||
return | |||||
load_complete.emit() | |||||
## Find the SaveLevelDataComponent within the tree which stores the save settings | |||||
func _load_save_settings() -> bool: | |||||
var _save_level_data_component: SaveLevelDataComponent = get_tree().get_first_node_in_group("save_level_data_component") | |||||
if _save_level_data_component == null: | |||||
error.emit("Failed to Save Settings. Could not find SaveLevelDataComponent node in tree") | |||||
return false | |||||
_save_game_settings = _save_level_data_component.settings | |||||
return true | |||||
## Increment autosave numbers and if we've reach max number of autosaves, remove the oldest one | |||||
func _rotate_autosaves() -> void: | |||||
var saves_dir = DirAccess.open(_save_game_settings.save_game_data_path) | |||||
if saves_dir == null: | |||||
DirAccess.make_dir_absolute(_save_game_settings.save_game_data_path) | |||||
saves_dir = DirAccess.open(_save_game_settings.save_game_data_path) | |||||
var autosaves: Array[String] = [] | |||||
for filename in saves_dir.get_files(): | |||||
if !filename.begins_with(_save_game_settings.autosave_file_name_prepend): continue | |||||
if !filename.ends_with(".tres"): continue | |||||
autosaves.append(filename) | |||||
if autosaves.size() == _save_game_settings.max_autosaves: # Delete oldest save | |||||
DirAccess.remove_absolute(_save_game_settings.save_game_data_path + autosaves.pop_back()) | |||||
var filepath: String = _save_game_settings.save_game_data_path + _save_game_settings.autosave_file_name_prepend | |||||
for index in range(autosaves.size(), 0, -1): | |||||
var old_save_path: String = "%s%02d.tres" % [filepath, index] | |||||
var old_screenshot_path: String = "%s%02d.png" % [filepath, index] | |||||
var new_save_path: String = "%s%02d.tres" % [filepath, index + 1] | |||||
var new_screenshot_path: String = "%s%02d.png" % [filepath, index + 1] | |||||
DirAccess.copy_absolute(old_save_path, new_save_path) | |||||
if FileAccess.file_exists(old_screenshot_path): | |||||
DirAccess.copy_absolute(old_screenshot_path, new_screenshot_path) | |||||
func _save_game_as_resource(save_name, resource_filename: String) -> void: | |||||
if not _load_save_settings(): return | |||||
if !DirAccess.dir_exists_absolute(_save_game_settings.save_game_data_path): | |||||
DirAccess.make_dir_absolute(_save_game_settings.save_game_data_path) | |||||
var save_game_file_path: String = _save_game_settings.save_game_data_path + resource_filename | |||||
var _save_resource: SaveGameDataResource = _generate_save_game_resource() | |||||
_save_resource.save_name = save_name | |||||
_save_resource.save_date = Time.get_datetime_string_from_system(false, true) | |||||
var result: int = ResourceSaver.save(_save_resource, save_game_file_path) | |||||
if result != OK: | |||||
error.emit("Failed to save game (" , result, "): " + save_game_file_path) | |||||
return | |||||
_take_save_screenshot(save_game_file_path) | |||||
save_complete.emit() | |||||
## Takes a screenshot and saves next to the save file[br] | |||||
## The icon utilizes the same filename as the save file, replacing `.tres` with `.png` | |||||
func _take_save_screenshot(save_game_file_path: String) -> void: | |||||
if !_enable_save_icon_generation: return | |||||
var _icon_filepath: String = save_game_file_path.replace(".tres", ".png") | |||||
var _icon: Image = get_viewport().get_texture().get_image() | |||||
if _save_icon_size != Vector2i.ZERO: | |||||
_icon.resize(_save_icon_size.x, _save_icon_size.y) | |||||
_icon.save_png(_icon_filepath) | |||||
## Apply the loaded save. Should be performed after load_save | |||||
func _on_apply_save() -> void: | |||||
if _loaded_save_resource == null: return | |||||
var root_node: Window = get_tree().root | |||||
for resource: Resource in _loaded_save_resource.save_data_nodes: | |||||
if resource is NodeDataResource: | |||||
(resource as NodeDataResource)._load_data(root_node) | |||||
apply_complete.emit() | |||||
func _on_autosave_timer_timeout() -> void: | |||||
if not _autosaves_enabled: return | |||||
_create_autosave() | |||||
## Delete both the save file and the related screenshot | |||||
func _on_delete_save(filename: String) -> void: | |||||
if filename.length() < 1: | |||||
error.emit("Failed to delete save. Empty filename provided.") | |||||
return | |||||
var save_file_path: String = _save_game_settings.save_game_data_path + filename | |||||
DirAccess.remove_absolute(save_file_path) | |||||
DirAccess.remove_absolute(save_file_path.replace(".tres", ".png")) # Delete icon | |||||
func _on_disable_autosaves() -> void: | |||||
_autosaves_enabled = false | |||||
stop_autosave.emit() | |||||
func _on_enable_autosaves() -> void: | |||||
_autosaves_enabled = true | |||||
func _on_load_game_save(resource_filename: String) -> void: | |||||
_load_game_resource(resource_filename) | |||||
func _on_quick_load() -> void: | |||||
if not _load_save_settings(): return | |||||
_load_game_resource(_save_game_settings.quicksave_file_name_prepend + "game_data.tres") | |||||
func _on_quick_save() -> void: | |||||
if not _load_save_settings(): return | |||||
_save_game_as_resource("Quick Save", _save_game_settings.quicksave_file_name_prepend + "game_data.tres") | |||||
## Save the game, with a filename of `<save_prepend><current_date>.tres | |||||
func _on_save_game_as_resource(save_name: String) -> void: | |||||
if not _load_save_settings(): return | |||||
var current_date: String = Time.get_datetime_string_from_system().replace(":", "") | |||||
var _filename: String = _save_game_settings.save_file_name_prepend + current_date + ".tres" | |||||
_loaded_save_resource.save_name = save_name | |||||
_save_game_as_resource(save_name, _filename) | |||||
save_complete.emit() | |||||
func _on_start_autosave() -> void: | |||||
if not _autosaves_enabled: return | |||||
if _save_game_settings == null: | |||||
_load_save_settings() | |||||
_autosave_timer.start(_save_game_settings.autosave_duration) | |||||
func _on_stop_autosave() -> void: | |||||
_autosave_timer.stop() | |||||
func _on_toggle_save_icon_generation(toggled: bool) -> void: | |||||
_enable_save_icon_generation = toggled |
@ -1,6 +1,6 @@ | |||||
[gd_scene load_steps=2 format=3 uid="uid://baki8rbf1ti0r"] | [gd_scene load_steps=2 format=3 uid="uid://baki8rbf1ti0r"] | ||||
[ext_resource type="Script" uid="uid://b0400fjcqflgh" path="res://save_load/components/save_data_component.gd" id="1_nm1vf"] | |||||
[ext_resource type="Script" uid="uid://b0400fjcqflgh" path="res://addons/save_load_system/components/save_data_component.gd" id="1_nm1vf"] | |||||
[node name="SaveDataComponent" type="Node"] | [node name="SaveDataComponent" type="Node"] | ||||
script = ExtResource("1_nm1vf") | script = ExtResource("1_nm1vf") |
@ -0,0 +1,11 @@ | |||||
## Provides an easy reference for save settings through an export rather than code | |||||
## Should be attached to the main world/level scene | |||||
class_name SaveLevelDataComponent | |||||
extends Node | |||||
@export var settings: SaveGameSettings ## The SaveGameSettings resource | |||||
func _ready() -> void: | |||||
add_to_group("save_level_data_component") |
@ -0,0 +1,8 @@ | |||||
[gd_scene load_steps=3 format=3 uid="uid://c3pqilb6yh5kc"] | |||||
[ext_resource type="Script" uid="uid://c7x2qvyu62230" path="res://addons/save_load_system/components/save_level_data_component.gd" id="1_exguq"] | |||||
[ext_resource type="Resource" uid="uid://o32fooj1lxg7" path="res://addons/save_load_system/resources/save_game_settings_resource.tres" id="2_rkr1f"] | |||||
[node name="SaveLevelDataComponent" type="Node"] | |||||
script = ExtResource("1_exguq") | |||||
settings = ExtResource("2_rkr1f") |
@ -0,0 +1,37 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://b4lam0dwtv8fq" | |||||
path="res://.godot/imported/default_icon.svg-8f0c0a4577d41c2e54da72bcdea5ad34.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://addons/save_load_system/default_icon.svg" | |||||
dest_files=["res://.godot/imported/default_icon.svg-8f0c0a4577d41c2e54da72bcdea5ad34.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 | |||||
svg/scale=1.0 | |||||
editor/scale_with_editor_scale=false | |||||
editor/convert_colors_with_editor_theme=false |
@ -0,0 +1,7 @@ | |||||
[plugin] | |||||
name="Save Load System" | |||||
description="An overengineered Save/Load system" | |||||
author="Ryan Reed" | |||||
version="1.0" | |||||
script="plugin.gd" |
@ -0,0 +1,17 @@ | |||||
@tool | |||||
extends EditorPlugin | |||||
func _enter_tree() -> void: | |||||
pass | |||||
func _exit_tree() -> void: | |||||
# Clean-up of the plugin goes here. | |||||
pass | |||||
func _enable_plugin() -> void: | |||||
add_autoload_singleton("SaveGameManager", "res://addons/save_load_system/autoloads/save_game_manager.gd") | |||||
func _disable_plugin() -> void: | |||||
remove_autoload_singleton("SaveGameManager") |
@ -0,0 +1 @@ | |||||
uid://bj77sfap4rqke |
@ -1,6 +1,6 @@ | |||||
## The base resource for saving a specific Node | ## The base resource for saving a specific Node | ||||
class_name Node3DDataResource | class_name Node3DDataResource | ||||
extends Resource | |||||
extends NodeDataResource | |||||
@export var transform: Transform3D | @export var transform: Transform3D |
@ -1,6 +1,6 @@ | |||||
[gd_resource type="Resource" script_class="NodeDataResource" load_steps=2 format=3 uid="uid://dald1lud7ktsj"] | [gd_resource type="Resource" script_class="NodeDataResource" load_steps=2 format=3 uid="uid://dald1lud7ktsj"] | ||||
[ext_resource type="Script" uid="uid://drj0sfem1gmsk" path="res://save_load/resources/node_types/node3d_data_resource.gd" id="1_b70a7"] | |||||
[ext_resource type="Script" uid="uid://drj0sfem1gmsk" path="res://addons/save_load_system/resources/node_types/node3d_data_resource.gd" id="1_b70a7"] | |||||
[resource] | [resource] | ||||
script = ExtResource("1_b70a7") | script = ExtResource("1_b70a7") |
@ -0,0 +1,10 @@ | |||||
## The base resource for saving a specific Node | |||||
class_name NodeDataResource | |||||
extends Resource | |||||
func _save_data(node: Node3D) -> void: | |||||
pass | |||||
func _load_data(_window: Window) -> void: | |||||
pass |
@ -0,0 +1 @@ | |||||
uid://ku7pqlxdta2r |
@ -0,0 +1,7 @@ | |||||
[gd_resource type="Resource" script_class="NodeDataResource" load_steps=2 format=3 uid="uid://btcyin8cer74n"] | |||||
[ext_resource type="Script" uid="uid://ku7pqlxdta2r" path="res://addons/save_load_system/resources/node_types/node_data_resource.gd" id="1_0d08a"] | |||||
[resource] | |||||
script = ExtResource("1_0d08a") | |||||
metadata/_custom_type_script = "uid://ku7pqlxdta2r" |
@ -0,0 +1,11 @@ | |||||
## Details related to a filesave | |||||
## Used for displaying save file details in the UI | |||||
class_name SaveFileDetailsResource | |||||
extends Node | |||||
@export var save_name: String = "My Save" | |||||
@export var filename: String = "" | |||||
@export var save_date: String = "" | |||||
@export var filesize: int = 0 | |||||
@export var save_icon_texture: Texture2D |
@ -0,0 +1 @@ | |||||
uid://hj7t2segibta |
@ -1,7 +1,7 @@ | |||||
[gd_resource type="Resource" script_class="SaveGameDataResource" load_steps=3 format=3 uid="uid://dkniygoky2jcx"] | [gd_resource type="Resource" script_class="SaveGameDataResource" load_steps=3 format=3 uid="uid://dkniygoky2jcx"] | ||||
[ext_resource type="Script" uid="uid://drj0sfem1gmsk" path="res://save_load/resources/node_types/node3d_data_resource.gd" id="1_7yx7n"] | |||||
[ext_resource type="Script" uid="uid://di6ov7tpewhft" path="res://save_load/resources/save_game_data_resource.gd" id="1_sbw5t"] | |||||
[ext_resource type="Script" uid="uid://drj0sfem1gmsk" path="res://addons/save_load_system/resources/node_types/node3d_data_resource.gd" id="1_7yx7n"] | |||||
[ext_resource type="Script" uid="uid://di6ov7tpewhft" path="res://addons/save_load_system/resources/save_game_data_resource.gd" id="1_sbw5t"] | |||||
[resource] | [resource] | ||||
script = ExtResource("1_sbw5t") | script = ExtResource("1_sbw5t") |
@ -0,0 +1,19 @@ | |||||
class_name SaveGameSettings | |||||
extends Resource | |||||
## See documentation to where this path is: https://docs.godotengine.org/en/stable/tutorials/io/data_paths.html#accessing-persistent-user-data-user[br][br] | |||||
## Default Paths:[br] | |||||
## * Windows: %APPDATA%\Godot\app_userdata\[project_name][br] | |||||
## * macOS: ~/Library/Application Support/Godot/app_userdata/[project_name][br] | |||||
## * Linux: ~/.local/share/godot/app_userdata/[project_name][br] | |||||
@export var save_game_data_path: String = "user://game_data/" | |||||
@export var max_autosaves: int = 5 | |||||
@export var autosave_duration: int = 60 ## How often, in seconds, should an autosave run[br]SaveGameManager will NOT autostart the autosave timer. This must be done by the world/scene. | |||||
@export var save_file_name_prepend: String = "save_" | |||||
@export var quicksave_file_name_prepend: String = "quicksave_" | |||||
@export var autosave_file_name_prepend: String = "autosave_" | |||||
@export var default_save_icon_resource: CompressedTexture2D |
@ -0,0 +1 @@ | |||||
uid://d0iptf06t7f47 |
@ -0,0 +1,15 @@ | |||||
[gd_resource type="Resource" script_class="SaveGameSettings" load_steps=3 format=3 uid="uid://o32fooj1lxg7"] | |||||
[ext_resource type="Texture2D" uid="uid://b4lam0dwtv8fq" path="res://addons/save_load_system/default_icon.svg" id="1_fm0fk"] | |||||
[ext_resource type="Script" uid="uid://d0iptf06t7f47" path="res://addons/save_load_system/resources/save_game_settings_resource.gd" id="1_o1tpj"] | |||||
[resource] | |||||
script = ExtResource("1_o1tpj") | |||||
save_game_data_path = "user://game_data/" | |||||
autosave_duration = 60 | |||||
max_autosaves = 5 | |||||
save_file_name_prepend = "save_" | |||||
quicksave_file_name_prepend = "quicksave_" | |||||
autosave_file_name_prepend = "autosave_" | |||||
default_save_icon_resource = ExtResource("1_fm0fk") | |||||
metadata/_custom_type_script = "uid://d0iptf06t7f47" |
@ -1,6 +1,6 @@ | |||||
[gd_resource type="Resource" script_class="SceneDataResource" load_steps=2 format=3 uid="uid://duqhhi2nlic6n"] | [gd_resource type="Resource" script_class="SceneDataResource" load_steps=2 format=3 uid="uid://duqhhi2nlic6n"] | ||||
[ext_resource type="Script" uid="uid://bxr74kmjt6heh" path="res://save_load/resources/scene_data_resource.gd" id="1_ihw72"] | |||||
[ext_resource type="Script" uid="uid://bxr74kmjt6heh" path="res://addons/save_load_system/resources/scene_data_resource.gd" id="1_ihw72"] | |||||
[resource] | [resource] | ||||
script = ExtResource("1_ihw72") | script = ExtResource("1_ihw72") |
@ -0,0 +1 @@ | |||||
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(32 32)"><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99z" fill="#363d52"/><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99zm0 4h96c6.64 0 12 5.35 12 11.99v95.98c0 6.64-5.35 11.99-12 11.99h-96c-6.64 0-12-5.35-12-11.99v-95.98c0-6.64 5.36-11.99 12-11.99z" fill-opacity=".4"/></g><g stroke-width="9.92746" transform="matrix(.10073078 0 0 .10073078 12.425923 2.256365)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 919.24059 771.67186)"/><path d="m0 0v-47.514-6.035-5.492c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325c5.09692 6.4164715 9.92323 13.494208 13.621 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.4426579-6.973692 9.2079702-13.9828876 13.621-19.449z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 104.69892 525.90697)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.066.067c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 784.07144 817.24284)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 389.21484 625.67104)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 367.36686 631.05679)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 511.99336 724.73954)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 634.78706 625.67104)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 656.64056 631.05679)"/></g></svg> |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://f6hee5t26uqt" | |||||
path="res://.godot/imported/skyblock-logo.png-10c2483abdf724a98544057e7ddffd26.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/skyblock-logo.png" | |||||
dest_files=["res://.godot/imported/skyblock-logo.png-10c2483abdf724a98544057e7ddffd26.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://by4w5ll3le7g6" | |||||
path="res://.godot/imported/folder-open-hover.png-30b298f03eda987ff7b4c29cb81e0196.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/folder-open-hover.png" | |||||
dest_files=["res://.godot/imported/folder-open-hover.png-30b298f03eda987ff7b4c29cb81e0196.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://cmq51cgasug81" | |||||
path="res://.godot/imported/folder-open-normal.png-f3b3efc8925c789ef79f8d8da6495fd6.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/folder-open-normal.png" | |||||
dest_files=["res://.godot/imported/folder-open-normal.png-f3b3efc8925c789ef79f8d8da6495fd6.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://blyryo60jydgi" | |||||
path="res://.godot/imported/folder-open-pressed.png-96c8378f366a7c3f00bd052101358d2d.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/folder-open-pressed.png" | |||||
dest_files=["res://.godot/imported/folder-open-pressed.png-96c8378f366a7c3f00bd052101358d2d.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://o3l0j53mgkan" | |||||
path="res://.godot/imported/save-hover.png-7fdcaa20e46d61abf1ad21c191339e15.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/save-hover.png" | |||||
dest_files=["res://.godot/imported/save-hover.png-7fdcaa20e46d61abf1ad21c191339e15.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://ja8bc1h5x85o" | |||||
path="res://.godot/imported/save-normal.png-52807bf4c046513286efdc3c8e34024f.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/save-normal.png" | |||||
dest_files=["res://.godot/imported/save-normal.png-52807bf4c046513286efdc3c8e34024f.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://crqgyft4gfilt" | |||||
path="res://.godot/imported/save-pressed.png-2e1c957e7e0122a7212e624479432e6e.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/save-pressed.png" | |||||
dest_files=["res://.godot/imported/save-pressed.png-2e1c957e7e0122a7212e624479432e6e.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://bsfqsslfq53dn" | |||||
path="res://.godot/imported/spinner-clockwise.png-bbb06e749ee5040cd72cec4de0d564b2.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/spinner-clockwise.png" | |||||
dest_files=["res://.godot/imported/spinner-clockwise.png-bbb06e749ee5040cd72cec4de0d564b2.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://cmrtuy0i5qc01" | |||||
path="res://.godot/imported/trash-hover.png-c251e165aefa0e6e3b0d1056b13c4069.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/trash-hover.png" | |||||
dest_files=["res://.godot/imported/trash-hover.png-c251e165aefa0e6e3b0d1056b13c4069.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://dvp5yeoqw36yt" | |||||
path="res://.godot/imported/trash-normal.png-1b0b4c772e81c3db536c4de9f47346e0.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/trash-normal.png" | |||||
dest_files=["res://.godot/imported/trash-normal.png-1b0b4c772e81c3db536c4de9f47346e0.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -0,0 +1,34 @@ | |||||
[remap] | |||||
importer="texture" | |||||
type="CompressedTexture2D" | |||||
uid="uid://brwa8yljyrlgy" | |||||
path="res://.godot/imported/trash-pressed.png-55019366421198956cc6c4016ef656ac.ctex" | |||||
metadata={ | |||||
"vram_texture": false | |||||
} | |||||
[deps] | |||||
source_file="res://assets/ui/trash-pressed.png" | |||||
dest_files=["res://.godot/imported/trash-pressed.png-55019366421198956cc6c4016ef656ac.ctex"] | |||||
[params] | |||||
compress/mode=0 | |||||
compress/high_quality=false | |||||
compress/lossy_quality=0.7 | |||||
compress/hdr_compression=1 | |||||
compress/normal_map=0 | |||||
compress/channel_pack=0 | |||||
mipmaps/generate=false | |||||
mipmaps/limit=-1 | |||||
roughness/mode=0 | |||||
roughness/src_normal="" | |||||
process/fix_alpha_border=true | |||||
process/premult_alpha=false | |||||
process/normal_map_invert_y=false | |||||
process/hdr_as_srgb=false | |||||
process/hdr_clamp_exposure=false | |||||
process/size_limit=0 | |||||
detect_3d/compress_to=1 |
@ -1,9 +0,0 @@ | |||||
extends Node | |||||
const BLOCK_PREFAB: PackedScene = preload("res://scenes/blocks/block.tscn") | |||||
const DROPPED_BLOCK_PREFAB: PackedScene = preload("res://scenes/blocks/dropped_block.tscn") | |||||
# TODO: Move the following into the GameSettingsManager | |||||
var enable_waila: bool = true ## Enable `What Am I Looking At` UI | |||||
var enable_block_highlight: bool = true |
@ -1 +0,0 @@ | |||||
uid://cp6sum1t6el0a |
@ -1,20 +1,197 @@ | |||||
extends Node | extends Node | ||||
signal item_dropped(item: DBItemResource) | |||||
signal item_picked_up(item: DBItemResource) | |||||
#region Inventory Specific | |||||
signal add_to_inventory(item_id: String, amount: int) | |||||
signal clear_inventory ## Remove all items in inventory | |||||
signal item_added(item_id: String, amount: int) | |||||
signal item_removed(item_id: String, amount: int) | |||||
signal inventory_closed | |||||
signal inventory_opened | |||||
signal inventory_slot_updated(slot_index: int) | |||||
signal remove_from_inventory(item_id: String, amount: int) | |||||
signal remove_from_quickslot(amount: int) | |||||
signal remove_from_slot(slot_index: int, amount: int) | |||||
#endregion | |||||
#region Quickslots | |||||
signal next_quick_slot | signal next_quick_slot | ||||
signal previous_quick_slot | signal previous_quick_slot | ||||
signal quick_slot_selected(slot_index: int) | |||||
signal select_quick_slot(slot_index: int) | signal select_quick_slot(slot_index: int) | ||||
signal quick_slot_item_changed(item_id: String) | |||||
signal item_picked_up(item: DBItemResource) | |||||
signal item_dropped(item: DBItemResource) | |||||
signal inventory_opened | |||||
signal inventory_closed | |||||
#endregion | |||||
var quick_slot_item_id: String = "001" | |||||
var max_inventory_items: int = 40 # 4 rows of 10 | |||||
var quick_slot_count: int = 10 | |||||
var selected_quick_slot: int = 0 | |||||
var inventory: Array[DBItemResource] = [] ## To ensure inventory is automatically sorted, "empty" inventory cells will be replaced with null to keep positions | |||||
var _inventory_cache: Dictionary[String, Dictionary] = {} ## Used for caching certain information | |||||
func _ready() -> void: | func _ready() -> void: | ||||
self.quick_slot_item_changed.connect(_on_quick_slot_item_changed) | |||||
self.item_picked_up.connect(_on_item_picked_up) | |||||
self.item_dropped.connect(_on_item_dropped) | |||||
self.add_to_inventory.connect(_on_add_to_inventory) | |||||
self.clear_inventory.connect(_on_clear_inventory) | |||||
self.quick_slot_selected.connect(_on_quick_slot_selected) | |||||
self.remove_from_inventory.connect(_on_remove_from_inventory) | |||||
self.remove_from_quickslot.connect(_on_remove_from_quickslot) | |||||
self.remove_from_slot.connect(_on_remove_from_slot) | |||||
func available_space(item_id: String) -> int: | |||||
var full_stacks: int = floor(_inventory_cache[item_id].total / DBItems.data[item_id].max_stack_size) | |||||
var space_in_stacks: int = ( | |||||
DBItems.data[item_id].max_stack_size - abs( | |||||
_inventory_cache[item_id].total - (DBItems.data[item_id].max_stack_size * full_stacks) | |||||
) | |||||
) | |||||
# This is a bit of a hack because I'm a math/logic noob | |||||
if space_in_stacks == DBItems.data[item_id].max_stack_size: | |||||
space_in_stacks = 0 | |||||
var open_stack_amount: int = (max_inventory_items - inventory.size()) * DBItems.data[item_id].max_stack_size | |||||
return open_stack_amount + space_in_stacks | |||||
func get_inventory_item(item_slot: int = selected_quick_slot) -> DBItemResource: | |||||
if item_slot >= inventory.size() or item_slot < 0: | |||||
return null | |||||
return inventory.get(item_slot) | |||||
func get_quick_slot_item_id(item_slot: int = selected_quick_slot) -> String: | |||||
return inventory[item_slot].id | |||||
func _find_stacks_by_id(item_resource: DBItemResource, item_id: String) -> bool: | |||||
return item_resource != null and item_resource.id == item_id | |||||
## Find the stack where at least one item can be added | |||||
func _find_stacks_with_space(item_resource: DBItemResource, item_id: String) -> bool: | |||||
return ( | |||||
item_resource == null or ( | |||||
item_resource.id == item_id and | |||||
item_resource.amount < item_resource.max_stack_size | |||||
) | |||||
) | |||||
## Removes an amount of items from a specific slot | |||||
## If the amount exceeds the amount of the slot, will NOT remove from other stacks | |||||
func _remove_from_slot(slot_index: int, amount: int) -> void: | |||||
if slot_index >= max_inventory_items: | |||||
printerr("Slot Index ", slot_index, " out of inventory range") | |||||
return | |||||
inventory[slot_index].amount -= amount | |||||
if inventory[slot_index].amount <= 0: | |||||
inventory[slot_index] = null | |||||
inventory_slot_updated.emit(slot_index) | |||||
func _update_cache_total(item_id: String, amount: int) -> void: | |||||
if not _inventory_cache.get(item_id): | |||||
_inventory_cache[item_id] = {"total": 0} | |||||
_inventory_cache[item_id].total += amount | |||||
func _on_add_to_inventory(item_id: String, amount: int = 1) -> void: | |||||
if not DBItems.data.get(item_id): | |||||
printerr("Cannot add item, ", item_id, ", to inventory. Could not find item within DBItems.data.") | |||||
return | |||||
if amount < 1: | |||||
printerr("Cannot add item, ", item_id, ", to inventory. Amount to add cannot be less then 1.") | |||||
return | |||||
var item_resource: DBItemResource = DBItems.data[item_id] | |||||
# This logic seems overly complicated and is a mess. | |||||
# Should look into fixing/simplifying this in the future | |||||
var amount_remaining: int = amount | |||||
while amount_remaining > 0: | |||||
var first_stack_index: int = inventory.find_custom(_find_stacks_with_space.bind(item_id)) | |||||
var stack_resource: DBItemResource | |||||
if first_stack_index == -1 and inventory.size() < max_inventory_items: # No existing stack and empty slots available | |||||
stack_resource = item_resource.duplicate() | |||||
inventory.append(stack_resource) | |||||
if amount_remaining <= stack_resource.max_stack_size: | |||||
_update_cache_total(item_id, amount_remaining) | |||||
inventory[-1].amount += amount_remaining | |||||
amount_remaining = 0 | |||||
else: | |||||
_update_cache_total(item_id, stack_resource.max_stack_size) | |||||
inventory[-1].amount = stack_resource.max_stack_size | |||||
amount_remaining -= stack_resource.max_stack_size | |||||
inventory_slot_updated.emit(inventory.size() - 1) | |||||
elif first_stack_index > -1: | |||||
if inventory[first_stack_index] == null: # Empty slot | |||||
inventory[first_stack_index] = item_resource.duplicate() | |||||
stack_resource = inventory[first_stack_index] | |||||
var current_amount: int = stack_resource.amount | |||||
var total_amount: int = current_amount + amount_remaining | |||||
if total_amount < stack_resource.max_stack_size: # Stack not full and has space | |||||
_update_cache_total(item_id, amount_remaining) | |||||
inventory[first_stack_index].amount = total_amount | |||||
amount_remaining = 0 | |||||
else: | |||||
_update_cache_total(item_id, stack_resource.max_stack_size - current_amount) | |||||
inventory[first_stack_index].amount = stack_resource.max_stack_size | |||||
amount_remaining -= stack_resource.max_stack_size | |||||
inventory_slot_updated.emit(first_stack_index) | |||||
item_added.emit(item_id, amount - amount_remaining) | |||||
func _on_remove_from_inventory(item_id: String, amount: int = 1) -> void: | |||||
var amount_remaining: int = amount | |||||
while amount_remaining > 0: | |||||
var last_stack_index: int = inventory.rfind_custom(_find_stacks_by_id.bind(item_id)) | |||||
if last_stack_index > -1: | |||||
var stack_resource: DBItemResource = inventory[last_stack_index] | |||||
var current_stack_amount: int = stack_resource.amount | |||||
var total_amount: int = current_stack_amount - amount_remaining | |||||
if total_amount > 0: # Stack will not be empty after removing | |||||
_update_cache_total(item_id, -amount_remaining) | |||||
inventory[last_stack_index].amount -= amount_remaining | |||||
amount_remaining = 0 | |||||
else: | |||||
var to_remove: int = stack_resource.max_stack_size - current_stack_amount | |||||
_update_cache_total(item_id, -amount_remaining) | |||||
inventory[last_stack_index] = null | |||||
amount_remaining -= to_remove | |||||
inventory_slot_updated.emit(last_stack_index) | |||||
else: # Received more to remove than we have stacks for | |||||
amount_remaining = 0 | |||||
item_removed.emit(item_id, amount - amount_remaining) | |||||
func _on_clear_inventory() -> void: | |||||
inventory.clear() | |||||
_inventory_cache.clear() | |||||
func _on_item_dropped(item: DBItemResource) -> void: | |||||
_on_remove_from_inventory(item.id, 1) | |||||
func _on_item_picked_up(item: DBItemResource) -> void: | |||||
_on_add_to_inventory(item.id, 1) | |||||
func _on_quick_slot_selected(slot_index: int) -> void: | |||||
selected_quick_slot = slot_index | |||||
func _on_remove_from_slot(slot_index: int, amount: int) -> void: | |||||
_remove_from_slot(slot_index, amount) | |||||
func _on_quick_slot_item_changed(item_id: String) -> void: | |||||
quick_slot_item_id = item_id | |||||
func _on_remove_from_quickslot(amount: int) -> void: | |||||
_remove_from_slot(selected_quick_slot, amount) |
@ -0,0 +1,5 @@ | |||||
class_name ItemResource | |||||
extends DBItemResource | |||||
@export var resource_scene: PackedScene = null |
@ -0,0 +1 @@ | |||||
uid://m32ytcig5ha5 |
@ -0,0 +1,14 @@ | |||||
[gd_resource type="Resource" script_class="ItemResource" load_steps=3 format=3 uid="uid://dqkdgxdjb8sk5"] | |||||
[ext_resource type="PackedScene" uid="uid://ccky0w7brcf1l" path="res://scenes/items/torch.tscn" id="1_7h82o"] | |||||
[ext_resource type="Script" uid="uid://m32ytcig5ha5" path="res://resources/item_resource.gd" id="2_e6rfx"] | |||||
[resource] | |||||
script = ExtResource("2_e6rfx") | |||||
resource_scene = ExtResource("1_7h82o") | |||||
id = "007" | |||||
name = "Torch" | |||||
amount = 1 | |||||
description = "A torch to light the way" | |||||
item_texture = "uid://dknv7amroftm8" | |||||
metadata/_custom_type_script = "uid://m32ytcig5ha5" |
@ -0,0 +1,4 @@ | |||||
[gd_resource type="StyleBoxFlat" format=3 uid="uid://bwm315lqbbb87"] | |||||
[resource] | |||||
bg_color = Color(0.728173, 0.579132, 0.164487, 1) |
@ -0,0 +1,8 @@ | |||||
[gd_resource type="StyleBoxFlat" format=3 uid="uid://biousyggn7iua"] | |||||
[resource] | |||||
content_margin_left = 5.0 | |||||
content_margin_top = 5.0 | |||||
content_margin_right = 5.0 | |||||
content_margin_bottom = 5.0 | |||||
bg_color = Color(0, 0.65098, 0.886275, 0) |
@ -1,6 +1,6 @@ | |||||
[gd_resource type="Resource" script_class="BlockDataResource" load_steps=2 format=3 uid="uid://dfos8np8agysk"] | [gd_resource type="Resource" script_class="BlockDataResource" load_steps=2 format=3 uid="uid://dfos8np8agysk"] | ||||
[ext_resource type="Script" uid="uid://syaia0l6vjt1" path="res://save_load/resources/node_types/block_data_resource.gd" id="1_a06et"] | |||||
[ext_resource type="Script" uid="uid://syaia0l6vjt1" path="res://resources/save_load_node_types/block_data_resource.gd" id="1_a06et"] | |||||
[resource] | [resource] | ||||
script = ExtResource("1_a06et") | script = ExtResource("1_a06et") |
@ -0,0 +1,19 @@ | |||||
## The base resource for saving a specific Node | |||||
class_name DayNightCycleDataResource | |||||
extends NodeDataResource | |||||
@export var time: float ## Time from DayNightCycleComponent.time | |||||
@export var node_path: NodePath | |||||
func _save_data(node: Node) -> void: | |||||
node_path = node.get_path() | |||||
time = node.time | |||||
func _load_data(window: Window) -> void: | |||||
var scene_node: DayNightCycleComponent = window.get_node_or_null(node_path) | |||||
if scene_node == null: | |||||
printerr("Couldn't find DayNightCycleDataResource.node_path") | |||||
return | |||||
scene_node.set_time.emit(time) |
@ -0,0 +1 @@ | |||||
uid://7gwknxsl1fgd |
@ -0,0 +1,9 @@ | |||||
[gd_resource type="Resource" script_class="DayNightCycleDataResource" load_steps=2 format=3 uid="uid://ccv6fi1lk8ofm"] | |||||
[ext_resource type="Script" uid="uid://7gwknxsl1fgd" path="res://resources/save_load_node_types/day_night_cycle_data_resource.gd" id="1_jnnxf"] | |||||
[resource] | |||||
script = ExtResource("1_jnnxf") | |||||
time = 0.0 | |||||
node_path = NodePath("") | |||||
metadata/_custom_type_script = "uid://7gwknxsl1fgd" |
@ -0,0 +1,15 @@ | |||||
class_name DroppedBlockDataResource | |||||
extends Node3DDataResource | |||||
@export var block_id: String = "001" | |||||
func _save_data(node: Node3D) -> void: | |||||
super._save_data(node) | |||||
block_id = node.id | |||||
func _load_data(_window: Window) -> void: | |||||
EntityManager.drop_block.emit(block_id, transform.origin, Vector3.ZERO, 0.0) |
@ -0,0 +1 @@ | |||||
uid://ddk34r80lscu0 |
@ -0,0 +1,11 @@ | |||||
[gd_resource type="Resource" script_class="DroppedBlockDataResource" load_steps=2 format=3 uid="uid://g26k1qtkabwf"] | |||||
[ext_resource type="Script" uid="uid://ddk34r80lscu0" path="res://resources/save_load_node_types/dropped_block_data_resource.gd" id="1_ax78v"] | |||||
[resource] | |||||
script = ExtResource("1_ax78v") | |||||
block_id = "001" | |||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) | |||||
node_path = NodePath("") | |||||
parent_node_path = NodePath("") | |||||
metadata/_custom_type_script = "uid://ddk34r80lscu0" |
@ -1,6 +1,6 @@ | |||||
[gd_resource type="Resource" script_class="PlayerDataResource" load_steps=2 format=3 uid="uid://bvsurbn5xgchr"] | [gd_resource type="Resource" script_class="PlayerDataResource" load_steps=2 format=3 uid="uid://bvsurbn5xgchr"] | ||||
[ext_resource type="Script" uid="uid://dodqpooodtguo" path="res://save_load/resources/node_types/player_data_resource.gd" id="1_scty6"] | |||||
[ext_resource type="Script" uid="uid://dodqpooodtguo" path="res://resources/save_load_node_types/player_data_resource.gd" id="1_scty6"] | |||||
[resource] | [resource] | ||||
script = ExtResource("1_scty6") | script = ExtResource("1_scty6") |
@ -1,5 +1,5 @@ | |||||
[gd_resource type="Gradient" format=3 uid="uid://dvpnjt66dtk46"] | [gd_resource type="Gradient" format=3 uid="uid://dvpnjt66dtk46"] | ||||
[resource] | [resource] | ||||
offsets = PackedFloat32Array(0.1, 0.25, 0.401515, 0.6, 0.75, 0.9) | |||||
colors = PackedColorArray(0.317647, 0.333333, 0.65098, 1, 0.87451, 0.521569, 0.407843, 1, 0.584314, 0.980392, 0.980392, 1, 0.584314, 0.980392, 0.980392, 1, 0.87451, 0.521569, 0.407843, 1, 0.317647, 0.333333, 0.65098, 1) | |||||
offsets = PackedFloat32Array(0.1, 0.25, 0.4, 0.6, 0.75, 0.9) | |||||
colors = PackedColorArray(0.317647, 0.333333, 0.65098, 1, 0.87451, 0.521569, 0.407843, 1, 0.243137, 0.584314, 0.85098, 1, 0.243137, 0.584314, 0.85098, 1, 0.87451, 0.521569, 0.407843, 1, 0.317647, 0.333333, 0.65098, 1) |
@ -1,41 +0,0 @@ | |||||
## Performs the actual Saving[br][br] | |||||
## | |||||
## Below is a basic definition of various components and resources for reference:[br] | |||||
## * SaveLevelDataComponent - The root save component that performs saving and loading of the actual data from disk | |||||
## * SaveDataComponent - Attached to a node that has properties that need to be saved | |||||
## * Node Type Resources - Each type of node that requires saving, such as a TileMapLayer, needs a resource created | |||||
extends Node | |||||
signal game_saved | |||||
signal game_loaded | |||||
func _unhandled_input(event: InputEvent) -> void: | |||||
if event.is_action_pressed("quick_save"): | |||||
save_game() | |||||
if event.is_action_pressed("quick_load"): | |||||
load_game() | |||||
func save_game() -> void: | |||||
var save_level_data_component: SaveLevelDataComponent = get_tree().get_first_node_in_group("save_level_data_component") | |||||
if save_level_data_component == null: | |||||
push_error("Could not find SaveLevelDataComponent node in level") | |||||
return | |||||
save_level_data_component.save_game() | |||||
game_saved.emit() | |||||
func load_game() -> void: | |||||
EntityManager.reset_world.emit() | |||||
var save_level_data_component: SaveLevelDataComponent = get_tree().get_first_node_in_group("save_level_data_component") | |||||
if save_level_data_component == null: | |||||
push_error("Could not find SaveLevelDataComponent node in level") | |||||
return | |||||
save_level_data_component.load_game() | |||||
game_loaded.emit() |
@ -1,64 +0,0 @@ | |||||
## Performs the actual saving and loading of data related to this level/scene | |||||
## Utilized by the SaveGameManager | |||||
class_name SaveLevelDataComponent | |||||
extends Node | |||||
## See documentation to where this path is: https://docs.godotengine.org/en/stable/tutorials/io/data_paths.html#accessing-persistent-user-data-user[br][br] | |||||
## Default Paths:[br] | |||||
## * Windows: %APPDATA%\Godot\app_userdata\[project_name][br] | |||||
## * macOS: ~/Library/Application Support/Godot/app_userdata/[project_name][br] | |||||
## * Linux: ~/.local/share/godot/app_userdata/[project_name][br] | |||||
@export var save_game_data_path: String = "user://game_data/" | |||||
@export var save_file_name: String = "save_%s_game_data.tres" | |||||
var level_scene_name: String | |||||
var game_data_resource: SaveGameDataResource | |||||
var level_save_file_name: String | |||||
var save_game_file_path: String | |||||
func _ready() -> void: | |||||
add_to_group("save_level_data_component") | |||||
level_scene_name = get_parent().name | |||||
level_save_file_name = save_file_name % level_scene_name | |||||
save_game_file_path = save_game_data_path + level_save_file_name | |||||
func save_node_data() -> void: | |||||
var nodes: Array = get_tree().get_nodes_in_group("save_data_component") | |||||
if nodes == null: return | |||||
game_data_resource = SaveGameDataResource.new() | |||||
for node: Node in nodes: | |||||
if node is SaveDataComponent: | |||||
@warning_ignore("unsafe_method_access") | |||||
var save_data_resource: Node3DDataResource = node._save_data() | |||||
var save_final_resource: Node3DDataResource = save_data_resource.duplicate() | |||||
game_data_resource.save_data_nodes.append(save_final_resource) | |||||
func save_game() -> void: | |||||
if !DirAccess.dir_exists_absolute(save_game_data_path): | |||||
DirAccess.make_dir_absolute(save_game_data_path) | |||||
save_node_data() | |||||
var result: int = ResourceSaver.save(game_data_resource, save_game_file_path) | |||||
if result != OK: | |||||
printerr("Failed to save game: ", result) | |||||
func load_game() -> void: | |||||
if !FileAccess.file_exists(save_game_file_path): | |||||
printerr("Failed to load save. File does not exist: ", save_game_file_path) | |||||
return | |||||
game_data_resource = ResourceLoader.load(save_game_file_path) | |||||
if game_data_resource == null: | |||||
printerr("Failed to load save. Unknown format? ", save_game_file_path) | |||||
return | |||||
var root_node: Window = get_tree().root | |||||
for resource: Resource in game_data_resource.save_data_nodes: | |||||
if resource is Node3DDataResource: | |||||
(resource as Node3DDataResource)._load_data(root_node) |
@ -1,6 +0,0 @@ | |||||
[gd_scene load_steps=2 format=3 uid="uid://c3pqilb6yh5kc"] | |||||
[ext_resource type="Script" uid="uid://c7x2qvyu62230" path="res://save_load/components/save_level_data_component.gd" id="1_exguq"] | |||||
[node name="SaveLevelDataComponent" type="Node"] | |||||
script = ExtResource("1_exguq") |