summaryrefslogtreecommitdiff
path: root/Game
diff options
context:
space:
mode:
Diffstat (limited to 'Game')
-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
12 files changed, 284 insertions, 37 deletions
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():