Add battlefield visuals and camp talk slice

This commit is contained in:
2026-06-18 16:45:46 +09:00
parent 909d497df0
commit d723d34a32
25 changed files with 604 additions and 35 deletions

View File

@@ -8,6 +8,7 @@ signal log_added(message: String)
signal dialogue_requested(lines: Array)
signal combat_feedback_requested(unit_id: String, text: String, kind: String)
signal unit_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i)
signal unit_action_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i, action_kind: String)
signal objective_updated(victory: String, defeat: String)
const TEAM_PLAYER := "player"
@@ -93,6 +94,7 @@ var shop := {}
var deployment_rules := {}
var formation_cells: Array[Vector2i] = []
var map_size := Vector2i.ZERO
var map_background := ""
var terrain_rows: Array[String] = []
var terrain_defs := DEFAULT_TERRAIN_DEFS.duplicate(true)
var units: Array[Dictionary] = []
@@ -176,6 +178,7 @@ func _apply_battle_data(data: Dictionary, roster_overrides := {}) -> void:
var map_data: Dictionary = data.get("map", {})
map_size = Vector2i(int(map_data.get("width", 0)), int(map_data.get("height", 0)))
map_background = str(map_data.get("background", ""))
terrain_rows.clear()
for row in map_data.get("terrain", []):
terrain_rows.append(String(row))
@@ -253,7 +256,7 @@ func _normalized_shop(source) -> Dictionary:
var seen := {}
var stock := {}
if typeof(source) != TYPE_DICTIONARY:
return {"items": item_ids, "stock": stock}
return {"items": item_ids, "stock": stock, "merchant": {}}
for entry in source.get("items", []):
var item_id := _shop_item_id_from_entry(entry)
if item_id.is_empty() or seen.has(item_id):
@@ -275,7 +278,24 @@ func _normalized_shop(source) -> Dictionary:
item_ids.append(item_id)
_apply_shop_stock_entry(stock, item_id, entry)
_apply_shop_stock_block(stock, block.get("stock", {}), seen)
return {"items": item_ids, "stock": stock}
return {"items": item_ids, "stock": stock, "merchant": _normalized_shop_merchant(source.get("merchant", {}))}
func _normalized_shop_merchant(source) -> Dictionary:
if typeof(source) != TYPE_DICTIONARY:
return {}
var lines := []
for line in source.get("lines", []):
var text := str(line).strip_edges()
if not text.is_empty():
lines.append(text)
var name := str(source.get("name", "Merchant")).strip_edges()
if name.is_empty() and lines.is_empty():
return {}
return {
"name": "Merchant" if name.is_empty() else name,
"lines": lines
}
func _shop_item_id_from_entry(entry) -> String:
@@ -1552,6 +1572,13 @@ func get_shop_stock_limit(item_id: String) -> int:
return int(stock.get(normalized, -1))
func get_shop_merchant() -> Dictionary:
var merchant = shop.get("merchant", {})
if typeof(merchant) != TYPE_DICTIONARY:
return {}
return merchant.duplicate(true)
func get_formation_cells() -> Array[Vector2i]:
return formation_cells.duplicate()
@@ -1842,6 +1869,10 @@ func get_terrain_key(cell: Vector2i) -> String:
return row.substr(cell.x, 1)
func get_map_background_path() -> String:
return map_background
func get_terrain_name(cell: Vector2i) -> String:
return String(terrain_defs.get(get_terrain_key(cell), DEFAULT_TERRAIN_DEFS["G"]).get("name", "Plain"))
@@ -3099,6 +3130,12 @@ func _resolve_combat(attacker: Dictionary, target: Dictionary) -> void:
func _resolve_attack(attacker: Dictionary, target: Dictionary, is_counter := false) -> Dictionary:
var hit_chance := calculate_hit_chance(attacker, target)
var verb := "counterattacks" if is_counter else "attacks"
unit_action_motion_requested.emit(
str(attacker.get("id", "")),
attacker.get("pos", Vector2i(-1, -1)),
target.get("pos", Vector2i(-1, -1)),
"counter" if is_counter else "attack"
)
if rng.randi_range(1, 100) > hit_chance:
_emit_log("%s %s %s but misses. Hit %d%%." % [attacker["name"], verb, target["name"], hit_chance])
_emit_combat_feedback(target, "MISS", "miss")