diff --git a/nodes/combat_map.gd b/nodes/combat_map.gd index 4143c81..9f76277 100644 --- a/nodes/combat_map.gd +++ b/nodes/combat_map.gd @@ -74,6 +74,11 @@ func _apply_deploy(unit: Unit, coords: Vector2i) -> void: func is_wall(coords: Vector2i) -> bool: return tile_map.get_cell_atlas_coords(coords) == tile_set.wall_tile_coords +func remove_unit(unit: Unit) -> void: + if unit.get_parent() == self: + remove_child(unit) + + func target_tile(coords: Vector2i) -> void: highlight_map.target_tile(coords) tile_hovered.emit(coords) diff --git a/nodes/combat_system.gd b/nodes/combat_system.gd index f062c47..9a1c6b2 100644 --- a/nodes/combat_system.gd +++ b/nodes/combat_system.gd @@ -20,6 +20,8 @@ func _snapshot(unit: Unit, opponent: Unit) -> CombatProposal.CombatantStats: return stats func process_combat(attacker: Unit, defender: Unit) -> void: + if not attacker.is_alive() or not defender.is_alive(): + return var proposal := create_proposal(attacker, defender) var atk_name := attacker.current_info.name var def_name := defender.current_info.name @@ -27,7 +29,9 @@ func process_combat(attacker: Unit, defender: Unit) -> void: print(" %s — HP:%d ATK:%d DEF:%d HIT:%d" % [atk_name, proposal.attacker.hp, proposal.attacker.atk, proposal.attacker.def, proposal.attacker.hit]) print(" %s — HP:%d ATK:%d DEF:%d HIT:%d" % [def_name, proposal.defender.hp, proposal.defender.atk, proposal.defender.def, proposal.defender.hit]) apply_proposal(proposal) - print(" Result: %s HP=%d, %s HP=%d" % [atk_name, attacker.current_stats.current_hp, def_name, defender.current_stats.current_hp]) + var atk_hp := attacker.current_stats.current_hp if is_instance_valid(attacker) else 0 + var def_hp := defender.current_stats.current_hp if is_instance_valid(defender) else 0 + print(" Result: %s HP=%d, %s HP=%d" % [atk_name, atk_hp, def_name, def_hp]) func apply_proposal(proposal: CombatProposal) -> void: @@ -40,11 +44,11 @@ func apply_proposal(proposal: CombatProposal) -> void: var atk_roll := randi_range(1, 100) if atk_roll <= atk_stats.hit: var damage := maxi(atk_stats.atk - def_stats.def, 0) - def_unit.current_stats.current_hp -= damage + def_unit.take_damage(damage) # Counterattack if defender survives - if def_unit.current_stats.current_hp > 0: + if def_unit.is_alive(): var def_roll := randi_range(1, 100) if def_roll <= def_stats.hit: var damage := maxi(def_stats.atk - atk_stats.def, 0) - atk_unit.current_stats.current_hp -= damage + atk_unit.take_damage(damage) diff --git a/nodes/player_controller.gd b/nodes/player_controller.gd index ee74b39..4e844a0 100644 --- a/nodes/player_controller.gd +++ b/nodes/player_controller.gd @@ -12,13 +12,31 @@ var _goal_pos: Vector2 var _moving := false +func _ready() -> void: + for unit: Unit in get_tree().get_nodes_in_group("units"): + unit.unit_died.connect(_on_unit_died) + get_tree().node_added.connect(_on_node_added) + + +func _on_node_added(node: Node) -> void: + if node is Unit and node.is_in_group("units"): + if not node.unit_died.is_connected(_on_unit_died): + node.unit_died.connect(_on_unit_died) + + +func _on_unit_died(unit: Unit) -> void: + if _selected_unit == unit: + _selected_unit = null + _moving = false + + func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseButton and not event.pressed and event.button_index == MOUSE_BUTTON_LEFT: var world_pos: Vector2 = get_viewport().get_canvas_transform().affine_inverse() * event.position var clicked_unit := _get_unit_at(world_pos) if clicked_unit: - if _selected_unit and clicked_unit != _selected_unit: + if _selected_unit and clicked_unit != _selected_unit and _selected_unit.is_alive() and clicked_unit.is_alive(): combat_requested.emit(_selected_unit, clicked_unit) else: _select_unit(clicked_unit) @@ -33,7 +51,7 @@ func _unhandled_input(event: InputEvent) -> void: func _physics_process(delta: float) -> void: - if not _selected_unit: + if not _selected_unit or not _selected_unit.is_alive(): return if _moving: @@ -78,6 +96,8 @@ func _select_unit(unit: Unit) -> void: func _get_unit_at(world_pos: Vector2) -> Unit: var snapped := dl_map.snap_to_grid(world_pos) for unit: Unit in get_tree().get_nodes_in_group("units"): + if not unit.is_alive(): + continue var unit_snapped := dl_map.snap_to_grid(unit.global_position) if unit_snapped == snapped: return unit diff --git a/nodes/unit.gd b/nodes/unit.gd index a59998a..3c0d0e7 100644 --- a/nodes/unit.gd +++ b/nodes/unit.gd @@ -1,5 +1,7 @@ class_name Unit extends Node2D +enum UnitState { ALIVE, DEAD } + #region Templates @export var stat_template: UnitStats @export var info_template: UnitInfo @@ -9,15 +11,33 @@ class_name Unit extends Node2D var current_stats: UnitStats var current_info: UnitInfo var current_allegiance: UnitAllegiance +var state: UnitState = UnitState.ALIVE signal unit_selected_changed(unit: Unit, selected: bool) signal unit_allegiance_changed(unit: Unit, allegiance: UnitAllegiance) +signal unit_died(unit: Unit) func _ready() -> void: current_stats = stat_template.duplicate(true) current_info = info_template.duplicate(true) current_allegiance = allegiance_template.duplicate(true) unit_allegiance_changed.emit(self, current_allegiance) - + func set_selected(selected: bool) -> void: unit_selected_changed.emit(self, selected) + +func is_alive() -> bool: + return state == UnitState.ALIVE + +func take_damage(amount: int) -> void: + if state != UnitState.ALIVE: + return + current_stats.current_hp -= amount + if current_stats.current_hp <= 0: + current_stats.current_hp = 0 + _die() + +func _die() -> void: + state = UnitState.DEAD + unit_died.emit(self) + queue_free() diff --git a/scripts/combat_ui.gd b/scripts/combat_ui.gd index 6b8b53a..2f89299 100644 --- a/scripts/combat_ui.gd +++ b/scripts/combat_ui.gd @@ -10,14 +10,23 @@ func _ready() -> void: unit_panel.visible = false for unit: Unit in get_tree().get_nodes_in_group("units"): unit.unit_selected_changed.connect(_on_unit_selected_changed) + unit.unit_died.connect(_on_unit_died) get_tree().node_added.connect(_on_node_added) func _on_node_added(node: Node) -> void: if node is Unit and node.is_in_group("units"): - node.unit_selected_changed.connect(_on_unit_selected_changed) + if not node.unit_selected_changed.is_connected(_on_unit_selected_changed): + node.unit_selected_changed.connect(_on_unit_selected_changed) + if not node.unit_died.is_connected(_on_unit_died): + node.unit_died.connect(_on_unit_died) + +func _on_unit_died(unit: Unit) -> void: + if _selected_unit == unit: + _selected_unit = null + unit_panel.visible = false func _process(_delta: float) -> void: - if _selected_unit: + if _selected_unit and is_instance_valid(_selected_unit): hp_bar.max_value = _selected_unit.current_stats.max_hp hp_bar.value = _selected_unit.current_stats.current_hp