summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Assets/UI/tilemap_white.pngbin0 -> 10054 bytes
-rw-r--r--Assets/UI/tilemap_white.png.import34
-rw-r--r--Game/Client.gd42
-rw-r--r--Game/Network.gd80
-rw-r--r--Game/Player.gd18
-rw-r--r--Game/Selection/MultiSelectArea.tscn13
-rw-r--r--Game/Selection/SelectableArea.tscn14
-rw-r--r--Game/Selection/SelectionRectangle.tscn20
-rw-r--r--Game/Selection/multi_select_area.gd19
-rw-r--r--Game/Selection/selectable_area.gd29
-rw-r--r--Game/Selection/selection_rectangle.gd51
-rw-r--r--Game/States/Build/BuilderElement.gd3
-rw-r--r--Game/States/Build/StateBuild.gd26
-rw-r--r--Game/States/Default/StateDefault.gd6
-rw-r--r--Readme.md5
-rw-r--r--Stages/Stage.gd15
-rw-r--r--Stages/world.gd13
-rw-r--r--Stages/world.tscn5
-rw-r--r--Towers/Assets/attack-component.pngbin0 -> 191 bytes
-rw-r--r--Towers/Assets/attack-component.png.import34
-rw-r--r--Towers/Assets/attack-component.png~bin0 -> 206 bytes
-rw-r--r--Towers/Assets/burn-component.pngbin0 -> 175 bytes
-rw-r--r--Towers/Assets/burn-component.png.import34
-rw-r--r--Towers/Assets/burn-component.png~bin0 -> 175 bytes
-rw-r--r--Towers/Assets/frost-component.pngbin0 -> 176 bytes
-rw-r--r--Towers/Assets/frost-component.png.import34
-rw-r--r--Towers/Assets/range-component.pngbin0 -> 162 bytes
-rw-r--r--Towers/Assets/range-component.png.import34
-rw-r--r--Towers/Assets/range-component.png~bin0 -> 176 bytes
-rw-r--r--Towers/Tower.gd70
-rw-r--r--Towers/Tower.tscn15
-rw-r--r--UI/GameMenu.tscn12
-rw-r--r--UI/HUD.gd39
-rw-r--r--UI/HUD.tscn127
-rw-r--r--UI/Lobby.gd23
-rw-r--r--UI/Lobby.tscn48
-rw-r--r--UI/PlayersList.tscn34
-rw-r--r--UI/PlayersListItem.tscn48
-rw-r--r--UI/SpawnButton.tscn2
-rw-r--r--UI/TowerConfiguration.tscn28
-rw-r--r--UI/game_menu.gd4
-rw-r--r--UI/players_list.gd33
-rw-r--r--UI/tower_configuration.gd35
-rw-r--r--Units/Unit.gd55
-rw-r--r--Units/Unit.tscn5
-rw-r--r--project.godot17
-rw-r--r--theme.tres3
47 files changed, 971 insertions, 156 deletions
diff --git a/Assets/UI/tilemap_white.png b/Assets/UI/tilemap_white.png
new file mode 100644
index 0000000..207208e
--- /dev/null
+++ b/Assets/UI/tilemap_white.png
Binary files differ
diff --git a/Assets/UI/tilemap_white.png.import b/Assets/UI/tilemap_white.png.import
new file mode 100644
index 0000000..bd87880
--- /dev/null
+++ b/Assets/UI/tilemap_white.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dlg78heamuf5g"
+path="res://.godot/imported/tilemap_white.png-24d00e6fec9bf913023169274ca175c1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/UI/tilemap_white.png"
+dest_files=["res://.godot/imported/tilemap_white.png-24d00e6fec9bf913023169274ca175c1.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
diff --git a/Game/Client.gd b/Game/Client.gd
index 77ce500..3d9cf34 100644
--- a/Game/Client.gd
+++ b/Game/Client.gd
@@ -1,37 +1,50 @@
extends Node
-var state: State
+signal stage_state_changed(state: State)
+
+var state: State :
+ set(value):
+ state = value
+ stage_state_changed.emit(value)
+
var stage: Stage
-var player: Player = Player.new()
+var player: Player
-func update_player(local_player: Player):
- player = local_player
- Network.players[multiplayer.get_unique_id()] = local_player
+func _ready():
+ player = Player.new()
+ player.id = multiplayer.get_unique_id()
+
+
+func update_player():
+ Network.players[multiplayer.get_unique_id()] = player
+ Network.update_player.rpc(Network.to_rpc_object(player))
func initialize_stage(current_stage: Stage):
stage = current_stage
- player = Player.new()
func place_tower(tower: Tower, position: Vector2):
- tower.owner_id = multiplayer.get_unique_id()
+ var network_id = multiplayer.get_unique_id()
+ tower.owner_id = network_id
+ tower.set_multiplayer_authority(network_id)
+ tower.name = "Tower@" + str(network_id) + "@" + str(Time.get_ticks_usec())
stage.place_tower(tower, position)
Network.place_tower.rpc(Network.to_rpc_object(tower), position)
player.towers[position] = tower
player.score += 1
- update_player(player)
+ update_player()
func remove_tower(tower: Tower):
if tower.owner_id == multiplayer.get_unique_id():
destroy_tower(tower)
player.score -= 1
- update_player(player)
+ update_player()
func destroy_tower(tower: Tower):
@@ -39,7 +52,7 @@ func destroy_tower(tower: Tower):
Network.destroy_tower.rpc(Network.to_rpc_object(tower))
player.towers.erase(tower.global_position)
- update_player(player)
+ update_player()
func select_tower(tower: Tower):
@@ -54,10 +67,15 @@ func deselect_tower():
func spawn_unit(unit: Unit, spawn: Spawn):
+ var network_id = multiplayer.get_unique_id()
+ unit.owner_id = network_id
+ unit.set_multiplayer_authority(network_id)
+ unit.name = "Unit@" + str(network_id) + "@" + str(Time.get_ticks_usec())
+
unit.global_position = spawn.spawn_position
unit.target = spawn.next_node
- unit.hp = 20000
- unit.speed = randi_range(50, 150)
+ unit.hp = randi_range(50, 150) #20000b
+ unit.speed = randi_range(100, 150)
stage.spawn_unit(unit, spawn)
Network.spawn_unit.rpc(Network.to_rpc_object(unit), Network.to_rpc_object(spawn))
diff --git a/Game/Network.gd b/Game/Network.gd
index 021de69..9349245 100644
--- a/Game/Network.gd
+++ b/Game/Network.gd
@@ -1,26 +1,29 @@
extends Node
+signal players_changed
+
var players = {}
func _ready():
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.peer_connected.connect(_on_peer_connected)
+ multiplayer.peer_disconnected.connect(_on_peer_disconnected)
multiplayer.allow_object_decoding = true
-func host_game():
+func host_game(port):
var peer = ENetMultiplayerPeer.new()
- peer.create_server(1234, 2)
+ peer.create_server(int(port))
multiplayer.multiplayer_peer = peer
players[1] = Client.player
-func join_game():
+func join_game(ip, port):
var peer = ENetMultiplayerPeer.new()
- peer.create_client("127.0.0.1", 1234)
+ peer.create_client(ip, int(port))
multiplayer.multiplayer_peer = peer
@@ -31,6 +34,23 @@ func _on_connected_to_server():
func _on_peer_connected(id):
print("peer connected: ", id)
add_to_players.rpc(to_rpc_object(Client.player))
+ # TODO: add existing towers to new peers
+
+func _on_peer_disconnected(id):
+ print("peer disconnected: ", id)
+ # TODO: move towers owned by peer to host
+
+ if id == 1:
+ get_tree().change_scene_to_file("res://UI/Lobby.tscn")
+
+
+func get_ordered_player_ids():
+ var keys = players.keys()
+ keys.sort_custom(func(a, b):
+ return int(str(a).substr(0, 8)) < int(str(b).substr(0, 8))
+ )
+
+ return keys
func to_rpc_object(object: Variant):
@@ -42,6 +62,8 @@ func to_rpc_object(object: Variant):
remote_object[property] = {}
for key in object[property]:
remote_object[property][key] = to_rpc_object(object[property][key])
+ elif property_class is String and property_class.begins_with("node://"):
+ remote_object[property] = object[property]
elif property_class:
remote_object[property] = to_rpc_object(object[property])
else:
@@ -66,6 +88,12 @@ func from_rpc_object(remote_object: Dictionary, remote_class_path: String):
if object[property] is Dictionary:
for key in object[property]:
object[property][key] = from_rpc_object(remote_object[property], property_class)
+ elif property_class is String and property_class.begins_with("node://"):
+ var node_path = property_class.substr(7) # after node://
+ object.remove_child(object.get_node(node_path))
+ remote_object[property].name = node_path.get_basename()
+ object[property] = remote_object[property]
+ object.add_child(remote_object[property])
elif property_class:
object[property] = from_rpc_object(remote_object[property], property_class)
else:
@@ -94,14 +122,37 @@ func merge_with_rpc_object(object: Variant, remote_object: Dictionary):
func add_to_players(remote_player: Dictionary):
var id = multiplayer.get_remote_sender_id()
var player = from_rpc_object(remote_player, "res://Game/Player.gd")
+ player.id = id
players[id] = player
+ players_changed.emit()
@rpc("call_local", "any_peer")
func update_player(remote_player: Dictionary):
var id = multiplayer.get_remote_sender_id()
var player = merge_with_rpc_object(players[id], remote_player)
+ player.id = id
players[id] = player
+ players_changed.emit()
+
+
+@rpc("any_peer", "reliable")
+func update_node(node_path: NodePath, properties: Dictionary):
+ var root_node = get_tree().root.get_node(node_path)
+
+ for property in properties:
+ if property.contains(":"):
+ var child_node = get_nested_node_and_property(root_node, property)
+ child_node.node[child_node.property] = properties[property]
+ else:
+ root_node[property] = properties[property]
+
+func get_nested_node_and_property(node: Node, property: String) -> Dictionary:
+ if property.contains(":"):
+ var nested_node_path = property.substr(0, property.find(":"))
+ var nested_property = property.substr(property.find(":") + 1)
+ return get_nested_node_and_property(node.get_node(nested_node_path), nested_property)
+ return { "node": node, "property": property }
@rpc("any_peer")
@@ -129,4 +180,25 @@ func spawn_unit(remote_unit: Dictionary, remote_spawn: Dictionary):
var unit = from_rpc_object(remote_unit, "res://Units/Unit.tscn")
var spawn = from_rpc_object(remote_spawn, "res://Stages/Paths/Spawn.tscn")
+ var remote_id = multiplayer.get_remote_sender_id()
+ unit.owner_id = remote_id
+ unit.set_multiplayer_authority(remote_id)
+
Client.stage.spawn_unit(unit, spawn)
+
+@rpc("any_peer")
+func remove_unit(remote_unit_node_path):
+ var unit = get_tree().current_scene.get_node_or_null(remote_unit_node_path)
+ if unit:
+ unit.queue_free()
+
+@rpc("any_peer")
+func update_unit(remote_unit_node_path, data):
+ var unit: Unit = get_tree().current_scene.get_node_or_null(remote_unit_node_path)
+ if unit:
+ if "position" in data:
+ unit.position = data.position
+ unit.hp = data.hp
+ if "sprite" in data:
+ unit.get_node("Sprite2D").self_modulate = data.sprite.self_modulate
+
diff --git a/Game/Player.gd b/Game/Player.gd
index 2726929..43856d5 100644
--- a/Game/Player.gd
+++ b/Game/Player.gd
@@ -2,6 +2,10 @@ class_name Player
extends Resource
+signal score_changed
+
+var id := 1
+
var towers: Dictionary :
set(value):
towers = value
@@ -9,12 +13,24 @@ var towers: Dictionary :
var score: int :
set(value):
score = value
- Client.stage.hud.score.text = str(score)
+ score_changed.emit()
var units: Array[Unit]
+func get_color():
+ if id == 1:
+ return Color("#fff")
+
+ var rng = RandomNumberGenerator.new()
+ rng.seed = id
+
+ @warning_ignore("integer_division")
+ return Color(rng.randf(), rng.randf(), rng.randf())
+
+
func get_rpc_properties() -> Dictionary:
return {
+ "id": null,
"score": null,
}
diff --git a/Game/Selection/MultiSelectArea.tscn b/Game/Selection/MultiSelectArea.tscn
new file mode 100644
index 0000000..9e6bd43
--- /dev/null
+++ b/Game/Selection/MultiSelectArea.tscn
@@ -0,0 +1,13 @@
+[gd_scene load_steps=3 format=3 uid="uid://bmi8eb80wghjs"]
+
+[ext_resource type="Script" path="res://Game/Selection/multi_select_area.gd" id="1_g76x3"]
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_8io84"]
+
+[node name="MultiSelectArea" type="Area2D"]
+collision_layer = 0
+collision_mask = 0
+script = ExtResource("1_g76x3")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("RectangleShape2D_8io84")
diff --git a/Game/Selection/SelectableArea.tscn b/Game/Selection/SelectableArea.tscn
new file mode 100644
index 0000000..3f64dec
--- /dev/null
+++ b/Game/Selection/SelectableArea.tscn
@@ -0,0 +1,14 @@
+[gd_scene load_steps=2 format=3 uid="uid://cqktpc8c7ecn3"]
+
+[ext_resource type="Script" path="res://Game/Selection/selectable_area.gd" id="1_8w2y0"]
+
+[node name="SelectableArea" type="Area2D"]
+collision_layer = 32
+collision_mask = 16
+script = ExtResource("1_8w2y0")
+
+[connection signal="area_entered" from="." to="." method="_on_area_entered"]
+[connection signal="area_exited" from="." to="." method="_on_area_exited"]
+[connection signal="input_event" from="." to="." method="_on_input_event"]
+[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"]
+[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"]
diff --git a/Game/Selection/SelectionRectangle.tscn b/Game/Selection/SelectionRectangle.tscn
new file mode 100644
index 0000000..61517d0
--- /dev/null
+++ b/Game/Selection/SelectionRectangle.tscn
@@ -0,0 +1,20 @@
+[gd_scene load_steps=3 format=3 uid="uid://ic2hc7gr27p3"]
+
+[ext_resource type="Script" path="res://Game/Selection/selection_rectangle.gd" id="1_on0pa"]
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_nq6xv"]
+size = Vector2(1, 1)
+
+[node name="SelectionRectangle" type="Area2D" groups=["selection_rectangle"]]
+z_index = 1
+collision_layer = 16
+collision_mask = 32
+script = ExtResource("1_on0pa")
+color_background = Color(0.0823529, 0.392157, 0.203922, 0.392157)
+color_border = Color(0.0823529, 1, 0.203922, 0.501961)
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("RectangleShape2D_nq6xv")
+
+[connection signal="area_entered" from="." to="." method="_on_area_entered"]
+[connection signal="area_exited" from="." to="." method="_on_area_exited"]
diff --git a/Game/Selection/multi_select_area.gd b/Game/Selection/multi_select_area.gd
new file mode 100644
index 0000000..9b2e8ce
--- /dev/null
+++ b/Game/Selection/multi_select_area.gd
@@ -0,0 +1,19 @@
+extends Area2D
+
+
+signal select(nodes: Array)
+
+
+func _ready() -> void:
+ var camera_rect = Client.stage.get_node("Camera").get_rect()
+
+ global_position = camera_rect.position + camera_rect.size / 2
+
+ $CollisionShape2D.shape.size = camera_rect.size
+
+
+func _process(_delta: float) -> void:
+ var nodes = get_overlapping_areas() + get_overlapping_bodies()
+ if nodes.size() > 0:
+ select.emit(nodes)
+ queue_free()
diff --git a/Game/Selection/selectable_area.gd b/Game/Selection/selectable_area.gd
new file mode 100644
index 0000000..59d4be7
--- /dev/null
+++ b/Game/Selection/selectable_area.gd
@@ -0,0 +1,29 @@
+extends Area2D
+
+
+signal hover_enter
+signal hover_exit
+signal select(event: InputEvent)
+
+
+func _on_area_entered(_area: Area2D) -> void:
+ hover_enter.emit()
+
+
+func _on_area_exited(_area: Area2D) -> void:
+ hover_exit.emit()
+
+
+func _on_mouse_entered() -> void:
+ if not get_tree().get_first_node_in_group("selection_rectangle").is_active:
+ hover_enter.emit()
+
+
+func _on_mouse_exited() -> void:
+ if not get_tree().get_first_node_in_group("selection_rectangle").is_active:
+ hover_exit.emit()
+
+
+func _on_input_event(_viewport: Node, event: InputEvent, _shape_idx: int) -> void:
+ if event.is_action_pressed("select"):
+ select.emit(event)
diff --git a/Game/Selection/selection_rectangle.gd b/Game/Selection/selection_rectangle.gd
new file mode 100644
index 0000000..a57f25b
--- /dev/null
+++ b/Game/Selection/selection_rectangle.gd
@@ -0,0 +1,51 @@
+extends Area2D
+
+
+@export_group("Color", "color")
+@export var color_background: Color
+@export var color_border: Color
+
+var anchor: Vector2 = Vector2.ZERO
+var size: Vector2 = Vector2.ZERO :
+ set(value):
+ size = value
+ $CollisionShape2D.position = size / 2
+ $CollisionShape2D.shape.size = abs(size)
+
+var is_active: bool :
+ get():
+ return abs(size) > Vector2(1, 1)
+
+
+func _process(_delta: float) -> void:
+ if Input.is_action_just_pressed("select"):
+ anchor = get_global_mouse_position()
+ global_position = anchor
+
+ if Input.is_action_pressed("select"):
+ size = get_global_mouse_position() - anchor
+ if is_active:
+ queue_redraw()
+
+ if is_active and Input.is_action_just_released("select"):
+ for area in get_overlapping_areas():
+ if "is_selected" in area.get_parent():
+ area.get_parent().is_selected = true
+
+ size = Vector2.ZERO
+ queue_redraw()
+
+
+func _draw():
+ if is_active:
+ var rect = Rect2(Vector2.ZERO, size)
+ draw_rect(rect, color_background)
+ draw_rect(rect, color_border, false, 2.0)
+
+
+func _on_area_entered(area: Area2D) -> void:
+ area.get_parent().is_hovered = true
+
+
+func _on_area_exited(area: Area2D) -> void:
+ area.get_parent().is_hovered = false
diff --git a/Game/States/Build/BuilderElement.gd b/Game/States/Build/BuilderElement.gd
index 8315792..bb10b06 100644
--- a/Game/States/Build/BuilderElement.gd
+++ b/Game/States/Build/BuilderElement.gd
@@ -67,6 +67,9 @@ func _process(_delta):
if global_position != previous_position:
queue_redraw()
+
+ # TODO: rpc to other peers
+ # TODO: only as preview not as blocking to build
func can_build():
diff --git a/Game/States/Build/StateBuild.gd b/Game/States/Build/StateBuild.gd
index b92020b..d2fb39d 100644
--- a/Game/States/Build/StateBuild.gd
+++ b/Game/States/Build/StateBuild.gd
@@ -7,19 +7,6 @@ static var current_builder_element: BuilderElement
func _state_enter():
%BuildGrid.visible = true
- var builder_element_scene = preload("res://Game/States/Build/BuilderElement.tscn")
- var builder_element = builder_element_scene.instantiate()
-
- var tower = preload("res://Towers/Tower.tscn").instantiate() as Tower
- tower.attack_range = [
- Client.stage.map.tile_set.tile_size.x * 2,
- Client.stage.map.tile_set.tile_size.x * 3,
- Client.stage.map.tile_set.tile_size.x * 4,
- ].pick_random()
-
- builder_element.element = tower
- get_tree().current_scene.add_child(builder_element)
- current_builder_element = builder_element
func _state_exit():
@@ -28,9 +15,8 @@ func _state_exit():
func _state_input(event: InputEvent):
if event.is_action_pressed("builder_tower_select"):
- get_viewport().set_input_as_handled()
- if current_builder_element.can_build():
+ if current_builder_element and current_builder_element.can_build():
var placed_tower = current_builder_element.element.duplicate() as Tower
Client.place_tower(placed_tower, current_builder_element.global_position)
@@ -42,9 +28,13 @@ func _state_input(event: InputEvent):
if not Input.is_action_pressed("builder_tower_place_keep"):
current_builder_element.queue_free()
- set_state("StateDefault")
+ current_builder_element = null
- if event.is_action_pressed("builder_cancel") or event.is_action_pressed("build_mode_start"):
+ if event.is_action_pressed("build_mode_start"):
get_viewport().set_input_as_handled()
- current_builder_element.queue_free()
+
+ if current_builder_element:
+ current_builder_element.queue_free()
+ current_builder_element = null
+
set_state("StateDefault")
diff --git a/Game/States/Default/StateDefault.gd b/Game/States/Default/StateDefault.gd
index 26384c5..1d2dd9f 100644
--- a/Game/States/Default/StateDefault.gd
+++ b/Game/States/Default/StateDefault.gd
@@ -4,9 +4,11 @@ extends State
func _state_input(event: InputEvent) -> void:
if event.is_action_pressed("build_mode_start"):
- set_state("StateBuild")
+ set_state("StateBuild")
- if event.is_action_pressed("builder_tower_select"):
+ # deselect
+ if event.is_action_pressed("select"):
+ # if not multi selecting
if not event.is_double_click() and not Input.is_action_pressed("select_multiple"):
if Tower.selected_towers:
for tower in Tower.selected_towers.duplicate():
diff --git a/Readme.md b/Readme.md
index 84362b6..7b2cd0c 100644
--- a/Readme.md
+++ b/Readme.md
@@ -2,4 +2,7 @@ Make Tower yourself
Power / Range / Speed - per cost and upgradelevel rpg-like points to distribute
Give them abilities/skills
Splash yes/no + splash range - could only be available at tower upgrade level 2+
-exp system for towers??
+exp system for towers?? - transfer xp to other selected tower that is over max_xp usable
+
+Add Components to Towers
+Pre-Build Tower Configurations
diff --git a/Stages/Stage.gd b/Stages/Stage.gd
index fec5b76..963ee46 100644
--- a/Stages/Stage.gd
+++ b/Stages/Stage.gd
@@ -37,24 +37,17 @@ func _ready() -> void:
func place_tower(tower: Tower, position: Vector2):
- if tower.owner_id != 1:
- var id = str(tower.owner_id)
- tower.get_node("Sprite2D").modulate = Color(
- int(id.substr(0, 3)) / 255,
- int(id.substr(3, 3)) / 255,
- int(id.substr(6, 3)) / 255
- )
+ var player: Player = Network.players[tower.owner_id]
+ tower.get_node("Sprite2D").modulate = player.get_color()
tower.global_position = position
fill_tower_region(tower, true)
towers.add_child(tower)
- path_grid_changed.emit()
func destroy_tower(tower: Tower):
fill_tower_region(tower, false)
tower.queue_free()
- path_grid_changed.emit()
func fill_tower_region(tower: Tower, solid = true):
@@ -76,8 +69,12 @@ func fill_tower_region(tower: Tower, solid = true):
),
solid
)
+ path_grid_changed.emit()
@warning_ignore("shadowed_variable")
func spawn_unit(unit: Unit, _spawn: Spawn):
+ var player: Player = Network.players[unit.owner_id]
+ unit.get_node("Sprite2D").modulate = player.get_color()
+
units.add_child(unit)
diff --git a/Stages/world.gd b/Stages/world.gd
index b5a4daa..0549a1c 100644
--- a/Stages/world.gd
+++ b/Stages/world.gd
@@ -19,16 +19,3 @@ func _input(event: InputEvent):
var scene = preload("res://Units/Unit.tscn")
var unit = scene.instantiate()
Client.spawn_unit(unit, %Spawn)
-
- if event.is_action_pressed("spawn_box_toggle"):
- hud.spawn_box.visible = not hud.spawn_box.visible
-
-
-func _on_build_mode_button_gui_input(event: InputEvent) -> void:
- if event.is_action_pressed("builder_tower_select"):
- $StateManager.set_state("StateBuild")
-
-
-func _on_spawner_box_button_gui_input(event: InputEvent) -> void:
- if event.is_action_pressed("select"):
- hud.spawn_box.visible = not hud.spawn_box.visible
diff --git a/Stages/world.tscn b/Stages/world.tscn
index d1072e1..e53d874 100644
--- a/Stages/world.tscn
+++ b/Stages/world.tscn
@@ -1,10 +1,11 @@
-[gd_scene load_steps=15 format=4 uid="uid://bl65jllb3e2py"]
+[gd_scene load_steps=16 format=4 uid="uid://bl65jllb3e2py"]
[ext_resource type="Texture2D" uid="uid://b1b18rd0tqbar" path="res://core_outdoor.png" id="1_luil3"]
[ext_resource type="Script" path="res://Stages/world.gd" id="1_o88ua"]
[ext_resource type="PackedScene" uid="uid://bylx30cweulmk" path="res://UI/HUD.tscn" id="2_v3f6l"]
[ext_resource type="PackedScene" uid="uid://of5ggu6lifwy" path="res://Stages/Paths/Spawn.tscn" id="3_f2sda"]
[ext_resource type="PackedScene" uid="uid://2lt8m7df0e2u" path="res://Stages/Paths/Goal.tscn" id="5_dp16q"]
+[ext_resource type="PackedScene" uid="uid://ic2hc7gr27p3" path="res://Game/Selection/SelectionRectangle.tscn" id="6_7dk4w"]
[ext_resource type="PackedScene" uid="uid://d0ukgoppkh1fn" path="res://Stages/Paths/PathNode.tscn" id="6_lh0f6"]
[ext_resource type="PackedScene" uid="uid://t8feyd2giabm" path="res://UI/Camera.tscn" id="6_yijl8"]
[ext_resource type="PackedScene" uid="uid://by1x56w21o165" path="res://Towers/Tower.tscn" id="7_5o3d3"]
@@ -1958,6 +1959,8 @@ zoom = Vector2(1.5, 1.5)
drag_horizontal_enabled = true
drag_vertical_enabled = true
+[node name="SelectionRectangle" parent="." instance=ExtResource("6_7dk4w")]
+
[node name="Paths" type="Node" parent="."]
[node name="Spawn" parent="Paths" node_paths=PackedStringArray("next_node") instance=ExtResource("3_f2sda")]
diff --git a/Towers/Assets/attack-component.png b/Towers/Assets/attack-component.png
new file mode 100644
index 0000000..bb9250c
--- /dev/null
+++ b/Towers/Assets/attack-component.png
Binary files differ
diff --git a/Towers/Assets/attack-component.png.import b/Towers/Assets/attack-component.png.import
new file mode 100644
index 0000000..ef9a73b
--- /dev/null
+++ b/Towers/Assets/attack-component.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://gbknvb38euuq"
+path="res://.godot/imported/attack-component.png-648f34932bc6754ec1b1094b76b170bc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Towers/Assets/attack-component.png"
+dest_files=["res://.godot/imported/attack-component.png-648f34932bc6754ec1b1094b76b170bc.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
diff --git a/Towers/Assets/attack-component.png~ b/Towers/Assets/attack-component.png~
new file mode 100644
index 0000000..e2f80bc
--- /dev/null
+++ b/Towers/Assets/attack-component.png~
Binary files differ
diff --git a/Towers/Assets/burn-component.png b/Towers/Assets/burn-component.png
new file mode 100644
index 0000000..33f9f7e
--- /dev/null
+++ b/Towers/Assets/burn-component.png
Binary files differ
diff --git a/Towers/Assets/burn-component.png.import b/Towers/Assets/burn-component.png.import
new file mode 100644
index 0000000..0783a55
--- /dev/null
+++ b/Towers/Assets/burn-component.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2djpswd6sgng"
+path="res://.godot/imported/burn-component.png-c625b666f5c33be88ffb5514acf984cb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Towers/Assets/burn-component.png"
+dest_files=["res://.godot/imported/burn-component.png-c625b666f5c33be88ffb5514acf984cb.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
diff --git a/Towers/Assets/burn-component.png~ b/Towers/Assets/burn-component.png~
new file mode 100644
index 0000000..c9c1619
--- /dev/null
+++ b/Towers/Assets/burn-component.png~
Binary files differ
diff --git a/Towers/Assets/frost-component.png b/Towers/Assets/frost-component.png
new file mode 100644
index 0000000..e1c12f7
--- /dev/null
+++ b/Towers/Assets/frost-component.png
Binary files differ
diff --git a/Towers/Assets/frost-component.png.import b/Towers/Assets/frost-component.png.import
new file mode 100644
index 0000000..d22126b
--- /dev/null
+++ b/Towers/Assets/frost-component.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ba3dmlce1wv2p"
+path="res://.godot/imported/frost-component.png-e1bd3aee287cb92be940679bf3553e5f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Towers/Assets/frost-component.png"
+dest_files=["res://.godot/imported/frost-component.png-e1bd3aee287cb92be940679bf3553e5f.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
diff --git a/Towers/Assets/range-component.png b/Towers/Assets/range-component.png
new file mode 100644
index 0000000..e5ef51e
--- /dev/null
+++ b/Towers/Assets/range-component.png
Binary files differ
diff --git a/Towers/Assets/range-component.png.import b/Towers/Assets/range-component.png.import
new file mode 100644
index 0000000..c626726
--- /dev/null
+++ b/Towers/Assets/range-component.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dx07y4scyi5a1"
+path="res://.godot/imported/range-component.png-5b23d157d3796cb80d7b2edb7addabe5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Towers/Assets/range-component.png"
+dest_files=["res://.godot/imported/range-component.png-5b23d157d3796cb80d7b2edb7addabe5.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
diff --git a/Towers/Assets/range-component.png~ b/Towers/Assets/range-component.png~
new file mode 100644
index 0000000..e1c12f7
--- /dev/null
+++ b/Towers/Assets/range-component.png~
Binary files differ
diff --git a/Towers/Tower.gd b/Towers/Tower.gd
index f3b6667..54687fd 100644
--- a/Towers/Tower.gd
+++ b/Towers/Tower.gd
@@ -27,14 +27,11 @@ var is_hovered = false :
var mobs_in_range: Array = []
-var selection_area: Area2D
+#var selection_area: Area2D
# rpc owner id
var owner_id = 1
-# unique shared id on the network on all clients
-var network_id
-
@export var attack_range: int = 32
@export var attack_power: int = 1
@export var attack_speed: int = 1
@@ -74,12 +71,12 @@ func _process(_delta: float) -> void:
shoot()
$ShootCooldown.start()
- if selection_area and is_instance_valid(selection_area):
- var bodies = selection_area.get_overlapping_bodies()
- if bodies.size() > 0:
- selection_area.queue_free()
- for body in bodies:
- Client.select_tower(body)
+ #if selection_area and is_instance_valid(selection_area):
+ #var bodies = selection_area.get_overlapping_bodies()
+ #if bodies.size() > 0:
+ #selection_area.queue_free()
+ #for body in bodies:
+ #Client.select_tower(body)
func _on_input_event(_viewport: Node, event: InputEvent, _shape_idx: int):
@@ -87,25 +84,7 @@ func _on_input_event(_viewport: Node, event: InputEvent, _shape_idx: int):
if owner_id != multiplayer.get_unique_id():
return
- if Client.state is StateDefault:
- if event.is_action_pressed("builder_tower_select"):
- Client.select_tower(self)
-
- if event is InputEventMouseButton:
- if event.is_double_click():
- selection_area = Area2D.new()
- selection_area.position = (
- Client.stage.get_node("Camera").get_rect().position +
- Client.stage.get_node("Camera").get_rect().size / 2
- )
- selection_area.set_collision_mask_value(3, true)
- var collision_shape = CollisionShape2D.new()
- var shape = RectangleShape2D.new()
- shape.size = Client.stage.get_node("Camera").get_rect().size
- collision_shape.shape = shape
- selection_area.add_child(collision_shape)
- get_tree().current_scene.add_child(selection_area)
-
+ if Client.state is StateBuild:
if event.is_action_pressed("builder_cancel"):
Client.remove_tower(self)
@@ -114,22 +93,45 @@ func _on_range_body_entered(body: Node2D) -> void:
mobs_in_range.append(body)
func _on_range_body_exited(body: Node2D) -> void:
- mobs_in_range.remove_at(mobs_in_range.find(body))
+ mobs_in_range.erase(body)
-func _on_mouse_entered() -> void:
+func _on_selectable_area_hover_enter() -> void:
is_hovered = true
-func _on_mouse_exited() -> void:
+func _on_selectable_area_hover_exit() -> void:
is_hovered = false
+func _on_selectable_area_select(event: InputEvent) -> void:
+ # disable remote select for now
+ if owner_id != multiplayer.get_unique_id():
+ return
+
+ if Client.state is StateDefault:
+ Client.select_tower(self)
+
+ if event.is_double_click():
+ var selection_area = preload("res://Game/Selection/MultiSelectArea.tscn").instantiate()
+ selection_area.set_collision_mask_value(3, true)
+ selection_area.select.connect(func(nodes):
+ for node in nodes:
+ Client.select_tower(node)
+ )
+ get_tree().current_scene.add_child(selection_area)
+
+
+
func is_melee_range():
return attack_range <= (Client.stage.map.tile_set.tile_size.x * 2)
func shoot():
- var target = mobs_in_range.pick_random() as Unit
+ if get_multiplayer_authority() != multiplayer.get_unique_id():
+ # TODO: do shoot animation, but don't subtract hp
+ return
+
+ var target = mobs_in_range[0] as Unit
if is_melee_range():
target.set_hp(target.hp - 1)
@@ -158,7 +160,7 @@ func _on_tree_exiting() -> void:
func get_rpc_properties() -> Dictionary:
return {
+ "name": null,
"global_position": null,
"owner_id": null,
- "network_id": null,
}
diff --git a/Towers/Tower.tscn b/Towers/Tower.tscn
index dfb2364..4177a3a 100644
--- a/Towers/Tower.tscn
+++ b/Towers/Tower.tscn
@@ -1,13 +1,17 @@
-[gd_scene load_steps=5 format=3 uid="uid://by1x56w21o165"]
+[gd_scene load_steps=7 format=3 uid="uid://by1x56w21o165"]
[ext_resource type="Script" path="res://Towers/Tower.gd" id="1_axo1d"]
[ext_resource type="Texture2D" uid="uid://b1b18rd0tqbar" path="res://core_outdoor.png" id="1_mrep8"]
+[ext_resource type="PackedScene" uid="uid://cqktpc8c7ecn3" path="res://Game/Selection/SelectableArea.tscn" id="3_57d5u"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_atm5x"]
size = Vector2(31, 31)
[sub_resource type="CircleShape2D" id="CircleShape2D_qa8kt"]
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_312i7"]
+size = Vector2(32, 32)
+
[node name="Tower" type="StaticBody2D"]
y_sort_enabled = true
collision_layer = 4
@@ -38,9 +42,18 @@ shape = SubResource("CircleShape2D_qa8kt")
[node name="ShootCooldown" type="Timer" parent="."]
one_shot = true
+[node name="SelectableArea" parent="." instance=ExtResource("3_57d5u")]
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="SelectableArea"]
+position = Vector2(16, 16)
+shape = SubResource("RectangleShape2D_312i7")
+
[connection signal="input_event" from="." to="." method="_on_input_event"]
[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"]
[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"]
[connection signal="tree_exiting" from="." to="." method="_on_tree_exiting"]
[connection signal="body_entered" from="Range" to="." method="_on_range_body_entered"]
[connection signal="body_exited" from="Range" to="." method="_on_range_body_exited"]
+[connection signal="hover_enter" from="SelectableArea" to="." method="_on_selectable_area_hover_enter"]
+[connection signal="hover_exit" from="SelectableArea" to="." method="_on_selectable_area_hover_exit"]
+[connection signal="select" from="SelectableArea" to="." method="_on_selectable_area_select"]
diff --git a/UI/GameMenu.tscn b/UI/GameMenu.tscn
new file mode 100644
index 0000000..cf6646e
--- /dev/null
+++ b/UI/GameMenu.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=3 uid="uid://c13v4wmjm4sev"]
+
+[ext_resource type="Script" path="res://UI/game_menu.gd" id="1_frq7d"]
+
+[node name="GameMenu" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_frq7d")
diff --git a/UI/HUD.gd b/UI/HUD.gd
index 7674ce3..023f157 100644
--- a/UI/HUD.gd
+++ b/UI/HUD.gd
@@ -5,3 +5,42 @@ extends CanvasLayer
@onready var score: Label = %Score
@onready var tower: Label = %Tower
@onready var spawn_box: Control = %SpawnBox
+@onready var players_list: PanelContainer = %PlayersList
+
+
+func _ready():
+ Client.player.score_changed.connect(func():
+ score.text = str(Client.player.score)
+ )
+
+ Client.stage_state_changed.connect(func(state: State):
+ if state is StateBuild:
+ $TowerConfigurationsContainer.visible = true
+ else:
+ $TowerConfigurationsContainer.visible = false
+ )
+
+
+func _input(event: InputEvent):
+ if event.is_action_pressed("spawn_box_toggle"):
+ spawn_box.visible = not spawn_box.visible
+ if event.is_action_pressed("players_list_toggle"):
+ players_list.visible = not players_list.visible
+
+
+func _on_build_mode_button_gui_input(event: InputEvent) -> void:
+ if event.is_action_pressed("select"):
+ if Client.state is StateDefault:
+ get_tree().current_scene.get_node("StateManager").set_state("StateBuild")
+ elif Client.state is StateBuild:
+ get_tree().current_scene.get_node("StateManager").set_state("StateDefault")
+
+
+func _on_spawner_box_button_gui_input(event: InputEvent) -> void:
+ if event.is_action_pressed("select"):
+ spawn_box.visible = not spawn_box.visible
+
+
+func _on_player_list_button_gui_input(event: InputEvent) -> void:
+ if event.is_action_pressed("select"):
+ players_list.visible = not players_list.visible
diff --git a/UI/HUD.tscn b/UI/HUD.tscn
index b73fc34..b4756cd 100644
--- a/UI/HUD.tscn
+++ b/UI/HUD.tscn
@@ -1,10 +1,10 @@
-[gd_scene load_steps=17 format=3 uid="uid://bylx30cweulmk"]
+[gd_scene load_steps=24 format=3 uid="uid://bylx30cweulmk"]
[ext_resource type="Script" path="res://UI/HUD.gd" id="1_2bu0v"]
-[ext_resource type="Texture2D" uid="uid://c7ntdvxvv16io" path="res://Assets/UI/key_e.png" id="1_d2guw"]
-[ext_resource type="Texture2D" uid="uid://hljlcokgys6y" path="res://Assets/UI/key_r.png" id="2_b00ni"]
+[ext_resource type="Texture2D" uid="uid://dlg78heamuf5g" path="res://Assets/UI/tilemap_white.png" id="2_dyehp"]
[ext_resource type="PackedScene" uid="uid://x6kohecnw7f5" path="res://UI/SpawnButton.tscn" id="3_7eaea"]
[ext_resource type="Texture2D" uid="uid://up1rtweit3ut" path="res://Assets/Mobs/angesnow-menu01.png" id="4_w7sef"]
+[ext_resource type="Theme" uid="uid://c7f1ftrx53ag1" path="res://theme.tres" id="5_121ry"]
[ext_resource type="Texture2D" uid="uid://dq2i36oe1wj0m" path="res://Assets/Mobs/mob-pressed.png" id="5_xcxr8"]
[ext_resource type="Texture2D" uid="uid://dnkr5y0cfxu68" path="res://Assets/Mobs/mob-hovered.png" id="6_4go2d"]
[ext_resource type="Texture2D" uid="uid://dsy7k2v5fhh6v" path="res://Assets/Mobs/angesnow-front.png" id="7_ba5tw"]
@@ -15,10 +15,30 @@
[ext_resource type="Texture2D" uid="uid://byrx3c087exvb" path="res://Assets/Mobs/windeye-menu01.png" id="12_20egp"]
[ext_resource type="Texture2D" uid="uid://coiiq1yaonxeg" path="res://Assets/Mobs/windeye-front.png" id="13_iq5a7"]
[ext_resource type="Texture2D" uid="uid://dr02nqmrnciy0" path="res://Assets/Mobs/mob.png" id="14_t3qlu"]
+[ext_resource type="PackedScene" uid="uid://cxd6c4kbnk04c" path="res://UI/PlayersList.tscn" id="16_mq4um"]
+[ext_resource type="PackedScene" uid="uid://c05aq7xd4kx1p" path="res://UI/TowerConfiguration.tscn" id="17_1c5dq"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3rjde"]
bg_color = Color(0, 0, 0, 0.54902)
+[sub_resource type="AtlasTexture" id="AtlasTexture_byi0r"]
+atlas = ExtResource("2_dyehp")
+region = Rect2(392, 69, 13, 13)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_dixt2"]
+atlas = ExtResource("2_dyehp")
+region = Rect2(341, 35, 13, 13)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_tuk1j"]
+atlas = ExtResource("2_dyehp")
+region = Rect2(443, 35, 13, 13)
+
+[sub_resource type="InputEventAction" id="InputEventAction_t6x4q"]
+action = &"spawn_unit"
+
+[sub_resource type="Shortcut" id="Shortcut_i6rmj"]
+events = [SubResource("InputEventAction_t6x4q")]
+
[node name="HUD" type="CanvasLayer"]
script = ExtResource("1_2bu0v")
@@ -37,7 +57,7 @@ layout_mode = 2
[node name="GridContainer" type="GridContainer" parent="Panel/VBoxContainer/Container"]
layout_mode = 2
-columns = 2
+columns = 3
[node name="MarginContainer" type="MarginContainer" parent="Panel/VBoxContainer/Container/GridContainer"]
layout_mode = 2
@@ -50,9 +70,10 @@ theme_override_constants/margin_bottom = 4
layout_mode = 2
[node name="BuildModeButton" type="TextureRect" parent="Panel/VBoxContainer/Container/GridContainer/MarginContainer/HBoxContainer"]
+texture_filter = 1
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
-texture = ExtResource("1_d2guw")
+texture = SubResource("AtlasTexture_byi0r")
expand_mode = 1
[node name="Label" type="Label" parent="Panel/VBoxContainer/Container/GridContainer/MarginContainer/HBoxContainer"]
@@ -70,15 +91,37 @@ theme_override_constants/margin_bottom = 4
layout_mode = 2
[node name="SpawnerBoxButton" type="TextureRect" parent="Panel/VBoxContainer/Container/GridContainer/MarginContainer2/HBoxContainer2"]
+texture_filter = 1
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
-texture = ExtResource("2_b00ni")
+texture = SubResource("AtlasTexture_dixt2")
expand_mode = 1
[node name="Label" type="Label" parent="Panel/VBoxContainer/Container/GridContainer/MarginContainer2/HBoxContainer2"]
layout_mode = 2
text = "Spawn Box"
+[node name="MarginContainer3" type="MarginContainer" parent="Panel/VBoxContainer/Container/GridContainer"]
+layout_mode = 2
+theme_override_constants/margin_left = 4
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 4
+theme_override_constants/margin_bottom = 4
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/VBoxContainer/Container/GridContainer/MarginContainer3"]
+layout_mode = 2
+
+[node name="PlayerListButton" type="TextureRect" parent="Panel/VBoxContainer/Container/GridContainer/MarginContainer3/HBoxContainer2"]
+texture_filter = 1
+custom_minimum_size = Vector2(24, 24)
+layout_mode = 2
+texture = SubResource("AtlasTexture_tuk1j")
+expand_mode = 1
+
+[node name="Label" type="Label" parent="Panel/VBoxContainer/Container/GridContainer/MarginContainer3/HBoxContainer2"]
+layout_mode = 2
+text = "Players List"
+
[node name="Control" type="Control" parent="Panel/VBoxContainer/Container"]
layout_mode = 2
size_flags_horizontal = 3
@@ -115,8 +158,10 @@ anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
-offset_left = -40.0
-offset_top = -40.0
+offset_left = -148.0
+offset_top = -116.0
+offset_right = -4.0
+offset_bottom = -4.0
grow_horizontal = 0
grow_vertical = 0
@@ -129,12 +174,13 @@ theme_override_constants/margin_bottom = 8
[node name="GridContainer" type="GridContainer" parent="SpawnBox/MarginContainer"]
layout_mode = 2
-theme_override_constants/h_separation = 12
-theme_override_constants/v_separation = 12
+theme_override_constants/h_separation = 0
+theme_override_constants/v_separation = 0
columns = 4
[node name="SpawnButton" parent="SpawnBox/MarginContainer/GridContainer" instance=ExtResource("3_7eaea")]
layout_mode = 2
+theme = ExtResource("5_121ry")
texture_normal = ExtResource("4_w7sef")
texture_pressed = ExtResource("5_xcxr8")
texture_hover = ExtResource("6_4go2d")
@@ -142,6 +188,7 @@ texture = ExtResource("7_ba5tw")
[node name="SpawnButton2" parent="SpawnBox/MarginContainer/GridContainer" instance=ExtResource("3_7eaea")]
layout_mode = 2
+shortcut = SubResource("Shortcut_i6rmj")
texture_normal = ExtResource("8_wmbg8")
texture_hover = ExtResource("6_4go2d")
texture = ExtResource("9_nmd8t")
@@ -208,26 +255,54 @@ texture_normal = ExtResource("14_t3qlu")
texture_pressed = ExtResource("5_xcxr8")
texture_hover = ExtResource("6_4go2d")
-[node name="SpawnButton13" parent="SpawnBox/MarginContainer/GridContainer" instance=ExtResource("3_7eaea")]
+[node name="PlayersList" parent="." instance=ExtResource("16_mq4um")]
+unique_name_in_owner = true
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -144.0
+offset_top = 42.0
+offset_right = -5.0
+offset_bottom = 85.0
+grow_horizontal = 0
+
+[node name="TowerConfigurationsContainer" type="MarginContainer" parent="."]
+visible = false
+anchors_preset = 12
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = -120.0
+offset_right = -152.0
+grow_horizontal = 2
+grow_vertical = 0
+theme_override_constants/margin_left = 4
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 4
+theme_override_constants/margin_bottom = 4
+
+[node name="PanelContainer" type="PanelContainer" parent="TowerConfigurationsContainer"]
layout_mode = 2
-texture_normal = ExtResource("14_t3qlu")
-texture_pressed = ExtResource("5_xcxr8")
-texture_hover = ExtResource("6_4go2d")
-[node name="SpawnButton14" parent="SpawnBox/MarginContainer/GridContainer" instance=ExtResource("3_7eaea")]
+[node name="MarginContainer" type="MarginContainer" parent="TowerConfigurationsContainer/PanelContainer"]
layout_mode = 2
-texture_normal = ExtResource("14_t3qlu")
-texture_pressed = ExtResource("5_xcxr8")
-texture_hover = ExtResource("6_4go2d")
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 8
-[node name="SpawnButton15" parent="SpawnBox/MarginContainer/GridContainer" instance=ExtResource("3_7eaea")]
+[node name="ScrollContainer" type="ScrollContainer" parent="TowerConfigurationsContainer/PanelContainer/MarginContainer"]
layout_mode = 2
-texture_normal = ExtResource("14_t3qlu")
-texture_pressed = ExtResource("5_xcxr8")
-texture_hover = ExtResource("6_4go2d")
+vertical_scroll_mode = 0
-[node name="SpawnButton16" parent="SpawnBox/MarginContainer/GridContainer" instance=ExtResource("3_7eaea")]
+[node name="TowerConfigurations" type="HBoxContainer" parent="TowerConfigurationsContainer/PanelContainer/MarginContainer/ScrollContainer"]
layout_mode = 2
-texture_normal = ExtResource("14_t3qlu")
-texture_pressed = ExtResource("5_xcxr8")
-texture_hover = ExtResource("6_4go2d")
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="TextureRect" parent="TowerConfigurationsContainer/PanelContainer/MarginContainer/ScrollContainer/TowerConfigurations" instance=ExtResource("17_1c5dq")]
+layout_mode = 2
+
+[connection signal="gui_input" from="Panel/VBoxContainer/Container/GridContainer/MarginContainer/HBoxContainer/BuildModeButton" to="." method="_on_build_mode_button_gui_input"]
+[connection signal="gui_input" from="Panel/VBoxContainer/Container/GridContainer/MarginContainer2/HBoxContainer2/SpawnerBoxButton" to="." method="_on_spawner_box_button_gui_input"]
+[connection signal="gui_input" from="Panel/VBoxContainer/Container/GridContainer/MarginContainer3/HBoxContainer2/PlayerListButton" to="." method="_on_player_list_button_gui_input"]
diff --git a/UI/Lobby.gd b/UI/Lobby.gd
index d4eb7c2..4fea0bc 100644
--- a/UI/Lobby.gd
+++ b/UI/Lobby.gd
@@ -1,11 +1,26 @@
extends Control
-func _on_button_pressed() -> void:
- Network.host_game()
+func get_ip():
+ var ip := "127.0.0.1"
+ if %IP.text:
+ ip = %IP.text
+
+ return ip
+
+
+func get_port():
+ var port := 1234
+ if %Port.text:
+ port = %Port.text
+
+ return port
+
+func _on_host_pressed() -> void:
+ Network.host_game(get_port())
get_tree().change_scene_to_file("res://Stages/world.tscn")
-func _on_button_2_pressed() -> void:
- Network.join_game()
+func _on_join_pressed() -> void:
+ Network.join_game(get_ip(), get_port())
get_tree().change_scene_to_file("res://Stages/world.tscn")
diff --git a/UI/Lobby.tscn b/UI/Lobby.tscn
index 64bfc97..f3775d8 100644
--- a/UI/Lobby.tscn
+++ b/UI/Lobby.tscn
@@ -11,18 +11,44 @@ grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_f10dr")
-[node name="Button" type="Button" parent="."]
-layout_mode = 0
-offset_right = 8.0
-offset_bottom = 8.0
+[node name="CenterContainer" type="CenterContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
+layout_mode = 2
+
+[node name="Host" type="Button" parent="CenterContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_font_sizes/font_size = 24
text = "Host"
-[node name="Button2" type="Button" parent="."]
-layout_mode = 0
-offset_top = 32.0
-offset_right = 38.0
-offset_bottom = 63.0
+[node name="IP" type="TextEdit" parent="CenterContainer/VBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(150, 50)
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_font_sizes/font_size = 18
+placeholder_text = "Join IP"
+
+[node name="Port" type="TextEdit" parent="CenterContainer/VBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(150, 50)
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_font_sizes/font_size = 18
+placeholder_text = "Host&Join Port"
+
+[node name="Join" type="Button" parent="CenterContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_font_sizes/font_size = 24
text = "Join"
-[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]
-[connection signal="pressed" from="Button2" to="." method="_on_button_2_pressed"]
+[connection signal="pressed" from="CenterContainer/VBoxContainer/Host" to="." method="_on_host_pressed"]
+[connection signal="pressed" from="CenterContainer/VBoxContainer/Join" to="." method="_on_join_pressed"]
diff --git a/UI/PlayersList.tscn b/UI/PlayersList.tscn
new file mode 100644
index 0000000..b1106b5
--- /dev/null
+++ b/UI/PlayersList.tscn
@@ -0,0 +1,34 @@
+[gd_scene load_steps=3 format=3 uid="uid://cxd6c4kbnk04c"]
+
+[ext_resource type="Script" path="res://UI/players_list.gd" id="1_67rpy"]
+[ext_resource type="PackedScene" uid="uid://wxe1hpn013y8" path="res://UI/PlayersListItem.tscn" id="2_ug8m7"]
+
+[node name="PlayersList" type="PanelContainer"]
+offset_right = 40.0
+offset_bottom = 40.0
+script = ExtResource("1_67rpy")
+
+[node name="MarginContainer" type="MarginContainer" parent="."]
+layout_mode = 2
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 8
+
+[node name="List" type="VBoxContainer" parent="MarginContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="PlayersListItem" parent="MarginContainer/List" instance=ExtResource("2_ug8m7")]
+layout_mode = 2
+
+[node name="ID" parent="MarginContainer/List/PlayersListItem/HBoxContainer" index="0"]
+text = "ID"
+
+[node name="Score" parent="MarginContainer/List/PlayersListItem/HBoxContainer" index="2"]
+text = "Score"
+
+[node name="Indicator" parent="MarginContainer/List/PlayersListItem" index="1"]
+visible = false
+
+[editable path="MarginContainer/List/PlayersListItem"]
diff --git a/UI/PlayersListItem.tscn b/UI/PlayersListItem.tscn
new file mode 100644
index 0000000..8349b05
--- /dev/null
+++ b/UI/PlayersListItem.tscn
@@ -0,0 +1,48 @@
+[gd_scene format=3 uid="uid://wxe1hpn013y8"]
+
+[node name="PlayersListItem" type="MarginContainer"]
+offset_right = 6.0
+offset_bottom = 23.0
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 2
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="ID" type="Label" parent="HBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(10, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+text = "1"
+horizontal_alignment = 1
+clip_text = true
+text_overrun_behavior = 1
+
+[node name="VSeparator" type="VSeparator" parent="HBoxContainer"]
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Score" type="Label" parent="HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "0"
+horizontal_alignment = 1
+
+[node name="Indicator" type="Control" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Indicator"]
+layout_mode = 2
+offset_left = -8.0
+offset_top = 5.0
+offset_right = -2.0
+offset_bottom = 19.0
+size_flags_horizontal = 0
+theme_override_font_sizes/font_size = 10
+text = ">"
diff --git a/UI/SpawnButton.tscn b/UI/SpawnButton.tscn
index ec2423b..7b3450b 100644
--- a/UI/SpawnButton.tscn
+++ b/UI/SpawnButton.tscn
@@ -3,10 +3,12 @@
[ext_resource type="Script" path="res://UI/spawn_button.gd" id="1_ayei4"]
[node name="SpawnButton" type="TextureButton"]
+custom_minimum_size = Vector2(32, 32)
offset_left = 2.0
offset_top = 2.0
offset_right = 38.0
offset_bottom = 38.0
+stretch_mode = 3
script = ExtResource("1_ayei4")
[connection signal="pressed" from="." to="." method="_on_pressed"]
diff --git a/UI/TowerConfiguration.tscn b/UI/TowerConfiguration.tscn
new file mode 100644
index 0000000..0f97073
--- /dev/null
+++ b/UI/TowerConfiguration.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=5 format=3 uid="uid://c05aq7xd4kx1p"]
+
+[ext_resource type="Texture2D" uid="uid://b1b18rd0tqbar" path="res://core_outdoor.png" id="1_3ypmu"]
+[ext_resource type="Script" path="res://UI/tower_configuration.gd" id="2_vvfd0"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_k0400"]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_7gm7m"]
+atlas = ExtResource("1_3ypmu")
+region = Rect2(400, 439, 32, 41)
+
+[node name="PanelContainer" type="PanelContainer"]
+custom_minimum_size = Vector2(74, 88)
+offset_right = 80.0
+offset_bottom = 104.0
+theme_override_styles/panel = SubResource("StyleBoxEmpty_k0400")
+script = ExtResource("2_vvfd0")
+
+[node name="TextureRect" type="TextureRect" parent="."]
+texture_filter = 1
+layout_mode = 2
+mouse_default_cursor_shape = 2
+texture = SubResource("AtlasTexture_7gm7m")
+stretch_mode = 4
+
+[connection signal="gui_input" from="TextureRect" to="." method="_on_texture_rect_gui_input"]
+[connection signal="mouse_entered" from="TextureRect" to="TextureRect" method="_on_mouse_entered"]
+[connection signal="mouse_exited" from="TextureRect" to="TextureRect" method="_on_mouse_exited"]
diff --git a/UI/game_menu.gd b/UI/game_menu.gd
new file mode 100644
index 0000000..357f369
--- /dev/null
+++ b/UI/game_menu.gd
@@ -0,0 +1,4 @@
+extends Control
+
+
+# TODO: show controls
diff --git a/UI/players_list.gd b/UI/players_list.gd
new file mode 100644
index 0000000..c1ca00f
--- /dev/null
+++ b/UI/players_list.gd
@@ -0,0 +1,33 @@
+extends PanelContainer
+
+
+@onready var list: Control = %List
+
+
+func _ready() -> void:
+ #players_list_container.visible = false
+ Network.players_changed.connect(update_players)
+ update_players()
+
+ multiplayer.peer_disconnected.connect(remove_player)
+
+
+func update_players():
+ for id in Network.players.keys():
+ var player: Player = Network.players[id]
+
+ var control: Control = list.get_node_or_null(str(id))
+ if not control:
+ control = preload("res://UI/PlayersListItem.tscn").instantiate()
+ control.name = str(id)
+ control.modulate = player.get_color()
+ control.get_node("%Indicator").visible = id == multiplayer.get_unique_id()
+ list.add_child(control)
+
+ control.get_node("%ID").text = str(id)
+ control.get_node("%Score").text = str(player.score)
+ list.move_child(control, Network.get_ordered_player_ids().find(id) + 1)
+
+
+func remove_player(id):
+ list.remove_child(list.get_node(str(id)))
diff --git a/UI/tower_configuration.gd b/UI/tower_configuration.gd
new file mode 100644
index 0000000..9eb12cd
--- /dev/null
+++ b/UI/tower_configuration.gd
@@ -0,0 +1,35 @@
+extends PanelContainer
+
+
+var is_hovered = false
+
+
+func _on_mouse_entered() -> void:
+ is_hovered = true
+
+ var stylebox := StyleBoxFlat.new()
+ stylebox.bg_color = Color(1.0, 1.0, 1.0, 0.25)
+ add_theme_stylebox_override("panel", stylebox)
+
+func _on_mouse_exited() -> void:
+ is_hovered = false
+
+ remove_theme_stylebox_override("panel")
+
+
+func _on_texture_rect_gui_input(event: InputEvent) -> void:
+ var state: StateBuild = get_tree().current_scene.get_node("StateManager/StateBuild")
+ if event.is_action_pressed("select") and not state.current_builder_element:
+ var builder_element_scene = preload("res://Game/States/Build/BuilderElement.tscn")
+ var builder_element = builder_element_scene.instantiate()
+
+ var tower = preload("res://Towers/Tower.tscn").instantiate() as Tower
+ tower.attack_range = [
+ Client.stage.map.tile_set.tile_size.x * 2,
+ Client.stage.map.tile_set.tile_size.x * 3,
+ Client.stage.map.tile_set.tile_size.x * 4,
+ ].pick_random()
+
+ builder_element.element = tower
+ get_tree().current_scene.add_child(builder_element)
+ state.current_builder_element = builder_element
diff --git a/Units/Unit.gd b/Units/Unit.gd
index 6c3b254..8373a59 100644
--- a/Units/Unit.gd
+++ b/Units/Unit.gd
@@ -23,6 +23,11 @@ var is_selected = false :
var is_hovered = false :
set(value):
+ if value:
+ $Label.visible = true
+ else:
+ if not is_selected:
+ $Label.visible = false
is_hovered = value
queue_redraw()
@@ -39,9 +44,6 @@ var current_path_idx = 0 :
if line:
line.points = PackedVector2Array(current_path.slice(value))
-var previous_path: PackedVector2Array
-var previous_path_idx = 0
-
var previous_position: Vector2
var recent_closest_paths: Array[PackedVector2Array]
@@ -52,15 +54,15 @@ var roaming_mode = false
# rpc owner id
var owner_id = 1
-# unique shared id on the network on all clients
-var network_id
-
@export var base_speed: float = 100
@export var speed: float = base_speed
-@export var hp = 50
+@export var hp = 50 :
+ set = set_hp
@onready var line: Line2D = $UnitPathLine.duplicate()
+@onready var sprite = $Sprite2D
+
func _ready():
if not target:
@@ -89,6 +91,12 @@ func _ready():
func _physics_process(delta):
+ if get_multiplayer_authority() != multiplayer.get_unique_id():
+ Network.update_unit.rpc(get_path(), {
+ "hp": hp,
+ })
+ return
+
previous_position = global_position
if not current_path.is_empty():
@@ -113,6 +121,12 @@ func _physics_process(delta):
reset_path()
else:
stuck_position_accumulator = 0
+
+ Network.update_unit.rpc(get_path(), {
+ "position": global_position,
+ "hp": hp,
+ "sprite": {"self_modulate": $Sprite2D.self_modulate}
+ })
func _draw():
@@ -139,8 +153,12 @@ func _draw():
func _on_navigation_base_area_entered(area: Area2D):
+ if get_multiplayer_authority() != multiplayer.get_unique_id():
+ return
+
if area.is_in_group("goal"):
Client.player.score += 1
+ Client.update_player()
queue_free()
if area.is_in_group("path"):
var path_node = area.get_parent()
@@ -150,7 +168,7 @@ func _on_navigation_base_area_entered(area: Area2D):
func walk_along_path(path: PackedVector2Array, index: int, delta: float):
immediate_target = path[index]
- var displacement := (path[index]) - global_position
+ var displacement := immediate_target - global_position
var direction := displacement.normalized()
var distance := displacement.length()
@@ -161,7 +179,7 @@ func walk_along_path(path: PackedVector2Array, index: int, delta: float):
if effect.has_method("apply_physics"):
effect.apply_physics(delta)
# todo: changing velocity here doesn't work nicely
- # todo: because the velocity expects to each the point, so it stutters
+ # todo: because the velocity expects ??to each the point??, so it stutters
move_and_slide()
@@ -170,9 +188,11 @@ func set_hp(value):
# TODO: rpc on damage
hp = value
- %HPBar.set_value(value)
- $Label.text = str(hp)
+ if get_node("%HPBar"):
+ %HPBar.set_value(value)
+ if get_node("Label"):
+ $Label.text = str(hp)
if hp <= 0:
queue_free()
@@ -192,6 +212,9 @@ func reset_path():
current_path = get_grid_path()
if current_path.is_empty():
+ # TODO: nimm letzte route davor die noch ging, falls verfügbar
+ # TODO: schneide ab bis eins vor neue tower position. (=partial path)
+ # TODO: laufe bis dahin und checke dann end of partial path
current_path = get_grid_path(true)
recent_closest_paths.append(current_path)
@@ -224,6 +247,10 @@ func get_grid_path(partial = false):
func _on_selection_area_input_event(_viewport: Node, event: InputEvent, _shape_idx: int) -> void:
+ # disable remote select for now
+ if owner_id != multiplayer.get_unique_id():
+ return
+
if Client.state is StateDefault:
if event.is_action_pressed("select"):
if selected_unit:
@@ -243,14 +270,20 @@ func _on_selection_area_mouse_exited() -> void:
func _on_tree_exiting() -> void:
is_selected = false
line.queue_free()
+
+ #if get_multiplayer_authority() == multiplayer.get_unique_id():
+ Network.remove_unit.rpc(get_path())
func get_rpc_properties():
return {
+ "name": null,
"global_position": null,
"target": "res://Stages/Paths/PathNode.tscn",
"hp": null,
"speed": null,
"current_path": null,
"current_path_idx": null,
+ "sprite": "node://Sprite2D",
+ "owner_id": null,
}
diff --git a/Units/Unit.tscn b/Units/Unit.tscn
index 312389e..879df08 100644
--- a/Units/Unit.tscn
+++ b/Units/Unit.tscn
@@ -53,7 +53,7 @@ text = "1000"
horizontal_alignment = 1
[node name="SelectionArea" type="Area2D" parent="."]
-collision_layer = 16
+collision_layer = 32
collision_mask = 0
[node name="CollisionShape2D" type="CollisionShape2D" parent="SelectionArea"]
@@ -65,9 +65,6 @@ width = 1.0
default_color = Color(1, 1, 1, 0.392157)
target_circle_radius = 4.0
-[connection signal="input_event" from="." to="." method="_on_input_event"]
-[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"]
-[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"]
[connection signal="tree_exiting" from="." to="." method="_on_tree_exiting"]
[connection signal="area_entered" from="NavigationBase" to="." method="_on_navigation_base_area_entered"]
[connection signal="input_event" from="SelectionArea" to="." method="_on_selection_area_input_event"]
diff --git a/project.godot b/project.godot
index 64ca3b8..7bad496 100644
--- a/project.godot
+++ b/project.godot
@@ -24,8 +24,6 @@ Client="*res://Game/Client.gd"
window/size/viewport_width=1280
window/size/viewport_height=720
-window/stretch/mode="viewport"
-window/stretch/aspect="expand"
[global_group]
@@ -70,7 +68,7 @@ select={
}
build_mode_start={
"deadzone": 0.5,
-"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":66,"key_label":0,"unicode":98,"location":0,"echo":false,"script":null)
]
}
spawn_box_toggle={
@@ -83,13 +81,24 @@ select_multiple={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
+spawn_unit={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+players_list_toggle={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":80,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null)
+]
+}
[layer_names]
2d_physics/layer_1="Mob"
2d_physics/layer_2="Path"
2d_physics/layer_3="Tower"
-2d_physics/layer_5="Selection"
+2d_physics/layer_5="SelectionRectangle"
+2d_physics/layer_6="SelectableArea"
[rendering]
diff --git a/theme.tres b/theme.tres
new file mode 100644
index 0000000..b351305
--- /dev/null
+++ b/theme.tres
@@ -0,0 +1,3 @@
+[gd_resource type="Theme" format=3 uid="uid://c7f1ftrx53ag1"]
+
+[resource]