summaryrefslogtreecommitdiff
path: root/Game
diff options
context:
space:
mode:
Diffstat (limited to 'Game')
-rw-r--r--Game/Client.gd75
-rw-r--r--Game/Network.gd132
-rw-r--r--Game/Player.gd20
-rw-r--r--Game/States/Build/BuildGrid.tscn6
-rw-r--r--Game/States/Build/BuilderElement.gd87
-rw-r--r--Game/States/Build/BuilderElement.tscn19
-rw-r--r--Game/States/Build/StateBuild.gd50
-rw-r--r--Game/States/Build/StateBuild.tscn11
-rw-r--r--Game/States/Build/build_grid.gd21
-rw-r--r--Game/States/Default/StateDefault.gd17
-rw-r--r--Game/States/Default/StateDefault.tscn6
-rw-r--r--Game/States/State.gd13
-rw-r--r--Game/States/StateManager.gd19
-rw-r--r--Game/States/StateManager.tscn6
14 files changed, 482 insertions, 0 deletions
diff --git a/Game/Client.gd b/Game/Client.gd
new file mode 100644
index 0000000..77ce500
--- /dev/null
+++ b/Game/Client.gd
@@ -0,0 +1,75 @@
+extends Node
+
+
+var state: State
+var stage: Stage
+var player: Player = Player.new()
+
+
+func update_player(local_player: Player):
+ player = local_player
+ Network.players[multiplayer.get_unique_id()] = local_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()
+
+ 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)
+
+
+func remove_tower(tower: Tower):
+ if tower.owner_id == multiplayer.get_unique_id():
+ destroy_tower(tower)
+ player.score -= 1
+ update_player(player)
+
+
+func destroy_tower(tower: Tower):
+ stage.destroy_tower(tower)
+ Network.destroy_tower.rpc(Network.to_rpc_object(tower))
+
+ player.towers.erase(tower.global_position)
+ update_player(player)
+
+
+func select_tower(tower: Tower):
+ if tower.owner_id == multiplayer.get_unique_id():
+ tower.is_selected = true
+
+
+func deselect_tower():
+ if not Tower.selected_towers.is_empty():
+ for tower in Tower.selected_towers:
+ tower.is_selected = false
+
+
+func spawn_unit(unit: Unit, spawn: Spawn):
+ unit.global_position = spawn.spawn_position
+ unit.target = spawn.next_node
+ unit.hp = 20000
+ unit.speed = randi_range(50, 150)
+
+ stage.spawn_unit(unit, spawn)
+ Network.spawn_unit.rpc(Network.to_rpc_object(unit), Network.to_rpc_object(spawn))
+
+
+func array_intersect(first, second):
+ var compare = {}
+ for value in first:
+ compare[value] = true
+
+ for value in second:
+ if compare.get(value, false):
+ return true
+
+ return false
diff --git a/Game/Network.gd b/Game/Network.gd
new file mode 100644
index 0000000..021de69
--- /dev/null
+++ b/Game/Network.gd
@@ -0,0 +1,132 @@
+extends Node
+
+
+var players = {}
+
+
+func _ready():
+ multiplayer.connected_to_server.connect(_on_connected_to_server)
+ multiplayer.peer_connected.connect(_on_peer_connected)
+
+ multiplayer.allow_object_decoding = true
+
+
+func host_game():
+ var peer = ENetMultiplayerPeer.new()
+ peer.create_server(1234, 2)
+
+ multiplayer.multiplayer_peer = peer
+ players[1] = Client.player
+
+func join_game():
+ var peer = ENetMultiplayerPeer.new()
+ peer.create_client("127.0.0.1", 1234)
+
+ multiplayer.multiplayer_peer = peer
+
+
+func _on_connected_to_server():
+ print("connected to server")
+
+func _on_peer_connected(id):
+ print("peer connected: ", id)
+ add_to_players.rpc(to_rpc_object(Client.player))
+
+
+func to_rpc_object(object: Variant):
+ var remote_object = {}
+ var properties = object.get_rpc_properties()
+ for property in properties:
+ var property_class = properties[property]
+ if object[property] is Dictionary:
+ remote_object[property] = {}
+ for key in object[property]:
+ remote_object[property][key] = to_rpc_object(object[property][key])
+ elif property_class:
+ remote_object[property] = to_rpc_object(object[property])
+ else:
+ remote_object[property] = object[property]
+
+ return remote_object
+
+
+func from_rpc_object(remote_object: Dictionary, remote_class_path: String):
+ var object
+ var remote_class = load(remote_class_path)
+ if remote_class is PackedScene:
+ object = remote_class.instantiate()
+ elif remote_class is GDScript:
+ object = remote_class.new()
+ else:
+ assert(false, "unexpected remote class type")
+
+ var properties = object.get_rpc_properties()
+ for property in properties:
+ var property_class = properties[property]
+ if object[property] is Dictionary:
+ for key in object[property]:
+ object[property][key] = from_rpc_object(remote_object[property], property_class)
+ elif property_class:
+ object[property] = from_rpc_object(remote_object[property], property_class)
+ else:
+ object[property] = remote_object[property]
+
+ return object
+
+
+func merge_with_rpc_object(object: Variant, remote_object: Dictionary):
+ var properties = object.get_rpc_properties()
+ for property in properties:
+ var property_class = properties[property]
+ if object[property] is Dictionary:
+ object[property] = {}
+ for key in remote_object[property]:
+ object[property][key] = from_rpc_object(remote_object[property][key], property_class)
+ elif property_class:
+ object[property] = merge_with_rpc_object(object[property], remote_object[property])
+ else:
+ object[property] = remote_object[property]
+
+ return object
+
+
+@rpc("call_local", "any_peer")
+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")
+ players[id] = player
+
+
+@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)
+ players[id] = player
+
+
+@rpc("any_peer")
+func place_tower(remote_tower: Dictionary, position: Vector2):
+ var remote_player_id = multiplayer.get_remote_sender_id()
+
+ var tower = from_rpc_object(remote_tower, "res://Towers/Tower.tscn")
+ tower.owner_id = remote_player_id
+
+ players[remote_player_id].towers[tower.global_position] = tower
+
+ Client.stage.place_tower(tower, position)
+
+
+@rpc("any_peer")
+func destroy_tower(remote_tower: Dictionary):
+ var player = players[remote_tower.owner_id] as Player
+ var tower = player.towers.get(remote_tower.global_position)
+
+ Client.stage.destroy_tower(tower)
+
+
+@rpc("any_peer")
+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")
+
+ Client.stage.spawn_unit(unit, spawn)
diff --git a/Game/Player.gd b/Game/Player.gd
new file mode 100644
index 0000000..2726929
--- /dev/null
+++ b/Game/Player.gd
@@ -0,0 +1,20 @@
+class_name Player
+extends Resource
+
+
+var towers: Dictionary :
+ set(value):
+ towers = value
+
+var score: int :
+ set(value):
+ score = value
+ Client.stage.hud.score.text = str(score)
+
+var units: Array[Unit]
+
+
+func get_rpc_properties() -> Dictionary:
+ return {
+ "score": null,
+ }
diff --git a/Game/States/Build/BuildGrid.tscn b/Game/States/Build/BuildGrid.tscn
new file mode 100644
index 0000000..ffb41d7
--- /dev/null
+++ b/Game/States/Build/BuildGrid.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://8oq8aa1q4c1h"]
+
+[ext_resource type="Script" path="res://Game/States/Build/build_grid.gd" id="1_jige0"]
+
+[node name="BuildGrid" type="Node2D"]
+script = ExtResource("1_jige0")
diff --git a/Game/States/Build/BuilderElement.gd b/Game/States/Build/BuilderElement.gd
new file mode 100644
index 0000000..8315792
--- /dev/null
+++ b/Game/States/Build/BuilderElement.gd
@@ -0,0 +1,87 @@
+class_name BuilderElement
+extends Node2D
+
+
+@export var element: Node2D
+
+var collision_shape: Shape2D
+
+var previous_position: Vector2
+
+var collision_areas: Array[Area2D] = []
+
+
+func _ready():
+ var element_sprite: Sprite2D = element.get_node("Sprite2D")
+ $Sprite2D.texture = element_sprite.texture
+ $Sprite2D.region_enabled = element_sprite.region_enabled
+ $Sprite2D.region_rect = element_sprite.region_rect
+ $Sprite2D.scale = element_sprite.scale
+ $Sprite2D.position = element_sprite.position
+
+ var element_collision_shape = element.get_node("CollisionShape2D").duplicate() as CollisionShape2D
+ collision_shape = element_collision_shape.shape
+
+ var tile_size = Client.stage.map.tile_set.tile_size
+ var snapped_size = collision_shape.size.snapped(tile_size)
+ for x in (snapped_size.x / tile_size.x):
+ for y in (snapped_size.y / tile_size.y):
+ var collision_visibility_area_scene = preload("res://UI/CollisionVisibilityArea.tscn")
+ var collision_visibility_area = collision_visibility_area_scene.instantiate()
+ collision_visibility_area.position = Vector2(
+ x * tile_size.x,
+ y * tile_size.y
+ )
+ collision_visibility_area.set_collision_layer_value(3, true)
+ collision_visibility_area.set_collision_mask_value(1, true)
+ collision_visibility_area.set_collision_mask_value(2, true)
+ collision_visibility_area.set_collision_mask_value(3, true)
+ collision_visibility_area.colliding_color = Color(1, 0, 0, 0.5)
+ collision_visibility_area.not_colliding_color = Color(0, 1, 0, 0.25)
+
+ var shape = RectangleShape2D.new()
+ shape.size = Vector2(tile_size - Vector2i(1, 1))
+ var cshape = CollisionShape2D.new()
+ cshape.shape = shape
+ cshape.position = tile_size / 2
+
+ collision_visibility_area.add_child(cshape)
+ add_child(collision_visibility_area)
+
+ collision_areas.append(collision_visibility_area)
+
+
+func _draw():
+ draw_circle(
+ Client.stage.map.tile_set.tile_size,
+ 8 + element.attack_range,
+ Color(1, 1, 1, 0.75),
+ false,
+ 1.0
+ )
+
+
+func _process(_delta):
+ previous_position = global_position
+ global_position = get_global_mouse_position().snapped(Client.stage.map.tile_set.tile_size) - Vector2(16,16)
+
+ if global_position != previous_position:
+ queue_redraw()
+
+
+func can_build():
+ for area in collision_areas:
+ if area.get_overlapping_areas().size() > 0:
+ return false
+
+ if area.get_overlapping_bodies().size() > 0:
+ return false
+
+ return true
+
+
+func _on_area_entered(_node) -> void:
+ queue_redraw()
+
+func _on_area_exited(_node) -> void:
+ queue_redraw()
diff --git a/Game/States/Build/BuilderElement.tscn b/Game/States/Build/BuilderElement.tscn
new file mode 100644
index 0000000..ea0a857
--- /dev/null
+++ b/Game/States/Build/BuilderElement.tscn
@@ -0,0 +1,19 @@
+[gd_scene load_steps=2 format=3 uid="uid://cleoiemwasbm5"]
+
+[ext_resource type="Script" path="res://Game/States/Build/BuilderElement.gd" id="1_xxacj"]
+
+[node name="BuilderElement" type="Node2D"]
+script = ExtResource("1_xxacj")
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+texture_filter = 1
+centered = false
+
+[node name="Area2D" type="Area2D" parent="."]
+collision_layer = 4
+collision_mask = 7
+
+[connection signal="area_entered" from="Area2D" to="." method="_on_area_entered"]
+[connection signal="area_exited" from="Area2D" to="." method="_on_area_exited"]
+[connection signal="body_entered" from="Area2D" to="." method="_on_area_entered"]
+[connection signal="body_exited" from="Area2D" to="." method="_on_area_exited"]
diff --git a/Game/States/Build/StateBuild.gd b/Game/States/Build/StateBuild.gd
new file mode 100644
index 0000000..b92020b
--- /dev/null
+++ b/Game/States/Build/StateBuild.gd
@@ -0,0 +1,50 @@
+class_name StateBuild
+extends State
+
+
+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():
+ %BuildGrid.visible = false
+
+
+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():
+ var placed_tower = current_builder_element.element.duplicate() as Tower
+ Client.place_tower(placed_tower, current_builder_element.global_position)
+
+ placed_tower.selected.connect(func():
+ Client.stage.hud.tower.text = "Range: %s - Power: %s - Speed: %s" % [
+ placed_tower.attack_range, placed_tower.attack_power, placed_tower.attack_speed
+ ]
+ )
+
+ if not Input.is_action_pressed("builder_tower_place_keep"):
+ current_builder_element.queue_free()
+ set_state("StateDefault")
+
+ if event.is_action_pressed("builder_cancel") or event.is_action_pressed("build_mode_start"):
+ get_viewport().set_input_as_handled()
+ current_builder_element.queue_free()
+ set_state("StateDefault")
diff --git a/Game/States/Build/StateBuild.tscn b/Game/States/Build/StateBuild.tscn
new file mode 100644
index 0000000..84583c2
--- /dev/null
+++ b/Game/States/Build/StateBuild.tscn
@@ -0,0 +1,11 @@
+[gd_scene load_steps=3 format=3 uid="uid://bo5dp02vlui3l"]
+
+[ext_resource type="Script" path="res://Game/States/Build/StateBuild.gd" id="1_s0n2d"]
+[ext_resource type="PackedScene" uid="uid://8oq8aa1q4c1h" path="res://Game/States/Build/BuildGrid.tscn" id="2_iheti"]
+
+[node name="StateBuild" type="Node"]
+script = ExtResource("1_s0n2d")
+
+[node name="BuildGrid" parent="." instance=ExtResource("2_iheti")]
+unique_name_in_owner = true
+visible = false
diff --git a/Game/States/Build/build_grid.gd b/Game/States/Build/build_grid.gd
new file mode 100644
index 0000000..6fd93aa
--- /dev/null
+++ b/Game/States/Build/build_grid.gd
@@ -0,0 +1,21 @@
+extends Node2D
+
+
+func _draw():
+ var area = Client.stage.map.get_used_rect()
+ var cell_size = Client.stage.map.tile_set.tile_size
+ var offset = Client.stage.map.position
+ for row in range(0, area.size.y + 1):
+ draw_line(
+ Vector2(offset.x, offset.y + row * cell_size.y),
+ Vector2(offset.x + area.size.x * cell_size.x, offset.y + row * cell_size.y),
+ Color(1, 1, 1, 0.25),
+ 1.0
+ )
+ for column in range(0, area.size.x + 1):
+ draw_line(
+ Vector2(offset.x + column * cell_size.x, offset.y),
+ Vector2(offset.x + column * cell_size.x, offset.y + area.size.y * cell_size.y),
+ Color(1, 1, 1, 0.25),
+ 1.0
+ )
diff --git a/Game/States/Default/StateDefault.gd b/Game/States/Default/StateDefault.gd
new file mode 100644
index 0000000..26384c5
--- /dev/null
+++ b/Game/States/Default/StateDefault.gd
@@ -0,0 +1,17 @@
+class_name StateDefault
+extends State
+
+
+func _state_input(event: InputEvent) -> void:
+ if event.is_action_pressed("build_mode_start"):
+ set_state("StateBuild")
+
+ if event.is_action_pressed("builder_tower_select"):
+ 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():
+ tower.is_selected = false
+
+ if event.is_action_pressed("select"):
+ if Unit.selected_unit:
+ Unit.selected_unit.is_selected = false
diff --git a/Game/States/Default/StateDefault.tscn b/Game/States/Default/StateDefault.tscn
new file mode 100644
index 0000000..1f73d30
--- /dev/null
+++ b/Game/States/Default/StateDefault.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://cg16o7eqqha70"]
+
+[ext_resource type="Script" path="res://Game/States/Default/StateDefault.gd" id="1_e8s2t"]
+
+[node name="StateDefault" type="Node"]
+script = ExtResource("1_e8s2t")
diff --git a/Game/States/State.gd b/Game/States/State.gd
new file mode 100644
index 0000000..c1a2bde
--- /dev/null
+++ b/Game/States/State.gd
@@ -0,0 +1,13 @@
+class_name State
+extends Node
+
+
+func _state_enter():
+ pass
+
+func _state_exit():
+ pass
+
+
+func set_state(state: NodePath):
+ get_parent().set_state(state)
diff --git a/Game/States/StateManager.gd b/Game/States/StateManager.gd
new file mode 100644
index 0000000..e62cdb4
--- /dev/null
+++ b/Game/States/StateManager.gd
@@ -0,0 +1,19 @@
+class_name StateManager
+extends Node
+
+
+func _ready():
+ Client.state = get_child(0)
+
+
+func _input(event: InputEvent) -> void:
+ Client.state._state_input(event)
+
+
+func set_state(state: Variant):
+ if state is not State:
+ state = get_node(state)
+
+ Client.state._state_exit()
+ Client.state = state
+ Client.state._state_enter()
diff --git a/Game/States/StateManager.tscn b/Game/States/StateManager.tscn
new file mode 100644
index 0000000..5584445
--- /dev/null
+++ b/Game/States/StateManager.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://d4hgrh7danbbx"]
+
+[ext_resource type="Script" path="res://Game/States/StateManager.gd" id="1_1q4x6"]
+
+[node name="StateManager" type="Node"]
+script = ExtResource("1_1q4x6")