feat(unit-specific): ✨ Introduce UnitManager and UnitRenderer for unit lifecycle management and visual rendering, plus update Tile logic for unit placement and load unit data from data_loader.gd
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
45137691bc
commit
a3de8efdd5
4 changed files with 139 additions and 21 deletions
|
|
@ -205,6 +205,13 @@ func _log_load_summary() -> void:
|
|||
total += count
|
||||
print("DataLoader: Loaded %d entries from theme '%s'" % [total, _active_theme])
|
||||
|
||||
# -- Bulk category access --
|
||||
|
||||
func get_data(category: String) -> Dictionary:
|
||||
## Return the full id→entry dictionary for a data category.
|
||||
## Returns an empty dictionary for unknown or empty categories.
|
||||
return _data.get(category, {})
|
||||
|
||||
# -- Single-item lookups --
|
||||
|
||||
func get_terrain(id: String) -> Dictionary:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ extends Resource
|
|||
const ImprovementScript: GDScript = preload("res://engine/src/entities/improvement.gd")
|
||||
const TileSerializerScript: GDScript = preload("res://engine/src/map/tile_serializer.gd")
|
||||
|
||||
var biome_id: String = "" # biome classification result
|
||||
var biome_id: String = "": # biome classification result
|
||||
set(value):
|
||||
if value == null:
|
||||
push_warning("Tile: null biome_id assignment at %s, defaulting to 'plains'" % str(position))
|
||||
biome_id = "plains"
|
||||
else:
|
||||
biome_id = value
|
||||
var substrate_id: String = "" # geological substrate from elevation
|
||||
var water_body_id: int = -1 # water body index (-1 if land)
|
||||
var water_body_type: String = "" # pond/river/lake/large_lake/ocean
|
||||
|
|
|
|||
|
|
@ -1,2 +1,60 @@
|
|||
class_name UnitManager
|
||||
extends RefCounted
|
||||
## Manages per-turn unit lifecycle: movement refresh, vision calculation,
|
||||
## and unit queries for the active player.
|
||||
|
||||
const HexUtilsScript: GDScript = preload("res://engine/src/map/hex_utils.gd")
|
||||
const PathfinderScript: GDScript = preload("res://engine/src/map/pathfinder.gd")
|
||||
const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
|
||||
const UnitScript: GDScript = preload("res://engine/src/entities/unit.gd")
|
||||
const GameMapScript: GDScript = preload("res://engine/src/map/game_map.gd")
|
||||
const TileScript: GDScript = preload("res://engine/src/map/tile.gd")
|
||||
|
||||
## Default vision radius when a unit's JSON data has no "vision" field.
|
||||
const DEFAULT_VISION_RADIUS: int = 2
|
||||
|
||||
|
||||
func refresh_player_units(player: RefCounted) -> void:
|
||||
## Reset per-turn state for all units owned by this player.
|
||||
## Called at the start of each player's turn by TurnManager.
|
||||
for raw_unit: RefCounted in player.units:
|
||||
var unit: UnitScript = raw_unit as UnitScript
|
||||
if unit != null:
|
||||
unit.refresh_turn()
|
||||
|
||||
|
||||
func recalculate_vision(player: RefCounted, game_map: RefCounted) -> void:
|
||||
## Update tile visibility for this player based on unit positions.
|
||||
## Tiles previously visible become fog-of-war (1); tiles in range
|
||||
## of any unit become currently visible (2).
|
||||
var gm: GameMapScript = game_map as GameMapScript
|
||||
if gm == null:
|
||||
return
|
||||
_dim_current_vision(player, gm)
|
||||
for raw_unit: RefCounted in player.units:
|
||||
var unit: UnitScript = raw_unit as UnitScript
|
||||
if unit == null:
|
||||
continue
|
||||
var vision_radius: int = _get_unit_vision(unit)
|
||||
var visible_tiles: Array[Vector2i] = PathfinderScript.visible_hexes(
|
||||
gm, unit.position, vision_radius
|
||||
)
|
||||
for pos: Vector2i in visible_tiles:
|
||||
var tile: TileScript = gm.get_tile(pos) as TileScript
|
||||
if tile != null:
|
||||
tile.set_visibility(player.index, 2)
|
||||
|
||||
|
||||
func _dim_current_vision(player: RefCounted, game_map: GameMapScript) -> void:
|
||||
## Downgrade all currently-visible (2) tiles to fog-of-war (1).
|
||||
for axial: Vector2i in game_map.tiles:
|
||||
var tile: TileScript = game_map.tiles[axial] as TileScript
|
||||
if tile != null and tile.get_visibility(player.index) == 2:
|
||||
tile.set_visibility(player.index, 1)
|
||||
|
||||
|
||||
func _get_unit_vision(unit: UnitScript) -> int:
|
||||
## Read the vision radius from the unit's JSON data.
|
||||
## Falls back to DEFAULT_VISION_RADIUS when the field is absent.
|
||||
var data: Dictionary = DataLoader.get_unit(unit.unit_id)
|
||||
return data.get("vision", DEFAULT_VISION_RADIUS)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,19 @@ 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)
|
||||
|
||||
## Combat type to marker label mapping for placeholder rendering.
|
||||
const COMBAT_TYPE_LABELS: Dictionary = {
|
||||
"settler": "F", "founder": "F",
|
||||
"worker": "W",
|
||||
"ranged": "R",
|
||||
"siege": "S",
|
||||
"flying": "^",
|
||||
"melee": "M",
|
||||
"cavalry": "C",
|
||||
"naval": "N",
|
||||
"civilian": "V",
|
||||
}
|
||||
|
||||
## Unit display data: unit_id -> { "position": Vector2i, "color": Color, "label": String }
|
||||
var _units: Dictionary = {}
|
||||
|
||||
|
|
@ -51,15 +64,17 @@ func sync_units(units: Array) -> void:
|
|||
var unit: UnitScript = raw_unit as UnitScript
|
||||
if unit == null:
|
||||
continue
|
||||
if unit.id == "":
|
||||
var uid: String = _resolve_unit_id(unit)
|
||||
if uid == "":
|
||||
continue
|
||||
_units[unit.id] = {
|
||||
var tid: String = _resolve_type_id(unit)
|
||||
_units[uid] = {
|
||||
"position": unit.position,
|
||||
"color": _get_unit_color(unit),
|
||||
"label": _get_unit_label(unit),
|
||||
"type_id": unit.type_id,
|
||||
"type_id": tid,
|
||||
}
|
||||
_cache_unit_sprite(unit.type_id)
|
||||
_cache_unit_sprite(tid)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
|
|
@ -188,12 +203,33 @@ func _get_unit_color(unit: UnitScript) -> Color:
|
|||
|
||||
|
||||
func _get_unit_label(unit: UnitScript) -> String:
|
||||
## Get a short label for the unit (first character of type_id, uppercased).
|
||||
if unit.type_id.length() > 0:
|
||||
return unit.type_id[0].to_upper()
|
||||
## Get a marker label based on the unit's combat type.
|
||||
## Falls back to first character of unit_id if combat type is unknown.
|
||||
var data: Dictionary = DataLoader.get_unit(unit.unit_id)
|
||||
var combat_type: String = data.get("combat_type", "")
|
||||
if COMBAT_TYPE_LABELS.has(combat_type):
|
||||
return COMBAT_TYPE_LABELS[combat_type]
|
||||
if unit.unit_id.length() > 0:
|
||||
return unit.unit_id[0].to_upper()
|
||||
return "?"
|
||||
|
||||
|
||||
func _resolve_unit_id(unit: UnitScript) -> String:
|
||||
## Return the instance identifier for a unit.
|
||||
## Checks 'id' first (if set by world_map), falls back to 'unit_id'.
|
||||
if "id" in unit and unit.get("id") != "":
|
||||
return str(unit.get("id"))
|
||||
return unit.unit_id
|
||||
|
||||
|
||||
func _resolve_type_id(unit: UnitScript) -> String:
|
||||
## Return the type identifier for sprite/label lookup.
|
||||
## Checks 'type_id' first, falls back to 'unit_id'.
|
||||
if "type_id" in unit and unit.get("type_id") != "":
|
||||
return str(unit.get("type_id"))
|
||||
return unit.unit_id
|
||||
|
||||
|
||||
func _cache_unit_sprite(type_id: String) -> void:
|
||||
## Pre-cache a unit sprite via ThemeAssets. Caches null on miss.
|
||||
if type_id == "" or _unit_sprite_cache.has(type_id):
|
||||
|
|
@ -219,7 +255,10 @@ func _get_unit_sprite(type_id: String) -> Texture2D:
|
|||
func _on_unit_moved(
|
||||
unit: RefCounted, from: Vector2i, to: Vector2i
|
||||
) -> void:
|
||||
var uid: String = unit.id if "id" in unit else ""
|
||||
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
|
||||
animate_move(uid, from, to)
|
||||
|
|
@ -227,24 +266,32 @@ func _on_unit_moved(
|
|||
|
||||
func _on_unit_created(unit: RefCounted, _player_index: int) -> void:
|
||||
var u: UnitScript = unit as UnitScript
|
||||
if u == null or u.id == "":
|
||||
if u == null:
|
||||
return
|
||||
_units[u.id] = {
|
||||
var uid: String = _resolve_unit_id(u)
|
||||
if uid == "":
|
||||
return
|
||||
var tid: String = _resolve_type_id(u)
|
||||
_units[uid] = {
|
||||
"position": u.position,
|
||||
"color": _get_unit_color(u),
|
||||
"label": _get_unit_label(u),
|
||||
"type_id": u.type_id,
|
||||
"type_id": tid,
|
||||
}
|
||||
_cache_unit_sprite(u.type_id)
|
||||
_cache_unit_sprite(tid)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _on_unit_destroyed(unit: RefCounted, _killer: RefCounted) -> void:
|
||||
var uid: String = unit.id if "id" in unit else ""
|
||||
if uid != "" and _units.has(uid):
|
||||
_units.erase(uid)
|
||||
_anim_pixels.erase(uid)
|
||||
if _selected_id == uid:
|
||||
_selected_id = ""
|
||||
_movement_range.clear()
|
||||
queue_redraw()
|
||||
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.erase(uid)
|
||||
_anim_pixels.erase(uid)
|
||||
if _selected_id == uid:
|
||||
_selected_id = ""
|
||||
_movement_range.clear()
|
||||
queue_redraw()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue