Antique-localize battle briefings and camp UI

This commit is contained in:
2026-06-19 05:48:39 +09:00
parent dc3dc06eb0
commit 30ac2479c4
11 changed files with 717 additions and 540 deletions

View File

@@ -1,9 +1,9 @@
{
"id": "001_yellow_turbans",
"name": "Yingchuan Skirmish",
"name": "영천 소전",
"objectives": {
"victory": "Defeat all Yellow Turban units.",
"defeat": "Cao Cao is defeated or the battle reaches Turn 8."
"victory": "황건적 전군을 격파하라.",
"defeat": "조조가 퇴각하거나 제8턴에 이르면 패한다."
},
"conditions": {
"victory": { "type": "all_units_defeated", "team": "enemy" },
@@ -13,62 +13,64 @@
]
},
"briefing": {
"title": "Yingchuan Skirmish",
"location": "Yingchuan, 184 CE",
"title": "영천 소전",
"location": "영천, 중평 원년",
"lines": [
"Yellow Turban rebels have gathered near Yingchuan.",
"Cao Cao and Xiahou Dun must break their formation before the revolt spreads."
"황건의 무리가 영천 근방에 깃발을 세웠다.",
"조조와 하후돈은 난세의 불길이 번지기 전에 그 진형을 꺾어야 한다."
],
"camp_dialogue": [
{ "speaker": "Cao Cao", "side": "left", "text": "Yingchuan is a spark in dry grass. If we move decisively, the court may still believe order can return." },
{ "speaker": "Xiahou Dun", "side": "right", "text": "The men are ready. Give me the front, and I will keep the rebels fixed while you cut their nerve." }
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "영천은 마른 풀밭의 불씨와 같다. 결단이 빠르면 조정도 아직 질서가 돌아올 수 있음을 믿을 것이다." },
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "군사는 이미 창끝을 세웠습니다. 선봉을 맡겨 주시면 적의 앞줄을 묶어 두겠습니다." }
],
"camp_conversations": [
{
"id": "cao_cao_strategy",
"group": "officer",
"officer_id": "cao_cao",
"label": "Cao Cao - Strategy",
"label": "조조 - 군략",
"speaker": "Cao Cao",
"summary": "Read the revolt before the first move.",
"display_speaker": "조조",
"summary": "첫 행군 전에 난세의 형세를 살핀다.",
"lines": [
{ "speaker": "Cao Cao", "side": "left", "text": "Yingchuan is a spark in dry grass. If we move decisively, the court may still believe order can return." },
{ "speaker": "Cao Cao", "side": "left", "text": "Their center is loose. Break the front, then deny them time to gather more banners." }
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "영천은 마른 풀밭의 불씨와 같다. 결단이 빠르면 조정도 아직 질서가 돌아올 수 있음을 믿을 것이다." },
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "적의 가운데가 허술하다. 앞줄을 깨고 깃발을 다시 모을 시간을 주지 마라." }
]
},
{
"id": "xiahou_dun_vanguard",
"group": "officer",
"officer_id": "xiahou_dun",
"label": "Xiahou Dun - Vanguard",
"label": "하후돈 - 선봉",
"speaker": "Xiahou Dun",
"summary": "Discuss the first clash.",
"display_speaker": "하후돈",
"summary": "첫 접전에 앞서 선봉의 길을 정한다.",
"lines": [
{ "speaker": "Xiahou Dun", "side": "right", "text": "The men are ready. Give me the front, and I will keep the rebels fixed while you cut their nerve." },
{ "speaker": "Cao Cao", "side": "left", "text": "Hold them in place, but do not chase too deep. We still need eyes on the woods." }
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "군사는 이미 창끝을 세웠습니다. 선봉을 맡겨 주시면 적의 앞줄을 묶어 두겠습니다." },
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "붙잡되 깊이 쫓지는 마라. 숲 쪽에도 눈을 두어야 한다." }
]
},
{
"id": "northern_woods_cache",
"group": "topic",
"label": "Northern Woods Cache",
"summary": "Follow up on the merchant's rumor.",
"label": "북쪽 숲 보급고",
"summary": "상인이 흘린 보급 소문을 살핀다.",
"lines": [
{ "speaker": "Cao Cao", "side": "left", "text": "The merchant's rumor may be useful. If the line holds, send a scout through the northern woods." },
{ "speaker": "Xiahou Dun", "side": "right", "text": "A hidden cache in rebel country? Then we take it before they remember where they left it." }
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "상인의 말이 허황되지만은 않을 것이다. 전열이 버티면 북쪽 숲으로 척후를 보내라." },
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "적지에 숨은 보급이라면, 저들이 기억해 내기 전에 우리가 거두겠습니다." }
],
"effects": [
{ "type": "grant_item", "item_id": "bean", "count": 1, "text": "The scout detail adds a Bean to the field supplies." }
{ "type": "grant_item", "item_id": "bean", "count": 1, "text": "척후가 북쪽 숲에서 콩 한 줌을 거두어 군수에 보탰다." }
]
}
]
},
"shop": {
"merchant": {
"name": "Camp Merchant",
"name": "군막 상인",
"lines": [
"Roadside stores are thin, my lord, but I saved beans and antidotes for your column.",
"The northern woods have rumors of a hidden cache, if your scouts can reach it."
"길가의 물자는 박하오나, 장군의 행렬을 위해 콩과 해독약만은 남겨 두었습니다.",
"북쪽 숲에는 묻어 둔 보급이 있다는 말도 도니, 척후가 닿으면 살펴보십시오."
]
},
"items": ["bean", "antidote"]
@@ -110,7 +112,7 @@
},
{
"unit_id": "yellow_turban_1",
"name": "Zhang Mancheng",
"name": "장만성",
"class_id": "bandit",
"team": "enemy",
"level": 1,
@@ -119,7 +121,7 @@
},
{
"unit_id": "yellow_turban_2",
"name": "Peasant Rebel",
"name": "농민 반군",
"class_id": "infantry",
"team": "enemy",
"level": 1,
@@ -128,7 +130,7 @@
},
{
"unit_id": "yellow_turban_3",
"name": "Rebel Archer",
"name": "황건 궁수",
"class_id": "archer",
"team": "enemy",
"level": 1,
@@ -142,8 +144,8 @@
"once": true,
"when": { "type": "battle_start" },
"actions": [
{ "type": "log", "text": "Cao Cao engages the Yellow Turbans." },
{ "type": "log", "text": "A small supply cache is hidden in the northern woods." }
{ "type": "log", "text": "조조군이 황건적과 맞붙었다." },
{ "type": "log", "text": "북쪽 숲 어딘가에 작은 보급고가 숨겨져 있다." }
]
},
{
@@ -154,8 +156,8 @@
{
"type": "dialogue",
"lines": [
{ "speaker": "Cao Cao", "side": "left", "text": "The rebels are scattered, but their numbers will swell if we hesitate." },
{ "speaker": "Xiahou Dun", "side": "right", "text": "Then we strike now. I will open a path through their front line." }
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "적은 흩어져 있으나, 머뭇거리면 그 수가 불어날 것이다." },
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "그렇다면 지금 치겠습니다. 제가 앞줄을 열어 길을 내겠습니다." }
]
}
]
@@ -165,7 +167,7 @@
"once": true,
"when": { "type": "unit_reaches_tile", "team": "player", "pos": [3, 2] },
"actions": [
{ "type": "log", "text": "The northern woods cache is recovered." },
{ "type": "log", "text": "북쪽 숲 보급고를 거두었다." },
{ "type": "grant_item", "item_id": "bean" }
]
},
@@ -174,7 +176,7 @@
"once": true,
"when": { "type": "turn_start", "team": "enemy", "turn": 2 },
"actions": [
{ "type": "log", "text": "The Yellow Turbans are looking for an opening." }
{ "type": "log", "text": "황건적이 빈틈을 엿보고 있다." }
]
}
],
@@ -184,9 +186,9 @@
"join_officers": ["xiahou_yuan", "cao_ren"]
},
"post_battle_dialogue": [
{ "speaker": "Xiahou Dun", "side": "right", "text": "The rebels are scattered. Yingchuan should breathe easier tonight." },
{ "speaker": "Cao Cao", "side": "left", "text": "This is only the first fire. If the court cannot move quickly, we must." },
{ "speaker": "Xiahou Yuan", "side": "right", "text": "Then call on us for the next campaign. We will ride with you." },
{ "speaker": "Cao Ren", "side": "right", "text": "The clan stands ready, my lord." }
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "적이 흩어졌습니다. 오늘 밤 영천은 한숨 돌릴 수 있겠습니다." },
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "이는 첫 불길일 뿐이다. 조정이 빨리 움직이지 못한다면 우리가 움직여야 한다." },
{ "speaker": "Xiahou Yuan", "display_speaker": "하후연", "side": "right", "text": "그렇다면 다음 출정에도 저희를 부르십시오. 함께 달리겠습니다." },
{ "speaker": "Cao Ren", "display_speaker": "조인", "side": "right", "text": "일족은 명을 기다리고 있습니다, 주공." }
]
}

