From b96e384d8299473b14edcbf885fa914a9308d18f Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Sat, 28 Sep 2024 15:27:43 +0200 Subject: next commit --- Game/Client.gd | 42 +++++++++++++----- Game/Network.gd | 80 ++++++++++++++++++++++++++++++++-- Game/Player.gd | 18 +++++++- Game/Selection/MultiSelectArea.tscn | 13 ++++++ Game/Selection/SelectableArea.tscn | 14 ++++++ Game/Selection/SelectionRectangle.tscn | 20 +++++++++ Game/Selection/multi_select_area.gd | 19 ++++++++ Game/Selection/selectable_area.gd | 29 ++++++++++++ Game/Selection/selection_rectangle.gd | 51 ++++++++++++++++++++++ Game/States/Build/BuilderElement.gd | 3 ++ Game/States/Build/StateBuild.gd | 26 ++++------- Game/States/Default/StateDefault.gd | 6 ++- 12 files changed, 284 insertions(+), 37 deletions(-) create mode 100644 Game/Selection/MultiSelectArea.tscn create mode 100644 Game/Selection/SelectableArea.tscn create mode 100644 Game/Selection/SelectionRectangle.tscn create mode 100644 Game/Selection/multi_select_area.gd create mode 100644 Game/Selection/selectable_area.gd create mode 100644 Game/Selection/selection_rectangle.gd (limited to 'Game') 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(): -- cgit v1.2.3