Compare commits

..

16 Commits
main ... dev

57 changed files with 2008 additions and 0 deletions

View File

@ -0,0 +1,123 @@
extends Node3D
class_name Actor
enum ACTOR_STATE {INACTIVE, ACTIVE, IDLE, PAUSED}
var actor_state = null: set = state_change
const red = preload("res://tiles/base_tile/red.tres")
const blue = preload("res://tiles/base_tile/lightblue.tres")
var current_place_path: Array
var current_room_path: Array
var temp_speed = 4
var time = 0
var current_tile = null: set = change_current_tile
var current_room = null
var lookup_actor_to_task = []
var current_task: Dictionary : set = task_change
@onready var task_creator: TaskCreator = $TaskCreator
signal current_tile_request
func _process(delta: float) -> void:
match actor_state:
ACTOR_STATE.ACTIVE:
get_current_tile()
if current_room_path:
if current_tile.grid_pos == current_task.location:
temp_complete_task()
else:
if self.position.distance_to(current_room_path[0]) > .1:
position += position.direction_to(Vector3(current_room_path[0])) * 6 * delta
else:
current_room_path.pop_front()
ACTOR_STATE.IDLE:
if not lookup_actor_to_task.is_empty():
current_task = lookup_actor_to_task[0]
else:
actor_state = ACTOR_STATE.PAUSED
ACTOR_STATE.PAUSED:
pass
func state_change(state):
actor_state = state
match state:
ACTOR_STATE.ACTIVE:
$SelfMesh.set_material_override(red)
ACTOR_STATE.IDLE:
$SelfMesh.set_material_override(null)
ACTOR_STATE.PAUSED:
$SelfMesh.set_material_override(blue)
await get_tree().create_timer(1).timeout
temp_create_task()
actor_state = ACTOR_STATE.IDLE
func task_change(task):
current_task = task
if current_task:
actor_state = ACTOR_STATE.ACTIVE
get_room_path()
func get_current_tile():
#Requests the tile that this actor is currently on
emit_signal("current_tile_request", self)
func change_current_tile(new_tile):
#General function for when this actor enters a new tile
if current_tile != new_tile:
current_tile = new_tile
get_room()
func get_room():
#Gets the room that the current tile is in
#TODO: can't handle tiles being in more than one room
if current_tile.lookup_tile_to_room:
current_room = current_tile.lookup_tile_to_room
else:
current_room = null
func temp_create_task():
var temp_task_target = Vector3i(randi_range(1, 31), 0, randi_range(1, 31))
task_creator.create_task(
self,
self,
temp_task_target,
)
func temp_complete_task():
if lookup_actor_to_task:
var completed_task = lookup_actor_to_task.pop_front()
current_task = {}
task_creator.complete_task(self, completed_task)
actor_state = ACTOR_STATE.IDLE
func get_room_path():
get_current_tile()
var start = current_tile.grid_pos
var end = current_task.location
if destination_in_current_room(end):
current_room_path = current_room.get_room_path(start, end)
else:
temp_complete_task()
func destination_in_current_room(destination):
for i in current_room.room_to_tile:
if i.grid_pos == destination:
return true
return false

View File

@ -0,0 +1 @@
uid://dilighbu5t233

View File

