feat(engine): Optimize village/lair placement logic and unit rendering performance with improved distribution algorithms and visual enhancements

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-16 17:37:37 -07:00
parent 3ffefc9057
commit 85abaf3a38
3 changed files with 62 additions and 3 deletions

View file

@ -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 --

View file

@ -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": [],
}

View file

@ -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: