Antique briefing UI and Puyang localization

This commit is contained in:
2026-06-19 07:14:56 +09:00
parent c2f0fe9041
commit b1fc4d4597
6 changed files with 199 additions and 138 deletions

View File

@@ -76,7 +76,7 @@
}, },
{ {
"id": "005_puyang_raid", "id": "005_puyang_raid",
"title": "Puyang Raid", "title": "복양 기습전",
"path": "res://data/scenarios/005_puyang_raid.json" "path": "res://data/scenarios/005_puyang_raid.json"
}, },
{ {

View File

@@ -1,9 +1,9 @@
{ {
"id": "005_puyang_raid", "id": "005_puyang_raid",
"name": "Puyang Raid", "name": "복양 기습전",
"objectives": { "objectives": {
"victory": "Defeat Lu Bu's forward guard.", "victory": "여포군 전위영을 격파하라.",
"defeat": "Cao Cao is defeated or the battle reaches Turn 11." "defeat": "조조가 퇴각하거나 제11턴에 이르면 패한다."
}, },
"conditions": { "conditions": {
"victory": { "type": "all_units_defeated", "team": "enemy", "after_event": "turn_3_lu_bu_arrives" }, "victory": { "type": "all_units_defeated", "team": "enemy", "after_event": "turn_3_lu_bu_arrives" },
@@ -13,77 +13,79 @@
] ]
}, },
"briefing": { "briefing": {
"title": "Puyang Raid", "title": "복양 기습전",
"location": "Puyang, 194 CE", "location": "복양, 흥평 원년",
"lines": [ "lines": [
"Lu Bu has seized momentum in Yan Province.", "연주가 흔들리자 여포는 복양을 움켜쥐고 기세를 돋우었다.",
"Cao Cao launches a night raid near Puyang to break the forward guard before the main cavalry can gather.", "조조는 밤 그늘을 타고 전위영을 찔러, 적 기병이 말고삐를 모으기 전에 그 앞줄을 꺾으려 한다.",
"Dian Wei joins the front line as Cao Cao's personal shield." "전위가 새로 군문에 들어와 주공의 깃발 곁을 지킨다."
], ],
"camp_conversations": [ "camp_conversations": [
{ {
"id": "puyang_night_raid_council", "id": "puyang_night_raid_council",
"group": "officer", "group": "officer",
"officer_id": "cao_cao", "officer_id": "cao_cao",
"label": "Cao Cao - Night Raid", "label": "조조 - 야습 군의",
"speaker": "Cao Cao", "speaker": "Cao Cao",
"summary": "Review the timing of the strike against Lu Bu's forward guard.", "display_speaker": "조조",
"summary": "여포군 전위영을 치는 시각과 퇴로를 정한다.",
"lines": [ "lines": [
{ "speaker": "Cao Cao", "side": "left", "text": "Lu Bu wins when a field becomes a race. Tonight we deny him daylight, distance, and the first clean charge." }, { "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "벌판이 달리기가 되면 여포가 이긴다. 오늘 밤에는 햇빛도, 거리도, 첫 돌격도 저들에게 주지 않겠다." },
{ "speaker": "Xiahou Dun", "side": "right", "text": "Then we hit the guard camp before the horses are bridled. I like battles that begin with the enemy half awake." } { "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "그렇다면 말에 굴레가 얹히기 전에 영채를 치겠습니다. 적이 반쯤 잠든 전장은 우리 쪽 예법에 가깝습니다." }
] ]
}, },
{ {
"id": "puyang_dian_wei_shield", "id": "puyang_dian_wei_shield",
"group": "officer", "group": "officer",
"officer_id": "dian_wei", "officer_id": "dian_wei",
"label": "Dian Wei - Iron Shield", "label": "전위 - 철문 호위",
"speaker": "Dian Wei", "speaker": "Dian Wei",
"summary": "Hear Dian Wei's first pledge as Cao Cao's personal shield.", "display_speaker": "전위",
"summary": "주공의 깃발 곁에 선 전위의 첫 맹세를 듣는다.",
"lines": [ "lines": [
{ "speaker": "Dian Wei", "side": "right", "text": "You gave me a place in the line. I will make that place difficult for any rider to cross." }, { "speaker": "Dian Wei", "display_speaker": "전위", "side": "right", "text": "제게 대열 한가운데 자리를 주셨습니다. 그 자리는 어떤 기병도 쉽게 넘지 못하게 하겠습니다." },
{ "speaker": "Cao Cao", "side": "left", "text": "Stand close when the alarm rises. Lu Bu's men will aim for the banner before they understand the field." } { "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "경고의 북이 울리면 내 깃발 가까이에 서라. 여포의 병사는 전장을 읽기 전에 먼저 군기를 노릴 것이다." }
] ]
}, },
{ {
"id": "puyang_raider_stores", "id": "puyang_raider_stores",
"group": "topic", "group": "topic",
"label": "Raider Stores", "label": "야습 보급 묶음",
"summary": "Pack a small supply bundle for the night raid.", "summary": "밤길에 들고 갈 작은 군수 꾸러미를 챙긴다.",
"lines": [ "lines": [
{ "speaker": "Puyang Night Trader", "side": "right", "text": "A night raid does not leave room for wagons. I tied the beans into dark cloth so the men can carry them under armor." }, { "speaker": "Puyang Night Trader", "display_speaker": "복양 야상", "side": "right", "text": "야습에는 큰 수레가 따라붙지 못합니다. 콩을 검은 천에 묶어 갑옷 밑에도 숨길 수 있게 했습니다." },
{ "speaker": "Xiahou Yuan", "side": "left", "text": "Good. When Lu Bu's riders wake, no one will have time to search a cart." } { "speaker": "Xiahou Yuan", "display_speaker": "하후연", "side": "left", "text": "좋다. 여포의 기병이 깨어나면 누구도 수레 속을 뒤질 틈이 없을 것이다." }
], ],
"effects": [ "effects": [
{ "type": "grant_item", "item_id": "bean", "count": 1, "text": "The raider stores add a Bean to the field supplies." } { "type": "grant_item", "item_id": "bean", "count": 1, "text": "야습 보급 묶음에서 콩 한 줌을 군수에 보탰다." }
] ]
}, },
{ {
"id": "puyang_hardened_vanguard", "id": "puyang_hardened_vanguard",
"campaign_flags": { "pursued_dong_zhuo": true }, "campaign_flags": { "pursued_dong_zhuo": true },
"group": "topic", "group": "topic",
"label": "Hardened Vanguard", "label": "형양 노병",
"summary": "Issue wine to veterans used to forced marches and sudden strikes.", "summary": "강행군과 매복을 넘긴 선봉에게 술을 나눈다.",
"lines": [ "lines": [
{ "speaker": "Xiahou Dun", "side": "right", "text": "The men who chased Dong Zhuo still move before the drum finishes. Puyang's night road will not frighten them." }, { "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "동탁을 쫓아 형양까지 달린 병사들은 아직 북소리가 끝나기 전에 움직입니다. 복양의 밤길도 겁내지 않을 것입니다." },
{ "speaker": "Cao Cao", "side": "left", "text": "Give those veterans wine before we step off. Men who endure hard roads should know their endurance is seen." } { "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "출진 전에 노병들에게 술을 조금 나누어라. 험한 길을 견딘 자에게는 그 수고를 안다는 말이 필요하다." }
], ],
"effects": [ "effects": [
{ "type": "grant_item", "item_id": "wine", "count": 1, "text": "The hardened vanguard adds Wine to the field supplies." } { "type": "grant_item", "item_id": "wine", "count": 1, "text": "형양 노병들이 술 한 병을 군수에 보탰다." }
] ]
}, },
{ {
"id": "puyang_qingzhou_reserve_wagon", "id": "puyang_qingzhou_reserve_wagon",
"campaign_flags": { "regrouped_after_sishui": true }, "campaign_flags": { "regrouped_after_sishui": true },
"group": "topic", "group": "topic",
"label": "Qingzhou Reserve Wagon", "label": "청주 예비 약거",
"summary": "Open a physician's bundle preserved during the steadier march.", "summary": "정비 행군 중 보존한 의원의 꾸러미를 연다.",
"lines": [ "lines": [
{ "speaker": "Puyang Night Trader", "side": "right", "text": "The reserve wagon from Qingzhou still has a sealed physician's bundle. Quiet hands packed it better than hurried ones." }, { "speaker": "Puyang Night Trader", "display_speaker": "복양 야상", "side": "right", "text": "청주에서 따라온 예비 수레에 아직 의원의 봉한 꾸러미가 남아 있습니다. 서두르지 않은 손이 싼 물건은 오래 버팁니다." },
{ "speaker": "Dian Wei", "side": "left", "text": "Keep it near the banner. If Lu Bu's riders break through, wounded men will need more than courage." } { "speaker": "Dian Wei", "display_speaker": "전위", "side": "left", "text": "깃발 가까이에 두게. 여포의 기병이 뚫고 들어오면 다친 병사에게 필요한 것은 담력만이 아닐 것이다." }
], ],
"effects": [ "effects": [
{ "type": "grant_item", "item_id": "panacea", "count": 1, "text": "The Qingzhou reserve wagon adds a Panacea to the field supplies." } { "type": "grant_item", "item_id": "panacea", "count": 1, "text": "청주 예비 약거에서 만능약 한 꾸러미를 군수에 보탰다." }
] ]
} }
] ]
@@ -100,10 +102,10 @@
"iron_armor" "iron_armor"
], ],
"merchant": { "merchant": {
"name": "Puyang Night Trader", "name": "복양 야상",
"lines": [ "lines": [
"Keep the lamps hooded. Lu Bu's scouts buy anything that shines.", "등불은 가려 두십시오. 여포의 척후는 빛나는 물건이라면 무엇이든 사 갑니다.",
"His riders spend silver on speed. You will spend it on armor, beans, and living long enough to use both." "저들의 기병은 빠른 말에 은을 쓰고, 장군께서는 갑옷과 콩, 그리고 그것을 쓸 만큼 오래 버티는 데 은을 쓰시면 됩니다."
] ]
} }
}, },
@@ -167,7 +169,7 @@
}, },
{ {
"unit_id": "zhang_liao_vanguard", "unit_id": "zhang_liao_vanguard",
"name": "Zhang Liao Vanguard", "name": "장료 선봉",
"class_id": "cavalry", "class_id": "cavalry",
"team": "enemy", "team": "enemy",
"level": 5, "level": 5,
@@ -177,7 +179,7 @@
}, },
{ {
"unit_id": "gao_shun_guard", "unit_id": "gao_shun_guard",
"name": "Gao Shun Guard", "name": "고순 군영",
"class_id": "infantry", "class_id": "infantry",
"team": "enemy", "team": "enemy",
"level": 5, "level": 5,
@@ -187,7 +189,7 @@
}, },
{ {
"unit_id": "puyang_cavalry_1", "unit_id": "puyang_cavalry_1",
"name": "Puyang Rider", "name": "복양 기병",
"class_id": "cavalry", "class_id": "cavalry",
"team": "enemy", "team": "enemy",
"level": 5, "level": 5,
@@ -197,7 +199,7 @@
}, },
{ {
"unit_id": "puyang_cavalry_2", "unit_id": "puyang_cavalry_2",
"name": "Puyang Rider", "name": "복양 기병",
"class_id": "cavalry", "class_id": "cavalry",
"team": "enemy", "team": "enemy",
"level": 5, "level": 5,
@@ -207,7 +209,7 @@
}, },
{ {
"unit_id": "puyang_archer", "unit_id": "puyang_archer",
"name": "Puyang Archer", "name": "복양 궁병",
"class_id": "archer", "class_id": "archer",
"team": "enemy", "team": "enemy",
"level": 5, "level": 5,
@@ -222,7 +224,7 @@
"once": true, "once": true,
"when": { "type": "battle_start" }, "when": { "type": "battle_start" },
"actions": [ "actions": [
{ "type": "log", "text": "Cao Cao's raiding force slips toward Puyang under cover of darkness." } { "type": "log", "text": "조조의 야습대가 밤 그늘을 타고 복양 전위영으로 스며든다." }
] ]
}, },
{ {
@@ -233,9 +235,9 @@
{ {
"type": "dialogue", "type": "dialogue",
"lines": [ "lines": [
{ "speaker": "Cao Cao", "text": "Lu Bu's cavalry is dangerous once it gathers speed. We strike before the whole camp wakes." }, { "speaker": "Cao Cao", "display_speaker": "조조", "text": "여포의 기병은 속도가 붙으면 사납다. 영채가 모두 깨기 전에 쳐라." },
{ "speaker": "Dian Wei", "text": "Stay behind me, my lord. Any rider who reaches you answers to my axe." }, { "speaker": "Dian Wei", "display_speaker": "전위", "text": "주공께서는 제 뒤에 계십시오. 주공께 닿는 말발굽은 제 도끼 앞에 설 것입니다." },
{ "speaker": "Xiahou Dun", "text": "Good. We cut open their guard and withdraw before dawn." } { "speaker": "Xiahou Dun", "display_speaker": "하후돈", "text": "좋습니다. 새벽이 오기 전에 전위영을 열고 빠져나가겠습니다." }
] ]
} }
] ]
@@ -245,12 +247,12 @@
"once": true, "once": true,
"when": { "type": "turn_start", "team": "enemy", "turn": 2 }, "when": { "type": "turn_start", "team": "enemy", "turn": 2 },
"actions": [ "actions": [
{ "type": "log", "text": "Puyang's forward camp sounds the alarm." }, { "type": "log", "text": "복양 전위영에서 경고의 북이 울린다." },
{ {
"type": "dialogue", "type": "dialogue",
"lines": [ "lines": [
{ "speaker": "Zhang Liao", "text": "Cao Cao comes in the night. Riders, close the road and pin him here." }, { "speaker": "Zhang Liao", "display_speaker": "장료", "text": "조조가 밤을 틈타 왔다. 기병은 길을 닫고 저들을 이곳에 묶어라." },
{ "speaker": "Cao Cao", "text": "The alarm is raised. Break their line before Lu Bu himself arrives." } { "speaker": "Cao Cao", "display_speaker": "조조", "text": "북이 울렸구나. 여포가 직접 오기 전에 저 전열을 깨라." }
] ]
} }
] ]
@@ -260,25 +262,25 @@
"once": true, "once": true,
"when": { "type": "turn_start", "team": "enemy", "turn": 3 }, "when": { "type": "turn_start", "team": "enemy", "turn": 3 },
"actions": [ "actions": [
{ "type": "log", "text": "Lu Bu's banner appears beyond the eastern road." }, { "type": "log", "text": "동쪽 길 너머로 여포의 군기가 모습을 드러낸다." },
{ {
"type": "dialogue", "type": "dialogue",
"lines": [ "lines": [
{ "speaker": "Lu Bu", "text": "Cao Cao dares raid Puyang? Then let him see how fast my riders wake." }, { "speaker": "Lu Bu", "display_speaker": "여포", "text": "조조가 감히 복양을 밤에 치느냐? 내 기병이 얼마나 빨리 깨어나는지 보여 주마." },
{ "speaker": "Dian Wei", "text": "That one has a loud voice. I will hold the road while you finish the guard." } { "speaker": "Dian Wei", "display_speaker": "전위", "text": "목소리 하나는 크군요. 주공께서 전위영을 깨는 동안 길목은 제가 막겠습니다." }
] ]
}, },
{ {
"type": "set_objective", "type": "set_objective",
"victory": "Defeat Zhang Liao's guard and the arriving riders.", "victory": "장료의 전위와 달려든 기병을 격파하라.",
"defeat": "Cao Cao is defeated or the battle reaches Turn 11." "defeat": "조조가 퇴각하거나 제11턴에 이르면 패한다."
}, },
{ {
"type": "spawn_deployments", "type": "spawn_deployments",
"deployments": [ "deployments": [
{ {
"unit_id": "lu_bu_banner", "unit_id": "lu_bu_banner",
"name": "Lu Bu Banner Guard", "name": "여포 기치병",
"class_id": "elite_cavalry", "class_id": "elite_cavalry",
"team": "enemy", "team": "enemy",
"level": 6, "level": 6,
@@ -288,7 +290,7 @@
}, },
{ {
"unit_id": "lu_bu_rider_north", "unit_id": "lu_bu_rider_north",
"name": "Lu Bu Rider", "name": "여포 기병",
"class_id": "cavalry", "class_id": "cavalry",
"team": "enemy", "team": "enemy",
"level": 5, "level": 5,
@@ -298,7 +300,7 @@
}, },
{ {
"unit_id": "lu_bu_rider_south", "unit_id": "lu_bu_rider_south",
"name": "Lu Bu Rider", "name": "여포 기병",
"class_id": "cavalry", "class_id": "cavalry",
"team": "enemy", "team": "enemy",
"level": 5, "level": 5,
@@ -318,8 +320,8 @@
{ {
"type": "dialogue", "type": "dialogue",
"lines": [ "lines": [
{ "speaker": "Dian Wei", "text": "Their riders hit hard, but their line bends. Give the word and I will split it." }, { "speaker": "Dian Wei", "display_speaker": "전위", "text": "저들의 기병은 세게 부딪치나 전열은 이미 휘었습니다. 명만 내리시면 제가 갈라 놓겠습니다." },
{ "speaker": "Cao Cao", "text": "Press them. We leave Puyang with their vanguard broken." } { "speaker": "Cao Cao", "display_speaker": "조조", "text": "몰아붙여라. 복양을 온전히 얻지 못하더라도, 오늘 밤 저들의 선봉은 꺾어 두고 간다." }
] ]
} }
] ]
@@ -330,21 +332,21 @@
"items": ["bean", "wine", "iron_sword"] "items": ["bean", "wine", "iron_sword"]
}, },
"post_battle_dialogue": [ "post_battle_dialogue": [
{ "speaker": "Xiahou Dun", "text": "Lu Bu's men still hold Puyang, but their forward guard will remember tonight." }, { "speaker": "Xiahou Dun", "display_speaker": "하후돈", "text": "여포의 군세는 아직 복양을 붙들고 있으나, 전위영은 오늘 밤을 잊지 못할 것입니다." },
{ "speaker": "Dian Wei", "text": "They came fast. Next time, let them come closer." }, { "speaker": "Dian Wei", "display_speaker": "전위", "text": "빠르게 달려오더군요. 다음에는 조금 더 가까이 오게 두십시오." },
{ "speaker": "Cao Cao", "text": "We have measured their strength. Now we choose whether to fortify Yan Province or strike again." } { "speaker": "Cao Cao", "display_speaker": "조조", "text": "저들의 힘을 가늠했다. 이제 연주를 굳힐지, 흔들린 복양을 다시 누를지 정해야 한다." }
], ],
"post_battle_choices": [ "post_battle_choices": [
{ {
"id": "fortify_yan_province", "id": "fortify_yan_province",
"label": "Fortify Yan Province", "label": "연주 보강",
"description": "Secure supply roads before another clash with Lu Bu.", "description": "여포와 다시 겨루기 전에 군량길과 성책을 단단히 묶는다.",
"set_flags": { "fortified_yan_province": true, "pressed_lu_bu": false } "set_flags": { "fortified_yan_province": true, "pressed_lu_bu": false }
}, },
{ {
"id": "press_lu_bu", "id": "press_lu_bu",
"label": "Press Lu Bu", "label": "여포 압박",
"description": "Keep pressure on Puyang while Lu Bu's guard is shaken.", "description": "전위영이 흔들린 틈을 놓치지 않고 복양을 계속 누른다.",
"set_flags": { "fortified_yan_province": false, "pressed_lu_bu": true } "set_flags": { "fortified_yan_province": false, "pressed_lu_bu": true }
} }
] ]

View File

@@ -7,16 +7,21 @@ const CAMPAIGN_PATH := "res://data/campaign/campaign.json"
const TILE_SIZE := 64 const TILE_SIZE := 64
const BOARD_OFFSET := Vector2(48, 104) const BOARD_OFFSET := Vector2(48, 104)
const DEFAULT_BATTLE_BACKGROUND_PATH := "res://art/backgrounds/battlefield_frontier.png" const DEFAULT_BATTLE_BACKGROUND_PATH := "res://art/backgrounds/battlefield_frontier.png"
const PLAYER_COLOR := Color(0.20, 0.42, 0.82) const PLAYER_COLOR := Color(0.17, 0.38, 0.29)
const ENEMY_COLOR := Color(0.72, 0.18, 0.16) const ENEMY_COLOR := Color(0.64, 0.085, 0.045)
const GRID_COLOR := Color(0.05, 0.06, 0.07, 0.45) const GRID_COLOR := Color(0.12, 0.070, 0.032, 0.36)
const SKILL_OVERLAY_COLOR := Color(0.56, 0.28, 0.95, 0.28) const MOVE_OVERLAY_COLOR := Color(0.72, 0.54, 0.24, 0.26)
const SKILL_AREA_OVERLAY_COLOR := Color(0.72, 0.46, 1.0, 0.30) const MOVE_BORDER_COLOR := Color(0.94, 0.73, 0.36, 0.48)
const ITEM_OVERLAY_COLOR := Color(0.24, 0.74, 0.34, 0.30) const ATTACK_OVERLAY_COLOR := Color(0.72, 0.055, 0.032, 0.24)
const FORMATION_OVERLAY_COLOR := Color(0.95, 0.76, 0.25, 0.34) const ATTACK_BORDER_COLOR := Color(0.92, 0.34, 0.18, 0.50)
const OBJECTIVE_OVERLAY_COLOR := Color(1.0, 0.78, 0.18, 0.30) const SKILL_OVERLAY_COLOR := Color(0.10, 0.25, 0.20, 0.28)
const THREAT_OVERLAY_COLOR := Color(1.0, 0.12, 0.10, 0.18) const SKILL_AREA_OVERLAY_COLOR := Color(0.26, 0.34, 0.23, 0.30)
const THREAT_BORDER_COLOR := Color(1.0, 0.16, 0.12, 0.44) const ITEM_OVERLAY_COLOR := Color(0.24, 0.42, 0.25, 0.28)
const ITEM_BORDER_COLOR := Color(0.58, 0.74, 0.42, 0.44)
const FORMATION_OVERLAY_COLOR := Color(0.78, 0.54, 0.18, 0.32)
const OBJECTIVE_OVERLAY_COLOR := Color(0.88, 0.52, 0.14, 0.30)
const THREAT_OVERLAY_COLOR := Color(0.76, 0.060, 0.035, 0.20)
const THREAT_BORDER_COLOR := Color(0.86, 0.18, 0.10, 0.48)
const BGM_MENU_PATH := "res://audio/bgm/menu_theme_placeholder.wav" const BGM_MENU_PATH := "res://audio/bgm/menu_theme_placeholder.wav"
const BGM_BATTLE_PATH := "res://audio/bgm/battle_loop_placeholder.wav" const BGM_BATTLE_PATH := "res://audio/bgm/battle_loop_placeholder.wav"
const SFX_BOW_RELEASE_PATH := "res://audio/sfx/bow_release_01.wav" const SFX_BOW_RELEASE_PATH := "res://audio/sfx/bow_release_01.wav"
@@ -99,6 +104,35 @@ const UI_CHARCOAL_WASH := Color(0.045, 0.026, 0.016, 1.0)
const UI_SCROLL_CENTER := Color(0.74, 0.58, 0.32, 0.996) const UI_SCROLL_CENTER := Color(0.74, 0.58, 0.32, 0.996)
const UI_SCROLL_EDGE := Color(0.43, 0.25, 0.095, 0.996) const UI_SCROLL_EDGE := Color(0.43, 0.25, 0.095, 0.996)
const MAP_SCROLL_FRAME_PADDING := 12.0 const MAP_SCROLL_FRAME_PADDING := 12.0
const SPEAKER_DISPLAY_NAMES := {
"Cao Cao": "조조",
"Xiahou Dun": "하후돈",
"Xiahou Yuan": "하후연",
"Cao Ren": "조인",
"Dian Wei": "전위",
"Guo Jia": "곽가",
"Zhang He": "장합",
"Zhang Liao": "장료",
"Gao Shun": "고순",
"Lu Bu": "여포",
"Puyang Night Trader": "복양 야상",
"Coalition Merchant": "군막 상인",
"Coalition Quartermaster": "연합 군수관",
"Camp Merchant": "군막 상인",
"Scout": "척후"
}
const SPEAKER_CANONICAL_NAMES := {
"조조": "Cao Cao",
"하후돈": "Xiahou Dun",
"하후연": "Xiahou Yuan",
"조인": "Cao Ren",
"전위": "Dian Wei",
"곽가": "Guo Jia",
"장합": "Zhang He",
"장료": "Zhang Liao",
"고순": "Gao Shun",
"여포": "Lu Bu"
}
var state: BattleState = BattleStateScript.new() var state: BattleState = BattleStateScript.new()
var campaign_state: CampaignState = CampaignStateScript.new() var campaign_state: CampaignState = CampaignStateScript.new()
@@ -949,7 +983,7 @@ func _create_hud() -> void:
selected_row.add_child(selected_label) selected_row.add_child(selected_label)
mission_title_label = Label.new() mission_title_label = Label.new()
mission_title_label.text = "군령 목패" mission_title_label.text = "軍令 목패"
mission_title_label.add_theme_font_size_override("font_size", 14) mission_title_label.add_theme_font_size_override("font_size", 14)
_apply_label_style(mission_title_label, UI_OLD_BRONZE) _apply_label_style(mission_title_label, UI_OLD_BRONZE)
side_column.add_child(mission_title_label) side_column.add_child(mission_title_label)
@@ -1022,7 +1056,7 @@ func _create_hud() -> void:
button_row.add_child(threat_button) button_row.add_child(threat_button)
end_turn_button = Button.new() end_turn_button = Button.new()
end_turn_button.text = "차례 종료" end_turn_button.text = "군령 종료"
end_turn_button.pressed.connect(_on_end_turn_pressed) end_turn_button.pressed.connect(_on_end_turn_pressed)
button_row.add_child(end_turn_button) button_row.add_child(end_turn_button)
@@ -1255,7 +1289,7 @@ func _create_hud() -> void:
briefing_column.add_child(briefing_location_label) briefing_column.add_child(briefing_location_label)
briefing_column.add_child(_make_scroll_rod(680, 4)) briefing_column.add_child(_make_scroll_rod(680, 4))
briefing_column.add_child(_make_section_caption("軍令 승패 조서", 680)) briefing_column.add_child(_make_section_caption("軍令 勝敗 죽간", 680))
briefing_objective_panel = PanelContainer.new() briefing_objective_panel = PanelContainer.new()
briefing_objective_panel.custom_minimum_size = Vector2(680, 104) briefing_objective_panel.custom_minimum_size = Vector2(680, 104)
@@ -1279,7 +1313,7 @@ func _create_hud() -> void:
briefing_objective_row.add_child(_make_bamboo_gutter(18, 84)) briefing_objective_row.add_child(_make_bamboo_gutter(18, 84))
briefing_column.add_child(_make_ink_wash_rule(680, 7)) briefing_column.add_child(_make_ink_wash_rule(680, 7))
briefing_column.add_child(_make_section_caption("비단 전장도 · 군막 장", 680)) briefing_column.add_child(_make_section_caption("비단 전장도 · 군막 장", 680))
briefing_camp_overview_panel = PanelContainer.new() briefing_camp_overview_panel = PanelContainer.new()
briefing_camp_overview_panel.custom_minimum_size = Vector2(680, 102) briefing_camp_overview_panel.custom_minimum_size = Vector2(680, 102)
@@ -1644,14 +1678,14 @@ func _create_hud() -> void:
dialogue_control_row.add_child(dialogue_progress_label) dialogue_control_row.add_child(dialogue_progress_label)
dialogue_previous_button = Button.new() dialogue_previous_button = Button.new()
dialogue_previous_button.text = "이전 죽간" dialogue_previous_button.text = " 죽간"
dialogue_previous_button.disabled = true dialogue_previous_button.disabled = true
dialogue_previous_button.custom_minimum_size = Vector2(88, 32) dialogue_previous_button.custom_minimum_size = Vector2(88, 32)
dialogue_previous_button.pressed.connect(_retreat_dialogue) dialogue_previous_button.pressed.connect(_retreat_dialogue)
dialogue_control_row.add_child(dialogue_previous_button) dialogue_control_row.add_child(dialogue_previous_button)
dialogue_continue_button = Button.new() dialogue_continue_button = Button.new()
dialogue_continue_button.text = "다음 죽간" dialogue_continue_button.text = " 죽간"
dialogue_continue_button.custom_minimum_size = Vector2(120, 32) dialogue_continue_button.custom_minimum_size = Vector2(120, 32)
dialogue_continue_button.pressed.connect(_advance_dialogue) dialogue_continue_button.pressed.connect(_advance_dialogue)
dialogue_control_row.add_child(dialogue_continue_button) dialogue_control_row.add_child(dialogue_continue_button)
@@ -2192,6 +2226,7 @@ func _draw_map() -> void:
if background != null: if background != null:
_draw_texture_cover(background, board_rect, Color(1.0, 1.0, 1.0, 0.96)) _draw_texture_cover(background, board_rect, Color(1.0, 1.0, 1.0, 0.96))
draw_rect(board_rect, Color(0.02, 0.025, 0.03, 0.12)) draw_rect(board_rect, Color(0.02, 0.025, 0.03, 0.12))
draw_rect(board_rect, Color(0.35, 0.22, 0.10, 0.10))
else: else:
draw_rect(board_rect, Color(0.18, 0.26, 0.18)) draw_rect(board_rect, Color(0.18, 0.26, 0.18))
@@ -2305,9 +2340,9 @@ func _draw_texture_cover(texture: Texture2D, rect: Rect2, modulate: Color) -> vo
func _terrain_fill_color(cell: Vector2i, terrain_key: String, has_background: bool) -> Color: func _terrain_fill_color(cell: Vector2i, terrain_key: String, has_background: bool) -> Color:
var color := state.get_terrain_color(cell) var color := state.get_terrain_color(cell)
var alpha := 0.16 if has_background else 0.34 var alpha := 0.13 if has_background else 0.34
if terrain_key == "W": if terrain_key == "W":
alpha += 0.03 alpha += 0.04
elif terrain_key == "C" or terrain_key == "H": elif terrain_key == "C" or terrain_key == "H":
alpha += 0.02 alpha += 0.02
color.a = alpha color.a = alpha
@@ -2316,7 +2351,7 @@ func _terrain_fill_color(cell: Vector2i, terrain_key: String, has_background: bo
func _map_grid_color(has_background: bool) -> Color: func _map_grid_color(has_background: bool) -> Color:
var color := GRID_COLOR var color := GRID_COLOR
color.a = 0.14 if has_background else 0.38 color.a = 0.09 if has_background else 0.34
return color return color
@@ -2535,18 +2570,24 @@ func _draw_overlays() -> void:
draw_rect(threat_rect.grow(-6.0), THREAT_BORDER_COLOR, false, 1.5) draw_rect(threat_rect.grow(-6.0), THREAT_BORDER_COLOR, false, 1.5)
for cell in move_cells: for cell in move_cells:
draw_rect(_rect_for_cell(cell), Color(0.16, 0.55, 0.95, 0.28)) var move_rect := _rect_for_cell(cell)
draw_rect(move_rect, MOVE_OVERLAY_COLOR)
draw_rect(move_rect.grow(-8.0), MOVE_BORDER_COLOR, false, 1.2)
if _has_pending_move() and state.is_inside(pending_move_to_cell): if _has_pending_move() and state.is_inside(pending_move_to_cell):
var pending_rect := _rect_for_cell(pending_move_to_cell) var pending_rect := _rect_for_cell(pending_move_to_cell)
draw_rect(pending_rect.grow(-4.0), Color(1.0, 0.82, 0.28, 0.32)) draw_rect(pending_rect.grow(-4.0), Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.34))
draw_rect(pending_rect.grow(-7.0), Color(1.0, 0.88, 0.36, 0.86), false, 2.0) draw_rect(pending_rect.grow(-7.0), Color(UI_SEAL_RED.r, UI_SEAL_RED.g, UI_SEAL_RED.b, 0.72), false, 2.0)
for cell in attack_cells: for cell in attack_cells:
draw_rect(_rect_for_cell(cell), Color(0.95, 0.22, 0.18, 0.24)) var attack_rect := _rect_for_cell(cell)
draw_rect(attack_rect, ATTACK_OVERLAY_COLOR)
draw_rect(attack_rect.grow(-7.0), ATTACK_BORDER_COLOR, false, 1.4)
for cell in skill_cells: for cell in skill_cells:
draw_rect(_rect_for_cell(cell), SKILL_OVERLAY_COLOR) var skill_rect := _rect_for_cell(cell)
draw_rect(skill_rect, SKILL_OVERLAY_COLOR)
draw_rect(skill_rect.grow(-8.0), Color(UI_MUTED_JADE.r, UI_MUTED_JADE.g, UI_MUTED_JADE.b, 0.54), false, 1.2)
if not selected_skill_id.is_empty() and skill_cells.has(hover_cell): if not selected_skill_id.is_empty() and skill_cells.has(hover_cell):
var area_selected := state.get_selected_unit() var area_selected := state.get_selected_unit()
@@ -2554,27 +2595,31 @@ func _draw_overlays() -> void:
for cell in state.get_skill_area_cells(area_selected["id"], selected_skill_id, hover_cell): for cell in state.get_skill_area_cells(area_selected["id"], selected_skill_id, hover_cell):
var area_rect := _rect_for_cell(cell) var area_rect := _rect_for_cell(cell)
draw_rect(area_rect, SKILL_AREA_OVERLAY_COLOR) draw_rect(area_rect, SKILL_AREA_OVERLAY_COLOR)
draw_rect(area_rect.grow(-7.0), Color(0.84, 0.62, 1.0, 0.62), false, 1.5) draw_rect(area_rect.grow(-7.0), Color(UI_MUTED_JADE.r, UI_MUTED_JADE.g, UI_MUTED_JADE.b, 0.62), false, 1.5)
for cell in item_cells: for cell in item_cells:
draw_rect(_rect_for_cell(cell), ITEM_OVERLAY_COLOR) var item_rect := _rect_for_cell(cell)
draw_rect(item_rect, ITEM_OVERLAY_COLOR)
draw_rect(item_rect.grow(-8.0), ITEM_BORDER_COLOR, false, 1.2)
if formation_menu != null and formation_menu.visible: if formation_menu != null and formation_menu.visible:
for cell in state.get_formation_cells(): for cell in state.get_formation_cells():
draw_rect(_rect_for_cell(cell), FORMATION_OVERLAY_COLOR) var formation_rect := _rect_for_cell(cell)
draw_rect(formation_rect, FORMATION_OVERLAY_COLOR)
draw_rect(formation_rect.grow(-7.0), Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.58), false, 1.4)
if state.is_inside(hover_cell): if state.is_inside(hover_cell):
var hover_color := Color(1.0, 1.0, 1.0, 0.22) var hover_color := Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.42)
var hover_unit := state.get_unit_at(hover_cell) var hover_unit := state.get_unit_at(hover_cell)
var selected := state.get_selected_unit() var selected := state.get_selected_unit()
if formation_menu != null and formation_menu.visible and state.is_formation_cell(hover_cell): if formation_menu != null and formation_menu.visible and state.is_formation_cell(hover_cell):
hover_color = Color(1.0, 0.86, 0.35, 0.62) hover_color = Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.66)
elif not selected_skill_id.is_empty() and skill_cells.has(hover_cell): elif not selected_skill_id.is_empty() and skill_cells.has(hover_cell):
hover_color = Color(0.72, 0.46, 1.0, 0.54) hover_color = Color(UI_MUTED_JADE.r, UI_MUTED_JADE.g, UI_MUTED_JADE.b, 0.58)
elif not selected_item_id.is_empty() and item_cells.has(hover_cell): elif not selected_item_id.is_empty() and item_cells.has(hover_cell):
hover_color = Color(0.36, 0.95, 0.42, 0.52) hover_color = Color(0.52, 0.70, 0.30, 0.54)
elif not hover_unit.is_empty() and not selected.is_empty() and hover_unit.get("team", "") != selected.get("team", ""): elif not hover_unit.is_empty() and not selected.is_empty() and hover_unit.get("team", "") != selected.get("team", ""):
hover_color = Color(1.0, 0.34, 0.24, 0.45) hover_color = Color(UI_SEAL_RED.r, UI_SEAL_RED.g, UI_SEAL_RED.b, 0.52)
draw_rect(_rect_for_cell(hover_cell), hover_color, false, 3.0) draw_rect(_rect_for_cell(hover_cell), hover_color, false, 3.0)
@@ -3591,12 +3636,12 @@ func _format_objective_hud_text() -> String:
var lines := [] var lines := []
var victory := String(state.objectives.get("victory", "적군을 모두 격파하라.")) var victory := String(state.objectives.get("victory", "적군을 모두 격파하라."))
if not victory.is_empty(): if not victory.is_empty():
lines.append("승리 조서: %s" % victory) lines.append("勝令: %s" % victory)
var defeat := String(state.objectives.get("defeat", "")) var defeat := String(state.objectives.get("defeat", ""))
if not defeat.is_empty(): if not defeat.is_empty():
lines.append("패배 흉조: %s" % defeat) lines.append("敗兆: %s" % defeat)
if lines.is_empty(): if lines.is_empty():
return "승리 조서" return "勝令"
return _join_strings(lines, "\n") return _join_strings(lines, "\n")
@@ -3792,24 +3837,24 @@ func _format_mission_panel_text() -> String:
var lines := [] var lines := []
var victory := str(state.objectives.get("victory", "")) var victory := str(state.objectives.get("victory", ""))
if not victory.is_empty(): if not victory.is_empty():
lines.append("승리 조서") lines.append("勝令")
lines.append(" %s" % victory) lines.append(" %s" % victory)
var defeat := str(state.objectives.get("defeat", "")) var defeat := str(state.objectives.get("defeat", ""))
if not defeat.is_empty(): if not defeat.is_empty():
lines.append("패배 흉조") lines.append("敗兆")
lines.append(" %s" % defeat) lines.append(" %s" % defeat)
var progress_lines := state.get_objective_progress_lines(false, false) var progress_lines := state.get_objective_progress_lines(false, false)
if not progress_lines.is_empty(): if not progress_lines.is_empty():
lines.append("전공 죽간") lines.append("戰功 죽간")
for progress_line in progress_lines: for progress_line in progress_lines:
lines.append(" %s" % progress_line) lines.append(" %s" % progress_line)
var risk_lines := state.get_defeat_progress_lines() var risk_lines := state.get_defeat_progress_lines()
if not risk_lines.is_empty(): if not risk_lines.is_empty():
lines.append("흉조 징후") lines.append("凶兆 징후")
for risk_line in risk_lines: for risk_line in risk_lines:
lines.append(" %s" % risk_line) lines.append(" %s" % risk_line)
if lines.is_empty(): if lines.is_empty():
return "아직 봉인된 조서가 없습니다." return "아직 봉인된 군령이 없습니다."
return _join_strings(lines, "\n") return _join_strings(lines, "\n")
@@ -4254,18 +4299,18 @@ func _on_log_added(message: String) -> void:
func _format_log_entry_text(message: String) -> String: func _format_log_entry_text(message: String) -> String:
if message.begins_with("Objective updated:"): if message.begins_with("Objective updated:"):
return "조서 개정:%s" % message.substr("Objective updated:".length()) return "軍令 개정:%s" % message.substr("Objective updated:".length())
if message.begins_with("Defeat condition updated:"): if message.begins_with("Defeat condition updated:"):
return "흉조 개정:%s" % message.substr("Defeat condition updated:".length()) return "敗兆 개정:%s" % message.substr("Defeat condition updated:".length())
return message return message
func _on_objective_updated(victory: String, defeat: String) -> void: func _on_objective_updated(victory: String, defeat: String) -> void:
var notice_lines := ["조서 봉인"] var notice_lines := ["軍令 봉인"]
if not victory.is_empty(): if not victory.is_empty():
notice_lines.append("승리 조서: %s" % victory) notice_lines.append("勝令: %s" % victory)
if not defeat.is_empty(): if not defeat.is_empty():
notice_lines.append("패배 흉조: %s" % defeat) notice_lines.append("敗兆: %s" % defeat)
if objective_notice_label != null: if objective_notice_label != null:
objective_notice_label.text = "\n".join(notice_lines) objective_notice_label.text = "\n".join(notice_lines)
if objective_notice_panel != null: if objective_notice_panel != null:
@@ -4539,7 +4584,7 @@ func _normalized_dialogue_lines(lines: Array) -> Array:
if text.is_empty(): if text.is_empty():
continue continue
var speaker := str(line.get("speaker", "")) var speaker := str(line.get("speaker", ""))
var display_speaker := str(line.get("display_speaker", speaker)) var display_speaker := _localized_speaker_name(str(line.get("display_speaker", speaker)))
result.append({ result.append({
"speaker": display_speaker, "speaker": display_speaker,
"text": text, "text": text,
@@ -4597,7 +4642,7 @@ func _render_dialogue_line() -> void:
return return
var line: Dictionary = active_dialogue_lines[active_dialogue_index] var line: Dictionary = active_dialogue_lines[active_dialogue_index]
var speaker := str(line.get("speaker", "")) var speaker := str(line.get("speaker", ""))
var display_speaker := str(line.get("display_speaker", speaker)) var display_speaker := _localized_speaker_name(str(line.get("display_speaker", speaker)))
var side := _normalized_dialogue_side(line.get("side", "left")) var side := _normalized_dialogue_side(line.get("side", "left"))
_apply_dialogue_side(side) _apply_dialogue_side(side)
dialogue_speaker_label.text = display_speaker dialogue_speaker_label.text = display_speaker
@@ -4612,10 +4657,10 @@ func _render_dialogue_line() -> void:
func _update_dialogue_controls() -> void: func _update_dialogue_controls() -> void:
if dialogue_progress_label != null: if dialogue_progress_label != null:
dialogue_progress_label.text = "죽간 %d / %d" % [active_dialogue_index + 1, active_dialogue_lines.size()] dialogue_progress_label.text = "죽간 %d %d" % [active_dialogue_lines.size(), active_dialogue_index + 1]
if dialogue_previous_button != null: if dialogue_previous_button != null:
dialogue_previous_button.disabled = active_dialogue_index <= 0 dialogue_previous_button.disabled = active_dialogue_index <= 0
dialogue_continue_button.text = "두루마리 봉" if active_dialogue_index >= active_dialogue_lines.size() - 1 else "다음 죽간" dialogue_continue_button.text = "두루마리 봉" if active_dialogue_index >= active_dialogue_lines.size() - 1 else " 죽간"
func _apply_dialogue_side(side: String) -> void: func _apply_dialogue_side(side: String) -> void:
@@ -4640,6 +4685,20 @@ func _update_dialogue_portrait(speaker: String, portrait_path: String) -> void:
dialogue_portrait_label.visible = not has_portrait and has_placeholder dialogue_portrait_label.visible = not has_portrait and has_placeholder
func _localized_speaker_name(speaker: String) -> String:
var normalized_speaker := speaker.strip_edges()
if normalized_speaker.is_empty():
return ""
return str(SPEAKER_DISPLAY_NAMES.get(normalized_speaker, normalized_speaker))
func _canonical_speaker_name(speaker: String) -> String:
var normalized_speaker := speaker.strip_edges()
if normalized_speaker.is_empty():
return ""
return str(SPEAKER_CANONICAL_NAMES.get(normalized_speaker, normalized_speaker))
func _load_art_texture(texture_path: String) -> Texture2D: func _load_art_texture(texture_path: String) -> Texture2D:
return _load_portrait_texture(texture_path) return _load_portrait_texture(texture_path)
@@ -4990,27 +5049,27 @@ func _format_briefing_objectives() -> String:
var text := "" var text := ""
var victory := str(state.objectives.get("victory", "")) var victory := str(state.objectives.get("victory", ""))
if not victory.is_empty(): if not victory.is_empty():
text = "승리 조서:\n %s" % victory text = "勝令:\n %s" % victory
var defeat := str(state.objectives.get("defeat", "")) var defeat := str(state.objectives.get("defeat", ""))
if not defeat.is_empty(): if not defeat.is_empty():
if not text.is_empty(): if not text.is_empty():
text += "\n" text += "\n"
text += "패배 흉조:\n %s" % defeat text += "敗兆:\n %s" % defeat
var progress_lines := state.get_objective_progress_lines(false, false) var progress_lines := state.get_objective_progress_lines(false, false)
if not progress_lines.is_empty(): if not progress_lines.is_empty():
if not text.is_empty(): if not text.is_empty():
text += "\n" text += "\n"
text += "전공 죽간:\n %s" % _join_strings(progress_lines, ", ") text += "戰功 죽간:\n %s" % _join_strings(progress_lines, ", ")
var risk_lines := state.get_defeat_progress_lines() var risk_lines := state.get_defeat_progress_lines()
if not risk_lines.is_empty(): if not risk_lines.is_empty():
if not text.is_empty(): if not text.is_empty():
text += "\n" text += "\n"
text += "흉조 징후:\n %s" % _join_strings(risk_lines, ", ") text += "凶兆 징후:\n %s" % _join_strings(risk_lines, ", ")
var rewards_text := _format_briefing_rewards() var rewards_text := _format_briefing_rewards()
if not rewards_text.is_empty(): if not rewards_text.is_empty():
if not text.is_empty(): if not text.is_empty():
text += "\n" text += "\n"
text += "전리품 봉인:\n %s" % rewards_text text += "戰利 봉인:\n %s" % rewards_text
return text return text
@@ -5512,7 +5571,7 @@ func _camp_conversation_entries() -> Array:
func _format_camp_conversation_button_text(conversation: Dictionary) -> String: func _format_camp_conversation_button_text(conversation: Dictionary) -> String:
var label := str(conversation.get("label", "군막 담화")) var label := str(conversation.get("label", "군막 담화"))
var speaker := str(conversation.get("speaker", "")) var speaker := _localized_speaker_name(str(conversation.get("speaker", "")))
var summary := str(conversation.get("summary", "")) var summary := str(conversation.get("summary", ""))
var group := _camp_conversation_group_text(str(conversation.get("group", "topic"))) var group := _camp_conversation_group_text(str(conversation.get("group", "topic")))
var parts := [group, label] var parts := [group, label]
@@ -5563,13 +5622,13 @@ func _camp_conversation_group_text(group: String) -> String:
func _camp_conversation_preview_speaker(conversation: Dictionary) -> String: func _camp_conversation_preview_speaker(conversation: Dictionary) -> String:
var speaker := str(conversation.get("speaker", "")).strip_edges() var speaker := str(conversation.get("speaker", "")).strip_edges()
if not speaker.is_empty(): if not speaker.is_empty():
return speaker return _localized_speaker_name(speaker)
for line in conversation.get("lines", []): for line in conversation.get("lines", []):
if typeof(line) != TYPE_DICTIONARY: if typeof(line) != TYPE_DICTIONARY:
continue continue
speaker = str(line.get("speaker", "")).strip_edges() speaker = str(line.get("speaker", "")).strip_edges()
if not speaker.is_empty(): if not speaker.is_empty():
return speaker return _localized_speaker_name(speaker)
return "" return ""
@@ -5600,7 +5659,7 @@ func _camp_conversation_preview_portrait_path(conversation: Dictionary) -> Strin
return portrait return portrait
var speaker := _camp_conversation_preview_speaker(conversation) var speaker := _camp_conversation_preview_speaker(conversation)
if not speaker.is_empty(): if not speaker.is_empty():
return state.data_catalog.get_portrait_for_speaker(speaker) return state.data_catalog.get_portrait_for_speaker(_canonical_speaker_name(speaker))
return "" return ""
@@ -7372,7 +7431,7 @@ func _rebuild_post_move_item_picker(selected: Dictionary) -> void:
_apply_item_button_icon(item_button, item) _apply_item_button_icon(item_button, item)
var disabled_reason := _post_move_item_disabled_reason(selected, item_id, count) var disabled_reason := _post_move_item_disabled_reason(selected, item_id, count)
item_button.disabled = not disabled_reason.is_empty() item_button.disabled = not disabled_reason.is_empty()
item_button.tooltip_text = disabled_reason if not disabled_reason.is_empty() else "Select this supply." item_button.tooltip_text = disabled_reason if not disabled_reason.is_empty() else "이 보급을 군령에 올립니다."
_apply_button_style(item_button) _apply_button_style(item_button)
item_button.pressed.connect(_on_post_move_item_option_pressed.bind(item_id)) item_button.pressed.connect(_on_post_move_item_option_pressed.bind(item_id))
post_move_picker_list.add_child(item_button) post_move_picker_list.add_child(item_button)
@@ -7697,9 +7756,9 @@ func _update_end_turn_button() -> void:
return return
var phase_block := _action_phase_block_reason() var phase_block := _action_phase_block_reason()
if not phase_block.is_empty(): if not phase_block.is_empty():
_set_action_button_blocked(end_turn_button, "차례 종료", str(phase_block.get("label", "처리 중")), str(phase_block.get("tooltip", ""))) _set_action_button_blocked(end_turn_button, "군령 종료", str(phase_block.get("label", "처리 중")), str(phase_block.get("tooltip", "")))
return return
_set_action_button_state(end_turn_button, "차례 종료", false, "아군 차례를 마치고 적군이 움직입니다.") _set_action_button_state(end_turn_button, "군령 종료", false, "아군 군령을 거두고 적군의 차례로 넘깁니다.")
func _update_threat_button() -> void: func _update_threat_button() -> void:

View File

@@ -328,7 +328,7 @@ func _check_puyang_raid_camp_supplies(failures: Array[String]) -> void:
scene.free() scene.free()
return return
var merchant: Dictionary = scene.state.get_shop_merchant() var merchant: Dictionary = scene.state.get_shop_merchant()
if str(merchant.get("name", "")) != "Puyang Night Trader": if str(merchant.get("name", "")) != "복양 야상":
failures.append("Puyang shop merchant name should be present") failures.append("Puyang shop merchant name should be present")
var merchant_lines: Array = merchant.get("lines", []) var merchant_lines: Array = merchant.get("lines", [])
if merchant_lines.size() < 2: if merchant_lines.size() < 2:

View File

@@ -211,16 +211,16 @@ func _check_objective_notice(failures: Array[String]) -> void:
scene._on_objective_updated("Defeat the new vanguard.", "") scene._on_objective_updated("Defeat the new vanguard.", "")
if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible: if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible:
failures.append("objective notice panel should be visible after objective update") failures.append("objective notice panel should be visible after objective update")
elif not scene.objective_notice_label.text.contains("조서 봉인") or not scene.objective_notice_label.text.contains("승리 조서: Defeat the new vanguard."): elif not scene.objective_notice_label.text.contains("軍令 봉인") or not scene.objective_notice_label.text.contains("勝令: Defeat the new vanguard."):
failures.append("objective notice label should use sealed edict wording: %s" % scene.objective_notice_label.text) failures.append("objective notice label should use sealed edict wording: %s" % scene.objective_notice_label.text)
scene._on_objective_updated("", "Supply train is lost.") scene._on_objective_updated("", "Supply train is lost.")
if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible: if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible:
failures.append("objective notice panel should be visible after defeat update") failures.append("objective notice panel should be visible after defeat update")
elif not scene.objective_notice_label.text.contains("조서 봉인") or not scene.objective_notice_label.text.contains("패배 흉조: Supply train is lost."): elif not scene.objective_notice_label.text.contains("軍令 봉인") or not scene.objective_notice_label.text.contains("敗兆: Supply train is lost."):
failures.append("objective notice label should use omen wording: %s" % scene.objective_notice_label.text) failures.append("objective notice label should use omen wording: %s" % scene.objective_notice_label.text)
if scene._format_log_entry_text("Objective updated: Defeat the new vanguard.") != "조서 개정: Defeat the new vanguard.": if scene._format_log_entry_text("Objective updated: Defeat the new vanguard.") != "軍令 개정: Defeat the new vanguard.":
failures.append("objective log display should use edict wording") failures.append("objective log display should use edict wording")
if scene._format_log_entry_text("Defeat condition updated: Supply train is lost.") != "흉조 개정: Supply train is lost.": if scene._format_log_entry_text("Defeat condition updated: Supply train is lost.") != "敗兆 개정: Supply train is lost.":
failures.append("defeat log display should use omen wording") failures.append("defeat log display should use omen wording")
if scene.objective_notice_timer <= 0.0: if scene.objective_notice_timer <= 0.0:
failures.append("objective notice timer should be active") failures.append("objective notice timer should be active")

View File

@@ -218,7 +218,7 @@ func _check_battle_visual_data(failures: Array[String]) -> void:
_check_camp_conversation(failures, puyang_conversations[2], "puyang_raider_stores", "topic", "") _check_camp_conversation(failures, puyang_conversations[2], "puyang_raider_stores", "topic", "")
_check_camp_conversation_effect(failures, puyang_conversations[2], "bean", 1) _check_camp_conversation_effect(failures, puyang_conversations[2], "bean", 1)
var puyang_merchant := puyang_state.get_shop_merchant() var puyang_merchant := puyang_state.get_shop_merchant()
if str(puyang_merchant.get("name", "")) != "Puyang Night Trader": if str(puyang_merchant.get("name", "")) != "복양 야상":
failures.append("005 shop should expose Puyang merchant name") failures.append("005 shop should expose Puyang merchant name")
if (puyang_merchant.get("lines", []) as Array).size() < 2: if (puyang_merchant.get("lines", []) as Array).size() < 2:
failures.append("005 shop should expose merchant flavor lines") failures.append("005 shop should expose merchant flavor lines")
@@ -2672,14 +2672,14 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
scene.free() scene.free()
return return
var briefing_text := scene._format_briefing_objectives() var briefing_text := scene._format_briefing_objectives()
for expected in ["승리 조서:", "패배 흉조:", "전공 죽간:", "흉조 징후:", "전리품 봉인:"]: for expected in ["勝令:", "敗兆:", "戰功 죽간:", "凶兆 징후:", "戰利 봉인:"]:
if not briefing_text.contains(expected): if not briefing_text.contains(expected):
failures.append("briefing objective parchment should use ancient heading `%s`: %s" % [expected, briefing_text]) failures.append("briefing objective parchment should use ancient heading `%s`: %s" % [expected, briefing_text])
var hud_objective := scene._format_objective_hud_text() var hud_objective := scene._format_objective_hud_text()
if not hud_objective.contains("승리 조서:") or not hud_objective.contains("패배 흉조:"): if not hud_objective.contains("勝令:") or not hud_objective.contains("敗兆:"):
failures.append("HUD objective should read as paired edict and omen text: %s" % hud_objective) failures.append("HUD objective should read as paired edict and omen text: %s" % hud_objective)
var mission_text := scene._format_mission_panel_text() var mission_text := scene._format_mission_panel_text()
for expected in ["승리 조서", "패배 흉조", "전공 죽간", "흉조 징후"]: for expected in ["勝令", "敗兆", "戰功 죽간", "凶兆 징후"]:
if not mission_text.contains(expected): if not mission_text.contains(expected):
failures.append("mission panel should use old campaign wording `%s`: %s" % [expected, mission_text]) failures.append("mission panel should use old campaign wording `%s`: %s" % [expected, mission_text])
_check_panel_style_fill(failures, scene.briefing_panel, "briefing panel", Color(0.68, 0.50, 0.25, 0.997)) _check_panel_style_fill(failures, scene.briefing_panel, "briefing panel", Color(0.68, 0.50, 0.25, 0.997))
@@ -2702,9 +2702,9 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
_check_hanging_tassel(failures, scene.dialogue_left_tassel, "dialogue left tassel") _check_hanging_tassel(failures, scene.dialogue_left_tassel, "dialogue left tassel")
_check_hanging_tassel(failures, scene.dialogue_right_tassel, "dialogue right tassel") _check_hanging_tassel(failures, scene.dialogue_right_tassel, "dialogue right tassel")
_check_dialogue_column_budget(failures, scene) _check_dialogue_column_budget(failures, scene)
if scene.dialogue_continue_button == null or scene.dialogue_continue_button.text != "다음 죽간": if scene.dialogue_continue_button == null or scene.dialogue_continue_button.text != " 죽간":
failures.append("dialogue continue button should use ancient slip text") failures.append("dialogue continue button should use ancient slip text")
if scene.dialogue_previous_button == null or scene.dialogue_previous_button.text != "이전 죽간": if scene.dialogue_previous_button == null or scene.dialogue_previous_button.text != " 죽간":
failures.append("dialogue previous button should use ancient slip text") failures.append("dialogue previous button should use ancient slip text")
var localized_lines := scene._normalized_dialogue_lines([{"speaker": "Cao Cao", "display_speaker": "조조", "text": "군령을 전하라."}]) var localized_lines := scene._normalized_dialogue_lines([{"speaker": "Cao Cao", "display_speaker": "조조", "text": "군령을 전하라."}])
if localized_lines.is_empty() or str((localized_lines[0] as Dictionary).get("speaker", "")) != "조조": if localized_lines.is_empty() or str((localized_lines[0] as Dictionary).get("speaker", "")) != "조조":
@@ -2739,7 +2739,7 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
failures.append("dialogue text should use the project serif font override") failures.append("dialogue text should use the project serif font override")
if scene.dialogue_continue_button == null or not scene.dialogue_continue_button.has_theme_font_override("font"): if scene.dialogue_continue_button == null or not scene.dialogue_continue_button.has_theme_font_override("font"):
failures.append("dialogue continue button should use the project serif font override") failures.append("dialogue continue button should use the project serif font override")
if scene.mission_title_label == null or scene.mission_title_label.text != "군령 목패": if scene.mission_title_label == null or scene.mission_title_label.text != "軍令 목패":
failures.append("mission panel title should use bronze tablet wording") failures.append("mission panel title should use bronze tablet wording")
if scene.shop_button == null or scene.shop_button.text != "군상": if scene.shop_button == null or scene.shop_button.text != "군상":
failures.append("briefing shop button should use sutler wording") failures.append("briefing shop button should use sutler wording")
@@ -2968,7 +2968,7 @@ func _check_action_button_disabled_reasons(failures: Array[String]) -> void:
scene.state.current_team = BattleState.TEAM_ENEMY scene.state.current_team = BattleState.TEAM_ENEMY
scene._update_end_turn_button() scene._update_end_turn_button()
if scene.end_turn_button.text != "차례 종료 - 적군 차례" or not scene.end_turn_button.tooltip_text.contains("아군 군령"): if scene.end_turn_button.text != "군령 종료 - 적군 차례" or not scene.end_turn_button.tooltip_text.contains("아군 군령"):
failures.append("enemy phase should explain disabled end turn button: %s | %s" % [scene.end_turn_button.text, scene.end_turn_button.tooltip_text]) failures.append("enemy phase should explain disabled end turn button: %s | %s" % [scene.end_turn_button.text, scene.end_turn_button.tooltip_text])
scene.state.current_team = BattleState.TEAM_PLAYER scene.state.current_team = BattleState.TEAM_PLAYER