diff --git a/src/game/engine/src/generation/map_placer.gd b/src/game/engine/src/generation/map_placer.gd index 953c7d60..c079e28d 100644 --- a/src/game/engine/src/generation/map_placer.gd +++ b/src/game/engine/src/generation/map_placer.gd @@ -221,10 +221,11 @@ func place_resources(game_map: RefCounted, multiplier: float) -> void: tile_idx = _rng.randi_range(0, candidates.size() - 1) var pos: Vector2i = candidates[tile_idx] - var tile: Variant = game_map.get_tile(pos) + var tile: Resource = game_map.get_tile(pos) if tile == null or tile.resource_id != "": candidates.remove_at(tile_idx) cand_weights.remove_at(tile_idx) + eligible_weights[res_id] = cand_weights # PackedArray is value type — write back continue tile.resource_id = res_id @@ -234,11 +235,16 @@ func place_resources(game_map: RefCounted, multiplier: float) -> void: var other_cands: Array = eligible[other_id] var other_weights: PackedFloat32Array = eligible_weights[other_id] var i_cand: int = other_cands.size() - 1 + var weights_dirty: bool = false while i_cand >= 0: if HexUtilsScript.hex_distance(other_cands[i_cand], pos) < min_resource_distance: + if i_cand < other_weights.size(): + other_weights.remove_at(i_cand) + weights_dirty = true other_cands.remove_at(i_cand) - other_weights.remove_at(i_cand) i_cand -= 1 + if weights_dirty: + eligible_weights[other_id] = other_weights # -- Density multiplier lookup -- diff --git a/src/game/engine/src/generation/village_lair_placer.gd b/src/game/engine/src/generation/village_lair_placer.gd index d8d35d21..d1a5f599 100644 --- a/src/game/engine/src/generation/village_lair_placer.gd +++ b/src/game/engine/src/generation/village_lair_placer.gd @@ -236,7 +236,7 @@ func _get_wilds_data() -> Dictionary: return data return { "lair_density_per_land_tile": 0.0083, - "min_distance_from_start": 5, + "min_distance_from_start": 8, "min_distance_between": 4, "lair_types": [], } diff --git a/src/game/engine/src/rendering/unit_renderer.gd b/src/game/engine/src/rendering/unit_renderer.gd index d178da16..532ac250 100644 --- a/src/game/engine/src/rendering/unit_renderer.gd +++ b/src/game/engine/src/rendering/unit_renderer.gd @@ -18,6 +18,12 @@ const SELECTION_COLOR: Color = Color(1.0, 1.0, 0.0, 0.9) const MOVE_RANGE_COLOR: Color = Color(0.2, 0.4, 0.9, 0.25) const MOVE_RANGE_BORDER_COLOR: Color = Color(0.3, 0.5, 1.0, 0.5) +## HP bar dimensions and position (below unit sprite) +const HP_BAR_WIDTH: float = 36.0 +const HP_BAR_HEIGHT: float = 4.0 +const HP_BAR_OFFSET_Y: float = 20.0 +const HP_BAR_BG_COLOR: Color = Color(0.15, 0.15, 0.15, 0.8) + ## Combat type to marker label mapping for placeholder rendering. const COMBAT_TYPE_LABELS: Dictionary = { "founder": "F", @@ -54,6 +60,8 @@ func _ready() -> void: EventBus.unit_moved.connect(_on_unit_moved) EventBus.unit_created.connect(_on_unit_created) EventBus.unit_destroyed.connect(_on_unit_destroyed) + EventBus.unit_healed.connect(_on_unit_hp_changed) + EventBus.combat_resolved.connect(_on_combat_resolved) func sync_units(units: Array) -> void: @@ -73,6 +81,8 @@ func sync_units(units: Array) -> void: "color": _get_unit_color(unit), "label": _get_unit_label(unit), "type_id": tid, + "hp": unit.hp, + "max_hp": unit.max_hp, } _cache_unit_sprite(tid) queue_redraw() @@ -165,11 +175,31 @@ func _draw() -> void: HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color.WHITE, ) + # Draw HP bar if damaged + _draw_hp_bar(pixel, data) + # Draw selection ring if uid == _selected_id: draw_arc(pixel, SELECTION_RADIUS, 0.0, TAU, 32, SELECTION_COLOR, SELECTION_WIDTH) +func _draw_hp_bar(pixel: Vector2, data: Dictionary) -> void: + ## Draw an HP bar below the unit when damaged. Green/yellow/red by fraction. + var max_hp: int = int(data.get("max_hp", 0)) + var hp: int = int(data.get("hp", 0)) + if max_hp <= 0 or hp >= max_hp: + return + var origin: Vector2 = Vector2(pixel.x - HP_BAR_WIDTH * 0.5, pixel.y + HP_BAR_OFFSET_Y) + draw_rect(Rect2(origin, Vector2(HP_BAR_WIDTH, HP_BAR_HEIGHT)), HP_BAR_BG_COLOR) + var frac: float = clampf(float(hp) / float(max_hp), 0.0, 1.0) + var bar_color: Color = Color.GREEN + if frac < 0.3: + bar_color = Color.RED + elif frac < 0.6: + bar_color = Color.YELLOW + draw_rect(Rect2(origin, Vector2(HP_BAR_WIDTH * frac, HP_BAR_HEIGHT)), bar_color) + + func _draw_movement_range() -> void: if _movement_range.is_empty(): return @@ -194,6 +224,8 @@ func _draw_movement_range() -> void: func _get_unit_color(unit: UnitScript) -> Color: ## Get the player color for this unit. + if unit.owner < 0: + return Color(0.6, 0.6, 0.6) var player: RefCounted = GameState.get_player(unit.owner) if player != null: var p: PlayerScript = player as PlayerScript @@ -282,6 +314,27 @@ func _on_unit_created(unit: RefCounted, _player_index: int) -> void: queue_redraw() +func _on_unit_hp_changed(unit: Variant, _amount: int) -> void: + _refresh_unit_hp(unit) + + +func _on_combat_resolved(attacker: Variant, defender: Variant, _result: Dictionary) -> void: + _refresh_unit_hp(attacker) + _refresh_unit_hp(defender) + + +func _refresh_unit_hp(unit: Variant) -> void: + var u: UnitScript = unit as UnitScript + if u == null: + return + var uid: String = _resolve_unit_id(u) + if uid == "" or not _units.has(uid): + return + _units[uid]["hp"] = u.hp + _units[uid]["max_hp"] = u.max_hp + queue_redraw() + + func _on_unit_destroyed(unit: RefCounted, _killer: RefCounted) -> void: var u: UnitScript = unit as UnitScript if u == null: