Compare commits
16 commits
v0.0.0-rc.
...
trunk
Author | SHA1 | Date | |
---|---|---|---|
e672567b3d | |||
cb480406c0 | |||
|
20466ed45f | ||
31075ba2b5 | |||
8b61ddb89e | |||
ca2fb20e6f | |||
8342b744a7 | |||
|
e6c1d4bf04 | ||
e509eedffe | |||
3abd94d604 | |||
|
f6e650b671 | ||
|
b1c2dd4eb5 | ||
8cafd27ff4 | |||
|
49f8d8608a | ||
38a2991198 | |||
1531505d2f |
44 changed files with 1371 additions and 1772 deletions
4
Justfile
4
Justfile
|
@ -3,9 +3,9 @@ _choose:
|
|||
|
||||
lint:
|
||||
-gdlint .
|
||||
-gdformat -d .
|
||||
-gdformat .
|
||||
|
||||
api_version := "v0.0.0-rc.4"
|
||||
api_version := "v0.0.0-rc.8"
|
||||
api_location := "scripts/api-client"
|
||||
generate:
|
||||
#!/bin/sh
|
||||
|
|
BIN
addons/JsonClassConverter/AssetIcon.jpg
Normal file
BIN
addons/JsonClassConverter/AssetIcon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
34
addons/JsonClassConverter/AssetIcon.jpg.import
Normal file
34
addons/JsonClassConverter/AssetIcon.jpg.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cbt51sclatky7"
|
||||
path="res://.godot/imported/AssetIcon.jpg-09518bcc644832efd4eedeb1abc08598.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/JsonClassConverter/AssetIcon.jpg"
|
||||
dest_files=["res://.godot/imported/AssetIcon.jpg-09518bcc644832efd4eedeb1abc08598.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
310
addons/JsonClassConverter/JsonClassConverter.gd
Normal file
310
addons/JsonClassConverter/JsonClassConverter.gd
Normal file
|
@ -0,0 +1,310 @@
|
|||
class_name JsonClassConverter
|
||||
|
||||
# Flag to control whether to save nested resources as separate .tres files
|
||||
static var save_temp_resources_tres: bool = false
|
||||
|
||||
## Checks if the provided class is valid (not null)
|
||||
static func _check_cast_class(castClass: GDScript) -> bool:
|
||||
if typeof(castClass) == Variant.Type.TYPE_NIL:
|
||||
printerr("The provided class is null.")
|
||||
return false
|
||||
return true
|
||||
|
||||
## Checks if the directory for the given file path exists, creating it if necessary.
|
||||
static func check_dir(file_path: String) -> void:
|
||||
if !DirAccess.dir_exists_absolute(file_path.get_base_dir()):
|
||||
DirAccess.make_dir_absolute(file_path.get_base_dir())
|
||||
|
||||
#region Json to Class
|
||||
|
||||
## Loads a JSON file and parses it into a Dictionary.
|
||||
## Supports optional decryption using a security key.
|
||||
static func json_file_to_dict(file_path: String, security_key: String = "") -> Dictionary:
|
||||
var file: FileAccess
|
||||
if FileAccess.file_exists(file_path):
|
||||
if security_key.length() == 0:
|
||||
file = FileAccess.open(file_path, FileAccess.READ)
|
||||
else:
|
||||
file = FileAccess.open_encrypted_with_pass(file_path, FileAccess.READ, security_key)
|
||||
if not file:
|
||||
printerr("Error opening file: ", file_path)
|
||||
return {}
|
||||
var parsed_results: Variant = JSON.parse_string(file.get_as_text())
|
||||
file.close()
|
||||
if parsed_results is Dictionary or parsed_results is Array:
|
||||
return parsed_results
|
||||
return {}
|
||||
|
||||
## Loads a JSON file and converts its contents into a Godot class instance.
|
||||
## Uses the provided GDScript (castClass) as a template for the class.
|
||||
static func json_file_to_class(castClass: GDScript, file_path: String, security_key: String = "") -> Object:
|
||||
if not _check_cast_class(castClass):
|
||||
printerr("The provided class is null.")
|
||||
return null
|
||||
var parsed_results = json_file_to_dict(file_path, security_key)
|
||||
if parsed_results == null:
|
||||
return castClass.new()
|
||||
return json_to_class(castClass, parsed_results)
|
||||
|
||||
## Converts a JSON string into a Godot class instance.
|
||||
static func json_string_to_class(castClass: GDScript, json_string: String) -> Object:
|
||||
if not _check_cast_class(castClass):
|
||||
printerr("The provided class is null.")
|
||||
return null
|
||||
var json: JSON = JSON.new()
|
||||
var parse_result: Error = json.parse(json_string)
|
||||
if parse_result == Error.OK:
|
||||
return json_to_class(castClass, json.data)
|
||||
return castClass.new()
|
||||
|
||||
## Converts a JSON dictionary into a Godot class instance.
|
||||
## This is the core deserialization function.
|
||||
static func json_to_class(castClass: GDScript, json: Dictionary) -> Object:
|
||||
# Create an instance of the target class
|
||||
var _class: Object = castClass.new() as Object
|
||||
var properties: Array = _class.get_property_list()
|
||||
|
||||
# Iterate through each key-value pair in the JSON dictionary
|
||||
for key: String in json.keys():
|
||||
var value: Variant = json[key]
|
||||
|
||||
# Special handling for Vector types (stored as strings in JSON)
|
||||
if type_string(typeof(value)) == "String" and value.begins_with("Vector"):
|
||||
value = str_to_var(value)
|
||||
|
||||
# Find the matching property in the target class
|
||||
for property: Dictionary in properties:
|
||||
# Skip the 'script' property (built-in)
|
||||
if property.name == "script":
|
||||
continue
|
||||
|
||||
# Get the current value of the property in the class instance
|
||||
var property_value: Variant = _class.get(property.name)
|
||||
|
||||
# If the property name matches the JSON key and is a script variable:
|
||||
if property.name == key and property.usage >= PROPERTY_USAGE_SCRIPT_VARIABLE:
|
||||
# Case 1: Property is an Object (not an array)
|
||||
if not property_value is Array and property.type == TYPE_OBJECT:
|
||||
var inner_class_path: String = ""
|
||||
if property_value:
|
||||
# If the property already holds an object, try to get its script path
|
||||
for inner_property: Dictionary in property_value.get_property_list():
|
||||
if inner_property.has("hint_string") and inner_property["hint_string"].contains(".gd"):
|
||||
inner_class_path = inner_property["hint_string"]
|
||||
# Recursively deserialize nested objects
|
||||
_class.set(property.name, json_to_class(load(inner_class_path), value))
|
||||
elif value:
|
||||
var script_type: GDScript = null
|
||||
# Determine the script type for the nested object
|
||||
if value is Dictionary and value.has("script_inheritance"):
|
||||
script_type = get_gdscript(value["script_inheritance"])
|
||||
else:
|
||||
script_type = get_gdscript(property. class_name )
|
||||
|
||||
# If the value is a resource path, load the resource
|
||||
if value is String and value.is_absolute_path():
|
||||
_class.set(property.name, ResourceLoader.load(get_main_tres_path(value)))
|
||||
else:
|
||||
# Recursively deserialize nested objects
|
||||
_class.set(property.name, json_to_class(script_type, value))
|
||||
|
||||
# Case 2: Property is an Array
|
||||
elif property_value is Array:
|
||||
if property.has("hint_string"):
|
||||
var class_hint: String = property["hint_string"]
|
||||
if class_hint.contains(":"):
|
||||
# Extract class name from hint string (e.g., "24/34:ClassName")
|
||||
class_hint = class_hint.split(":")[1]
|
||||
|
||||
# Recursively convert the JSON array to a Godot array
|
||||
var arrayTemp: Array = convert_json_to_array(value, get_gdscript(class_hint))
|
||||
|
||||
# Handle Vector arrays (convert string elements back to Vectors)
|
||||
if type_string(property_value.get_typed_builtin()).begins_with("Vector"):
|
||||
for obj_array: Variant in arrayTemp:
|
||||
_class.get(property.name).append(str_to_var(obj_array))
|
||||
else:
|
||||
_class.get(property.name).assign(arrayTemp)
|
||||
|
||||
# Case 3: Property is a simple type (not an object or array)
|
||||
else:
|
||||
# Special handling for Color type (stored as a hex string)
|
||||
if property.type == TYPE_COLOR:
|
||||
value = Color(value)
|
||||
if property.type == TYPE_INT and property.hint == PROPERTY_HINT_ENUM:
|
||||
var enum_strs: Array = property.hint_string.split(",")
|
||||
var enum_value: int = 0
|
||||
for enum_str: String in enum_strs:
|
||||
if enum_str.contains(":"):
|
||||
var enum_keys: Array = enum_str.split(":")
|
||||
for i: int in enum_keys.size():
|
||||
if enum_keys[i].to_lower() == value.to_lower():
|
||||
enum_value = int(enum_keys[i + 1])
|
||||
_class.set(property.name, enum_value)
|
||||
else:
|
||||
_class.set(property.name, value)
|
||||
|
||||
# Return the fully deserialized class instance
|
||||
return _class
|
||||
|
||||
## Helper function to find a GDScript by its class name.
|
||||
static func get_gdscript(hint_class: String) -> GDScript:
|
||||
for className: Dictionary in ProjectSettings.get_global_class_list():
|
||||
if className. class == hint_class:
|
||||
return load(className.path)
|
||||
return null
|
||||
|
||||
## Helper function to recursively convert JSON arrays to Godot arrays.
|
||||
static func convert_json_to_array(json_array: Array, cast_class: GDScript = null) -> Array:
|
||||
var godot_array: Array = []
|
||||
for element: Variant in json_array:
|
||||
if typeof(element) == TYPE_DICTIONARY:
|
||||
# If json element has a script_inheritance, get the script (for inheritance or for untyped array/dictionary)
|
||||
if "script_inheritance" in element:
|
||||
cast_class = get_gdscript(element["script_inheritance"])
|
||||
godot_array.append(json_to_class(cast_class, element))
|
||||
elif typeof(element) == TYPE_ARRAY:
|
||||
godot_array.append(convert_json_to_array(element))
|
||||
else:
|
||||
godot_array.append(element)
|
||||
return godot_array
|
||||
|
||||
#endregion
|
||||
|
||||
#region Class to Json
|
||||
## Stores a JSON dictionary to a file, optionally with encryption.
|
||||
static func store_json_file(file_path: String, data: Dictionary, security_key: String = "") -> bool:
|
||||
check_dir(file_path)
|
||||
var file: FileAccess
|
||||
if security_key.length() == 0:
|
||||
file = FileAccess.open(file_path, FileAccess.WRITE)
|
||||
else:
|
||||
file = FileAccess.open_encrypted_with_pass(file_path, FileAccess.WRITE, security_key)
|
||||
if not file:
|
||||
printerr("Error writing to a file")
|
||||
return false
|
||||
var json_string: String = JSON.stringify(data, "\t")
|
||||
file.store_string(json_string)
|
||||
file.close()
|
||||
return true
|
||||
|
||||
## Converts a Godot class instance into a JSON string.
|
||||
static func class_to_json_string(_class: Object, save_temp_res: bool = false) -> String:
|
||||
return JSON.stringify(class_to_json(_class, save_temp_res))
|
||||
|
||||
## Converts a Godot class instance into a JSON dictionary.
|
||||
## This is the core serialization function.
|
||||
static func class_to_json(_class: Object, save_temp_res: bool = false, inheritance: bool = false) -> Dictionary:
|
||||
var dictionary: Dictionary = {}
|
||||
save_temp_resources_tres = save_temp_res
|
||||
# Store the script name for reference during deserialization if inheritance exists
|
||||
if inheritance:
|
||||
dictionary["script_inheritance"] = _class.get_script().get_global_name()
|
||||
var properties: Array = _class.get_property_list()
|
||||
|
||||
# Iterate through each property of the class
|
||||
for property: Dictionary in properties:
|
||||
var property_name: String = property["name"]
|
||||
# Skip the built-in 'script' property
|
||||
if property_name == "script":
|
||||
continue
|
||||
var property_value: Variant = _class.get(property_name)
|
||||
|
||||
# Only serialize properties that are exported or marked for storage
|
||||
if not property_name.is_empty() and property.usage >= PROPERTY_USAGE_SCRIPT_VARIABLE and property.usage & PROPERTY_USAGE_STORAGE > 0:
|
||||
if property_value is Array:
|
||||
# Recursively convert arrays to JSON
|
||||
dictionary[property_name] = convert_array_to_json(property_value)
|
||||
elif property_value is Dictionary:
|
||||
# Recursively convert dictionaries to JSON
|
||||
dictionary[property_name] = convert_dictionary_to_json(property_value)
|
||||
# If the property is a Resource:
|
||||
elif property["type"] == TYPE_OBJECT and property_value != null and property_value.get_property_list():
|
||||
if property_value is Resource and ResourceLoader.exists(property_value.resource_path):
|
||||
var main_src: String = get_main_tres_path(property_value.resource_path)
|
||||
if main_src.get_extension() != "tres":
|
||||
# Store the resource path if it's not a .tres file
|
||||
dictionary[property.name] = property_value.resource_path
|
||||
elif save_temp_resources_tres:
|
||||
# Save the resource as a separate .tres file
|
||||
var tempfile = "user://temp_resource/"
|
||||
check_dir(tempfile)
|
||||
var nodePath: String = get_node_tres_path(property_value.resource_path)
|
||||
if not nodePath.is_empty():
|
||||
tempfile += nodePath
|
||||
tempfile += ".tres"
|
||||
else:
|
||||
tempfile += property_value.resource_path.get_file()
|
||||
dictionary[property.name] = tempfile
|
||||
ResourceSaver.save(property_value, tempfile)
|
||||
else:
|
||||
# Recursively serialize the nested resource
|
||||
dictionary[property.name] = class_to_json(property_value, save_temp_resources_tres)
|
||||
else:
|
||||
dictionary[property.name] = class_to_json(property_value, save_temp_resources_tres, property. class_name != property_value.get_script().get_global_name())
|
||||
# Special handling for Vector types (store as strings)
|
||||
elif type_string(typeof(property_value)).begins_with("Vector"):
|
||||
dictionary[property_name] = var_to_str(property_value)
|
||||
elif property["type"] == TYPE_COLOR:
|
||||
# Store Color as a hex string
|
||||
dictionary[property_name] = property_value.to_html()
|
||||
else:
|
||||
# Store other basic types directly
|
||||
if property.type == TYPE_INT and property.hint == PROPERTY_HINT_ENUM:
|
||||
var enum_value: String = property.hint_string.split(",")[property_value]
|
||||
if enum_value.contains(":"):
|
||||
dictionary[property.name] = enum_value.split(":")[0]
|
||||
else:
|
||||
dictionary[property.name] = enum_value
|
||||
else:
|
||||
dictionary[property.name] = property_value
|
||||
return dictionary
|
||||
|
||||
## Extracts the main path from a resource path (removes node path if present).
|
||||
static func get_main_tres_path(path: String) -> String:
|
||||
var path_parts: PackedStringArray = path.split("::", true, 1)
|
||||
if path_parts.size() > 0:
|
||||
return path_parts[0]
|
||||
else:
|
||||
return path
|
||||
|
||||
## Extracts the node path from a resource path.
|
||||
static func get_node_tres_path(path: String) -> String:
|
||||
var path_parts: PackedStringArray = path.split("::", true, 1)
|
||||
if path_parts.size() > 1:
|
||||
return path_parts[1]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
## Helper function to recursively convert Godot arrays to JSON arrays.
|
||||
static func convert_array_to_json(array: Array) -> Array:
|
||||
var json_array: Array = []
|
||||
for element: Variant in array:
|
||||
if element is Object:
|
||||
json_array.append(class_to_json(element, save_temp_resources_tres,!array.is_typed()))
|
||||
elif element is Array:
|
||||
json_array.append(convert_array_to_json(element))
|
||||
elif element is Dictionary:
|
||||
json_array.append(convert_dictionary_to_json(element))
|
||||
elif type_string(typeof(element)).begins_with("Vector"):
|
||||
json_array.append(var_to_str(element))
|
||||
else:
|
||||
json_array.append(element)
|
||||
return json_array
|
||||
|
||||
## Helper function to recursively convert Godot dictionaries to JSON dictionaries.
|
||||
static func convert_dictionary_to_json(dictionary: Dictionary) -> Dictionary:
|
||||
var json_dictionary: Dictionary = {}
|
||||
for key: Variant in dictionary.keys():
|
||||
var value: Variant = dictionary[key]
|
||||
if value is Object:
|
||||
json_dictionary[key] = class_to_json(value, save_temp_resources_tres)
|
||||
elif value is Array:
|
||||
json_dictionary[key] = convert_array_to_json(value)
|
||||
elif value is Dictionary:
|
||||
json_dictionary[key] = convert_dictionary_to_json(value)
|
||||
else:
|
||||
json_dictionary[key] = value
|
||||
return json_dictionary
|
||||
#endregion
|
1
addons/JsonClassConverter/JsonClassConverter.gd.uid
Normal file
1
addons/JsonClassConverter/JsonClassConverter.gd.uid
Normal file
|
@ -0,0 +1 @@
|
|||
uid://ch1r4c54yetfx
|
2
gdlintrc
2
gdlintrc
|
@ -37,7 +37,7 @@ loop-variable-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
|||
max-file-lines: 1000
|
||||
max-line-length: 80
|
||||
max-public-methods: 20
|
||||
max-returns: 6
|
||||
max-returns: 20
|
||||
mixed-tabs-and-spaces: null
|
||||
no-elif-return: null
|
||||
no-else-return: null
|
||||
|
|
|
@ -15,6 +15,18 @@ run/main_scene="res://scenes/main_menu.tscn"
|
|||
config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
config/icon="res://textures/icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
ConnectionChannel="*res://scripts/channel/connection/connection_channel.gd"
|
||||
MatchmakingChannel="*res://scripts/channel/matchmaking/matchmaking_channel.gd"
|
||||
MatchChannel="*res://scripts/channel/match/match_channel.gd"
|
||||
CurrentMatch="*res://scripts/match/current_match.gd"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1920
|
||||
window/size/viewport_height=1080
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/format_on_save/plugin.cfg")
|
||||
|
@ -23,6 +35,14 @@ enabled=PackedStringArray("res://addons/format_on_save/plugin.cfg")
|
|||
|
||||
theme/custom="res://ui/theme.tres"
|
||||
|
||||
[input]
|
||||
|
||||
place={
|
||||
"deadzone": 0.0,
|
||||
"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)
|
||||
]
|
||||
}
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
|
|
107
scenes/game.tscn
Normal file
107
scenes/game.tscn
Normal file
|
@ -0,0 +1,107 @@
|
|||
[gd_scene load_steps=7 format=4 uid="uid://co1x3hlc2efr6"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/match/ui/opponent_name.gd" id="1_saby1"]
|
||||
[ext_resource type="Script" path="res://scripts/match/ui/player_money.gd" id="2_3jcub"]
|
||||
[ext_resource type="TileSet" uid="uid://cvp867israfjk" path="res://tile_set.tres" id="3_0etcd"]
|
||||
[ext_resource type="Script" path="res://scripts/match/ui/player_hitpoints.gd" id="3_oc5vk"]
|
||||
[ext_resource type="Script" path="res://scripts/match/map.gd" id="4_4c550"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_86u83"]
|
||||
bg_color = Color(0.198848, 0.584132, 0.720474, 1)
|
||||
|
||||
[node name="Game" type="Node2D"]
|
||||
|
||||
[node name="UI" type="CanvasLayer" parent="."]
|
||||
|
||||
[node name="HUD" type="Control" parent="UI"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Opponent Name" type="RichTextLabel" parent="UI/HUD"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -0.5
|
||||
offset_right = 0.5
|
||||
offset_bottom = 45.0
|
||||
grow_horizontal = 2
|
||||
text = "Unknown"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
autowrap_mode = 0
|
||||
script = ExtResource("1_saby1")
|
||||
|
||||
[node name="Money" type="RichTextLabel" parent="UI/HUD"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -582.0
|
||||
offset_top = -529.0
|
||||
offset_right = -436.0
|
||||
offset_bottom = -484.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "Test"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
autowrap_mode = 0
|
||||
script = ExtResource("2_3jcub")
|
||||
|
||||
[node name="Hitpoints" type="RichTextLabel" parent="UI/HUD"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -946.0
|
||||
offset_top = 479.0
|
||||
offset_right = -800.0
|
||||
offset_bottom = 524.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "HP
|
||||
"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
autowrap_mode = 0
|
||||
script = ExtResource("3_oc5vk")
|
||||
|
||||
[node name="Seperator" type="Panel" parent="UI/HUD"]
|
||||
custom_minimum_size = Vector2(4, 0)
|
||||
layout_mode = 1
|
||||
anchors_preset = 13
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_86u83")
|
||||
|
||||
[node name="Camera" type="Camera2D" parent="."]
|
||||
|
||||
[node name="Map" type="Node2D" parent="." node_paths=PackedStringArray("player_map", "player_tower_map", "opponent_map", "opponent_tower_map", "camera")]
|
||||
script = ExtResource("4_4c550")
|
||||
tile_set = ExtResource("3_0etcd")
|
||||
player_map = NodePath("PlayerMap")
|
||||
player_tower_map = NodePath("PlayerMap/Tower")
|
||||
opponent_map = NodePath("OpentMap")
|
||||
opponent_tower_map = NodePath("OpentMap/Tower")
|
||||
camera = NodePath("../Camera")
|
||||
|
||||
[node name="PlayerMap" type="Node2D" parent="Map"]
|
||||
|
||||
[node name="Tower" type="TileMapLayer" parent="Map/PlayerMap"]
|
||||
|
||||
[node name="OpentMap" type="Node2D" parent="Map"]
|
||||
|
||||
[node name="Tower" type="TileMapLayer" parent="Map/OpentMap"]
|
||||
tile_map_data = PackedByteArray("AAAAAAAAAQAAAAAAAAAAAAEAAQAAAAAAAAAAAAIAAQAAAAAAAAAAAAMAAQAAAAAAAAAAAAQAAQAAAAAAAAAAAAUAAQAAAAAAAAAAAAYAAQAAAAAAAAAAAAcAAQAAAAAAAAAAAAgAAQAAAAAAAAAAAAkAAQAAAAAAAAAAAAoAAQAAAAAAAAAAAAsAAQAAAAAAAAAAAAwAAQAAAAAAAAAAAA0AAQAAAAAAAAAAAA4AAQAAAAAAAAAAAA8AAQAAAAAAAAAAABAAAQAAAAAAAAAAABEAAQAAAAAAAAAAABIAAQAAAAAAAAAAABMAAQAAAAAAAAABAAAAAQAAAAAAAAABABMAAQAAAAAAAAACAAAAAQAAAAAAAAACABMAAQAAAAAAAAADAAAAAQAAAAAAAAADABMAAQAAAAAAAAAEAAAAAQAAAAAAAAAEABMAAQAAAAAAAAAFAAAAAQAAAAAAAAAFABMAAQAAAAAAAAAGAAAAAQAAAAAAAAAGABMAAQAAAAAAAAAHAAAAAQAAAAAAAAAHABMAAQAAAAAAAAAIAAAAAQAAAAAAAAAIABMAAQAAAAAAAAAJAAAAAQAAAAAAAAAJAAEAAQAAAAAAAAAJAAIAAQAAAAAAAAAJAAMAAQAAAAAAAAAJAAQAAQAAAAAAAAAJAAUAAQAAAAAAAAAJAAYAAQAAAAAAAAAJAAcAAQAAAAAAAAAJAAgAAQAAAAAAAAAJAAkAAQAAAAAAAAAJAAoAAQAAAAAAAAAJAAsAAQAAAAAAAAAJAAwAAQAAAAAAAAAJAA0AAQAAAAAAAAAJAA4AAQAAAAAAAAAJAA8AAQAAAAAAAAAJABAAAQAAAAAAAAAJABEAAQAAAAAAAAAJABIAAQAAAAAAAAAJABMAAQAAAAAAAAA=")
|
|
@ -1,86 +0,0 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://dtaaw31x3n22f"]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/ui/login.gd" id="1_12w35"]
|
||||
[ext_resource type="Resource" uid="uid://cdixdbu3sqgjn" path="res://config/api_config.tres" id="2_60hb8"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_d0bbp"]
|
||||
|
||||
[node name="Login" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_d0bbp")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -150.0
|
||||
offset_top = -108.0
|
||||
offset_right = 150.0
|
||||
offset_bottom = 108.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="Header" type="RichTextLabel" parent="Panel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/normal_font_size = 30
|
||||
text = "Login"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
autowrap_mode = 0
|
||||
|
||||
[node name="InputContainer" type="VBoxContainer" parent="Panel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="UsernameContainer" type="VBoxContainer" parent="Panel/VBoxContainer/InputContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="UsernameLabel" type="RichTextLabel" parent="Panel/VBoxContainer/InputContainer/UsernameContainer"]
|
||||
layout_mode = 2
|
||||
text = "Username:
|
||||
"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
|
||||
[node name="UsernameInput" type="LineEdit" parent="Panel/VBoxContainer/InputContainer/UsernameContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PasswordContainer" type="VBoxContainer" parent="Panel/VBoxContainer/InputContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PaswordLabel" type="RichTextLabel" parent="Panel/VBoxContainer/InputContainer/PasswordContainer"]
|
||||
layout_mode = 2
|
||||
text = "Password:"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
|
||||
[node name="PasswordInput" type="LineEdit" parent="Panel/VBoxContainer/InputContainer/PasswordContainer"]
|
||||
layout_mode = 2
|
||||
secret = true
|
||||
|
||||
[node name="Button" type="Button" parent="Panel/VBoxContainer" node_paths=PackedStringArray("username_field", "password_field")]
|
||||
layout_mode = 2
|
||||
text = "Login"
|
||||
script = ExtResource("1_12w35")
|
||||
username_field = NodePath("../InputContainer/UsernameContainer/UsernameInput")
|
||||
password_field = NodePath("../InputContainer/PasswordContainer/PasswordInput")
|
||||
api_config = ExtResource("2_60hb8")
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="Panel/VBoxContainer/Button"]
|
|
@ -1,7 +1,24 @@
|
|||
[gd_scene load_steps=3 format=3 uid="uid://bqfijb7bk2g7j"]
|
||||
[gd_scene load_steps=12 format=3 uid="uid://bqfijb7bk2g7j"]
|
||||
|
||||
[ext_resource type="Theme" uid="uid://bt4hxdwromnxs" path="res://ui/theme.tres" id="1_6qgep"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/switch_to_scene.gd" id="2_c477a"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/login/show_banner_on_error.gd" id="2_1tbi1"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/swap_menu.gd" id="2_c477a"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/login/login.gd" id="3_33cgr"]
|
||||
[ext_resource type="Resource" uid="uid://cdixdbu3sqgjn" path="res://config/api_config.tres" id="4_5vuod"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/quit.gd" id="6_cixwl"]
|
||||
[ext_resource type="Script" path="res://scripts/ui/lobby/search_match.gd" id="7_dmfpl"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q2pxx"]
|
||||
bg_color = Color(0.52, 0.52, 0.52, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_socr1"]
|
||||
bg_color = Color(0.74463, 0.147328, 0, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fh0xh"]
|
||||
bg_color = Color(0.135012, 0.135012, 0.135012, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x4jly"]
|
||||
bg_color = Color(7.89344e-06, 0.65411, 0.87403, 1)
|
||||
|
||||
[node name="Root" type="Panel"]
|
||||
anchors_preset = 15
|
||||
|
@ -12,22 +29,26 @@ offset_right = -1.0
|
|||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_6qgep")
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_q2pxx")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
[node name="Screens" type="TabContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -180.5
|
||||
offset_top = -44.0
|
||||
offset_right = 180.5
|
||||
offset_bottom = 44.0
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
current_tab = 0
|
||||
tabs_visible = false
|
||||
|
||||
[node name="Text" type="RichTextLabel" parent="VBoxContainer"]
|
||||
[node name="Start" type="CenterContainer" parent="Screens"]
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 0
|
||||
|
||||
[node name="V" type="VBoxContainer" parent="Screens/Start"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="RichTextLabel" parent="Screens/Start/V"]
|
||||
clip_contents = false
|
||||
layout_mode = 2
|
||||
size_flags_stretch_ratio = 7.45
|
||||
|
@ -37,14 +58,141 @@ fit_content = true
|
|||
scroll_active = false
|
||||
autowrap_mode = 0
|
||||
|
||||
[node name="Button" type="Button" parent="VBoxContainer"]
|
||||
[node name="Login" type="Button" parent="Screens/Start/V" node_paths=PackedStringArray("menu")]
|
||||
layout_mode = 2
|
||||
text = "Login"
|
||||
script = ExtResource("2_c477a")
|
||||
scene_name = "login"
|
||||
menu = NodePath("../../../Login")
|
||||
|
||||
[node name="Button2" type="Button" parent="VBoxContainer"]
|
||||
[node name="Settings" type="Button" parent="Screens/Start/V"]
|
||||
layout_mode = 2
|
||||
text = "Testszene"
|
||||
text = "Settings"
|
||||
script = ExtResource("2_c477a")
|
||||
scene_name = "theme_test"
|
||||
|
||||
[node name="Quit" type="Button" parent="Screens/Start/V"]
|
||||
layout_mode = 2
|
||||
text = "Quit"
|
||||
script = ExtResource("6_cixwl")
|
||||
|
||||
[node name="Login" type="VBoxContainer" parent="Screens"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 1
|
||||
|
||||
[node name="Banner" type="PanelContainer" parent="Screens/Login" node_paths=PackedStringArray("login", "text")]
|
||||
layout_mode = 2
|
||||
script = ExtResource("2_1tbi1")
|
||||
login = NodePath("../CenterContainer/VBoxContainer/Actions/Login")
|
||||
text = NodePath("CenterContainer/MarginContainer/RichTextLabel")
|
||||
|
||||
[node name="Panel" type="Panel" parent="Screens/Login/Banner"]
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_socr1")
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="Screens/Login/Banner"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Screens/Login/Banner/CenterContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="Screens/Login/Banner/CenterContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
text = "Keine Verbindung"
|
||||
fit_content = true
|
||||
autowrap_mode = 0
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="Screens/Login"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Screens/Login/CenterContainer"]
|
||||
custom_minimum_size = Vector2(500, 0)
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="UsernameInput" type="LineEdit" parent="Screens/Login/CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "username"
|
||||
|
||||
[node name="PasswordInput" type="LineEdit" parent="Screens/Login/CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
placeholder_text = "password"
|
||||
secret = true
|
||||
|
||||
[node name="Actions" type="HBoxContainer" parent="Screens/Login/CenterContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Back" type="Button" parent="Screens/Login/CenterContainer/VBoxContainer/Actions" node_paths=PackedStringArray("menu")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Back"
|
||||
script = ExtResource("2_c477a")
|
||||
menu = NodePath("../../../../../Start")
|
||||
|
||||
[node name="Login" type="Button" parent="Screens/Login/CenterContainer/VBoxContainer/Actions" node_paths=PackedStringArray("username_field", "password_field", "next_view")]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Login"
|
||||
script = ExtResource("3_33cgr")
|
||||
username_field = NodePath("../../UsernameInput")
|
||||
password_field = NodePath("../../PasswordInput")
|
||||
api_config = ExtResource("4_5vuod")
|
||||
next_view = NodePath("../../../../../Lobby")
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="Screens/Login/CenterContainer/VBoxContainer/Actions/Login"]
|
||||
|
||||
[node name="Lobby" type="CenterContainer" parent="Screens" node_paths=PackedStringArray("popup", "search_button", "abort_button", "accept_button", "decline_button", "time_bar", "login")]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
script = ExtResource("7_dmfpl")
|
||||
popup = NodePath("Popup")
|
||||
search_button = NodePath("Search")
|
||||
abort_button = NodePath("Abort")
|
||||
accept_button = NodePath("Popup/MarginContainer/HBoxContainer/Accept")
|
||||
decline_button = NodePath("Popup/MarginContainer/HBoxContainer/Decline")
|
||||
time_bar = NodePath("Popup/Time Bar")
|
||||
login = NodePath("../Login/CenterContainer/VBoxContainer/Actions/Login")
|
||||
metadata/_tab_index = 2
|
||||
|
||||
[node name="Search" type="Button" parent="Screens/Lobby"]
|
||||
layout_mode = 2
|
||||
text = "Search"
|
||||
|
||||
[node name="Abort" type="Button" parent="Screens/Lobby"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Abort"
|
||||
|
||||
[node name="Popup" type="PanelContainer" parent="Screens/Lobby"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_fh0xh")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Screens/Lobby/Popup"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 16
|
||||
theme_override_constants/margin_right = 16
|
||||
theme_override_constants/margin_bottom = 16
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Screens/Lobby/Popup/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="Accept" type="Button" parent="Screens/Lobby/Popup/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Accept
|
||||
"
|
||||
|
||||
[node name="Decline" type="Button" parent="Screens/Lobby/Popup/MarginContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Decline"
|
||||
|
||||
[node name="Time Bar" type="ProgressBar" parent="Screens/Lobby/Popup"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_x4jly")
|
||||
value = 100.0
|
||||
show_percentage = false
|
||||
|
|
File diff suppressed because it is too large
Load diff
19
scripts/channel/channel.gd
Normal file
19
scripts/channel/channel.gd
Normal file
|
@ -0,0 +1,19 @@
|
|||
class_name Channel
|
||||
extends Node
|
||||
|
||||
const SOCKET_FALLBACK_URL := "ws://localhost:8080/ws"
|
||||
|
||||
var socket = WebSocketPeer.new()
|
||||
var socket_url := OS.get_environment("TD_SERVER_WS")
|
||||
|
||||
|
||||
func get_channel_location() -> String:
|
||||
push_error("Not Implemented")
|
||||
return ""
|
||||
|
||||
|
||||
func connect_socket(token: String):
|
||||
socket.handshake_headers = PackedStringArray(["Authorization: " + token])
|
||||
if socket_url == "":
|
||||
socket_url = SOCKET_FALLBACK_URL
|
||||
socket.connect_to_url(socket_url + "/" + get_channel_location())
|
42
scripts/channel/connection/connection_channel.gd
Normal file
42
scripts/channel/connection/connection_channel.gd
Normal file
|
@ -0,0 +1,42 @@
|
|||
extends Channel
|
||||
|
||||
signal on_channel_token_received(msg: ProvidedConnectionTokenMessage)
|
||||
|
||||
var queue: Array[Message.Channels]
|
||||
|
||||
|
||||
func get_channel_location() -> String:
|
||||
return "connection"
|
||||
|
||||
|
||||
func connect_to_channel(token: String) -> void:
|
||||
if self.socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
|
||||
return
|
||||
self.connect_socket(token)
|
||||
|
||||
|
||||
func request_channel_token(channel: Message.Channels) -> void:
|
||||
if not queue.has(channel):
|
||||
queue.push_back(channel)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
self.socket.poll()
|
||||
if self.socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
return
|
||||
for i in queue.size():
|
||||
var msg := RequestConnectionTokenMessage.new()
|
||||
msg.channel = queue[i]
|
||||
self.socket.send_text(Message.serialize(msg))
|
||||
queue.remove_at(i)
|
||||
while self.socket.get_available_packet_count():
|
||||
var msg: ProvidedConnectionTokenMessage = (
|
||||
Message
|
||||
. deserialize(
|
||||
self.socket.get_packet().get_string_from_utf8(),
|
||||
[ProvidedConnectionTokenMessage],
|
||||
)
|
||||
)
|
||||
if msg == null:
|
||||
continue
|
||||
on_channel_token_received.emit(msg)
|
|
@ -0,0 +1,9 @@
|
|||
class_name ProvidedConnectionTokenMessage
|
||||
extends Message
|
||||
|
||||
@export var channel: Channels
|
||||
var token: String
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return "ProvidedConnectionToken"
|
|
@ -0,0 +1,8 @@
|
|||
class_name RequestConnectionTokenMessage
|
||||
extends Message
|
||||
|
||||
@export var channel: Channels
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return "RequestConnectionToken"
|
13
scripts/channel/match/invalid_placement_message.gd
Normal file
13
scripts/channel/match/invalid_placement_message.gd
Normal file
|
@ -0,0 +1,13 @@
|
|||
class_name InvalidPlacementMessage
|
||||
extends Message
|
||||
|
||||
enum Reason { OUT_OF_BOUNDS, LOCATION_USED }
|
||||
const MESSAGE_ID: String = "InvalidPlacement"
|
||||
|
||||
@export var x: int
|
||||
@export var y: int
|
||||
@export var reason: Reason
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
49
scripts/channel/match/match_channel.gd
Normal file
49
scripts/channel/match/match_channel.gd
Normal file
|
@ -0,0 +1,49 @@
|
|||
extends Channel
|
||||
|
||||
signal on_match_update(msg: TowerPlacedMessage)
|
||||
signal on_invalid_placing(msg: InvalidPlacementMessage)
|
||||
signal on_money_update(msg: PlayerMoneyMessage)
|
||||
signal on_hitpoints_update(msg: PlayerHitpointsMessage)
|
||||
|
||||
|
||||
func get_channel_location() -> String:
|
||||
return "match"
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
self.socket.poll()
|
||||
if self.socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
return
|
||||
while self.socket.get_available_packet_count():
|
||||
var msg: Message = (
|
||||
Message
|
||||
. deserialize(
|
||||
self.socket.get_packet().get_string_from_utf8(),
|
||||
[
|
||||
TowerPlacedMessage,
|
||||
InvalidPlacementMessage,
|
||||
PlayerMoneyMessage,
|
||||
PlayerHitpointsMessage,
|
||||
],
|
||||
)
|
||||
)
|
||||
match msg.get_message_id():
|
||||
TowerPlacedMessage.MESSAGE_ID:
|
||||
on_match_update.emit(msg)
|
||||
InvalidPlacementMessage.MESSAGE_ID:
|
||||
on_invalid_placing.emit(msg)
|
||||
PlayerMoneyMessage.MESSAGE_ID:
|
||||
on_money_update.emit(msg)
|
||||
PlayerHitpointsMessage.MESSAGE_ID:
|
||||
on_hitpoints_update.emit(msg)
|
||||
_:
|
||||
continue
|
||||
|
||||
|
||||
func send_request_place(x: int, y: int) -> void:
|
||||
if self.socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
return
|
||||
var msg := RequestTowerPlacingMessage.new()
|
||||
msg.x = x
|
||||
msg.y = y
|
||||
self.socket.send_text(Message.serialize(msg))
|
11
scripts/channel/match/player_hitpoints_message.gd
Normal file
11
scripts/channel/match/player_hitpoints_message.gd
Normal file
|
@ -0,0 +1,11 @@
|
|||
class_name PlayerHitpointsMessage
|
||||
extends Message
|
||||
|
||||
const MESSAGE_ID: String = "PlayerHitpoints"
|
||||
|
||||
# gdlint:ignore = class-variable-name
|
||||
@export var playerHitpoints: int
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
11
scripts/channel/match/player_money_message.gd
Normal file
11
scripts/channel/match/player_money_message.gd
Normal file
|
@ -0,0 +1,11 @@
|
|||
class_name PlayerMoneyMessage
|
||||
extends Message
|
||||
|
||||
const MESSAGE_ID: String = "PlayerMoney"
|
||||
|
||||
# gdlint:ignore = class-variable-name
|
||||
@export var playerMoney: int
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
11
scripts/channel/match/request_tower_placing_message.gd
Normal file
11
scripts/channel/match/request_tower_placing_message.gd
Normal file
|
@ -0,0 +1,11 @@
|
|||
class_name RequestTowerPlacingMessage
|
||||
extends Message
|
||||
|
||||
const MESSAGE_ID: String = "RequestTowerPlacing"
|
||||
|
||||
@export var x: int
|
||||
@export var y: int
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
13
scripts/channel/match/tower_placed_message.gd
Normal file
13
scripts/channel/match/tower_placed_message.gd
Normal file
|
@ -0,0 +1,13 @@
|
|||
class_name TowerPlacedMessage
|
||||
extends Message
|
||||
|
||||
enum GamePlayerMaps { PLAYER, OPPONENT }
|
||||
const MESSAGE_ID: String = "TowerPlaced"
|
||||
|
||||
@export var x: int
|
||||
@export var y: int
|
||||
@export var map: GamePlayerMaps
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
11
scripts/channel/matchmaking/match_aborted_message.gd
Normal file
11
scripts/channel/matchmaking/match_aborted_message.gd
Normal file
|
@ -0,0 +1,11 @@
|
|||
class_name MatchAbortedMessage
|
||||
extends Message
|
||||
|
||||
const MESSAGE_ID := "MatchAborted"
|
||||
|
||||
# gdlint:ignore = class-variable-name
|
||||
@export var matchId: String
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
10
scripts/channel/matchmaking/match_accepted_message.gd
Normal file
10
scripts/channel/matchmaking/match_accepted_message.gd
Normal file
|
@ -0,0 +1,10 @@
|
|||
class_name MatchAcceptedMessage
|
||||
extends Message
|
||||
|
||||
# gdlint:ignore = class-variable-name
|
||||
@export var matchId: String
|
||||
@export var accepted: bool
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return "MatchAccepted"
|
14
scripts/channel/matchmaking/match_established_message.gd
Normal file
14
scripts/channel/matchmaking/match_established_message.gd
Normal file
|
@ -0,0 +1,14 @@
|
|||
class_name MatchEstablishedMessage
|
||||
extends Message
|
||||
|
||||
const MESSAGE_ID := "MatchEstablished"
|
||||
|
||||
# gdlint:ignore = class-variable-name
|
||||
@export var matchId: String
|
||||
# gdlint:ignore = class-variable-name
|
||||
@export var opponentName: String
|
||||
@export var token: String
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
13
scripts/channel/matchmaking/match_found_message.gd
Normal file
13
scripts/channel/matchmaking/match_found_message.gd
Normal file
|
@ -0,0 +1,13 @@
|
|||
class_name MatchFoundMessage
|
||||
extends Message
|
||||
|
||||
const MESSAGE_ID := "MatchFound"
|
||||
|
||||
# gdlint:ignore = class-variable-name
|
||||
@export var matchId: String
|
||||
@export var created: int
|
||||
@export var ttl: int
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
|
@ -0,0 +1,10 @@
|
|||
class_name MatchSetSearchStateMessage
|
||||
extends Message
|
||||
|
||||
const MESSAGE_ID := "MatchSetSearchState"
|
||||
|
||||
@export var searching: bool
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
return MESSAGE_ID
|
57
scripts/channel/matchmaking/matchmaking_channel.gd
Normal file
57
scripts/channel/matchmaking/matchmaking_channel.gd
Normal file
|
@ -0,0 +1,57 @@
|
|||
extends Channel
|
||||
|
||||
signal on_match_set_search_state(msg: MatchSetSearchStateMessage)
|
||||
signal on_match_found(msg: MatchFoundMessage)
|
||||
signal on_match_aborted
|
||||
signal on_match_established(msg: MatchEstablishedMessage)
|
||||
|
||||
|
||||
func get_channel_location() -> String:
|
||||
return "matchmaking"
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
self.socket.poll()
|
||||
if self.socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
return
|
||||
while self.socket.get_available_packet_count():
|
||||
var msg := (
|
||||
Message
|
||||
. deserialize(
|
||||
self.socket.get_packet().get_string_from_utf8(),
|
||||
[
|
||||
MatchSetSearchStateMessage,
|
||||
MatchFoundMessage,
|
||||
MatchAbortedMessage,
|
||||
MatchEstablishedMessage,
|
||||
]
|
||||
)
|
||||
)
|
||||
match msg.get_message_id():
|
||||
MatchSetSearchStateMessage.MESSAGE_ID:
|
||||
on_match_set_search_state.emit(msg)
|
||||
MatchFoundMessage.MESSAGE_ID:
|
||||
on_match_found.emit(msg)
|
||||
MatchAbortedMessage.MESSAGE_ID:
|
||||
on_match_aborted.emit()
|
||||
MatchEstablishedMessage.MESSAGE_ID:
|
||||
on_match_established.emit(msg)
|
||||
_:
|
||||
continue
|
||||
|
||||
|
||||
func send_search_state(searching: bool) -> void:
|
||||
if self.socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
return
|
||||
var msg := MatchSetSearchStateMessage.new()
|
||||
msg.searching = searching
|
||||
self.socket.send_text(Message.serialize(msg))
|
||||
|
||||
|
||||
func send_match_accepted(accepted: bool, match_id: String) -> void:
|
||||
if self.socket.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
return
|
||||
var msg := MatchAcceptedMessage.new()
|
||||
msg.accepted = accepted
|
||||
msg.matchId = match_id
|
||||
self.socket.send_text(Message.serialize(msg))
|
30
scripts/channel/message.gd
Normal file
30
scripts/channel/message.gd
Normal file
|
@ -0,0 +1,30 @@
|
|||
class_name Message
|
||||
|
||||
enum Channels { CONNECTION, MATCHMAKING }
|
||||
|
||||
|
||||
func get_message_id() -> String:
|
||||
push_error("Not Implemented")
|
||||
return ""
|
||||
|
||||
|
||||
static func serialize(message: Message) -> String:
|
||||
var msg: Dictionary = JsonClassConverter.class_to_json(message)
|
||||
msg["$id"] = message.get_message_id()
|
||||
return JSON.stringify(msg)
|
||||
|
||||
|
||||
static func deserialize(payload: String, messages: Array[GDScript]) -> Message:
|
||||
var json := JSON.new()
|
||||
var err = json.parse(payload)
|
||||
if err != OK:
|
||||
return null
|
||||
var data: Variant = json.data
|
||||
if data == null:
|
||||
return null
|
||||
var msg_id: String = data.get("$id")
|
||||
for msg in messages:
|
||||
if msg_id != msg.new().get_message_id():
|
||||
continue
|
||||
return JsonClassConverter.json_to_class(msg, data)
|
||||
return null
|
13
scripts/match/current_match.gd
Normal file
13
scripts/match/current_match.gd
Normal file
|
@ -0,0 +1,13 @@
|
|||
extends Node
|
||||
|
||||
enum State { NONE, RUNNING, WON, LOST }
|
||||
|
||||
var state: State = State.NONE
|
||||
var opponent_name: String = ""
|
||||
|
||||
|
||||
func start_game(msg: MatchEstablishedMessage) -> void:
|
||||
self.opponent_name = msg.opponentName
|
||||
self.state = State.RUNNING
|
||||
MatchChannel.connect_socket(msg.token)
|
||||
get_tree().change_scene_to_file("res://scenes/game.tscn")
|
65
scripts/match/map.gd
Normal file
65
scripts/match/map.gd
Normal file
|
@ -0,0 +1,65 @@
|
|||
extends Node2D
|
||||
|
||||
@export var tile_set: TileSet
|
||||
@export var player_map: Node2D
|
||||
@export var player_tower_map: TileMapLayer
|
||||
@export var opponent_map: Node2D
|
||||
@export var opponent_tower_map: TileMapLayer
|
||||
@export var map_size := Vector2i(10, 20)
|
||||
@export var camera: Camera2D
|
||||
|
||||
var clicked: bool = false
|
||||
var clicked_pos: Vector2
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
player_tower_map.tile_set = tile_set
|
||||
opponent_tower_map.tile_set = tile_set
|
||||
player_tower_map.clear()
|
||||
opponent_tower_map.clear()
|
||||
MatchChannel.connect("on_match_update", on_match_update)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
# gdlint:ignore = max-line-length
|
||||
var new_zoom := float(get_window().size.y) / float(map_size.y * tile_set.tile_size.y)
|
||||
camera.zoom = Vector2(new_zoom, new_zoom)
|
||||
var new_base_postion := get_window().size / 2 / new_zoom
|
||||
camera.position = new_base_postion
|
||||
|
||||
self.position.x = new_base_postion.x - map_size.x * tile_set.tile_size.x
|
||||
opponent_map.position.x = map_size.x * tile_set.tile_size.x
|
||||
|
||||
|
||||
func _input(_event) -> void:
|
||||
if Input.is_action_just_pressed("place"):
|
||||
clicked_pos = get_global_mouse_position()
|
||||
clicked = true
|
||||
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if clicked:
|
||||
clicked = false
|
||||
var pos: Vector2i = world_to_game_pos(clicked_pos)
|
||||
if pos == Vector2i(-1, -1):
|
||||
return
|
||||
MatchChannel.send_request_place(pos.x, pos.y)
|
||||
|
||||
|
||||
func on_match_update(msg: TowerPlacedMessage) -> void:
|
||||
if msg.map == TowerPlacedMessage.GamePlayerMaps.PLAYER:
|
||||
player_tower_map.set_cell(Vector2i(msg.x, msg.y), 1, Vector2i(0, 0))
|
||||
else:
|
||||
opponent_tower_map.set_cell(Vector2i(msg.x, msg.y), 1, Vector2i(0, 0))
|
||||
|
||||
|
||||
func world_to_game_pos(world_pos: Vector2) -> Vector2i:
|
||||
if (
|
||||
world_pos.x < self.position.x
|
||||
|| world_pos.x > self.position.x + map_size.x * tile_set.tile_size.x
|
||||
):
|
||||
return Vector2i(-1, -1)
|
||||
return Vector2i(
|
||||
(world_pos.x - self.position.x) / tile_set.tile_size.x,
|
||||
world_pos.y / tile_set.tile_size.y,
|
||||
)
|
5
scripts/match/ui/opponent_name.gd
Normal file
5
scripts/match/ui/opponent_name.gd
Normal file
|
@ -0,0 +1,5 @@
|
|||
extends RichTextLabel
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
self.text = CurrentMatch.opponent_name
|
10
scripts/match/ui/player_hitpoints.gd
Normal file
10
scripts/match/ui/player_hitpoints.gd
Normal file
|
@ -0,0 +1,10 @@
|
|||
extends RichTextLabel
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
MatchChannel.connect("on_hitpoints_update", on_hitpoints_update)
|
||||
|
||||
|
||||
func on_hitpoints_update(msg: PlayerHitpointsMessage) -> void:
|
||||
print(msg.playerHitpoints, "test")
|
||||
self.text = str(msg.playerHitpoints, "HP")
|
10
scripts/match/ui/player_money.gd
Normal file
10
scripts/match/ui/player_money.gd
Normal file
|
@ -0,0 +1,10 @@
|
|||
extends RichTextLabel
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
MatchChannel.connect("on_money_update", on_money_update)
|
||||
|
||||
|
||||
func on_money_update(msg: PlayerMoneyMessage) -> void:
|
||||
print(msg.playerMoney, "test")
|
||||
self.text = str(msg.playerMoney)
|
162
scripts/ui/lobby/search_match.gd
Normal file
162
scripts/ui/lobby/search_match.gd
Normal file
|
@ -0,0 +1,162 @@
|
|||
extends Control
|
||||
|
||||
@export var popup: PanelContainer
|
||||
@export var search_button: Button
|
||||
@export var abort_button: Button
|
||||
@export var accept_button: Button
|
||||
@export var decline_button: Button
|
||||
@export var time_bar: ProgressBar
|
||||
@export var login: Login
|
||||
|
||||
var searching: bool = false
|
||||
var sent_accept_message: bool = false
|
||||
var found_match: MatchFoundMessage
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if !export_valid():
|
||||
return
|
||||
login.connect("login_successful", on_login)
|
||||
|
||||
# Websockets
|
||||
(
|
||||
ConnectionChannel
|
||||
. connect(
|
||||
"on_channel_token_received",
|
||||
on_channel_token_received,
|
||||
)
|
||||
)
|
||||
(
|
||||
MatchmakingChannel
|
||||
. connect(
|
||||
"on_match_set_search_state",
|
||||
on_match_set_search_state,
|
||||
)
|
||||
)
|
||||
MatchmakingChannel.connect("on_match_found", on_match_found)
|
||||
MatchmakingChannel.connect("on_match_aborted", on_match_aborted)
|
||||
MatchmakingChannel.connect("on_match_established", on_match_established)
|
||||
|
||||
# UI
|
||||
search_button.connect("pressed", on_search_pressed)
|
||||
abort_button.connect("pressed", on_abort_pressed)
|
||||
accept_button.connect("pressed", on_accept_pressed)
|
||||
decline_button.connect("pressed", on_decline_pressed)
|
||||
popup.visible = false
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if found_match:
|
||||
var now := int(Time.get_unix_time_from_system() * 1000)
|
||||
time_bar.max_value = found_match.ttl
|
||||
time_bar.value = found_match.ttl - (now - found_match.created)
|
||||
|
||||
|
||||
# gdlint:ignore = max-public-methods
|
||||
func export_valid() -> bool:
|
||||
if not popup:
|
||||
push_error("Popup not Set")
|
||||
return false
|
||||
if not search_button:
|
||||
push_error("Search Button not Set")
|
||||
return false
|
||||
if not abort_button:
|
||||
push_error("Abort Button not Set")
|
||||
return false
|
||||
if not accept_button:
|
||||
push_error("Accept Button not Set")
|
||||
return false
|
||||
if not decline_button:
|
||||
push_error("Decline Button not Set")
|
||||
return false
|
||||
if not time_bar:
|
||||
push_error("Time Bar not Set")
|
||||
return false
|
||||
if not login:
|
||||
push_error("No login configured")
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func on_login(_session: PlayerLoginSession) -> void:
|
||||
ConnectionChannel.request_channel_token(Message.Channels.MATCHMAKING)
|
||||
|
||||
|
||||
##############################
|
||||
# Websocket Interactions #
|
||||
##############################
|
||||
|
||||
|
||||
func on_channel_token_received(msg: ProvidedConnectionTokenMessage) -> void:
|
||||
if msg.channel != Message.Channels.MATCHMAKING:
|
||||
return
|
||||
MatchmakingChannel.connect_socket(msg.token)
|
||||
|
||||
|
||||
func on_match_set_search_state(msg: MatchSetSearchStateMessage) -> void:
|
||||
set_searching_ui(msg.searching)
|
||||
|
||||
|
||||
func on_match_found(msg: MatchFoundMessage) -> void:
|
||||
set_match_accptance_ui(true)
|
||||
set_accaptence_buttons_enabled(true)
|
||||
found_match = msg
|
||||
|
||||
|
||||
func on_match_aborted() -> void:
|
||||
set_match_accptance_ui(false)
|
||||
set_searching_ui(false)
|
||||
found_match = null
|
||||
|
||||
|
||||
func on_match_established(msg: MatchEstablishedMessage) -> void:
|
||||
CurrentMatch.start_game(msg)
|
||||
|
||||
|
||||
#############################
|
||||
# UI Interactions #
|
||||
#############################
|
||||
|
||||
|
||||
func on_search_pressed() -> void:
|
||||
if searching:
|
||||
return
|
||||
set_searching_ui(true)
|
||||
MatchmakingChannel.send_search_state(true)
|
||||
|
||||
|
||||
func on_abort_pressed() -> void:
|
||||
if not searching:
|
||||
return
|
||||
set_searching_ui(false)
|
||||
MatchmakingChannel.send_search_state(false)
|
||||
|
||||
|
||||
func on_accept_pressed() -> void:
|
||||
set_accaptence_buttons_enabled(false)
|
||||
MatchmakingChannel.send_match_accepted(true, found_match.matchId)
|
||||
|
||||
|
||||
func on_decline_pressed() -> void:
|
||||
set_accaptence_buttons_enabled(false)
|
||||
MatchmakingChannel.send_match_accepted(false, found_match.matchId)
|
||||
|
||||
|
||||
#############################
|
||||
# UI Management #
|
||||
#############################
|
||||
|
||||
|
||||
func set_searching_ui(new_searching: bool) -> void:
|
||||
searching = new_searching
|
||||
search_button.visible = !new_searching
|
||||
abort_button.visible = new_searching
|
||||
|
||||
|
||||
func set_accaptence_buttons_enabled(enabled: bool) -> void:
|
||||
accept_button.disabled = !enabled
|
||||
decline_button.disabled = !enabled
|
||||
|
||||
|
||||
func set_match_accptance_ui(vissible: bool) -> void:
|
||||
popup.visible = vissible
|
|
@ -1,8 +1,13 @@
|
|||
class_name Login
|
||||
extends Button
|
||||
|
||||
signal login_successful(session: PlayerLoginSession)
|
||||
signal login_error(error: ApiError)
|
||||
|
||||
@export var username_field: LineEdit
|
||||
@export var password_field: LineEdit
|
||||
@export var api_config: ApiConfig
|
||||
@export var next_view: Control
|
||||
var api: ServerApi
|
||||
|
||||
|
||||
|
@ -16,6 +21,9 @@ func _ready() -> void:
|
|||
if not api_config:
|
||||
push_error("No API Configuration provided")
|
||||
return
|
||||
if not next_view:
|
||||
push_error("No next view configured")
|
||||
return
|
||||
api = ServerApi.new(api_config)
|
||||
connect("pressed", login)
|
||||
username_field.connect("text_submitted", login_wrapper)
|
||||
|
@ -34,10 +42,10 @@ func login() -> void:
|
|||
|
||||
|
||||
func on_success(response: ApiResponse) -> void:
|
||||
var session: PlayerLoginSession = response.data
|
||||
print("username: ", session.username)
|
||||
print("token: ", session.token.to_utf8_buffer())
|
||||
ConnectionChannel.connect_to_channel(response.data.token)
|
||||
next_view.visible = true
|
||||
login_successful.emit(response.data)
|
||||
|
||||
|
||||
func on_error(error: ApiError) -> void:
|
||||
print("Error: ", error.message)
|
||||
login_error.emit(error)
|
45
scripts/ui/login/show_banner_on_error.gd
Normal file
45
scripts/ui/login/show_banner_on_error.gd
Normal file
|
@ -0,0 +1,45 @@
|
|||
extends Control
|
||||
|
||||
@export var login: Login
|
||||
@export var text: RichTextLabel
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not login:
|
||||
push_error("No Login connectd")
|
||||
return
|
||||
if not text:
|
||||
push_error("No Text label connectd")
|
||||
return
|
||||
login.connect("login_successful", on_successful)
|
||||
login.connect("login_error", on_error)
|
||||
hide_banner()
|
||||
|
||||
|
||||
func on_successful(_session: PlayerLoginSession) -> void:
|
||||
hide_banner()
|
||||
|
||||
|
||||
func on_error(error: ApiError) -> void:
|
||||
if error.identifier == "apibee.request.no_response":
|
||||
return
|
||||
|
||||
show_banner()
|
||||
var msg: String = ""
|
||||
|
||||
if error.identifier == "apibee.connect_to_host.status_failure":
|
||||
msg = "Server nicht ereichbar"
|
||||
if error.response_code == HTTPClient.RESPONSE_UNAUTHORIZED:
|
||||
msg = "Falscher Benutzername oder Passwort"
|
||||
|
||||
if msg == "":
|
||||
msg = error.message
|
||||
text.text = msg
|
||||
|
||||
|
||||
func hide_banner() -> void:
|
||||
self.modulate = Color.TRANSPARENT
|
||||
|
||||
|
||||
func show_banner() -> void:
|
||||
self.modulate = Color.WHITE
|
9
scripts/ui/quit.gd
Normal file
9
scripts/ui/quit.gd
Normal file
|
@ -0,0 +1,9 @@
|
|||
extends Button
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
connect("pressed", on_pressed)
|
||||
|
||||
|
||||
func on_pressed() -> void:
|
||||
get_tree().quit()
|
14
scripts/ui/swap_menu.gd
Normal file
14
scripts/ui/swap_menu.gd
Normal file
|
@ -0,0 +1,14 @@
|
|||
extends Button
|
||||
|
||||
@export var menu: Control
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
connect("pressed", _on_Button_pressed)
|
||||
|
||||
|
||||
func _on_Button_pressed() -> void:
|
||||
if not menu:
|
||||
push_error("Menu to swap to not configgured")
|
||||
return
|
||||
menu.visible = true
|
|
@ -1,14 +0,0 @@
|
|||
extends Button
|
||||
|
||||
@export var scene_name: String
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
connect("pressed", _on_Button_pressed)
|
||||
|
||||
|
||||
func _on_Button_pressed() -> void:
|
||||
if not scene_name:
|
||||
push_error("Scene to switch to is not configured")
|
||||
return
|
||||
get_tree().change_scene_to_file("res://scenes/" + scene_name + ".tscn")
|
|
@ -1,28 +0,0 @@
|
|||
extends RichTextLabel
|
||||
|
||||
# docs.redotengine.org/en/stable/tutorials/networking/websocket.html
|
||||
@export var fallpack_websocket_url = "ws://localhost:8080/ws/server"
|
||||
var websocket_url = OS.get_environment("TD_SERVER_WS")
|
||||
var socket = WebSocketPeer.new()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if websocket_url.is_empty():
|
||||
websocket_url = fallpack_websocket_url
|
||||
var err = socket.connect_to_url(websocket_url)
|
||||
if err != OK:
|
||||
error_string(err)
|
||||
set_process(false)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
socket.poll()
|
||||
var state = socket.get_ready_state()
|
||||
|
||||
if state == WebSocketPeer.STATE_CLOSED:
|
||||
self.text = "Disconnected"
|
||||
return
|
||||
|
||||
if state == WebSocketPeer.STATE_OPEN:
|
||||
while socket.get_available_packet_count():
|
||||
self.text = "Current Unixtime: " + socket.get_packet().get_string_from_utf8()
|
BIN
textures/tiles/tower.png
Normal file
BIN
textures/tiles/tower.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 161 B |
34
textures/tiles/tower.png.import
Normal file
34
textures/tiles/tower.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bj8jwha8g5y8n"
|
||||
path="res://.godot/imported/tower.png-afe2561faadc2586f630d8a5c5939231.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/tiles/tower.png"
|
||||
dest_files=["res://.godot/imported/tower.png-afe2561faadc2586f630d8a5c5939231.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
12
tile_set.tres
Normal file
12
tile_set.tres
Normal file
|
@ -0,0 +1,12 @@
|
|||
[gd_resource type="TileSet" load_steps=3 format=3 uid="uid://cvp867israfjk"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://bj8jwha8g5y8n" path="res://textures/tiles/tower.png" id="1_4stpq"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_tivyh"]
|
||||
texture = ExtResource("1_4stpq")
|
||||
texture_region_size = Vector2i(32, 32)
|
||||
0:0/0 = 0
|
||||
|
||||
[resource]
|
||||
tile_size = Vector2i(32, 32)
|
||||
sources/1 = SubResource("TileSetAtlasSource_tivyh")
|
|
@ -16,6 +16,7 @@ Button/colors/icon_normal_color = Color(1, 1, 1, 0)
|
|||
Button/colors/icon_pressed_color = Color(1, 1, 1, 1)
|
||||
Button/constants/h_separation = 4
|
||||
Button/constants/icon_max_width = 8
|
||||
Button/font_sizes/font_size = 32
|
||||
Button/icons/icon = ExtResource("1_2gn16")
|
||||
Button/styles/focus = ExtResource("2_deyos")
|
||||
Button/styles/hover = ExtResource("3_nfsuc")
|
||||
|
@ -42,3 +43,10 @@ ColorPickerButton/styles/hover = ExtResource("3_nfsuc")
|
|||
ColorPickerButton/styles/hover_pressed = ExtResource("6_vlfw3")
|
||||
ColorPickerButton/styles/normal = ExtResource("4_wi0tw")
|
||||
ColorPickerButton/styles/pressed = ExtResource("5_kgc35")
|
||||
Label/font_sizes/font_size = 32
|
||||
LineEdit/font_sizes/font_size = 32
|
||||
RichTextLabel/font_sizes/normal_font_size = 32
|
||||
TabBar/font_sizes/font_size = 32
|
||||
TabContainer/font_sizes/font_size = 32
|
||||
TextEdit/font_sizes/font_size = 32
|
||||
Tree/font_sizes/font_size = 32
|
||||
|
|
Loading…
Add table
Reference in a new issue