diff --git a/core/battlefield.gd b/core/battlefield.gd index d4bf3d8..4e28c26 100644 --- a/core/battlefield.gd +++ b/core/battlefield.gd @@ -1,8 +1,14 @@ extends Node2D class_name BattleField +@export var commands: Node + var _ticker1 := 0.0 +func _ready(): + if not commands: + push_warning("No commands manager set to battlefield") + func _physics_process(delta): _ticker1 += delta if _ticker1 > 1.0: @@ -12,5 +18,14 @@ func _physics_process(delta): func ticker(group_name: String): get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFERRED, group_name, "tick") -func _on_commands_changed(commander, commands): - get_tree().call_group("unit", "_on_commands_changed", commander, commands) +func _on_commands_changed(comms): + get_tree().call_group("unit", "_on_commands_changed", comms) + +func _on_child_entered_tree(node): + if node is Unit: + node.promoted.connect(_send_commands_to_unit.bind(node)) + _send_commands_to_unit(node) + +func _send_commands_to_unit(unit: Unit): + if commands: + unit._on_commands_changed(commands.get_active()) diff --git a/core/spawner.gd b/core/spawner.gd index b9559fb..c7b42e3 100644 --- a/core/spawner.gd +++ b/core/spawner.gd @@ -30,13 +30,19 @@ func _physics_process(delta): if _spawning_since > spawn_duration: spawn() +func get_battlefield() -> BattleField: + return find_parent("battlefield") as BattleField + func spawn(): if unit_type: var unit := unit_type.instantiate() if unit is Unit: _spawning_since = min(0.0, _spawning_since - spawn_duration) - # FIXME add to battlefield unit.player = player - add_child(unit) + var battlefield := get_battlefield() + if battlefield: + battlefield.add_child(unit) + else: + add_child(unit) unit.global_position = global_position unit.move_to(delivery) diff --git a/core/tools.gd b/core/tools.gd new file mode 100644 index 0000000..060f026 --- /dev/null +++ b/core/tools.gd @@ -0,0 +1,19 @@ +class_name Tools + +# Get the nearest node from a list, satisfying a predicate +static func get_nearest(from: Node2D, nodes: Array[Node], predicate: Callable) -> Node2D: + var from_position := from.global_position + var nearest: Node2D = null + var distance := INF + for node in nodes: + if predicate.call(node): + var d := from_position.distance_to(node.global_position) + if d < distance: + distance = d + nearest = node + return nearest + +# Get a random position around a fixed position, in a given radius +static func jitter(pos: Vector2, min_dist = 0.0, max_dist = 100.0) -> Vector2: + # TODO uniform distribution + return pos + Vector2.from_angle(randf_range(0.0, PI * 2.0)) * randf_range(min_dist, max_dist) diff --git a/core/ui/command.gd b/core/ui/command.gd index e19c815..d153e6b 100644 --- a/core/ui/command.gd +++ b/core/ui/command.gd @@ -12,19 +12,19 @@ signal clicked icon = PlaceholderTexture2D.new() _check_compose() -@export var color: Color: +@export var player: Node: set(val): - if val is Color: - color = val + if val is Player: + player = val else: - color = Color.BLACK + player = null _check_compose() @export var code: String func _compose(): %icon.texture = icon - %badge.self_modulate = color + %badge.self_modulate = player.color if player else Color.GRAY super._compose() func apply(container: Node2D, pos: Vector2): diff --git a/core/ui/command.tscn b/core/ui/command.tscn index 280a148..a08daaa 100644 --- a/core/ui/command.tscn +++ b/core/ui/command.tscn @@ -16,7 +16,6 @@ fill_to = Vector2(0.5, 0) [node name="command" type="Node2D"] script = ExtResource("1_bwbsk") -color = Color(0, 0, 0, 1) code = "" [node name="badge" type="TextureButton" parent="."] diff --git a/core/ui/commands.gd b/core/ui/commands.gd index ace8cf9..42163ba 100644 --- a/core/ui/commands.gd +++ b/core/ui/commands.gd @@ -11,7 +11,7 @@ extends Node2D @export var available_commands: Array[PackedScene] -signal commands_changed(commander, commands) +signal commands_changed(commands) func _ready(): %stock.visible = false @@ -20,6 +20,7 @@ func _ready(): if available_commands: for command_scene in available_commands: var command := command_scene.instantiate() + command.player = player %stock.add_child(command) func _show_command_wheel(pos: Vector2): @@ -37,7 +38,6 @@ func _show_command_wheel(pos: Vector2): for command in %stock.get_children(): # TODO only available command = command.duplicate() - command.color = player.color command.position = Vector2.from_angle(inc * idx) * 200.0 command.connect("clicked", _on_wheel_command_clicked.bind(command), CONNECT_ONE_SHOT) var line := Line2D.new() @@ -55,11 +55,11 @@ func _on_wheel_command_clicked(command: Command): command.apply(%active, %wheel.position) command.scale *= 0.5 command.connect("clicked", _on_active_command_clicked.bind(command)) - commands_changed.emit(player, %active.get_children()) + commands_changed.emit(%active.get_children()) func _on_active_command_clicked(command: Command): command.queue_free() - commands_changed.emit(player, %active.get_children()) + commands_changed.emit(%active.get_children()) func background_clicked(pos): if %wheel.visible: @@ -68,3 +68,6 @@ func background_clicked(pos): if camera: pos = get_viewport().canvas_transform.affine_inverse() * pos _show_command_wheel(pos) + +func get_active(): + return %active.get_children() diff --git a/core/unit.gd b/core/unit.gd index 8029761..2cf94c6 100644 --- a/core/unit.gd +++ b/core/unit.gd @@ -2,6 +2,8 @@ class_name Unit extends Composer +signal promoted + @export var sprite: Texture2D: set(val): sprite = val @@ -68,9 +70,11 @@ func die(): queue_free() func promote_chief(): - is_chief = true - if player: - add_to_group("chief:" + player.code) + if not is_chief: + is_chief = true + if player: + add_to_group("chief:" + player.code) + promoted.emit() func set_chief(chief): if chief is Unit and chief.is_chief and chief != self: diff --git a/main.tscn b/main.tscn index 30fb7ca..eccaa42 100644 --- a/main.tscn +++ b/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=3 uid="uid://c6omib6txy3qh"] +[gd_scene load_steps=9 format=3 uid="uid://c6omib6txy3qh"] [ext_resource type="PackedScene" uid="uid://ck41r2je85sm3" path="res://core/ui/camera.tscn" id="1_7evrt"] [ext_resource type="PackedScene" uid="uid://brbtq46uk18gg" path="res://core/battlefield.tscn" id="1_x63ik"] @@ -7,6 +7,7 @@ [ext_resource type="PackedScene" uid="uid://b8uik6q4v35o3" path="res://tac/units/factory.tscn" id="2_wnc50"] [ext_resource type="PackedScene" uid="uid://xoup4vukp3ni" path="res://core/ui/commands.tscn" id="4_1gkbi"] [ext_resource type="PackedScene" uid="uid://dr1e0h27nuam" path="res://tac/commands/delivery.tscn" id="5_fi2mi"] +[ext_resource type="PackedScene" uid="uid://cmaup0f5ud8vr" path="res://tac/commands/defend.tscn" id="5_fmegp"] [node name="main" type="Node2D"] @@ -17,7 +18,7 @@ [node name="commands" parent="." node_paths=PackedStringArray("player", "camera") instance=ExtResource("4_1gkbi")] player = NodePath("../world/player1") camera = NodePath("../camera") -available_commands = [ExtResource("5_fi2mi")] +available_commands = [ExtResource("5_fi2mi"), ExtResource("5_fmegp")] [node name="world" type="Node2D" parent="."] @@ -29,7 +30,8 @@ code = "p1" color = Color(1, 0, 0, 1) code = "p2" -[node name="battlefield" parent="world" instance=ExtResource("1_x63ik")] +[node name="battlefield" parent="world" node_paths=PackedStringArray("commands") instance=ExtResource("1_x63ik")] +commands = NodePath("../../commands") [node name="factory1" parent="world/battlefield" node_paths=PackedStringArray("player") instance=ExtResource("2_wnc50")] position = Vector2(270, 222) @@ -43,3 +45,4 @@ player = NodePath("../../player2") [connection signal="scrolled" from="inputs" to="camera" method="scroll"] [connection signal="zoomed" from="inputs" to="camera" method="change_zoom"] [connection signal="commands_changed" from="commands" to="world/battlefield" method="_on_commands_changed"] +[connection signal="child_entered_tree" from="world/battlefield" to="world/battlefield" method="_on_child_entered_tree"] diff --git a/project.godot b/project.godot index eef998f..20188fc 100644 --- a/project.godot +++ b/project.godot @@ -34,6 +34,11 @@ _global_script_classes=[{ "language": &"GDScript", "path": "res://core/spawner.gd" }, { +"base": "RefCounted", +"class": &"Tools", +"language": &"GDScript", +"path": "res://core/tools.gd" +}, { "base": "Composer", "class": &"Unit", "language": &"GDScript", @@ -50,6 +55,7 @@ _global_script_class_icons={ "Composer": "", "Player": "", "Spawner": "", +"Tools": "", "Unit": "", "Weapon": "" } diff --git a/tac/assets/commands/defend.png b/tac/assets/commands/defend.png new file mode 100644 index 0000000..8736650 Binary files /dev/null and b/tac/assets/commands/defend.png differ diff --git a/tac/assets/commands/defend.png.import b/tac/assets/commands/defend.png.import new file mode 100644 index 0000000..3cebf1b --- /dev/null +++ b/tac/assets/commands/defend.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgatvtgtb6j8d" +path="res://.godot/imported/defend.png-0d992a483323ca4da1938cf20130bd2f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://tac/assets/commands/defend.png" +dest_files=["res://.godot/imported/defend.png-0d992a483323ca4da1938cf20130bd2f.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +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/tac/commands/defend.tscn b/tac/commands/defend.tscn new file mode 100644 index 0000000..f1656f9 --- /dev/null +++ b/tac/commands/defend.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=3 format=3 uid="uid://cmaup0f5ud8vr"] + +[ext_resource type="PackedScene" uid="uid://b3l4jfjieucrc" path="res://core/ui/command.tscn" id="1_s822j"] +[ext_resource type="Texture2D" uid="uid://dgatvtgtb6j8d" path="res://tac/assets/commands/defend.png" id="2_6gky5"] + +[node name="command_defend" instance=ExtResource("1_s822j")] +icon = ExtResource("2_6gky5") +code = "defend" + +[node name="icon" parent="." index="1"] +texture = ExtResource("2_6gky5") diff --git a/tac/units/factory.gd b/tac/units/factory.gd index 240e590..b0801b0 100644 --- a/tac/units/factory.gd +++ b/tac/units/factory.gd @@ -11,15 +11,8 @@ func _compose(): $spawner.player = player super._compose() -func _on_commands_changed(commander, commands): - if commander == player: - var nearest := Vector2(INF, INF) - var distance := INF - for command in commands: - if command.code == "delivery": - var d := position.distance_to(command.position) - if d < distance: - distance = d - nearest = command.position - if distance != INF: - $spawner.delivery = nearest +func _on_commands_changed(commands): + var delivery = Tools.get_nearest(self, commands, + func filter(command): return command.player == player and command.code == "delivery") + if delivery: + $spawner.delivery = delivery.global_position diff --git a/tac/units/fighter.gd b/tac/units/fighter.gd index ad88b36..f94a258 100644 --- a/tac/units/fighter.gd +++ b/tac/units/fighter.gd @@ -1,5 +1,7 @@ extends Unit +var current_command := WeakRef.new() + func _compose(): $chief.visible = is_chief super._compose() @@ -7,17 +9,24 @@ func _compose(): func tick(): chief_maintenance() - # go to nearest enemy - var enemy: Unit = null - var closest := INF - for unit in get_tree().get_nodes_in_group("unit"): - if unit is Unit and unit.player != player: - var distance := global_position.distance_to(unit.global_position) - if distance < closest: - closest = distance - enemy = unit + if is_chief: + var command := current_command.get_ref() as Command + if command and command.global_position.distance_to(global_position) > 100.0: + # go near current command + move_to(Tools.jitter(command.global_position, 50.0, 100.0)) + return + else: + var chief = get_chief() + if chief and chief.global_position.distance_to(global_position) > 100.0: + # go near chief + move_to(Tools.jitter(chief.global_position, 50.0, 100.0)) + return + + var enemy := Tools.get_nearest(self, get_tree().get_nodes_in_group("unit"), + func filter(unit): return unit is Unit and unit.player != player) if enemy: - move_to(enemy.global_position + Vector2.from_angle(randf_range(0.0, PI * 2.0)) * randf_range(100.0, 300.0)) + # go near current target enemy + move_to(Tools.jitter(enemy.global_position, 100.0, 300.0)) $turret.set_target(enemy) func chief_maintenance(): @@ -31,7 +40,15 @@ func chief_maintenance(): var chiefs = list_chiefs() # TODO sort by distance, experience and subordinate count if chiefs.size() > 0: - chief = chiefs[0] + set_chief(chiefs[0]) else: promote_chief() _compose() + +func _on_commands_changed(commands): + if is_chief: + var defend := Tools.get_nearest(self, commands, + func filter(command): return command.player == player and command.code == "defend") + + if defend: + current_command = weakref(defend)