@ -0,0 +1,14 @@
[gd_scene load_steps=4 format=3 uid="uid://jcmf542by4e6"]
[ext_resource type="Script" uid="uid://dilighbu5t233" path="res://actors/base_actor/base_actor.gd" id="1_fkyrj"]
[ext_resource type="PackedScene" uid="uid://btpcj4sv1qgck" path="res://task_creator.tscn" id="2_onjxy"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_wghh8"]
[node name="BaseActor" type="Node3D"]
script = ExtResource("1_fkyrj")
[node name="TaskCreator" parent="." instance=ExtResource("2_onjxy")]
[node name="SelfMesh" type="MeshInstance3D" parent="."]
mesh = SubResource("CapsuleMesh_wghh8")

View File

@ -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.

View File

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

View File

@ -0,0 +1 @@
uid://82dofbvl1g6n

View File

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

View File

@ -0,0 +1 @@
uid://cejorwceoqbqe

View File

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

View File

@ -0,0 +1 @@
uid://cnmoj6e2jq737

View File

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

View File

@ -0,0 +1 @@
uid://dn0k1lmeff72p

View File

@ -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=""

1
game.gd Normal file
View File

@ -0,0 +1 @@
extends Node

1
game.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://b2ytri51qn3r0

79
game.tscn Normal file
View File

@ -0,0 +1,79 @@
[gd_scene load_steps=6 format=3 uid="uid://x8xo5b1b3q41"]
[ext_resource type="Script" uid="uid://b2ytri51qn3r0" path="res://game.gd" id="1_feb5d"]
[ext_resource type="Script" uid="uid://bi7t6jxlfl0ln" path="res://interface/build_toggle.gd" id="2_7jktm"]
[ext_resource type="PackedScene" uid="uid://0d1d5e1u2fys" path="res://places/place_manager.tscn" id="2_fc0e3"]
[ext_resource type="Script" uid="uid://cgfibq8ku7tey" path="res://interface/game_cam.gd" id="3_7jktm"]
[ext_resource type="Script" uid="uid://bdf7ll8y2htic" path="res://interface/save_load.gd" id="5_7jktm"]
[node name="Game" type="Node"]
script = ExtResource("1_feb5d")
[node name="InterfaceLayer" type="CanvasLayer" parent="."]
[node name="BuildToggle" type="CheckButton" parent="InterfaceLayer"]
offset_left = 7.0
offset_top = 603.0
offset_right = 119.0
offset_bottom = 634.0
text = "Build A Room"
script = ExtResource("2_7jktm")
[node name="BuildButton" type="Button" parent="InterfaceLayer"]
offset_left = 172.0
offset_top = 604.0
offset_right = 243.0
offset_bottom = 635.0
text = "Confirm"
[node name="SaveButton" type="Button" parent="InterfaceLayer"]
offset_left = 995.0
offset_top = 609.0
offset_right = 1141.0
offset_bottom = 640.0
text = "Save Current Map
"
[node name="LoadButton" type="Button" parent="InterfaceLayer"]
offset_left = 996.0
offset_top = 573.0
offset_right = 1142.0
offset_bottom = 604.0
text = "Load Map"
[node name="GridButton" type="Button" parent="InterfaceLayer"]
offset_left = 18.0
offset_top = 568.0
offset_right = 89.0
offset_bottom = 599.0
text = "Init Grid"
[node name="DoorButton" type="Button" parent="InterfaceLayer"]
offset_left = 255.0
offset_top = 603.0
offset_right = 347.0
offset_bottom = 634.0
text = "Place Door"
[node name="WorkerButton" type="Button" parent="InterfaceLayer"]
offset_right = 8.0
offset_bottom = 8.0
text = "Place Worker"
[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
script = ExtResource("3_7jktm")
[node name="PlaceManager" parent="." instance=ExtResource("2_fc0e3")]
[node name="SaveLoad" type="Node" parent="."]
script = ExtResource("5_7jktm")
[connection signal="toggled" from="InterfaceLayer/BuildToggle" to="PlaceManager" method="_on_build_toggle"]
[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/GridButton" to="PlaceManager" method="_on_init_grid_button_pressed"]
[connection signal="pressed" from="InterfaceLayer/DoorButton" to="PlaceManager" method="_on_door_button_pressed"]
[connection signal="pressed" from="InterfaceLayer/WorkerButton" to="PlaceManager" method="_on_worker_button_pressed"]

View File

@ -0,0 +1,5 @@
extends CheckButton
func _on_room_built() -> void:
button_pressed = false

View File

@ -0,0 +1 @@
uid://bi7t6jxlfl0ln

26
interface/game_cam.gd Normal file
View File

@ -0,0 +1,26 @@
extends Camera3D
@export var cam_max_pan_speed = 0.5
@export var cam_max_zoom_speed = 2
func _process(delta: float) -> void:
move_camera()
func move_camera():
if Input.is_action_pressed("main_cam_up"):
position.z = position.z - cam_max_pan_speed
if Input.is_action_pressed("main_cam_right"):
position.x = position.x + cam_max_pan_speed
if Input.is_action_pressed("main_cam_down"):
position.z = position.z + cam_max_pan_speed
if Input.is_action_pressed("main_cam_left"):
position.x = position.x - cam_max_pan_speed
if Input.is_action_just_released("main_cam_in"):
fov = fov - cam_max_zoom_speed
if Input.is_action_just_released("main_cam_out"):
fov = fov + cam_max_zoom_speed

View File

@ -0,0 +1 @@
uid://cgfibq8ku7tey

181
interface/save_load.gd Normal file
View File

@ -0,0 +1,181 @@
extends Node
class SaveData:
var id: int
var type: String
var parent: String
var scene_file_path: String
var position: Vector3
var rotation_degrees
#Connections
var place_id: int
var room_ids: Array
var tile_ids: Array
var door_to_tile: Dictionary
#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,
"Door": Door,
})
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)
for node in save_nodes:
if not node.has_method("save"):
print("no method")
continue
var node_data = node.save()
var save_data = SaveData.new()
for i in node_data:
save_data.set(i, node_data[i])
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.
func _on_load_button_pressed():
if not FileAccess.file_exists("user://savegame.save"):
print("no save to load")
return
var save_nodes = get_tree().get_nodes_in_group("SaveObjects")
for i in save_nodes:
if i:
i.free()
var save_file = FileAccess.open("user://savegame.save", FileAccess.READ)
var place_id_dict: Dictionary = {}
var room_id_dict: Dictionary = {}
var tile_id_dict: Dictionary = {}
var load_place_to_room: Dictionary = {}
var load_place_to_tile: Dictionary = {}
var load_room_to_tile: Dictionary = {}
var load_room_to_door: Dictionary = {}
var load_tile_to_door: Array = []
while save_file.get_position() < save_file.get_length():
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()
var id = next_load["id"]
match next_load["type"]:
"Place":
place_id_dict[id] = load_object.get_instance_id()
load_place_to_room[load_object] = []
load_place_to_tile[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_to_tile[load_object] = []
load_room_to_door[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_to_room[place].append(load_object)
load_object.set(j, new_place_id)
else:
load_object.set(j, next_load[j])
instance_from_id(load_object["place_id"]).get_node("RoomContainer").add_child(load_object)
"Tile":
tile_id_dict[id] = load_object.get_instance_id()
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_to_tile[place].merge({next_load["grid_pos"]: load_object})
load_object.set(j, new_place_id)
elif j == "main_room":
var new_room_id = room_id_dict[j]
var room = instance_from_id(new_room_id)
load_room_to_tile[room].append(load_object)
load_object["lookup_tile_to_room"] = room
elif j == "face_dict":
if next_load[j]:
var temp_new_dict: Dictionary
for k in next_load[j]:
temp_new_dict[k] = null
load_object[j] = temp_new_dict.duplicate()
else:
load_object.set(j, next_load[j])
instance_from_id(load_object["place_id"]).get_node("TileContainer").add_child(load_object)
"Door":
for j in next_load.keys():
if j == "place_id":
var new_place_id = place_id_dict[next_load[j]]
load_object.set(j, new_place_id)
elif j == "room_ids":
for k in next_load["room_ids"]:
var new_room_id = room_id_dict[k]
var room = instance_from_id(new_room_id)
load_room_to_door[room].append(load_object)
load_object["lookup_door_to_room"].append(room)
elif j == "door_to_tile":
var tile_dict = next_load["door_to_tile"]
for k in tile_dict.keys():
var new_tile_id = tile_id_dict[tile_dict[k]]
var tile = instance_from_id(new_tile_id)
load_tile_to_door.append({tile: {k: load_object}})
load_object["lookup_door_to_tile"].merge({k: tile})
else:
load_object.set(j, next_load[j])
instance_from_id(load_object["place_id"]).get_node("ObjectContainer").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_to_room.keys():
i.lookup_place_to_room = load_place_to_room[i]
for i in load_place_to_tile.keys():
i.lookup_place_to_tile.merge(load_place_to_tile[i])
for i in load_room_to_tile.keys():
i.room_to_tile = load_room_to_tile[i]
for i in load_room_to_door.keys():
i.room_to_door = load_room_to_door[i]
for i in load_tile_to_door:
for j in i.keys():
for k in i[j].keys():
var one = i[j]
var two = one[k]
j.face_dict[k] = two

View File

@ -0,0 +1 @@
uid://bdf7ll8y2htic

5
objects/base_object.gd Normal file
View File

@ -0,0 +1,5 @@
extends Node3D
class_name GameObject
enum PLACEMENTS {DOOR}

View File

@ -0,0 +1 @@
uid://ca63k51v37jyg

View File

@ -0,0 +1,401 @@
extends Node3D
class_name Place
var tile_grid_size: int = 32
var load_tile = preload("res://tiles/base_tile/base_tile.tscn")
var load_room = preload("res://places/base_place/base_room.tscn")
var load_door = preload("res://tiles/base_tile/base_door.tscn")
var load_actor = preload("res://actors/base_actor/base_actor.tscn")
@onready var task_list: Node = $TaskList
#Contains all tiles in a workplace.
var lookup_place_to_tile: Dictionary = {}
#Contains all rooms in a workplace.
var lookup_place_to_room: Array = []
#Contains the tiles in the current selection drag
var selection_drag_dict: Dictionary = {}
#Contains all the tiles that have been selected in the current build action
var selection_dict: Dictionary = {}
#Tracks the previous amount of tiles counted in a selection drac
var tile_count_x_hist: int = 0
var tile_count_z_hist: int = 0
#Tracks if confirming a build is allowed
var build_confirm_allowed: bool = true
#The object that's currently being built
var current_object: Object = null
#Where that object can be placed
var current_placement: Array = []
#Tracks parts of the room currently being built
var current_room: Object = null
var map = PlaceAStar3D.new()
func save():
var save_data = {
#Basics
"id": self.get_instance_id(),
"type": "Place",
"scene_file_path": scene_file_path,
"parent": get_parent().get_path(),
#Connections
#Data
}
return save_data
func init_grid():
#TEMP: Sets up a simple 2D grid of blank tiles.
for x in range(tile_grid_size):
for z in range (tile_grid_size):
var pos = Vector3i(x, 0, z)
var tile: Object = load_tile.instantiate()
tile.set_position(pos)
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
$TileContainer.add_child(tile)
func draw_tile_click(click_pos):
#starts a selection drag
var build_start_pos: Vector3i = click_pos.floor()
if lookup_place_to_tile.has(build_start_pos):
if not lookup_place_to_tile[build_start_pos].construction_mode == Tile.CON_MODE.BUILT:
select_tile(build_start_pos)
func init_select_drag(float_build_start_pos, float_build_mouse_pos):
#Creats an array of dragged tiles between mouse start and current position
var select_drag_array: Array = []
var build_start_pos: Vector3i = float_build_start_pos.floor()
var build_mouse_pos: Vector3i = float_build_mouse_pos.floor()
var tile_count_x: int = build_mouse_pos.x - build_start_pos.x
var tile_count_z: int = build_mouse_pos.z - build_start_pos.z
if not tile_count_x_hist == tile_count_x or not tile_count_z_hist == tile_count_z:
tile_count_x_hist = tile_count_x
tile_count_z_hist = tile_count_z
for x in range(min(0, tile_count_x), max(0, tile_count_x) + 1):
for z in range(min(0, tile_count_z), max(0, tile_count_z) + 1):
var select_drag_pos: Vector3i = build_start_pos + Vector3i(x, 0, z)
if lookup_place_to_tile.has(select_drag_pos):
if not lookup_place_to_tile[select_drag_pos].construction_mode == Tile.CON_MODE.BUILT:
select_drag_array.append(select_drag_pos)
if select_drag_array:
draw_select_drag(select_drag_array)
func draw_select_drag(array):
#Clears previous drag, then calls tile selection on all currently dragged tiles
for i in lookup_place_to_tile:
if not selection_dict.has(i):
var tile: Object = lookup_place_to_tile[i]
tile.selection_mode = Tile.SEL_MODE.NONE
selection_drag_dict.clear()
for i in array:
select_tile(i)
func select_tile(pos):
#Tells tiles to be selected
var tile: Object = lookup_place_to_tile[pos]
selection_drag_dict[pos] = tile
if build_confirm_allowed:
tile.selection_mode = Tile.SEL_MODE.BUILD
else:
tile.selection_mode = Tile.SEL_MODE.INVALID
func verify_room():
#Verifies that a given selection is fully contiguous
var verify_array: Array = selection_dict.keys()
var verify_queue_array: Array = [verify_array[0]]
var verify_checked_array: Array = []
while verify_array:
if not verify_queue_array:
return false
var verify_pos: Vector3i = verify_queue_array.pop_back()
var verify_neighbor_array: Array = lookup_place_to_tile[verify_pos].direction_vector_dict.values()
for n in verify_neighbor_array:
if selection_dict.has(verify_pos + n):
if not verify_checked_array.has(verify_pos + n):
if not verify_queue_array.has(verify_pos + n):
verify_queue_array.append(verify_pos + n)
verify_checked_array.append(verify_pos)
verify_array.erase(verify_pos)
return true
func end_select_drag():
#Adds dragged tiles to the current selection on mouse-up
if not selection_drag_dict:
return
tile_count_x_hist = 0
tile_count_z_hist = 0
selection_dict.merge(selection_drag_dict)
if verify_room():
build_confirm_allowed = true
for i in selection_dict:
lookup_place_to_tile[i].selection_mode = Tile.SEL_MODE.BUILD
else:
build_confirm_allowed = false
for i in selection_dict:
lookup_place_to_tile[i].selection_mode = Tile.SEL_MODE.INVALID
selection_drag_dict.clear()
func build_selection():
#When the build button is clicked, changes the selected tiles to match the button's request
if not build_confirm_allowed:
return
if selection_dict:
var room: Object = create_room(selection_dict)
for i in selection_dict:
var tile: Object = lookup_place_to_tile[i]
tile.selection_mode = Tile.SEL_MODE.NONE
tile.construction_mode = Tile.CON_MODE.BUILT
selection_dict.clear()
return room
func clear_selection():
#When the clear button is clicked, it clears the selected tiles without doing anything.
for i in selection_dict:
var tile: Object = selection_dict[i]
tile.selection_mode = Tile.SEL_MODE.NONE
build_confirm_allowed = true
selection_dict.clear()
func create_room(selection):
#Creates a room from the selected tiles.
var room: Object = load_room.instantiate()
room.position = (selection.keys().min())
room.room_to_tile = selection.values()
room.place_id = self.get_instance_id()
room.name = str("Room", room.get_instance_id())
$RoomContainer.add_child(room)
for i in room.room_to_tile:
i.lookup_tile_to_room = room
lookup_place_to_room.append(room)
return room
func init_object(object):
#Instantiates the object to be placed
match object:
"door":
var door: Object = load_door.instantiate()
init_object_locations(door)
door.place_id = self.get_instance_id()
door.visible = false
$ObjectContainer.add_child(door)
current_object = door
"actor":
var actor: Actor = load_actor.instantiate()
current_object = actor
actor.visible = false
$ActorContainer.add_child(actor)
func init_object_locations(object):
#Gets the list of locations that the object can be placed
match object.placement:
GameObject.PLACEMENTS.DOOR:
for i in lookup_place_to_tile.keys():
for j in lookup_place_to_tile[i].face_dict.keys():
if lookup_place_to_tile[i].face_dict[j] is not Door:
current_placement.append((Vector3(i) + Vector3(0.5, 0, 0.5)) + Vector3(j) / 2)
func hover_object(mouse_pos):
#Hovers the object at the closest acceptable placement location to the mouse
current_object.visible = true
if current_placement:
var closest: Vector3
var closest_distance: float = INF
for i in current_placement:
var distance: float = mouse_pos.distance_to(i)
if closest_distance > distance:
closest_distance = distance
closest = i
current_object.position = closest
current_object.rotation_degrees = Vector3(0, 180 * fmod(closest.x, 1), 0)
else:
current_object.position = mouse_pos
func confirm_object():
#Places the object at the hovered location
#TODO: this is very sloppy
var check = current_object.get_class()
if current_object is Door:
var tile_1: Object
var tile_2: Object
if fmod(current_object.position.x, 1) == 0:
tile_1 = lookup_place_to_tile[Vector3i(current_object.position.x, 0, current_object.position.z)]
tile_2 = lookup_place_to_tile[Vector3i(current_object.position.x - 1, 0, current_object.position.z)]
else:
tile_1 = lookup_place_to_tile[Vector3i(current_object.position.x, 0, current_object.position.z)]
tile_2 = lookup_place_to_tile[Vector3i(current_object.position.x, 0, current_object.position.z - 1)]
var tile_1_new_dict = tile_1.face_dict.duplicate()
var tile_2_new_dict = tile_2.face_dict.duplicate()
var tile_1_door_face: Vector3i = tile_2.position - tile_1.position
tile_1_new_dict[tile_1_door_face] = current_object
tile_1.update_faces(tile_1_new_dict)
tile_1.lookup_tile_to_room.room_to_door.append(current_object)
tile_2.lookup_connected_rooms.append(tile_1.lookup_tile_to_room)
tile_2.lookup_tile_to_room.room_to_tile.append(tile_1)
var tile_2_door_face: Vector3i = tile_1.position - tile_2.position
tile_2_new_dict[tile_2_door_face] = current_object
tile_2.update_faces(tile_2_new_dict)
tile_2.lookup_tile_to_room.room_to_door.append(current_object)
tile_1.lookup_connected_rooms.append(tile_2.lookup_tile_to_room)
tile_1.lookup_tile_to_room.room_to_tile.append(tile_2)
current_object.lookup_door_to_room.append(tile_1.lookup_tile_to_room)
current_object.lookup_door_to_room.append(tile_2.lookup_tile_to_room)
current_object.lookup_door_to_tile[tile_1] = tile_1_door_face
current_object.lookup_door_to_tile[tile_2] = tile_2_door_face
var id = map.get_available_point_id()
map.add_point(id, current_object.position)
current_object.map_point = id
prints(map.get_point_ids())
for i in tile_1.lookup_tile_to_room.room_to_door:
if i.map_point != id:
map.connect_points(i.map_point, id)
for i in tile_2.lookup_tile_to_room.room_to_door:
if i.map_point != id:
map.connect_points(i.map_point, id)
prints(map.get_point_connections(id))
elif current_object is Actor:
current_object.actor_state = Actor.ACTOR_STATE.PAUSED
current_room = null
current_placement = []
current_object = null
func give_neighbors(tile, grid_pos, directions):
#Responds to a tile's request for it's neighbors
var neighbor_dict = {}
for i in directions.keys():
if lookup_place_to_tile.has(directions[i] + grid_pos):
neighbor_dict[directions[i]] = lookup_place_to_tile[directions[i] + grid_pos]
tile.neighbor_dict = neighbor_dict
func _on_tile_container_child_entered_tree(node: Node) -> void:
#Connects signals from created tiles
lookup_place_to_tile[node.grid_pos] = node
node.connect("neighbor_request", give_neighbors)
func _on_actor_container_child_entered_tree(node: Node) -> void:
#Connects signals from created actors
node.connect("current_tile_request", give_tile)
if node.has_node("TaskCreator"):
var tasker = node.get_node("TaskCreator")
tasker.connect("record_task", record_task)
tasker.connect("finish_task", finish_task)
func give_tile(actor):
#Responds to an actor's request for its current tile
#var closest: Vector3
#var closest_distance: float = INF
#
#for i in lookup_place_to_tile:
#var distance: float = actor.position.distance_to(i)
#if closest_distance > distance:
#closest_distance = distance
#closest = i
actor.current_tile = lookup_place_to_tile[Vector3i(floor(actor.position.x), 0, floor(actor.position.z))]
func record_task(task):
#Records created tasks in the task list.
task_list.record_task(task)
func finish_task(source, task):
#Marks tasks as completed in the task list.
task_list.finish_task(source, task)

View File

@ -0,0 +1 @@
uid://n4vvqmlmq5fp

View File

@ -0,0 +1,24 @@
[gd_scene load_steps=3 format=3 uid="uid://bujuwgn3y42ek"]
[ext_resource type="Script" uid="uid://n4vvqmlmq5fp" path="res://places/base_place/base_place.gd" id="1_uq3a8"]
[ext_resource type="Script" uid="uid://3tnkke3ugl3t" path="res://places/base_place/task_list.gd" id="2_tkpnh"]
[node name="BasePlace" type="Node3D" groups=["SaveObjects"]]
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="ObjectContainer" type="Node" parent="."]
[node name="ActorContainer" type="Node" parent="."]
[node name="TaskList" type="Node" parent="."]
script = ExtResource("2_tkpnh")
[connection signal="child_entered_tree" from="TileContainer" to="." method="_on_tile_container_child_entered_tree"]
[connection signal="child_entered_tree" from="ActorContainer" to="." method="_on_actor_container_child_entered_tree"]

View File

@ -0,0 +1,60 @@
extends Node
class_name Room
var room_to_tile = []: set = setup_path_grid
var path_grid = AStar3D.new()
var room_to_door = []
var place_id: int = 0
func save():
var save_data = {
#Basics
"id": self.get_instance_id(),
"type": "Room",
"scene_file_path": scene_file_path,
"parent": get_parent().get_instance_id(),
#Connections
"place_id": place_id,
#Data
}
return save_data
func init_room(new_dict):
#When a room is created, run through these steps
setup_path_grid(new_dict)
for i in new_dict.values():
i.room = self
func setup_path_grid(new_array):
#Sets up the A* grid of tiles that are in this room.
#TODO: Better grid changing
path_grid.clear()
room_to_tile = new_array
var room_to_tile_positions: Array
for i in room_to_tile:
var id = path_grid.get_available_point_id()
var point_pos = Vector3(i.grid_pos) + Vector3(0.5, 0, 0.5)
path_grid.add_point(id, point_pos)
room_to_tile_positions.append(point_pos)
for n in Tile.direction_vector_dict.values():
var neighbor_pos = point_pos + Vector3(n)
if room_to_tile_positions.has(neighbor_pos):
var closest = path_grid.get_closest_point(neighbor_pos)
if (neighbor_pos) == Vector3(path_grid.get_point_position(closest)):
path_grid.connect_points(id, closest)
func get_room_path(start, end):
for i in room_to_tile:
if i.grid_pos == end:
var start_point = path_grid.get_closest_point(Vector3(start) + Vector3(0.5, 0, 0.5))
var end_point = path_grid.get_closest_point(Vector3(end) + Vector3(0.5, 0, 0.5))
var path = path_grid.get_point_path(start_point, end_point)
return path
return false

View File

@ -0,0 +1 @@
uid://c2neixe7kpvma

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://f0b4ptwari8y"]
[ext_resource type="Script" uid="uid://c2neixe7kpvma" path="res://places/base_place/base_room.gd" id="1_3l4mp"]
[node name="BaseRoom" type="Node3D" groups=["SaveObjects"]]
script = ExtResource("1_3l4mp")

View File

@ -0,0 +1,2 @@
class_name PlaceAStar3D
extends AStar3D

View File

@ -0,0 +1 @@
uid://dk4yug4ofcoc3

View File

@ -0,0 +1,33 @@
extends Node
class_name TaskList
var unassigned_tasks = []
var assigned_task_dict: Dictionary = {}
func record_task(task):
#Records a provided task in the list
if task.assignment:
var assignment: Actor = task["assignment"]
#var assignment_task_list: Array = []
assign_task(task, assignment)
if not assigned_task_dict.has(assignment):
assigned_task_dict[assignment] = []
assigned_task_dict[assignment].append(task)
func finish_task(source, task):
#Removes a completed task from the list
var source_task_array = assigned_task_dict[source]
var finished_task_index = source_task_array.find(task)
source_task_array.pop_at(finished_task_index)
if not assigned_task_dict[source]:
assigned_task_dict.erase(source)
func assign_task(task, assignment: Actor):
#Alerts an actor that it has a new task.
assignment.lookup_actor_to_task.append(task)

View File

@ -0,0 +1 @@
uid://3tnkke3ugl3t

100
places/place_manager.gd Normal file
View File

@ -0,0 +1,100 @@
extends Node3D
var load_place = preload("res://places/base_place/base_place.tscn")
var place = null
enum ROOM_BUILD_STATE {NONE, SELECT, DRAG, CONFIRM, PLACE}
#Tracks the current build state.
var room_build_state = 0
var current_room = null
var current_object = null
#Tracks the position that was first clicked to start a build drag
var build_drag_start_pos = null
signal room_built
func _on_child_entered_tree(node: Node) -> void:
if node is Place:
place = node
func _on_build_toggle(toggled_on):
#Enables construction of a room
if toggled_on:
room_build_state = ROOM_BUILD_STATE.SELECT
else:
place.clear_selection()
room_build_state = ROOM_BUILD_STATE.NONE
func _on_confirm_button_pressed() -> void:
#Confirms the room that is currently being constructed
if place.selection_dict:
if room_build_state == ROOM_BUILD_STATE.SELECT:
if place.build_confirm_allowed:
current_room = place.build_selection()
room_build_state = ROOM_BUILD_STATE.NONE
else:
prints("Building not allowed.")
else:
prints("Nothing selected.")
func _on_init_grid_button_pressed() -> void:
#TEMP: Responds to the grid initialization button
if place:
place.queue_free()
var new_place = load_place.instantiate()
add_child(new_place)
place.init_grid()
func _on_door_button_pressed() -> void:
#Sets up door creation
match room_build_state:
ROOM_BUILD_STATE.NONE:
current_object = "door"
place.current_room = current_room
place.init_object(current_object)
room_build_state = ROOM_BUILD_STATE.PLACE
_:
return
func _on_worker_button_pressed() -> void:
#Sets up worker creation
match room_build_state:
ROOM_BUILD_STATE.NONE:
current_object = "actor"
place.init_object(current_object)
room_build_state = ROOM_BUILD_STATE.PLACE
func _on_area_3d_input_event(_camera, _event, event_position, _normal, _shade_id):
#Checks input events from the mouse plane
match room_build_state:
ROOM_BUILD_STATE.SELECT:
if Input.is_action_pressed("select"):
room_build_state = ROOM_BUILD_STATE.DRAG
build_drag_start_pos = event_position
place.draw_tile_click(build_drag_start_pos)
ROOM_BUILD_STATE.DRAG:
if Input.is_action_pressed("select"):
place.init_select_drag(build_drag_start_pos, event_position)
else:
room_build_state = ROOM_BUILD_STATE.SELECT
place.end_select_drag()
ROOM_BUILD_STATE.PLACE:
if Input.is_action_pressed("select"):
room_build_state = ROOM_BUILD_STATE.NONE
place.confirm_object()
emit_signal("room_built")
else:
place.hover_object(event_position)

View File

@ -0,0 +1 @@
uid://d24sr2dat4lj0

18
places/place_manager.tscn Normal file
View File

@ -0,0 +1,18 @@
[gd_scene load_steps=3 format=3 uid="uid://0d1d5e1u2fys"]
[ext_resource type="Script" uid="uid://d24sr2dat4lj0" path="res://places/place_manager.gd" id="1_rcbs8"]
[sub_resource type="BoxShape3D" id="BoxShape3D_rcbs8"]
size = Vector3(256, 0, 256)
[node name="PlaceManager" type="Node3D"]
script = ExtResource("1_rcbs8")
[node name="FloorPlane" type="Area3D" parent="."]
[node name="CollisionShape3D" type="CollisionShape3D" parent="FloorPlane"]
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"]

View File

@ -0,0 +1,18 @@
[gd_scene load_steps=3 format=3 uid="uid://0d1d5e1u2fys"]
[ext_resource type="Script" uid="uid://d24sr2dat4lj0" path="res://places/place_manager.gd" id="1_rcbs8"]
[sub_resource type="BoxShape3D" id="BoxShape3D_rcbs8"]
size = Vector3(256, 0, 256)
[node name="PlaceManager" type="Node3D"]
script = ExtResource("1_rcbs8")
[node name="FloorPlane" type="Area3D" parent="."]
[node name="CollisionShape3D" type="CollisionShape3D" parent="FloorPlane"]
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"]

View File

@ -11,5 +11,59 @@ config_version=5
[application] [application]
config/name="Project Villain" config/name="Project Villain"
run/main_scene="uid://x8xo5b1b3q41"
config/features=PackedStringArray("4.4", "Forward Plus") config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[global_group]
SaveObjects="Anything that will need to be saved when the game is."
[input]
select={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null)
]
}
main_cam_up={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":79,"key_label":0,"unicode":111,"location":0,"echo":false,"script":null)
]
}
main_cam_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
main_cam_down={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
main_cam_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":105,"location":0,"echo":false,"script":null)
]
}
main_cam_in={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
main_cam_out={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
[physics]
3d/default_gravity=0.0
3d/default_gravity_vector=Vector3(0, 0, 0)
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"

37
task_creator.gd Normal file
View File

@ -0,0 +1,37 @@
extends Node
class_name TaskCreator
signal record_task
signal finish_task
var task_list = null
func _ready():
pass
func create_task(
source: Object,
assignment = null,
location = null,
interaction = null,
urgency = null,
):
#Creates a task with the information provided and sends it to the list.
var task: Dictionary = {
"source": source,
"assignment": assignment,
"location": location,
"interaction": interaction,
"urgency": urgency,
}
emit_signal("record_task", task)
func complete_task(
source,
task
):
#Requests removal of a completed task
emit_signal("finish_task", source, task)

1
task_creator.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://crchqrpts6q1i

6
task_creator.tscn Normal file
View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://btpcj4sv1qgck"]
[ext_resource type="Script" uid="uid://crchqrpts6q1i" path="res://task_creator.gd" id="1_atcl4"]
[node name="TaskCreator" type="Node"]
script = ExtResource("1_atcl4")

View File

@ -0,0 +1,47 @@
extends GameObject
class_name Door
var place_id: int
var lookup_door_to_room = []
var lookup_door_to_tile = {}
var direction = null
var placement = GameObject.PLACEMENTS.DOOR
var map_point: int
func save():
var room_ids: Array
var door_to_tile: Dictionary
var door_to_tile_direction: Dictionary = {}
for i in lookup_door_to_room:
room_ids.append(i.get_instance_id())
for i in lookup_door_to_tile.keys():
var id = i.get_instance_id()
door_to_tile[lookup_door_to_tile[i]] = id
door_to_tile_direction[id] = i.face_dict.find_key(self)
var save_data = {
#Basics
"id": self.get_instance_id(),
"type": "Door",
"scene_file_path": scene_file_path,
"parent": get_parent().get_path(),
#Connections
"place_id": place_id,
"room_ids": room_ids,
"door_to_tile": door_to_tile,
"door_to_tile_direction": door_to_tile_direction,
#Data
"position": position,
"rotation_degrees": rotation_degrees
}
return save_data

View File

@ -0,0 +1 @@
uid://dptw3xl225fxk

View File

@ -0,0 +1,16 @@
[gd_scene load_steps=4 format=3 uid="uid://b6qehpvvnv5r3"]
[ext_resource type="Script" uid="uid://dptw3xl225fxk" path="res://tiles/base_tile/base_door.gd" id="1_8olbq"]
[sub_resource type="BoxMesh" id="BoxMesh_8olbq"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8olbq"]
albedo_color = Color(0.625824, 0.328179, 0.426969, 1)
[node name="BaseDoor" type="Node3D" groups=["SaveObjects"]]
script = ExtResource("1_8olbq")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(-4.37114e-08, 0, 0.4, 0, 0.5, 0, -1, 0, -1.74846e-08, 0, 0.25, 0)
mesh = SubResource("BoxMesh_8olbq")
surface_material_override/0 = SubResource("StandardMaterial3D_8olbq")

View File

@ -0,0 +1,4 @@
[gd_resource type="PlaneMesh" format=3 uid="uid://bis4hdushjnjm"]
[resource]
size = Vector2(1, 1)

View File

@ -0,0 +1,161 @@
extends Node3D
class_name Tile
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
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}
#List of vectors for each direction a wall can be in
const direction_vector_dict = {
"North": Vector3i(0, 0, -1),
"East": Vector3i(1, 0, 0),
"South": Vector3i(0, 0, 1),
"West": Vector3i(-1, 0, 0),
}
#TODO: replace this with better math on the above 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),
}
const load_wall = preload("res://tiles/base_tile/base_wall.tscn")
var grid_pos: Vector3i
var face_dict = {}: set = update_faces
var lookup_tile_to_room: Object
var lookup_connected_rooms: Array = []
var neighbor_dict = {}
signal neighbor_request
func save():
#var room_ids: Array
#for i in lookup_tile_to_room:
#room_ids.append(i.get_instance_id())
var save_data = {
#Basics
"id": self.get_instance_id(),
"type": "Tile",
"scene_file_path": scene_file_path,
"parent": get_parent().get_path(),
#Connections
"place_id": place_id,
"main_room": lookup_tile_to_room,
"connected_rooms": lookup_connected_rooms,
#Data
"position": position,
"grid_pos": grid_pos,
"face_dict": face_dict.keys(),
"construction_mode": construction_mode,
}
return save_data
func update_faces(new_dict):
#Changes the walls when relevant
for i in new_dict.keys():
if face_dict.has(i):
if face_dict[i] == new_dict[i]:
continue
face_dict[i].queue_free()
face_dict[i] = null
if new_dict[i] is Door:
face_dict[i] = new_dict[i]
continue
var direction: String = direction_vector_dict.find_key(i)
var wall = load_wall.instantiate()
wall.position = wall_position_dict[direction]
wall.name = str(direction, "Wall")
match direction:
"East":
wall.rotation_degrees = Vector3(0, -90, 0)
"South":
wall.rotation_degrees = Vector3(0, -180, 0)
"West":
wall.rotation_degrees = Vector3(0, -270, 0)
$Walls.add_child(wall)
face_dict[i] = wall
func update_neighbors():
#Requests the current neighboring tiles
neighbor_dict = {}
emit_signal("neighbor_request", self, grid_pos, direction_vector_dict)
pass
func update_construction(mode):
#Changes the tile based on its construction mode
construction_mode = mode
match mode:
CON_MODE.NON_INTERACTABLE:
face_dict = {}
CON_MODE.CLOSED:
face_dict = {}
$Floor/FloorMesh.set_material_override(gray)
CON_MODE.OPEN:
face_dict = {}
CON_MODE.BUILT:
$Floor/FloorMesh.set_material_override(null)
if not face_dict:
update_neighbors()
var temp_face_dict = {}
for i in neighbor_dict.keys():
if not neighbor_dict[i].lookup_tile_to_room == lookup_tile_to_room:
temp_face_dict[i] = null
face_dict = temp_face_dict.duplicate()
CON_MODE.REINFORCED:
face_dict = {}
CON_MODE.CONSTRUCTION:
face_dict = {}
func update_selection(mode):
#Changes the tile based on whether it has been selected
selection_mode = mode
match mode:
SEL_MODE.NONE:
$Floor/FloorMesh.set_material_overlay(null)
SEL_MODE.ROOM:
for i in $Walls.get_children():
i.Mesh.set_material_override(orange)
SEL_MODE.BUILD:
$Floor/FloorMesh.set_material_overlay(lightblue)
SEL_MODE.INVALID:
$Floor/FloorMesh.set_material_overlay(orange)

View File

@ -0,0 +1 @@
uid://jqjcr7dxjnbt

View File

@ -0,0 +1,21 @@
[gd_scene load_steps=3 format=3 uid="uid://gs6yynwvvot2"]
[ext_resource type="Script" uid="uid://jqjcr7dxjnbt" path="res://tiles/base_tile/base_tile.gd" id="1_yoisu"]
[sub_resource type="PlaneMesh" id="PlaneMesh_6tbiv"]
size = Vector2(1, 1)
[node name="BaseTile" type="Node3D" groups=["SaveObjects"]]
script = ExtResource("1_yoisu")
[node name="Floor" type="Node3D" parent="."]
[node name="FloorMesh" type="MeshInstance3D" parent="Floor"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 0.5)
mesh = SubResource("PlaneMesh_6tbiv")
skeleton = NodePath("../..")
[node name="Walls" type="Node3D" parent="."]
[node name="Label3D" type="Label3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.0668138, 0.997765, 0, -0.997765, 0.0668138, 0.341829, 0.0223614, 0.130147)

View File

@ -0,0 +1,3 @@
extends Node3D
class_name Wall

View File

@ -0,0 +1 @@
uid://cx6bmej4pcjlt

View File

@ -0,0 +1,16 @@
[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"]
[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")

View File

@ -0,0 +1,4 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://cunwv438ndyai"]
[resource]
albedo_color = Color(0.0629363, 0.460903, 0.811309, 1)

View File

@ -0,0 +1,4 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://cm5lg0k4vihh3"]
[resource]
albedo_color = Color(0.143918, 0.143918, 0.143918, 1)

View File

@ -0,0 +1,4 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://5s3s8phwidfy"]
[resource]
albedo_color = Color(0.638985, 0.806077, 0.995958, 1)

View File

@ -0,0 +1,4 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://dbfgv7cy7t7vo"]
[resource]
albedo_color = Color(1, 0.522709, 0.205897, 1)

4
tiles/base_tile/red.tres Normal file
View File

@ -0,0 +1,4 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://nkwjhj76xffi"]
[resource]
albedo_color = Color(0.996113, 0, 0.229677, 1)