From 949bc9f4ff025fe027ebe934c986501ccf0012f6 Mon Sep 17 00:00:00 2001 From: effie Date: Sat, 12 Jul 2025 19:32:01 +1000 Subject: [PATCH] Save / load advanced to usability --- addons/godot_object_serializer/LICENSE | 21 ++ .../binary_serializer.gd | 63 ++++++ .../binary_serializer.gd.uid | 1 + .../dictionary_serializer.gd | 101 ++++++++++ .../dictionary_serializer.gd.uid | 1 + addons/godot_object_serializer/example | 131 +++++++++++++ addons/godot_object_serializer/example.uid | 1 + .../object_serializer.gd | 185 ++++++++++++++++++ .../object_serializer.gd.uid | 1 + addons/godot_object_serializer/plugin.cfg | 7 + game.gd | 6 - game.tscn | 6 + interface/save_load.gd | 118 +++++++++-- places/base_place/base_place.gd | 146 ++++++++------ places/base_place/base_place.tscn | 8 + places/base_place/base_room.gd | 39 ++-- places/base_place/base_room.tscn | 2 +- places/place_manager.gd | 18 +- places/place_manager.tscn | 1 + tiles/base_tile/base_tile.gd | 69 ++++--- tiles/base_tile/base_tile.tscn | 2 +- tiles/base_tile/base_wall.gd | 3 + tiles/base_tile/base_wall.gd.uid | 1 + tiles/base_tile/base_wall.tscn | 11 +- 24 files changed, 807 insertions(+), 135 deletions(-) create mode 100644 addons/godot_object_serializer/LICENSE create mode 100644 addons/godot_object_serializer/binary_serializer.gd create mode 100644 addons/godot_object_serializer/binary_serializer.gd.uid create mode 100644 addons/godot_object_serializer/dictionary_serializer.gd create mode 100644 addons/godot_object_serializer/dictionary_serializer.gd.uid create mode 100644 addons/godot_object_serializer/example create mode 100644 addons/godot_object_serializer/example.uid create mode 100644 addons/godot_object_serializer/object_serializer.gd create mode 100644 addons/godot_object_serializer/object_serializer.gd.uid create mode 100644 addons/godot_object_serializer/plugin.cfg create mode 100644 tiles/base_tile/base_wall.gd create mode 100644 tiles/base_tile/base_wall.gd.uid diff --git a/addons/godot_object_serializer/LICENSE b/addons/godot_object_serializer/LICENSE new file mode 100644 index 0000000..eff7d83 --- /dev/null +++ b/addons/godot_object_serializer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Charles Crete + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/godot_object_serializer/binary_serializer.gd b/addons/godot_object_serializer/binary_serializer.gd new file mode 100644 index 0000000..d8fbcbf --- /dev/null +++ b/addons/godot_object_serializer/binary_serializer.gd @@ -0,0 +1,63 @@ +## Serializer to be used with Godot's built-in binary serialization ([method @GlobalScope.var_to_bytes] and [method @GlobalScope.bytes_to_var]). +## This serializes objects but leaves built-in Godot types as-is. +class_name BinarySerializer + + +## Serialize [param data] to value which can be passed to [method @GlobalScope.var_to_bytes]. +static func serialize_var(value: Variant) -> Variant: + match typeof(value): + TYPE_OBJECT: + var name: StringName = value.get_script().get_global_name() + var object_entry := ObjectSerializer._get_entry(name, value.get_script()) + if !object_entry: + assert( + false, + ( + "Could not find type (%s) in registry\n%s" + % [name if name else "no name", value.get_script().source_code] + ) + ) + + return object_entry.serialize(value, serialize_var) + TYPE_ARRAY: + return value.map(serialize_var) + TYPE_DICTIONARY: + var result := {} + for i: Variant in value: + result[i] = serialize_var(value[i]) + return result + + return value + + +## Serialize [param data] into bytes with [method BinaryObjectSerializer.serialize_var] and [method @GlobalScope.var_to_bytes]. +static func serialize_bytes(value: Variant) -> PackedByteArray: + return var_to_bytes(serialize_var(value)) + + +## Deserialize [param data] from [method @GlobalScope.bytes_to_var] to value. +static func deserialize_var(value: Variant) -> Variant: + match typeof(value): + TYPE_DICTIONARY: + if value.has(ObjectSerializer.type_field): + var type: String = value.get(ObjectSerializer.type_field) + if type.begins_with(ObjectSerializer.object_type_prefix): + var entry := ObjectSerializer._get_entry(type) + if !entry: + assert(false, "Could not find type (%s) in registry" % type) + + return entry.deserialize(value, deserialize_var) + + var result := {} + for i: Variant in value: + result[i] = deserialize_var(value[i]) + return result + TYPE_ARRAY: + return value.map(deserialize_var) + + return value + + +## Deserialize bytes [param data] to value with [method @GlobalScope.bytes_to_var] and [method BinaryObjectSerializer.deserialize_var]. +static func deserialize_bytes(value: PackedByteArray) -> Variant: + return deserialize_var(bytes_to_var(value)) diff --git a/addons/godot_object_serializer/binary_serializer.gd.uid b/addons/godot_object_serializer/binary_serializer.gd.uid new file mode 100644 index 0000000..148d337 --- /dev/null +++ b/addons/godot_object_serializer/binary_serializer.gd.uid @@ -0,0 +1 @@ +uid://82dofbvl1g6n diff --git a/addons/godot_object_serializer/dictionary_serializer.gd b/addons/godot_object_serializer/dictionary_serializer.gd new file mode 100644 index 0000000..6acbaf4 --- /dev/null +++ b/addons/godot_object_serializer/dictionary_serializer.gd @@ -0,0 +1,101 @@ +## Serializer to be used with JSON serialization ([method JSON.stringify] and [method JSON.parse_string]). +## This serializes objects and built-in Godot types. +class_name DictionarySerializer + +# Types that can natively be represented in JSON +const _JSON_SERIALIZABLE_TYPES = [ + TYPE_NIL, TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_STRING_NAME +] + +## Controls if [PackedByteArray] should be serialized as base64 (instead of array of bytes as uint8). +## It's highly recommended to leave this enabled as it will result to smaller serialized payloads and should be faster. +## This can be changed, but must be configured before any serialization or deserialization. +static var bytes_as_base64 := true +## The type of the object for [PackedByteArray] when [member bytes_as_base64] is enabled. +## This should be set to something unlikely to clash with built-in type names or [member ObjectSerializer.object_type_prefix]. +## This can be changed, but must be configured before any serialization or deserialization. +static var bytes_to_base64_type := "PackedByteArray_Base64" + + +## Serialize [param data] into value which can be passed to [method JSON.stringify]. +static func serialize_var(value: Variant) -> Variant: + match typeof(value): + TYPE_OBJECT: + var name: StringName = value.get_script().get_global_name() + var object_entry := ObjectSerializer._get_entry(name, value.get_script()) + if !object_entry: + assert( + false, + ( + "Could not find type (%s) in registry\n%s" + % [name if name else "no name", value.get_script().source_code] + ) + ) + + return object_entry.serialize(value, serialize_var) + + TYPE_ARRAY: + return value.map(serialize_var) + + TYPE_DICTIONARY: + var result := {} + for i: Variant in value: + result[i] = serialize_var(value[i]) + return result + + TYPE_PACKED_BYTE_ARRAY: + if bytes_as_base64: + return { + ObjectSerializer.type_field: bytes_to_base64_type, + ObjectSerializer.args_field: + Marshalls.raw_to_base64(value) if !value.is_empty() else "" + } + + if _JSON_SERIALIZABLE_TYPES.has(typeof(value)): + return value + + return { + ObjectSerializer.type_field: type_string(typeof(value)), + ObjectSerializer.args_field: JSON.from_native(value)["args"] + } + + +## Serialize [param data] into JSON string with [method DictionaryObjectSerializer.serialize_var] and [method JSON.stringify]. Supports same arguments as [method JSON.stringify] +static func serialize_json( + value: Variant, indent := "", sort_keys := true, full_precision := false +) -> String: + return JSON.stringify(serialize_var(value), indent, sort_keys, full_precision) + + +## Deserialize [data] from [JSON.parse_string] into value. +static func deserialize_var(value: Variant) -> Variant: + match typeof(value): + TYPE_DICTIONARY: + if value.has(ObjectSerializer.type_field): + var type: String = value.get(ObjectSerializer.type_field) + if bytes_as_base64 and type == bytes_to_base64_type: + return Marshalls.base64_to_raw(value[ObjectSerializer.args_field]) + + if type.begins_with(ObjectSerializer.object_type_prefix): + var entry := ObjectSerializer._get_entry(type) + if !entry: + assert(false, "Could not find type (%s) in registry" % type) + + return entry.deserialize(value, deserialize_var, true) + + return JSON.to_native({"type": type, "args": value[ObjectSerializer.args_field]}) + + var result := {} + for i: Variant in value: + result[i] = deserialize_var(value[i]) + return result + + TYPE_ARRAY: + return value.map(deserialize_var) + + return value + + +## Deserialize JSON string [param data] to value with [method JSON.parse_string] and [method DictionaryObjectSerializer.deserialize_var]. +static func deserialize_json(value: String) -> Variant: + return deserialize_var(JSON.parse_string(value)) diff --git a/addons/godot_object_serializer/dictionary_serializer.gd.uid b/addons/godot_object_serializer/dictionary_serializer.gd.uid new file mode 100644 index 0000000..c44d828 --- /dev/null +++ b/addons/godot_object_serializer/dictionary_serializer.gd.uid @@ -0,0 +1 @@ +uid://cejorwceoqbqe diff --git a/addons/godot_object_serializer/example b/addons/godot_object_serializer/example new file mode 100644 index 0000000..8990a19 --- /dev/null +++ b/addons/godot_object_serializer/example @@ -0,0 +1,131 @@ +extends SceneTree + + +# Example data class. Can extend any type, include Resource +class Data: + # Supports all primitive types (String, int, float, bool, null), including @export variables + @export var string: String + # Supports all extended built-in types (Vector2/3/4/i, Rect2/i, Transform2D/3D, Color, Packed*Array, etc) + var vector: Vector3 + # Supports enum + var enum_state: State + # Supports arrays, including Array[Variant] + var array: Array[int] + # Supports dictionaries, including Dictionary[Variant, Variant] + var dictionary: Dictionary[String, Vector2] + # Supports efficient byte array serialization to base64 + var packed_byte_array: PackedByteArray + # Supports nested data, either as a field or in array/dictionary + var nested: DataResource + + +class DataResource: + extends Resource + var name: String + + +enum State { OPENED, CLOSED } + + +# _static_init is used to register scripts without having to instanciate the script. +# It's recommended to either place all registrations in a single script, or have each script register itself. +static func _static_init() -> void: + # Required: Register possible object scripts + ObjectSerializer.register_script("Data", Data) + ObjectSerializer.register_script("DataResource", DataResource) + + +# Setup testing data +var data := Data.new() + + +func _init() -> void: + data.string = "Lorem ipsum" + data.vector = Vector3(1, 2, 3) + data.enum_state = State.CLOSED + data.array = [1, 2] + data.dictionary = {"position": Vector2(1, 2)} + data.packed_byte_array = PackedByteArray([1, 2, 3, 4, 5, 6, 7, 8]) + var data_resource := DataResource.new() + data_resource.name = "dolor sit amet" + data.nested = data_resource + + json_serialization() + binary_serialization() + + +func json_serialization() -> void: + # Serialize to JSON + # Alternative: DictionarySerializer.serialize_json(data) + var serialized: Variant = DictionarySerializer.serialize_var(data) + var json := JSON.stringify(serialized, "\t") + print(json) + """ Output: + { + "._type": "Object_Data", + "string": "Lorem ipsum", + "vector": { + "._type": "Vector3", + "._": [ + 1.0, + 2.0, + 3.0 + ] + }, + "enum_state": 1, + "array": [ + 1, + 2 + ], + "dictionary": { + "position": { + "._type": "Vector2", + "._": [ + 1.0, + 2.0 + ] + } + }, + "packed_byte_array": { + "._type": "PackedByteArray_Base64", + "._": "AQIDBAUGBwg=" + }, + "nested": { + "._type": "Object_DataResource", + "name": "dolor sit amet" + } + } + """ + + # Verify after JSON deserialization + # Alternative: DictionarySerializer.deserialize_json(json) + var parsed_json = JSON.parse_string(json) + var deserialized: Data = DictionarySerializer.deserialize_var(parsed_json) + _assert_data(deserialized) + + +func binary_serialization() -> void: + # Serialize to bytes + # Alternative: BinarySerializer.serialize_bytes(data) + var serialized: Variant = BinarySerializer.serialize_var(data) + var bytes := var_to_bytes(serialized) + print(bytes) + # Output: List of bytes + + # Verify after bytes deserialization. + # Alternative: BinarySerializer.deserialize_bytes(bytes) + var parsed_bytes = bytes_to_var(bytes) + var deserialized: Data = BinarySerializer.deserialize_var(parsed_bytes) + _assert_data(deserialized) + + +func _assert_data(deserialized: Data) -> void: + assert(data.string == deserialized.string, "string is different") + assert(data.vector == deserialized.vector, "vector is different") + assert(data.enum_state == deserialized.enum_state, "enum_state is different") + assert(data.array == deserialized.array, "array is different") + assert(data.dictionary == deserialized.dictionary, "dictionary is different") + assert( + data.packed_byte_array == deserialized.packed_byte_array, "packed_byte_array is different" + ) + assert(data.nested.name == deserialized.nested.name, "nested.name is different") diff --git a/addons/godot_object_serializer/example.uid b/addons/godot_object_serializer/example.uid new file mode 100644 index 0000000..e220422 --- /dev/null +++ b/addons/godot_object_serializer/example.uid @@ -0,0 +1 @@ +uid://cnmoj6e2jq737 diff --git a/addons/godot_object_serializer/object_serializer.gd b/addons/godot_object_serializer/object_serializer.gd new file mode 100644 index 0000000..7267f6b --- /dev/null +++ b/addons/godot_object_serializer/object_serializer.gd @@ -0,0 +1,185 @@ +## Main godot-object-serializer class. Stores the script registry. +## [url]https://github.com/Cretezy/godot-object-serializer[/url] +class_name ObjectSerializer + +## The field containing the type in serialized object values. Not recommended to change. +## +## This should be set to something unlikely to clash with keys in objects/dictionaries. +## +## This can be changed, but must be configured before any serialization or deserialization. +static var type_field := "._type" + +## The field containing the constructor arguments in serialized object values. Not recommended to change. +## +## This should be set to something unlikely to clash with keys in objects. +## +## This can be changed, but must be configured before any serialization or deserialization. +static var args_field := "._" + +## The prefix for object types stored in [member type_field]. Not recommended to change. +## +## This should be set to something unlikely to clash with built-in type names. +## +## This can be changed, but must be configured before any serialization or deserialization. +static var object_type_prefix := "Object_" + +## By default, variables with [@GlobalScope.PROPERTY_USAGE_SCRIPT_VARIABLE] are serialized (all variables have this by default). +## When [member require_export_storage] is true, variables will also require [@GlobalScope.PROPERTY_USAGE_STORAGE] to be serialized. +## This can be set on variables using [annotation @GDScript.@export_storage]. Example: [code]@export_storage var name: String[/code] +static var require_export_storage := false + +## Registry of object types +static var _script_registry: Dictionary[String, _ScriptRegistryEntry] + + +## Registers a script (an object type) to be serialized/deserialized. All custom types (including nested types) must be registered [b]before[/b] using this library. +## [param name] can be empty if script uses [code]class_name[/code] (e.g [code]ObjectSerializer.register_script("", Data)[/code]), but it's generally better to set the name. +static func register_script(name: StringName, script: Script) -> void: + var script_name := _get_script_name(script, name) + assert(script_name, "Script must have name\n" + script.source_code) + var entry := _ScriptRegistryEntry.new() + entry.script_type = script + entry.type = object_type_prefix + script_name + _script_registry[entry.type] = entry + + +## Registers multiple scripts (object types) to be serialized/deserialized from a dictionary. +## See [method ObjectSerializer.register_script] +static func register_scripts(scripts: Dictionary[String, Script]) -> void: + for name in scripts: + register_script(name, scripts[name]) + + +static func _get_script_name(script: Script, name: StringName = "") -> StringName: + if name: + return name + if script.resource_name: + return script.resource_name + if script.get_global_name(): + return script.get_global_name() + return "" + + +static func _get_entry(name: StringName = "", script: Script = null) -> _ScriptRegistryEntry: + if name: + var entry: _ScriptRegistryEntry = _script_registry.get(name) + if entry: + return entry + + if script: + for i: String in _script_registry: + var entry: _ScriptRegistryEntry = _script_registry.get(i) + if entry: + if script == entry.script_type: + return entry + + return null + + +class _ScriptRegistryEntry: + var type: String + var script_type: Script + + func serialize(value: Variant, next: Callable) -> Variant: + if value.has_method("_serialize"): + var result: Dictionary = value._serialize(next) + result[ObjectSerializer.type_field] = type + return result + + var result := {ObjectSerializer.type_field: type} + + var excluded_properties: Array[String] = [] + if value.has_method("_get_excluded_properties"): + excluded_properties = value._get_excluded_properties() + + var partial: Dictionary = {} + if value.has_method("_serialize_partial"): + partial = value._serialize_partial(next) + + for property: Dictionary in value.get_property_list(): + if ( + property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE + and ( + !ObjectSerializer.require_export_storage + or property.usage & PROPERTY_USAGE_STORAGE + ) + and !excluded_properties.has(property.name) + and !partial.has(property.name) + ): + result[property.name] = next.call(value.get(property.name)) + + for key in partial: + result[key] = partial[key] + + if value.has_method("_get_constructor_args"): + var args: Array = value._get_constructor_args() + result[ObjectSerializer.args_field] = args + + return result + + ## When [param json_keys] is enabled, attempt to convert int/float/bool string keys into values + func deserialize(value: Variant, next: Callable, json_keys := false) -> Variant: + if script_type.has_method("_deserialize"): + return script_type._deserialize(value, next) + + var instance: Variant + if value.has(ObjectSerializer.args_field): + instance = script_type.new.callv(value[ObjectSerializer.args_field]) + else: + instance = script_type.new() + + var excluded_properties: Array[String] = [] + if instance.has_method("_get_excluded_properties"): + excluded_properties = instance._get_excluded_properties() + + var partial: Dictionary = {} + if instance.has_method("_deserialize_partial"): + partial = instance._deserialize_partial(value, next) + + for key: String in value: + if ( + key == ObjectSerializer.type_field + or key == ObjectSerializer.args_field + or excluded_properties.has(key) + or partial.has(key) + ): + continue + + var key_value: Variant = next.call(value[key]) + match typeof(key_value): + TYPE_DICTIONARY: + if json_keys and instance[key].is_typed_key(): + match instance[key].get_typed_key_builtin(): + TYPE_STRING: + instance[key].assign(key_value) + TYPE_BOOL: + var dict: Dictionary[bool, Variant] = {} + for i in key_value: + dict[i == "true"] = key_value[i] + instance[key].assign(dict) + TYPE_INT: + var dict: Dictionary[int, Variant] = {} + for i in key_value: + dict[int(i)] = key_value[i] + instance[key].assign(dict) + TYPE_FLOAT: + var dict: Dictionary[float, Variant] = {} + for i in key_value: + dict[float(i)] = key_value[i] + instance[key].assign(dict) + _: + assert( + false, + "Trying to deserialize from JSON to a dictionary with non-primitive (String/int/float/bool) keys" + ) + else: + instance[key].assign(key_value) + TYPE_ARRAY: + instance[key].assign(key_value) + _: + instance[key] = key_value + + for key in partial: + instance[key] = partial[key] + + return instance diff --git a/addons/godot_object_serializer/object_serializer.gd.uid b/addons/godot_object_serializer/object_serializer.gd.uid new file mode 100644 index 0000000..04f718a --- /dev/null +++ b/addons/godot_object_serializer/object_serializer.gd.uid @@ -0,0 +1 @@ +uid://dn0k1lmeff72p diff --git a/addons/godot_object_serializer/plugin.cfg b/addons/godot_object_serializer/plugin.cfg new file mode 100644 index 0000000..77c1f97 --- /dev/null +++ b/addons/godot_object_serializer/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Godot Object Serializer" +description="Safely serialize/deserialize objects and built-in types in Godot" +author="Cretezy" +version="0.3.0" +script="" diff --git a/game.gd b/game.gd index c98a430..61510e1 100644 --- a/game.gd +++ b/game.gd @@ -1,7 +1 @@ extends Node - -func save_game(): - - var save_data = {} - - diff --git a/game.tscn b/game.tscn index 98690b3..8d3ff2b 100644 --- a/game.tscn +++ b/game.tscn @@ -41,6 +41,11 @@ offset_right = 1142.0 offset_bottom = 604.0 text = "Load Map" +[node name="Button" type="Button" parent="InterfaceLayer"] +offset_right = 8.0 +offset_bottom = 8.0 +text = "Init Grid" + [node name="GameCam" type="Camera3D" parent="."] transform = Transform3D(1, 0, 0, 0, 0.182236, 0.983255, 0, -0.983255, 0.182236, 24.5127, 23.2849, 20.9667) size = 31.34 @@ -55,4 +60,5 @@ script = ExtResource("5_7jktm") [connection signal="pressed" from="InterfaceLayer/BuildButton" to="PlaceManager" method="_on_confirm_button_pressed"] [connection signal="pressed" from="InterfaceLayer/SaveButton" to="SaveLoad" method="_on_save_button_pressed"] [connection signal="pressed" from="InterfaceLayer/LoadButton" to="SaveLoad" method="_on_load_button_pressed"] +[connection signal="pressed" from="InterfaceLayer/Button" to="PlaceManager" method="_on_init_grid_button_pressed"] [connection signal="room_built" from="PlaceManager" to="InterfaceLayer/BuildToggle" method="_on_room_built"] diff --git a/interface/save_load.gd b/interface/save_load.gd index cfd1497..bf3a806 100644 --- a/interface/save_load.gd +++ b/interface/save_load.gd @@ -1,5 +1,29 @@ extends Node +class SaveData: + var type: String + var position: Vector3 + var parent: String + var scene_file_path: String + var id: int + +#Connections + var place_id: int + var room_array: Array + +#Tile data + var grid_pos: Vector3i + var face_dict: Array + var construction_mode: int + +func _init(): + ObjectSerializer.register_scripts({ + "SaveData": SaveData, + "Tile": Tile, + "Room": Room, + "Wall": Wall, +}) + func _on_save_button_pressed() -> void: var save_nodes = get_tree().get_nodes_in_group("SaveObjects") var save_file = FileAccess.open("user://savegame.save", FileAccess.WRITE) @@ -10,8 +34,15 @@ func _on_save_button_pressed() -> void: continue var node_data = node.save() + var save_data = SaveData.new() + for i in node_data: + save_data.set(i, node_data[i]) - save_file.store_var(node_data) + var serialized_save_data = BinarySerializer.serialize_var(save_data) + + var byte_data = var_to_bytes(serialized_save_data) + + save_file.store_var(byte_data) pass # Replace with function body. @@ -21,18 +52,83 @@ func _on_load_button_pressed(): print("no save to load") return - #var save_nodes = get_tree().get_nodes_in_group("SaveObjects") - #for i in save_nodes: - #print(i) - #i.free() - - get_node("/root/Game/PlaceManager/BasePlace").free() + var save_nodes = get_tree().get_nodes_in_group("SaveObjects") + for i in save_nodes: + if i: + i.free() + + #if get_node("/root/Game/PlaceManager/BasePlace"): + #get_node("/root/Game/PlaceManager/BasePlace").free() var save_file = FileAccess.open("user://savegame.save", FileAccess.READ) + var place_id_dict: Dictionary = {} + var room_id_dict: Dictionary = {} + + var load_place_room_array: Dictionary = {} + var load_place_tile_dict: Dictionary = {} + var load_room_tile_array: Dictionary = {} + while save_file.get_position() < save_file.get_length(): - var next_load = save_file.get_var() + + + + var next_load_bytes = save_file.get_var() + var next_load = bytes_to_var(next_load_bytes) var load_object = load(next_load["scene_file_path"]).instantiate() - for i in next_load.keys(): - load_object.set(i, next_load[i]) - get_node(next_load["parent"]).add_child(load_object) + var id = next_load["id"] + + match next_load["type"]: + "Place": + var new_place_id = load_object.get_instance_id() + place_id_dict[id] = new_place_id + load_place_room_array[load_object] = [] + load_place_tile_dict[load_object] = {} + for j in next_load.keys(): + load_object.set(j, next_load[j]) + get_node(next_load["parent"]).add_child(load_object) + + "Room": + room_id_dict[id] = load_object.get_instance_id() + load_room_tile_array[load_object] = [] + for j in next_load.keys(): + if j == "place_id": + var new_place_id = place_id_dict[next_load[j]] + var place = instance_from_id(new_place_id) + load_place_room_array[place].append(load_object) + load_object.set(j, new_place_id) + else: + load_object.set(j, next_load[j]) + get_node(next_load["parent"]).add_child(load_object) + + "Tile": + for j in next_load.keys(): + if j == "place_id": + var new_place_id = place_id_dict[next_load[j]] + var place = instance_from_id(new_place_id) + load_place_tile_dict[place].merge({next_load["grid_pos"]: load_object}) + load_object.set(j, new_place_id) + elif j == "room_array": + for k in next_load["room_array"]: + var new_room_id = room_id_dict[k] + var room = instance_from_id(new_room_id) + load_room_tile_array[room].append(load_object) + load_object[j].append(room) + else: + load_object.set(j, next_load[j]) + get_node(next_load["parent"]).add_child(load_object) + + _: + for j in next_load.keys(): + load_object.set(j, next_load[j]) + get_node(next_load["parent"]).add_child(load_object) + + for i in load_place_room_array.keys(): + i.place_room_array = load_place_room_array[i] + + for i in load_place_tile_dict.keys(): + i.place_tile_dict.merge(load_place_tile_dict[i]) + + for i in load_room_tile_array.keys(): + i.room_tile_array = load_room_tile_array[i] + diff --git a/places/base_place/base_place.gd b/places/base_place/base_place.gd index f51e2e2..2d1b51a 100644 --- a/places/base_place/base_place.gd +++ b/places/base_place/base_place.gd @@ -1,5 +1,7 @@ extends Node3D +class_name Place + var tile_grid_size: int = 8 var load_tile = preload("res://tiles/base_tile/base_tile.tscn") @@ -15,7 +17,7 @@ var selection_drag_dict: Dictionary = {} var selection_dict: Dictionary = {} #Contains all rooms in a workplace. -var room_dict: Dictionary = {} +var place_room_array: Array = [] #Tracks the previous amount of tiles counted in a selection drac var tile_count_x_hist: int = 0 @@ -30,7 +32,19 @@ var current_room_walls: Array = [] var current_door: Object = null func _ready(): - self.name = "BasePlace" + + pass + +func place_tile_check(new_dict): + place_tile_dict = new_dict + #if type_string(typeof(new_dict)) == "Array": + #for i in new_dict: + # + #place_tile_dict[] + + +func init_grid(): + #TEMP: Sets up a simple 2D grid of blank tiles. for x in range(tile_grid_size): @@ -42,40 +56,39 @@ func _ready(): tile.grid_pos = pos tile.name = str("Tile", tile.get_instance_id()) - + tile.place_id = self.get_instance_id() tile.selection_mode = Tile.SEL_MODE.NONE tile.construction_mode = Tile.CON_MODE.CLOSED place_tile_dict[pos] = tile - add_child(tile) - - tile.connect("neighbor_request", give_neighbors) + $TileContainer.add_child(tile) - var pos = Vector3i(1, 0, 1) - - var tile = place_tile_dict[pos] - - var trickydict = {pos: tile} - - tile.room = create_room(trickydict) - - tile.construction_mode = Tile.CON_MODE.BUILT - - + #var pos = Vector3i(1, 0, 1) + # + #var tile = place_tile_dict[pos] + # + #var trickydict = {pos: tile} + # + #tile.room = create_room(trickydict) + # + #tile.construction_mode = Tile.CON_MODE.BUILT +# #selection_drag_dict[pos] = tile #end_select_drag() #build_selection() func save(): var save_data = { - "test": "test", +# Basics + "name": name, + "type": "Place", + "id": self.get_instance_id(), "scene_file_path": scene_file_path, "parent": get_parent().get_path(), - "place_tile_dict": place_tile_dict, - "room_dict": room_dict +# Connections +# Data } - return save_data func draw_tile_click(click_pos): @@ -234,16 +247,17 @@ func create_room(selection): room.position = (selection.keys().min()) - room.room_tile_dict = selection.duplicate() + room.room_tile_array = selection.values() + room.place_id = self.get_instance_id() room.name = str("Room", room.get_instance_id()) - add_child(room) + $RoomContainer.add_child(room) - for i in room.room_tile_dict: - place_tile_dict[i].room = room + for i in room.room_tile_array: + i.room_array.append(room) - current_room = room + place_room_array.append(room) return room @@ -280,44 +294,44 @@ func hover_door(mouse_pos): current_door.position = closest current_door.rotation_degrees = Vector3(0, 180 * fmod(closest.z, 1), 0) -func confirm_door(): -#Builds the door at the hovered location - - var tile_1: Object - var tile_2: Object - - if fmod(current_door.position.x, 1) == 0: - tile_1 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z)] - tile_2 = place_tile_dict[Vector3i(current_door.position.x - 1, 0, current_door.position.z)] - else: - tile_1 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z)] - tile_2 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z - 1)] - - var tile_1_door_face: Vector3i = tile_2.position - tile_1.position - - var tile_1_door_face_direction: int = tile_1.direction_vector_array.find(tile_1_door_face) - - tile_1.update_face(tile_1_door_face_direction, Tile.FACE_MODE.DOOR, current_door) - - var tile_1_room: Object = tile_1.room_id - - tile_1_room.room_door_array.append(current_door) - - var tile_2_door_face: Vector3i = tile_1.position - tile_2.position - - var tile_2_door_face_direction: int = tile_2.direction_vector_array.find(tile_2_door_face) - - tile_2.update_face(tile_2_door_face_direction, Tile.FACE_MODE.DOOR, current_door) - - var tile_2_room: Object = tile_2.room_id - - tile_2_room.room_door_array.append(current_door) - - current_door.door_room_array = [tile_1_room, tile_2_room] - - current_room = null - current_room_walls = [] - current_door = null +#func confirm_door(): +##Builds the door at the hovered location +# + #var tile_1: Object + #var tile_2: Object +# + #if fmod(current_door.position.x, 1) == 0: + #tile_1 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z)] + #tile_2 = place_tile_dict[Vector3i(current_door.position.x - 1, 0, current_door.position.z)] + #else: + #tile_1 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z)] + #tile_2 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z - 1)] + # + #var tile_1_door_face: Vector3i = tile_2.position - tile_1.position +# + #var tile_1_door_face_direction: int = tile_1.direction_vector_array.find(tile_1_door_face) + # + #tile_1.update_face(tile_1_door_face_direction, Tile.FACE_MODE.DOOR, current_door) + # + #var tile_1_room: Object = tile_1.room_id + # + #tile_1_room.room_door_array.append(current_door) + # + #var tile_2_door_face: Vector3i = tile_1.position - tile_2.position + # + #var tile_2_door_face_direction: int = tile_2.direction_vector_array.find(tile_2_door_face) + # + #tile_2.update_face(tile_2_door_face_direction, Tile.FACE_MODE.DOOR, current_door) + # + #var tile_2_room: Object = tile_2.room_id + # + #tile_2_room.room_door_array.append(current_door) + # + #current_door.door_room_array = [tile_1_room, tile_2_room] + # + #current_room = null + #current_room_walls = [] + #current_door = null func give_neighbors(tile, grid_pos, directions): var neighbor_dict = {} @@ -325,3 +339,7 @@ func give_neighbors(tile, grid_pos, directions): if place_tile_dict.has(directions[i] + grid_pos): neighbor_dict[directions[i]] = place_tile_dict[directions[i] + grid_pos] tile.neighbor_dict = neighbor_dict + + +func _on_tile_container_child_entered_tree(node: Node) -> void: + node.connect("neighbor_request", give_neighbors) diff --git a/places/base_place/base_place.tscn b/places/base_place/base_place.tscn index 161b6d4..f5d0aae 100644 --- a/places/base_place/base_place.tscn +++ b/places/base_place/base_place.tscn @@ -7,3 +7,11 @@ script = ExtResource("1_uq3a8") [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] transform = Transform3D(1, 0, 0, 0, 0.345446, 0.938439, 0, -0.938439, 0.345446, 41.393, 18.7409, 34.4224) + +[node name="RoomContainer" type="Node" parent="."] + +[node name="TileContainer" type="Node" parent="."] + +[node name="Objects" type="Node" parent="."] + +[connection signal="child_entered_tree" from="TileContainer" to="." method="_on_tile_container_child_entered_tree"] diff --git a/places/base_place/base_room.gd b/places/base_place/base_room.gd index c2c096c..823542e 100644 --- a/places/base_place/base_room.gd +++ b/places/base_place/base_room.gd @@ -1,11 +1,14 @@ extends Node +class_name Room -var room_tile_dict = {} +var room_tile_array = []: set = setup_path_grid var path_grid = AStar3D.new() var room_door_array = [] +var place_id: int = 0 + #TODO: replace with global dict? var direction_array = [ Vector3i(0, 0, -1), @@ -14,32 +17,36 @@ var direction_array = [ Vector3i(-1, 0, 0), ] -func _ready(): - self.name = str("Room", self.get_instance_id()) - - init_path_grid() - - pass - func save(): var save_data = { - "test": "test", +# Basics + "name": name, + "type": "Room", + "id": self.get_instance_id(), "scene_file_path": scene_file_path, "parent": get_parent().get_path(), - "room_tile_dict": room_tile_dict, - "room_door_array": room_door_array, - "path_grid": path_grid +# Connections + "place_id": place_id, +# Data } return save_data -func init_path_grid(): +func init_room(new_dict): + setup_path_grid(new_dict) + for i in new_dict.values(): + i.room = self + + +func setup_path_grid(new_array): - for i in room_tile_dict.keys(): + room_tile_array = new_array + + for i in room_tile_array: var id = path_grid.get_available_point_id() - path_grid.add_point(id, i) + path_grid.add_point(id, i.grid_pos) for n in direction_array: - if room_tile_dict.has(i + n): + if room_tile_array.has(i.grid_pos + n): var closest = path_grid.get_closest_point(i + n) if (i + n) == Vector3i(path_grid.get_point_position(closest)): path_grid.connect_points(id, closest) diff --git a/places/base_place/base_room.tscn b/places/base_place/base_room.tscn index 1ab74ed..6f87a6f 100644 --- a/places/base_place/base_room.tscn +++ b/places/base_place/base_room.tscn @@ -2,5 +2,5 @@ [ext_resource type="Script" uid="uid://c2neixe7kpvma" path="res://places/base_place/base_room.gd" id="1_3l4mp"] -[node name="BaseRoom" type="Node3D"] +[node name="BaseRoom" type="Node3D" groups=["SaveObjects"]] script = ExtResource("1_3l4mp") diff --git a/places/place_manager.gd b/places/place_manager.gd index 4e4658a..f50b1f5 100644 --- a/places/place_manager.gd +++ b/places/place_manager.gd @@ -1,7 +1,7 @@ extends Node3D var load_place = preload("res://places/base_place/base_place.tscn") -var place = load_place.instantiate() +var place = null enum ROOM_BUILD_STATE {NONE, ALLOWED, BUILDING, IS_PLACING_DOOR} @@ -16,10 +16,6 @@ var build_drag_start_pos = null signal room_built -func _ready(): -#TEMP loads in a workplace. - add_child(place) - func _on_build_toggle(toggled_on): #Responds to the 'Build A Room' toggle if toggled_on: @@ -63,3 +59,15 @@ func _on_confirm_button_pressed() -> void: else: pass + + +func _on_init_grid_button_pressed() -> void: + if place: + place.queue_free() + var new_place = load_place.instantiate() + add_child(new_place) + place.init_grid() + +func _on_child_entered_tree(node: Node) -> void: + if node is Place: + place = node diff --git a/places/place_manager.tscn b/places/place_manager.tscn index 1120b0a..47b13df 100644 --- a/places/place_manager.tscn +++ b/places/place_manager.tscn @@ -14,4 +14,5 @@ script = ExtResource("1_rcbs8") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 128, 0, 128) shape = SubResource("BoxShape3D_rcbs8") +[connection signal="child_entered_tree" from="." to="." method="_on_child_entered_tree"] [connection signal="input_event" from="FloorPlane" to="." method="_on_area_3d_input_event"] diff --git a/tiles/base_tile/base_tile.gd b/tiles/base_tile/base_tile.gd index 6ecac14..18a4bda 100644 --- a/tiles/base_tile/base_tile.gd +++ b/tiles/base_tile/base_tile.gd @@ -1,79 +1,91 @@ extends Node3D class_name Tile -var id = null +var place_id: int = 0 #This tile's current construction and selection modes. @export var construction_mode = 0: set = update_construction @export var selection_mode = 0: set = update_selection -var orange = preload("res://tiles/base_tile/orange.tres") -var gray = preload("res://tiles/base_tile/gray.tres") -var red = preload("res://tiles/base_tile/red.tres") -var blue = preload("res://tiles/base_tile/blue.tres") -var lightblue = preload("res://tiles/base_tile/lightblue.tres") +const orange = preload("res://tiles/base_tile/orange.tres") +const gray = preload("res://tiles/base_tile/gray.tres") +const red = preload("res://tiles/base_tile/red.tres") +const blue = preload("res://tiles/base_tile/blue.tres") +const lightblue = preload("res://tiles/base_tile/lightblue.tres") #Lists of possible states for various modes. enum SEL_MODE {NONE, ROOM, BUILD, INVALID} enum CON_MODE {NONE, NON_INTERACTABLE, CLOSED, OPEN, BUILT, REINFORCED, CONSTRUCTION} -enum FACE_MODE {NONE, FULL, PARTIAL, DOOR} +#enum FACE_MODE {NONE, FULL, PARTIAL, DOOR} -var direction_vector_dict = { +const direction_vector_dict = { "North": Vector3i(0, 0, -1), "East": Vector3i(1, 0, 0), "South": Vector3i(0, 0, 1), "West": Vector3i(-1, 0, 0), } -var wall_position_dict = { +const wall_position_dict = { "North": Vector3i(0, 0, 0), "East": Vector3i(1, 0, 0), "South": Vector3i(1, 0, 1), "West": Vector3i(0, 0, 1), } -var load_wall = preload("res://tiles/base_tile/base_wall.tscn") +const load_wall = preload("res://tiles/base_tile/base_wall.tscn") @export var grid_pos = null @export var face_dict = {}: set = update_faces -@export var room = null +@export var room_array = [] -var neighbor_dict = [] - -signal wall_built +var neighbor_dict = {} signal neighbor_request func _ready(): - pass func save(): - + + var room_id_array: Array + + for i in room_array: + room_id_array.append(i.get_instance_id()) + var save_data = { - "test": "test", - "position": position, +# Basics + "name": name, + "type": "Tile", + "id": self.get_instance_id(), "scene_file_path": scene_file_path, "parent": get_parent().get_path(), +# Connections + "place_id": place_id, + "room_array": room_id_array, +# Data + "position": position, "grid_pos": grid_pos, - "face_dict": face_dict, - "room": room + "face_dict": face_dict.keys(), + "construction_mode": construction_mode, } - return save_data func update_faces(new_dict): - face_dict = new_dict - - for i in $Walls.get_children(): - i.queue_free() - + #for i in $Walls.get_children(): + #i.queue_free() + + if type_string(typeof(new_dict)) == "Array": + var temp_new_dict: Dictionary + for i in new_dict: + temp_new_dict[i] = null + new_dict = temp_new_dict.duplicate() + for i in new_dict.keys(): var direction: String = direction_vector_dict.find_key(i) var wall_pos = wall_position_dict[direction] @@ -88,6 +100,7 @@ func update_faces(new_dict): "West": wall.rotation_degrees = Vector3(0, -270, 0) $Walls.add_child(wall) + face_dict[i] = wall func update_neighbors(): neighbor_dict = {} @@ -120,12 +133,10 @@ func update_construction(mode): var temp_face_dict = {} for i in neighbor_dict.keys(): - if not neighbor_dict[i].room == room: - #print(neighbor_dict[i].room) + if not neighbor_dict[i].room_array == room_array: temp_face_dict[i] = null face_dict = temp_face_dict.duplicate() - print(face_dict) CON_MODE.REINFORCED: face_dict = {} diff --git a/tiles/base_tile/base_tile.tscn b/tiles/base_tile/base_tile.tscn index d081af2..ea2a47c 100644 --- a/tiles/base_tile/base_tile.tscn +++ b/tiles/base_tile/base_tile.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://jqjcr7dxjnbt" path="res://tiles/base_tile/base_tile.gd" id="1_yoisu"] [ext_resource type="PlaneMesh" uid="uid://bis4hdushjnjm" path="res://tiles/base_tile/base_floor.tres" id="2_wxg2y"] -[node name="Tile" type="Node3D" groups=["SaveObjects"]] +[node name="BaseTile" type="Node3D" groups=["SaveObjects"]] script = ExtResource("1_yoisu") [node name="Floor" type="Node3D" parent="."] diff --git a/tiles/base_tile/base_wall.gd b/tiles/base_tile/base_wall.gd new file mode 100644 index 0000000..3e256ee --- /dev/null +++ b/tiles/base_tile/base_wall.gd @@ -0,0 +1,3 @@ +extends Node3D + +class_name Wall diff --git a/tiles/base_tile/base_wall.gd.uid b/tiles/base_tile/base_wall.gd.uid new file mode 100644 index 0000000..1fefae4 --- /dev/null +++ b/tiles/base_tile/base_wall.gd.uid @@ -0,0 +1 @@ +uid://cx6bmej4pcjlt diff --git a/tiles/base_tile/base_wall.tscn b/tiles/base_tile/base_wall.tscn index bd722b6..9bbad39 100644 --- a/tiles/base_tile/base_wall.tscn +++ b/tiles/base_tile/base_wall.tscn @@ -1,9 +1,16 @@ -[gd_scene load_steps=2 format=3 uid="uid://diovc4myisuow"] +[gd_scene load_steps=4 format=3 uid="uid://diovc4myisuow"] + +[ext_resource type="Script" uid="uid://cx6bmej4pcjlt" path="res://tiles/base_tile/base_wall.gd" id="1_lirtl"] [sub_resource type="BoxMesh" id="BoxMesh_xdfmn"] -[node name="Node3D" type="Node3D"] +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_lirtl"] +albedo_color = Color(0.0574887, 2.24891e-05, 0.240434, 1) + +[node name="BaseWall" type="Node3D"] +script = ExtResource("1_lirtl") [node name="MeshInstance3D" type="MeshInstance3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 0.1, 0.5, 0.5, 0.05) mesh = SubResource("BoxMesh_xdfmn") +surface_material_override/0 = SubResource("StandardMaterial3D_lirtl")