View File

@@ -271,12 +271,14 @@ func _normalized_camp_conversations(source) -> Array:
var label := str(entry.get("label", conversation_id.capitalize())).strip_edges()
if label.is_empty():
label = conversation_id.capitalize()
var speaker := str(entry.get("speaker", "")).strip_edges()
var display_speaker := str(entry.get("display_speaker", speaker)).strip_edges()
result.append({
"id": conversation_id,
"group": str(entry.get("group", "topic")).strip_edges(),
"officer_id": str(entry.get("officer_id", "")).strip_edges(),
"label": label,
"speaker": str(entry.get("speaker", "")).strip_edges(),
"speaker": display_speaker,
"summary": str(entry.get("summary", "")).strip_edges(),
"lines": lines,
"effects": _normalized_camp_conversation_effects(entry.get("effects", []))
@@ -1309,7 +1311,7 @@ func end_player_turn() -> void:
selected_unit_id = ""
current_team = TEAM_ENEMY
_emit_log("Enemy phase begins.")
_emit_log("적군 차례가 열렸습니다.")
_reset_team_actions(TEAM_ENEMY)
if battle_status == STATUS_ACTIVE:
_run_events("turn_start", current_team, turn_number)
@@ -2068,13 +2070,21 @@ func can_player_act() -> bool:
func get_status_text() -> String:
if battle_status == STATUS_VICTORY:
return "Victory"
return "승전"
if battle_status == STATUS_DEFEAT:
return "Defeat"
return "패전"
var turn_limit := get_turn_limit()
if turn_limit > 0:
return "Turn %d/%d - %s" % [turn_number, turn_limit, current_team.capitalize()]
return "Turn %d - %s" % [turn_number, current_team.capitalize()]
return "%d/%d · %s" % [turn_number, turn_limit, _team_status_text(current_team)]
return "%d턴 · %s" % [turn_number, _team_status_text(current_team)]
func _team_status_text(team: String) -> String:
if team == TEAM_PLAYER:
return "아군"
if team == TEAM_ENEMY:
return "적군"
return team.capitalize()
func get_turn_limit() -> int:
@@ -2793,7 +2803,7 @@ func _run_enemy_turn() -> void:
if battle_status == STATUS_ACTIVE:
turn_number += 1
current_team = TEAM_PLAYER
_emit_log("Player phase begins.")
_emit_log("아군 차례가 열렸습니다.")
_reset_team_actions(TEAM_PLAYER)
if battle_status == STATUS_ACTIVE:
_run_events("turn_start", current_team, turn_number)
@@ -3494,9 +3504,9 @@ func _append_all_units_progress_line(result: Array[String], team: String, mode:
return
var living := _count_living_deployed_units(team)
if mode == "defeat":
_append_progress_line(result, "%s remaining %d/%d" % [_condition_team_label(team), living, total], pending)
_append_progress_line(result, "%s 잔존 %d/%d" % [_condition_team_label(team), living, total], pending)
return
_append_progress_line(result, "%s defeated %d/%d" % [_condition_team_label(team), total - living, total], pending)
_append_progress_line(result, "%s 격파 %d/%d" % [_condition_team_label(team), total - living, total], pending)
func _append_target_units_progress_line(result: Array[String], unit_ids, mode: String, pending := false) -> void:
@@ -3507,11 +3517,11 @@ func _append_target_units_progress_line(result: Array[String], unit_ids, mode: S
var defeated := int(counts.get("defeated", 0))
if mode == "defeat" and total == 1:
var unit_name := _condition_unit_display_name(unit_ids[0])
var status := "defeated" if defeated > 0 else "safe"
var status := "퇴각" if defeated > 0 else "건재"
_append_progress_line(result, "%s %s" % [unit_name, status], pending)
return
var label := "Protected losses" if mode == "defeat" else "Target units defeated"
var suffix := " (defeat at 1)" if mode == "defeat" else ""
var label := "호위 손실" if mode == "defeat" else "격파 표식"
var suffix := " (1 이상이면 패전)" if mode == "defeat" else ""
_append_progress_line(result, "%s %d/%d%s" % [label, defeated, total, suffix], pending)
@@ -3529,11 +3539,11 @@ func _append_target_officers_progress_line(
var defeated := int(counts.get("defeated", 0))
if mode == "defeat" and total == 1:
var officer_name := _condition_officer_display_name(str(officer_ids[0]), team)
var status := "defeated" if defeated > 0 else "safe"
var status := "퇴각" if defeated > 0 else "건재"
_append_progress_line(result, "%s %s" % [officer_name, status], pending)
return
var label := "Officer losses" if mode == "defeat" else "Target officers defeated"
var suffix := " (defeat at 1)" if mode == "defeat" else ""
var label := "장수 손실" if mode == "defeat" else "대상 장수 격파"
var suffix := " (1 이상이면 패전)" if mode == "defeat" else ""
_append_progress_line(result, "%s %d/%d%s" % [label, defeated, total, suffix], pending)
@@ -3541,8 +3551,8 @@ func _append_destination_progress_line(result: Array[String], condition: Diction
var targets := _condition_cells(condition)
if targets.is_empty():
return
var status := "reached" if _unit_reaches_tile(condition) else "not reached"
_append_progress_line(result, "Destination %s: %s" % [_format_cells(targets), status], pending)
var status := "도달" if _unit_reaches_tile(condition) else "미도달"
_append_progress_line(result, "목표 지점 %s: %s" % [_format_cells(targets), status], pending)
func _append_turn_limit_progress_line(result: Array[String]) -> void:
@@ -3550,7 +3560,7 @@ func _append_turn_limit_progress_line(result: Array[String]) -> void:
if limit <= 0:
return
var turns_left: int = maxi(0, limit - turn_number + 1)
_append_progress_line(result, "Turns left %d" % turns_left)
_append_progress_line(result, "남은 턴 %d" % turns_left)
func _append_after_event_progress_line(result: Array[String], event_id: String) -> void:
@@ -3566,15 +3576,15 @@ func _append_after_event_progress_line(result: Array[String], event_id: String)
elif trigger_type == "turn_start":
var target_turn := int(when.get("turn", 0))
if target_turn > 0:
_append_progress_line(result, "Objective unlocks on Turn %d" % target_turn)
_append_progress_line(result, "%d턴에 전공 갱신" % target_turn)
func _append_event_destination_progress_line(result: Array[String], when: Dictionary) -> void:
var targets := _condition_cells(when)
if targets.is_empty():
return
var status := "reached" if _unit_reaches_tile(when) else "not reached"
_append_progress_line(result, "Reach %s: %s" % [_format_cells(targets), status])
var status := "도달" if _unit_reaches_tile(when) else "미도달"
_append_progress_line(result, "목표 지점 %s: %s" % [_format_cells(targets), status])
func _append_turn_condition_progress_line(result: Array[String], condition: Dictionary, mode: String, pending := false) -> void:
@@ -3583,7 +3593,7 @@ func _append_turn_condition_progress_line(result: Array[String], condition: Dict
var limit := int(condition.get("turn", condition.get("limit", 0)))
if limit <= 0:
return
_append_progress_line(result, "Turns left %d" % maxi(0, limit - turn_number + 1), pending)
_append_progress_line(result, "남은 턴 %d" % maxi(0, limit - turn_number + 1), pending)
elif condition_type == "turn_reached":
var target_turn := int(condition.get("turn", 0))
if target_turn <= 0:
@@ -3591,16 +3601,16 @@ func _append_turn_condition_progress_line(result: Array[String], condition: Dict
if mode == "defeat":
var target_team := str(condition.get("team", ""))
var limit_turn := target_turn - 1 if target_team == TEAM_PLAYER else target_turn
_append_progress_line(result, "Turns left %d" % maxi(0, limit_turn - turn_number + 1), pending)
_append_progress_line(result, "남은 턴 %d" % maxi(0, limit_turn - turn_number + 1), pending)
return
_append_progress_line(result, "Turn %d/%d" % [min(turn_number, target_turn), target_turn], pending)
_append_progress_line(result, "%d/%d" % [min(turn_number, target_turn), target_turn], pending)
func _append_progress_line(result: Array[String], text: String, pending := false) -> void:
if text.is_empty():
return
if pending:
text += " (pending)"
text += " (대기)"
if not result.has(text):
result.append(text)
@@ -3700,10 +3710,10 @@ func _condition_officer_display_name(officer_id: String, team := "") -> String:
func _condition_team_label(team: String) -> String:
if team == TEAM_ENEMY:
return "Enemies"
return "적군"
if team == TEAM_PLAYER:
return "Allies"
return "Units"
return "아군"
return "부대"
func _event_by_id(event_id: String) -> Dictionary:
@@ -4157,11 +4167,12 @@ func _normalize_dialogue_line(line) -> Dictionary:
if text.is_empty():
return {}
var speaker := str(line.get("speaker", ""))
var display_speaker := str(line.get("display_speaker", speaker))
var portrait := str(line.get("portrait", ""))
if portrait.is_empty():
portrait = data_catalog.get_portrait_for_speaker(speaker)
return {
"speaker": speaker,
"speaker": display_speaker,
"text": text,
"portrait": portrait,
"side": _normalize_dialogue_side(line.get("side", "left"))

File diff suppressed because it is too large Load Diff

View File

@@ -65,7 +65,7 @@ try {
$jsonFiles = Get-ChildItem -Path (Join-Path $Root "data") -Filter "*.json" -Recurse
foreach ($file in $jsonFiles) {
try {
Get-Content $file.FullName -Raw | ConvertFrom-Json | Out-Null
Get-Content $file.FullName -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null
} catch {
Add-Failure "invalid JSON: $($file.FullName) - $($_.Exception.Message)"
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,49 +11,49 @@ func _init() -> void:
failures,
"001 HUD progress",
_progress_text("res://data/scenarios/001_yellow_turbans.json", false),
"Enemies defeated 0/"
"적군 격파 0/"
)
_check_contains(
failures,
"001 commander defeat risk",
_defeat_text("res://data/scenarios/001_yellow_turbans.json"),
"Cao Cao safe"
"Cao Cao 건재"
)
_check_contains(
failures,
"001 turn pressure risk",
_defeat_text("res://data/scenarios/001_yellow_turbans.json"),
"Turns left 7"
"남은 턴 7"
)
_check_contains(
failures,
"002 gated victory progress",
_progress_text("res://data/scenarios/002_sishui_gate.json", false),
"Objective unlocks on Turn 2"
"제2턴에 전공 갱신"
)
_check_contains(
failures,
"002 pending enemy progress",
_progress_text("res://data/scenarios/002_sishui_gate.json", false),
"Enemies defeated 0/"
"적군 격파 0/"
)
_check_contains(
failures,
"007 destination progress",
_progress_text("res://data/scenarios/007_xian_emperor_escort.json", false),
"Destination (14, 5): not reached"
"목표 지점 (14, 5): 미도달"
)
_check_contains(
failures,
"007 protected-unit risk progress",
_defeat_text("res://data/scenarios/007_xian_emperor_escort.json"),
"Imperial Envoy safe"
"Imperial Envoy 건재"
)
_check_contains(
failures,
"045 gated destination progress",
_progress_text("res://data/scenarios/045_fancheng_relief.json", false),
"Reach (10, 5): not reached"
"목표 지점 (10, 5): 미도달"
)
_check_event_gated_objective_markers(failures)
_check_event_gated_multi_cell_objective_markers(failures)

View File

@@ -327,7 +327,7 @@ func _check_scene_post_move_tactic_picker_flow(failures: Array[String]) -> void:
if tactic_markers.size() != 1 or tactic_markers[0].get("cell", Vector2i.ZERO) != Vector2i(3, 3):
failures.append("tactic target markers should include only the Spark target: %s" % str(tactic_markers))
else:
if str(tactic_markers[0].get("text", "")) != "KO":
if str(tactic_markers[0].get("text", "")) != "퇴각":
failures.append("tactic target marker should preview the deterministic KO: %s" % str(tactic_markers[0]))
if str(tactic_markers[0].get("kind", "")) != "damage":
failures.append("tactic target marker should use damage color: %s" % str(tactic_markers[0]))

View File

@@ -37,15 +37,15 @@ func _init() -> void:
var result_text: String = scene._format_battle_result_summary()
_check_contains(failures, "reward section", result_text, "전리품")
_check_contains(failures, "gold reward", result_text, "군자금 +100")
_check_contains(failures, "item reward", result_text, "Bean x2")
_check_contains(failures, "roster section", result_text, "합류: Dian Wei")
_check_contains(failures, "item reward", result_text, " x2")
_check_contains(failures, "roster section", result_text, "합류: 전위")
_check_contains(failures, "growth section", result_text, "Cao Cao Lv.2")
_check_contains(failures, "campaign section", result_text, "다음 행군: Sishui Gate")
_check_contains(failures, "next camp section", result_text, "다음 군막")
_check_contains(failures, "next camp location", result_text, "Sishui Gate, 190 CE")
_check_contains(failures, "next camp talk", result_text, "군막 회의 3")
_check_contains(failures, "next camp shop", result_text, "군상 물품 6")
_check_contains(failures, "next camp deploy", result_text, "출진 명부 2/3")
_check_contains(failures, "next camp talk", result_text, "군막 회의 · 3건")
_check_contains(failures, "next camp shop", result_text, "군상 물품 · 6종")
_check_contains(failures, "next camp deploy", result_text, "출진 명부 · 2/3")
_check_contains(failures, "next camp formation", result_text, "진형 배치 6칸")
scene.battle_result_summary = {
@@ -104,8 +104,8 @@ func _init() -> void:
"join_officers": ["dian_wei"]
})
_check_contains(failures, "choice gold", choice_text, "+50G")
_check_contains(failures, "choice item", choice_text, "War Axe")
_check_contains(failures, "choice join", choice_text, "합류 Dian Wei")
_check_contains(failures, "choice item", choice_text, "전부")
_check_contains(failures, "choice join", choice_text, "합류 전위")
if failures.is_empty():
scene.free()

View File

@@ -66,8 +66,8 @@ func _check_hud_reuses_status_summary(failures: Array[String]) -> void:
scene.state.selected_unit_id = "cao_cao"
scene.hover_cell = Vector2i(1, 3)
scene._update_hud()
_check_contains(failures, "selected HUD status", scene.selected_label.text, "Conditions: DEF -2 (2 phases)")
_check_contains(failures, "hover tile status", scene.cell_info_label.text, "Conditions: DEF -2 (2 phases)")
_check_contains(failures, "selected HUD status", scene.selected_label.text, "상태: DEF -2 (2 phases)")
_check_contains(failures, "hover tile status", scene.cell_info_label.text, "상태: DEF -2 (2 phases)")
scene.free()

View File

@@ -1,4 +1,4 @@
extends SceneTree
extends SceneTree
const BattleStateScript := preload("res://scripts/core/battle_state.gd")
const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd")
@@ -2518,50 +2518,50 @@ func _check_scene_texture_loading(failures: Array[String]) -> void:
var strategy := scene._camp_conversation_by_id("cao_cao_strategy")
if strategy.is_empty():
failures.append("battle scene should find Cao Cao camp conversation by id")
elif not scene._format_camp_conversation_button_text(strategy).contains("Cao Cao"):
elif not scene._format_camp_conversation_button_text(strategy).contains("조조"):
failures.append("camp conversation button text should include label/speaker")
elif scene._camp_conversation_preview_portrait_path(strategy).is_empty():
failures.append("camp conversation preview should resolve Cao Cao portrait")
elif scene._load_art_texture(scene._camp_conversation_preview_portrait_path(strategy)) == null:
failures.append("camp conversation preview portrait should load")
elif not scene._format_camp_conversation_row_title(strategy).contains("Officer"):
elif not scene._format_camp_conversation_row_title(strategy).contains("장수"):
failures.append("camp conversation row title should include group")
elif not scene._format_camp_conversation_preview_text(strategy).contains("Yingchuan"):
elif not scene._format_camp_conversation_preview_text(strategy).contains("영천"):
failures.append("camp conversation preview should include first dialogue text")
var cache := scene._camp_conversation_by_id("northern_woods_cache")
if cache.is_empty():
failures.append("battle scene should find cache camp conversation by id")
elif not scene._format_camp_conversation_button_text(cache).contains("보급 Bean"):
elif not scene._format_camp_conversation_button_text(cache).contains("보급 "):
failures.append("camp conversation button text should preview supply effect")
var merchant_notice := scene._format_shop_merchant_notice_text(scene.state.get_shop_merchant())
if not merchant_notice.contains("Camp Merchant") or not merchant_notice.contains("Roadside stores"):
if not merchant_notice.contains("군막 상인") or not merchant_notice.contains("길가의 물자"):
failures.append("shop merchant notice should include name and flavor lines")
var camp_overview := scene._format_briefing_camp_overview_text(scene.state.get_briefing(), false)
for expected in ["Yingchuan, 184 CE", "군자금 0G", "군막 회의 3", "보급 1", "군상 물품 2", "출진 명부 2/2", "진형 배치 4칸", "병장고 비축"]:
for expected in ["영천, 중평 원년", "군자금 0G", "군막 회의 · 3건", "보급 대기 1건", "군상 물품 · 2종", "출진 명부 2/2", "진형 배치 4칸", "병장고 비축"]:
if not camp_overview.contains(expected):
failures.append("camp overview should include %s: %s" % [expected, camp_overview])
var bean: Dictionary = scene.state.get_item_def("bean")
var bean_buy_detail := scene._format_shop_buy_item_detail_text("bean", bean, int(bean.get("price", 0)), 0)
if not bean_buy_detail.contains("Heal 20") or not bean_buy_detail.contains("Owned x0"):
if not bean_buy_detail.contains("병력 +20") or not bean_buy_detail.contains("보유 x0"):
failures.append("shop buy detail should summarize item effect and owned count")
scene.campaign_state.gold = 10
var need_gold_detail := scene._format_shop_buy_item_detail_text("bean", bean, int(bean.get("price", 0)), 0)
if not need_gold_detail.contains("Need 40G"):
if not need_gold_detail.contains("부족 40G"):
failures.append("shop buy detail should explain missing gold")
var sold_out_detail := scene._format_shop_buy_item_detail_text("bean", bean, int(bean.get("price", 0)), 2, 3, 0)
if not sold_out_detail.contains("Stock 0/3") or not sold_out_detail.contains("Sold out"):
if not sold_out_detail.contains("재고 0/3") or not sold_out_detail.contains("품절"):
failures.append("shop buy detail should explain exhausted finite stock")
var sell_detail := scene._format_shop_sell_item_detail_text("bean", bean, 25, 2)
if not sell_detail.contains("Half-price sale") or not sell_detail.contains("Owned x2"):
if not sell_detail.contains("반값 매각") or not sell_detail.contains("보유 x2"):
failures.append("shop sell detail should summarize sale rule and owned count")
var bean_action := scene._format_shop_buy_action_text("bean", bean, int(bean.get("price", 0)))
if not bean_action.contains("Acquire Bean") or not bean_action.contains("50G"):
if not bean_action.contains("매입 콩") or not bean_action.contains("50G"):
failures.append("shop buy action should be concise and priced")
var bean_tooltip := scene._format_shop_item_tooltip_text(
scene._format_shop_item_button_text("bean", bean, int(bean.get("price", 0)), 0),
bean_buy_detail
)
if not bean_tooltip.contains("\n") or not bean_tooltip.contains("Owned x0"):
if not bean_tooltip.contains("\n") or not bean_tooltip.contains("보유 x0"):
failures.append("shop item tooltip should preserve full item detail")
scene.free()
@@ -2598,32 +2598,32 @@ func _check_hud_focus_text(failures: Array[String]) -> void:
return
var cao_cao: Dictionary = scene.state.get_unit("cao_cao")
var cao_text := scene._format_unit_focus_text(cao_cao, "Selected")
if not cao_text.contains("Command authority"):
if not cao_text.contains("군령 지휘"):
failures.append("Cao Cao HUD focus should describe command role: %s" % cao_text)
if not cao_text.contains("March 4 (Foot)") or not cao_text.contains("Strike reach 1-1"):
if not cao_text.contains("행군 4 (보병)") or not cao_text.contains("타격 1-1"):
failures.append("Cao Cao HUD focus should clarify move/attack affordance: %s" % cao_text)
if not cao_text.contains("군령:") or not cao_text.contains("March") or not cao_text.contains("Strike") or not cao_text.contains("책략") or not cao_text.contains("병장") or not cao_text.contains("대기"):
if not cao_text.contains("군령:") or not cao_text.contains("행군") or not cao_text.contains("타격") or not cao_text.contains("책략") or not cao_text.contains("병장") or not cao_text.contains("대기"):
failures.append("Cao Cao HUD focus should summarize available actions: %s" % cao_text)
if not cao_text.contains("Ground 2,4"):
if not cao_text.contains("지형 2,4"):
failures.append("Cao Cao HUD focus should include current tile: %s" % cao_text)
if not cao_text.contains("March 1"):
if not cao_text.contains("행군 1"):
failures.append("Cao Cao HUD focus should include terrain move cost: %s" % cao_text)
if not cao_text.contains("Guard +0"):
if not cao_text.contains("방비 +0"):
failures.append("Cao Cao HUD focus should include terrain defense: %s" % cao_text)
var xiahou_dun: Dictionary = scene.state.get_unit("xiahou_dun")
xiahou_dun["pos"] = Vector2i(3, 1)
var forest_text := scene._unit_current_terrain_text(xiahou_dun)
if not forest_text.contains("Forest") or not forest_text.contains("March 3"):
if not forest_text.contains("수풀") or not forest_text.contains("행군 3"):
failures.append("Cavalry movement should use mounted move_type cost on forest: %s" % forest_text)
var archer: Dictionary = scene.state.get_unit("yellow_turban_3")
var archer_text := scene._format_unit_focus_text(archer, "Hover")
if not archer_text.contains("Bow pressure"):
if not archer_text.contains("원거리 견제"):
failures.append("Archer HUD focus should describe ranged role: %s" % archer_text)
if not archer_text.contains("March 4 (Archer)") or not archer_text.contains("Strike reach 2-2"):
if not archer_text.contains("행군 4 (궁병)") or not archer_text.contains("타격 2-2"):
failures.append("Archer HUD focus should clarify ranged attack affordance: %s" % archer_text)
if not archer_text.contains("군령: 적군 군기"):
failures.append("Enemy archer HUD focus should describe enemy control: %s" % archer_text)
if not scene._unit_current_terrain_text(archer).contains("Plain"):
if not scene._unit_current_terrain_text(archer).contains("평지"):
failures.append("Archer terrain text should reflect its current plain tile")
scene.free()
@@ -2894,17 +2894,17 @@ func _check_hover_intent_badges(failures: Array[String]) -> void:
scene.hover_cell = Vector2i(2, 3)
var move_badge := scene._target_preview_badge()
if str(move_badge.get("text", "")) != "MOVE" or str(move_badge.get("kind", "")) != "move":
if str(move_badge.get("text", "")) != "행군" or str(move_badge.get("kind", "")) != "move":
failures.append("reachable empty tile should show MOVE badge: %s" % str(move_badge))
scene.hover_cell = Vector2i(1, 4)
var select_badge := scene._target_preview_badge()
if str(select_badge.get("text", "")) != "SELECT" or str(select_badge.get("kind", "")) != "select":
if str(select_badge.get("text", "")) != "선택" or str(select_badge.get("kind", "")) != "select":
failures.append("friendly selectable unit should show SELECT badge: %s" % str(select_badge))
scene.hover_cell = Vector2i(7, 2)
var attack_badge := scene._target_preview_badge()
if attack_badge.is_empty() or str(attack_badge.get("kind", "")) == "move" or str(attack_badge.get("text", "")) == "MOVE":
if attack_badge.is_empty() or str(attack_badge.get("kind", "")) == "move" or str(attack_badge.get("text", "")) == "행군":
failures.append("enemy hover should keep attack badge priority: %s" % str(attack_badge))
var cao_cao: Dictionary = scene.state.get_unit("cao_cao")
@@ -2912,7 +2912,7 @@ func _check_hover_intent_badges(failures: Array[String]) -> void:
scene._refresh_ranges()
scene.hover_cell = Vector2i(2, 3)
var moved_badge := scene._target_preview_badge()
if str(moved_badge.get("kind", "")) == "move" or str(moved_badge.get("text", "")) == "MOVE":
if str(moved_badge.get("kind", "")) == "move" or str(moved_badge.get("text", "")) == "행군":
failures.append("moved unit should not show MOVE badge: %s" % str(moved_badge))
var default_color := scene._target_preview_badge_color("")

View File

@@ -7,7 +7,7 @@ function Read-Json($RelativePath) {
if (-not (Test-Path $path)) {
throw "Missing file: $RelativePath"
}
return Get-Content $path -Raw | ConvertFrom-Json
return Get-Content $path -Raw -Encoding UTF8 | ConvertFrom-Json
}
function Has-Prop($Object, [string]$Name) {