Compare commits

...

17 commits

Author SHA1 Message Date
e672567b3d
Merge pull request '[Feature]: Hp und Geld anzeigen' (!9) from task/TD-47-Lebenspunkte-Anzeigen into trunk
All checks were successful
Quality Check / Linting (push) Successful in 6s
Reviewed-on: #9
Reviewed-by: Snoweuph <snow+git@euph.email>
2025-03-13 12:26:44 +00:00
cb480406c0
scripts/channel/message.gd aktualisiert
All checks were successful
Quality Check / Linting (push) Successful in 7s
Quality Check / Linting (pull_request) Successful in 6s
2025-03-13 12:25:58 +00:00
Kevin Schmidt
20466ed45f TD-47: Show Money and Hitpoints
All checks were successful
Quality Check / Linting (pull_request) Successful in 6s
Quality Check / Linting (push) Successful in 6s
2025-03-13 13:21:53 +01:00
31075ba2b5
Chore: Bump to newest API version
All checks were successful
Quality Check / Linting (push) Successful in 6s
Build Application / generate (push) Successful in 21s
Build Application / build (tower_defence_web, build/*, build, build/index.html, Web) (push) Successful in 16s
Build Application / build (tower_defence.elf, tower_defence.elf, tower_defence.elf, Linux) (push) Successful in 19s
Build Application / build (tower_defence.exe, tower_defence.exe, tower_defence.exe, Windows) (push) Successful in 21s
Build Application / build-docker (push) Successful in 16s
Build Application / release (push) Successful in 9s
2025-03-13 04:22:29 +01:00
8b61ddb89e
Merge pull request '[Fix]: Dead Code' (!8) from fix/unused-breaking-old-template-code into trunk
All checks were successful
Quality Check / Linting (push) Successful in 7s
Build Application / generate (push) Successful in 22s
Build Application / build (tower_defence_web, build/*, build, build/index.html, Web) (push) Successful in 15s
Build Application / build (tower_defence.elf, tower_defence.elf, tower_defence.elf, Linux) (push) Successful in 18s
Build Application / build (tower_defence.exe, tower_defence.exe, tower_defence.exe, Windows) (push) Successful in 20s
Build Application / build-docker (push) Successful in 17s
Build Application / release (push) Successful in 9s
Reviewed-on: #8
Reviewed-by: SZUT-Kevin <kevin.schmidt9101@gmail.com>
2025-03-12 10:07:21 +00:00
ca2fb20e6f
Fix: remove old Code that breaks the current one
All checks were successful
Quality Check / Linting (push) Successful in 6s
Quality Check / Linting (pull_request) Successful in 7s
2025-03-12 09:58:08 +01:00
8342b744a7
Merge pull request '[Feature]: Tower Placement' (!7) from story/TD-11-tower-platzieren into trunk
All checks were successful
Quality Check / Linting (push) Successful in 6s
Reviewed-on: #7
Reviewed-by: SZUT-Rajbir <rajbir2@schule.bremen.de>
2025-03-11 07:17:34 +00:00
Kevin Schmidt
e6c1d4bf04 TD-11: Placement of Towers
All checks were successful
Quality Check / Linting (push) Successful in 6s
Quality Check / Linting (pull_request) Successful in 7s
2025-03-10 13:25:09 +01:00
e509eedffe
Merge pull request '[Feature]: Matchmaking Part2' (!6) from ticket/TD-18-Matchmaking-pt2 into trunk
All checks were successful
Quality Check / Linting (push) Successful in 6s
Reviewed-on: #6
Reviewed-by: SZUT-Kevin <kevin.schmidt9101@gmail.com>
2025-03-05 11:30:15 +00:00
3abd94d604
export_presets.cfg aktualisiert
All checks were successful
Quality Check / Linting (push) Successful in 6s
Quality Check / Linting (pull_request) Successful in 7s
2025-03-05 11:28:00 +00:00
Kevin Schmidt
f6e650b671 TD-18: Matchmaking UI
All checks were successful
Quality Check / Linting (push) Successful in 6s
Quality Check / Linting (pull_request) Successful in 7s
2025-03-05 11:35:40 +01:00
Kevin Schmidt
b1c2dd4eb5 TD-18: Setup Matchmaking
All checks were successful
Quality Check / Linting (push) Successful in 7s
2025-03-04 15:25:27 +01:00
8cafd27ff4
Merge pull request '[Feature]: Login Banner für Login Probleme' (!5) from task/td-64-meldung-bei-server-timeout into trunk
All checks were successful
Quality Check / Linting (push) Successful in 6s
Reviewed-on: #5
Reviewed-by: Snoweuph <snow+git@euph.email>
2025-02-26 10:07:49 +00:00
Rajbir Singh
49f8d8608a TD-64: Error Banner on Login Problems
All checks were successful
Quality Check / Linting (push) Successful in 7s
Quality Check / Linting (pull_request) Successful in 6s
2025-02-25 11:27:21 +01:00
38a2991198
Merge pull request '[Feature]:' (!4) from task/ws-setup into trunk
All checks were successful
Quality Check / Linting (push) Successful in 6s
Reviewed-on: #4
Reviewed-by: SZUT-Kevin <kevin.schmidt9101@gmail.com>
2025-02-19 07:10:33 +00:00
1531505d2f
NOTICKET: Adding WS Structure and Example
All checks were successful
Quality Check / Linting (push) Successful in 6s
Quality Check / Linting (pull_request) Successful in 6s
2025-02-18 13:02:40 +01:00
f43d186888
NOTICKET: Cleanup
All checks were successful
Quality Check / Linting (push) Successful in 5s
Build Application / generate (push) Successful in 23s
Build Application / build (tower_defence_web, build/*, build, build/index.html, Web) (push) Successful in 14s
Build Application / build (tower_defence.elf, tower_defence.elf, tower_defence.elf, Linux) (push) Successful in 17s
Build Application / build (tower_defence.exe, tower_defence.exe, tower_defence.exe, Windows) (push) Successful in 19s
Build Application / build-docker (push) Successful in 10s
Build Application / release (push) Successful in 9s
2025-02-15 12:50:25 +01:00
44 changed files with 1379 additions and 1774 deletions

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View 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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

@ -0,0 +1,9 @@
class_name ProvidedConnectionTokenMessage
extends Message
@export var channel: Channels
var token: String
func get_message_id() -> String:
return "ProvidedConnectionToken"

View file

@ -0,0 +1,8 @@
class_name RequestConnectionTokenMessage
extends Message
@export var channel: Channels
func get_message_id() -> String:
return "RequestConnectionToken"

View 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

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

View 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

View 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

View 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

View 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

View 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

View 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"

View 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

View 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

View file

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

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

View 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

View 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
View 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,
)

View file

@ -0,0 +1,5 @@
extends RichTextLabel
func _ready() -> void:
self.text = CurrentMatch.opponent_name

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

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

View 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

View file

@ -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,10 +21,17 @@ 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("pressed", login)
password_field.connect("pressed", login)
username_field.connect("text_submitted", login_wrapper)
password_field.connect("text_submitted", login_wrapper)
func login_wrapper(_text) -> void:
login()
func login() -> void:
@ -29,9 +41,11 @@ func login() -> void:
api.player_login(login_data, on_success, on_error)
func on_success(a: ApiResponse) -> void:
print(a.body)
func on_success(response: ApiResponse) -> void:
ConnectionChannel.connect_to_channel(response.data.token)
next_view.visible = true
login_successful.emit(response.data)
func on_error(b: ApiError) -> void:
print(b.response_code)
func on_error(error: ApiError) -> void:
login_error.emit(error)

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

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

View file

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