diff --git a/data/campaign/campaign.json b/data/campaign/campaign.json index cf7e35c..21970d2 100644 --- a/data/campaign/campaign.json +++ b/data/campaign/campaign.json @@ -76,7 +76,7 @@ }, { "id": "005_puyang_raid", - "title": "Puyang Raid", + "title": "복양 기습전", "path": "res://data/scenarios/005_puyang_raid.json" }, { diff --git a/data/scenarios/005_puyang_raid.json b/data/scenarios/005_puyang_raid.json index 398abb1..1ec9c9c 100644 --- a/data/scenarios/005_puyang_raid.json +++ b/data/scenarios/005_puyang_raid.json @@ -1,9 +1,9 @@ { "id": "005_puyang_raid", - "name": "Puyang Raid", + "name": "복양 기습전", "objectives": { - "victory": "Defeat Lu Bu's forward guard.", - "defeat": "Cao Cao is defeated or the battle reaches Turn 11." + "victory": "여포군 전위영을 격파하라.", + "defeat": "조조가 퇴각하거나 제11턴에 이르면 패한다." }, "conditions": { "victory": { "type": "all_units_defeated", "team": "enemy", "after_event": "turn_3_lu_bu_arrives" }, @@ -13,77 +13,79 @@ ] }, "briefing": { - "title": "Puyang Raid", - "location": "Puyang, 194 CE", + "title": "복양 기습전", + "location": "복양, 흥평 원년", "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": [ { "id": "puyang_night_raid_council", "group": "officer", "officer_id": "cao_cao", - "label": "Cao Cao - Night Raid", + "label": "조조 - 야습 군의", "speaker": "Cao Cao", - "summary": "Review the timing of the strike against Lu Bu's forward guard.", + "display_speaker": "조조", + "summary": "여포군 전위영을 치는 시각과 퇴로를 정한다.", "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": "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": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "벌판이 달리기가 되면 여포가 이긴다. 오늘 밤에는 햇빛도, 거리도, 첫 돌격도 저들에게 주지 않겠다." }, + { "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "그렇다면 말에 굴레가 얹히기 전에 영채를 치겠습니다. 적이 반쯤 잠든 전장은 우리 쪽 예법에 가깝습니다." } ] }, { "id": "puyang_dian_wei_shield", "group": "officer", "officer_id": "dian_wei", - "label": "Dian Wei - Iron Shield", + "label": "전위 - 철문 호위", "speaker": "Dian Wei", - "summary": "Hear Dian Wei's first pledge as Cao Cao's personal shield.", + "display_speaker": "전위", + "summary": "주공의 깃발 곁에 선 전위의 첫 맹세를 듣는다.", "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": "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": "Dian Wei", "display_speaker": "전위", "side": "right", "text": "제게 대열 한가운데 자리를 주셨습니다. 그 자리는 어떤 기병도 쉽게 넘지 못하게 하겠습니다." }, + { "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "경고의 북이 울리면 내 깃발 가까이에 서라. 여포의 병사는 전장을 읽기 전에 먼저 군기를 노릴 것이다." } ] }, { "id": "puyang_raider_stores", "group": "topic", - "label": "Raider Stores", - "summary": "Pack a small supply bundle for the night raid.", + "label": "야습 보급 묶음", + "summary": "밤길에 들고 갈 작은 군수 꾸러미를 챙긴다.", "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": "Xiahou Yuan", "side": "left", "text": "Good. When Lu Bu's riders wake, no one will have time to search a cart." } + { "speaker": "Puyang Night Trader", "display_speaker": "복양 야상", "side": "right", "text": "야습에는 큰 수레가 따라붙지 못합니다. 콩을 검은 천에 묶어 갑옷 밑에도 숨길 수 있게 했습니다." }, + { "speaker": "Xiahou Yuan", "display_speaker": "하후연", "side": "left", "text": "좋다. 여포의 기병이 깨어나면 누구도 수레 속을 뒤질 틈이 없을 것이다." } ], "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", "campaign_flags": { "pursued_dong_zhuo": true }, "group": "topic", - "label": "Hardened Vanguard", - "summary": "Issue wine to veterans used to forced marches and sudden strikes.", + "label": "형양 노병", + "summary": "강행군과 매복을 넘긴 선봉에게 술을 나눈다.", "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": "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": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "동탁을 쫓아 형양까지 달린 병사들은 아직 북소리가 끝나기 전에 움직입니다. 복양의 밤길도 겁내지 않을 것입니다." }, + { "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "출진 전에 노병들에게 술을 조금 나누어라. 험한 길을 견딘 자에게는 그 수고를 안다는 말이 필요하다." } ], "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", "campaign_flags": { "regrouped_after_sishui": true }, "group": "topic", - "label": "Qingzhou Reserve Wagon", - "summary": "Open a physician's bundle preserved during the steadier march.", + "label": "청주 예비 약거", + "summary": "정비 행군 중 보존한 의원의 꾸러미를 연다.", "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": "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": "Puyang Night Trader", "display_speaker": "복양 야상", "side": "right", "text": "청주에서 따라온 예비 수레에 아직 의원의 봉한 꾸러미가 남아 있습니다. 서두르지 않은 손이 싼 물건은 오래 버팁니다." }, + { "speaker": "Dian Wei", "display_speaker": "전위", "side": "left", "text": "깃발 가까이에 두게. 여포의 기병이 뚫고 들어오면 다친 병사에게 필요한 것은 담력만이 아닐 것이다." } ], "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" ], "merchant": { - "name": "Puyang Night Trader", + "name": "복양 야상", "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", - "name": "Zhang Liao Vanguard", + "name": "장료 선봉", "class_id": "cavalry", "team": "enemy", "level": 5, @@ -177,7 +179,7 @@ }, { "unit_id": "gao_shun_guard", - "name": "Gao Shun Guard", + "name": "고순 군영", "class_id": "infantry", "team": "enemy", "level": 5, @@ -187,7 +189,7 @@ }, { "unit_id": "puyang_cavalry_1", - "name": "Puyang Rider", + "name": "복양 기병", "class_id": "cavalry", "team": "enemy", "level": 5, @@ -197,7 +199,7 @@ }, { "unit_id": "puyang_cavalry_2", - "name": "Puyang Rider", + "name": "복양 기병", "class_id": "cavalry", "team": "enemy", "level": 5, @@ -207,7 +209,7 @@ }, { "unit_id": "puyang_archer", - "name": "Puyang Archer", + "name": "복양 궁병", "class_id": "archer", "team": "enemy", "level": 5, @@ -222,7 +224,7 @@ "once": true, "when": { "type": "battle_start" }, "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", "lines": [ - { "speaker": "Cao Cao", "text": "Lu Bu's cavalry is dangerous once it gathers speed. We strike before the whole camp wakes." }, - { "speaker": "Dian Wei", "text": "Stay behind me, my lord. Any rider who reaches you answers to my axe." }, - { "speaker": "Xiahou Dun", "text": "Good. We cut open their guard and withdraw before dawn." } + { "speaker": "Cao Cao", "display_speaker": "조조", "text": "여포의 기병은 속도가 붙으면 사납다. 영채가 모두 깨기 전에 쳐라." }, + { "speaker": "Dian Wei", "display_speaker": "전위", "text": "주공께서는 제 뒤에 계십시오. 주공께 닿는 말발굽은 제 도끼 앞에 설 것입니다." }, + { "speaker": "Xiahou Dun", "display_speaker": "하후돈", "text": "좋습니다. 새벽이 오기 전에 전위영을 열고 빠져나가겠습니다." } ] } ] @@ -245,12 +247,12 @@ "once": true, "when": { "type": "turn_start", "team": "enemy", "turn": 2 }, "actions": [ - { "type": "log", "text": "Puyang's forward camp sounds the alarm." }, + { "type": "log", "text": "복양 전위영에서 경고의 북이 울린다." }, { "type": "dialogue", "lines": [ - { "speaker": "Zhang Liao", "text": "Cao Cao comes in the night. Riders, close the road and pin him here." }, - { "speaker": "Cao Cao", "text": "The alarm is raised. Break their line before Lu Bu himself arrives." } + { "speaker": "Zhang Liao", "display_speaker": "장료", "text": "조조가 밤을 틈타 왔다. 기병은 길을 닫고 저들을 이곳에 묶어라." }, + { "speaker": "Cao Cao", "display_speaker": "조조", "text": "북이 울렸구나. 여포가 직접 오기 전에 저 전열을 깨라." } ] } ] @@ -260,25 +262,25 @@ "once": true, "when": { "type": "turn_start", "team": "enemy", "turn": 3 }, "actions": [ - { "type": "log", "text": "Lu Bu's banner appears beyond the eastern road." }, + { "type": "log", "text": "동쪽 길 너머로 여포의 군기가 모습을 드러낸다." }, { "type": "dialogue", "lines": [ - { "speaker": "Lu Bu", "text": "Cao Cao dares raid Puyang? Then let him see how fast my riders wake." }, - { "speaker": "Dian Wei", "text": "That one has a loud voice. I will hold the road while you finish the guard." } + { "speaker": "Lu Bu", "display_speaker": "여포", "text": "조조가 감히 복양을 밤에 치느냐? 내 기병이 얼마나 빨리 깨어나는지 보여 주마." }, + { "speaker": "Dian Wei", "display_speaker": "전위", "text": "목소리 하나는 크군요. 주공께서 전위영을 깨는 동안 길목은 제가 막겠습니다." } ] }, { "type": "set_objective", - "victory": "Defeat Zhang Liao's guard and the arriving riders.", - "defeat": "Cao Cao is defeated or the battle reaches Turn 11." + "victory": "장료의 전위와 달려든 기병을 격파하라.", + "defeat": "조조가 퇴각하거나 제11턴에 이르면 패한다." }, { "type": "spawn_deployments", "deployments": [ { "unit_id": "lu_bu_banner", - "name": "Lu Bu Banner Guard", + "name": "여포 기치병", "class_id": "elite_cavalry", "team": "enemy", "level": 6, @@ -288,7 +290,7 @@ }, { "unit_id": "lu_bu_rider_north", - "name": "Lu Bu Rider", + "name": "여포 기병", "class_id": "cavalry", "team": "enemy", "level": 5, @@ -298,7 +300,7 @@ }, { "unit_id": "lu_bu_rider_south", - "name": "Lu Bu Rider", + "name": "여포 기병", "class_id": "cavalry", "team": "enemy", "level": 5, @@ -318,8 +320,8 @@ { "type": "dialogue", "lines": [ - { "speaker": "Dian Wei", "text": "Their riders hit hard, but their line bends. Give the word and I will split it." }, - { "speaker": "Cao Cao", "text": "Press them. We leave Puyang with their vanguard broken." } + { "speaker": "Dian Wei", "display_speaker": "전위", "text": "저들의 기병은 세게 부딪치나 전열은 이미 휘었습니다. 명만 내리시면 제가 갈라 놓겠습니다." }, + { "speaker": "Cao Cao", "display_speaker": "조조", "text": "몰아붙여라. 복양을 온전히 얻지 못하더라도, 오늘 밤 저들의 선봉은 꺾어 두고 간다." } ] } ] @@ -330,21 +332,21 @@ "items": ["bean", "wine", "iron_sword"] }, "post_battle_dialogue": [ - { "speaker": "Xiahou Dun", "text": "Lu Bu's men still hold Puyang, but their forward guard will remember tonight." }, - { "speaker": "Dian Wei", "text": "They came fast. Next time, let them come closer." }, - { "speaker": "Cao Cao", "text": "We have measured their strength. Now we choose whether to fortify Yan Province or strike again." } + { "speaker": "Xiahou Dun", "display_speaker": "하후돈", "text": "여포의 군세는 아직 복양을 붙들고 있으나, 전위영은 오늘 밤을 잊지 못할 것입니다." }, + { "speaker": "Dian Wei", "display_speaker": "전위", "text": "빠르게 달려오더군요. 다음에는 조금 더 가까이 오게 두십시오." }, + { "speaker": "Cao Cao", "display_speaker": "조조", "text": "저들의 힘을 가늠했다. 이제 연주를 굳힐지, 흔들린 복양을 다시 누를지 정해야 한다." } ], "post_battle_choices": [ { "id": "fortify_yan_province", - "label": "Fortify Yan Province", - "description": "Secure supply roads before another clash with Lu Bu.", + "label": "연주 보강", + "description": "여포와 다시 겨루기 전에 군량길과 성책을 단단히 묶는다.", "set_flags": { "fortified_yan_province": true, "pressed_lu_bu": false } }, { "id": "press_lu_bu", - "label": "Press Lu Bu", - "description": "Keep pressure on Puyang while Lu Bu's guard is shaken.", + "label": "여포 압박", + "description": "전위영이 흔들린 틈을 놓치지 않고 복양을 계속 누른다.", "set_flags": { "fortified_yan_province": false, "pressed_lu_bu": true } } ] diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 29bd9f8..220dcc7 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -7,16 +7,21 @@ const CAMPAIGN_PATH := "res://data/campaign/campaign.json" const TILE_SIZE := 64 const BOARD_OFFSET := Vector2(48, 104) const DEFAULT_BATTLE_BACKGROUND_PATH := "res://art/backgrounds/battlefield_frontier.png" -const PLAYER_COLOR := Color(0.20, 0.42, 0.82) -const ENEMY_COLOR := Color(0.72, 0.18, 0.16) -const GRID_COLOR := Color(0.05, 0.06, 0.07, 0.45) -const SKILL_OVERLAY_COLOR := Color(0.56, 0.28, 0.95, 0.28) -const SKILL_AREA_OVERLAY_COLOR := Color(0.72, 0.46, 1.0, 0.30) -const ITEM_OVERLAY_COLOR := Color(0.24, 0.74, 0.34, 0.30) -const FORMATION_OVERLAY_COLOR := Color(0.95, 0.76, 0.25, 0.34) -const OBJECTIVE_OVERLAY_COLOR := Color(1.0, 0.78, 0.18, 0.30) -const THREAT_OVERLAY_COLOR := Color(1.0, 0.12, 0.10, 0.18) -const THREAT_BORDER_COLOR := Color(1.0, 0.16, 0.12, 0.44) +const PLAYER_COLOR := Color(0.17, 0.38, 0.29) +const ENEMY_COLOR := Color(0.64, 0.085, 0.045) +const GRID_COLOR := Color(0.12, 0.070, 0.032, 0.36) +const MOVE_OVERLAY_COLOR := Color(0.72, 0.54, 0.24, 0.26) +const MOVE_BORDER_COLOR := Color(0.94, 0.73, 0.36, 0.48) +const ATTACK_OVERLAY_COLOR := Color(0.72, 0.055, 0.032, 0.24) +const ATTACK_BORDER_COLOR := Color(0.92, 0.34, 0.18, 0.50) +const SKILL_OVERLAY_COLOR := Color(0.10, 0.25, 0.20, 0.28) +const SKILL_AREA_OVERLAY_COLOR := Color(0.26, 0.34, 0.23, 0.30) +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_BATTLE_PATH := "res://audio/bgm/battle_loop_placeholder.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_EDGE := Color(0.43, 0.25, 0.095, 0.996) 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 campaign_state: CampaignState = CampaignStateScript.new() @@ -949,7 +983,7 @@ func _create_hud() -> void: selected_row.add_child(selected_label) mission_title_label = Label.new() - mission_title_label.text = "군령 목패" + mission_title_label.text = "軍令 목패" mission_title_label.add_theme_font_size_override("font_size", 14) _apply_label_style(mission_title_label, UI_OLD_BRONZE) side_column.add_child(mission_title_label) @@ -1022,7 +1056,7 @@ func _create_hud() -> void: button_row.add_child(threat_button) end_turn_button = Button.new() - end_turn_button.text = "차례 종료" + end_turn_button.text = "군령 종료" end_turn_button.pressed.connect(_on_end_turn_pressed) 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(_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.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_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.custom_minimum_size = Vector2(680, 102) @@ -1644,14 +1678,14 @@ func _create_hud() -> void: dialogue_control_row.add_child(dialogue_progress_label) dialogue_previous_button = Button.new() - dialogue_previous_button.text = "이전 죽간" + dialogue_previous_button.text = "앞 죽간" dialogue_previous_button.disabled = true dialogue_previous_button.custom_minimum_size = Vector2(88, 32) dialogue_previous_button.pressed.connect(_retreat_dialogue) dialogue_control_row.add_child(dialogue_previous_button) 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.pressed.connect(_advance_dialogue) dialogue_control_row.add_child(dialogue_continue_button) @@ -2192,6 +2226,7 @@ func _draw_map() -> void: if background != null: _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.35, 0.22, 0.10, 0.10)) else: 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: 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": - alpha += 0.03 + alpha += 0.04 elif terrain_key == "C" or terrain_key == "H": alpha += 0.02 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: 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 @@ -2535,18 +2570,24 @@ func _draw_overlays() -> void: draw_rect(threat_rect.grow(-6.0), THREAT_BORDER_COLOR, false, 1.5) 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): 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(-7.0), Color(1.0, 0.88, 0.36, 0.86), false, 2.0) + 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(UI_SEAL_RED.r, UI_SEAL_RED.g, UI_SEAL_RED.b, 0.72), false, 2.0) 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: - 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): 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): var area_rect := _rect_for_cell(cell) 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: - 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: 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): - 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 selected := state.get_selected_unit() 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): - 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): - 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", ""): - 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) @@ -3591,12 +3636,12 @@ func _format_objective_hud_text() -> String: var lines := [] var victory := String(state.objectives.get("victory", "적군을 모두 격파하라.")) if not victory.is_empty(): - lines.append("승리 조서: %s" % victory) + lines.append("勝令: %s" % victory) var defeat := String(state.objectives.get("defeat", "")) if not defeat.is_empty(): - lines.append("패배 흉조: %s" % defeat) + lines.append("敗兆: %s" % defeat) if lines.is_empty(): - return "승리 조서" + return "勝令" return _join_strings(lines, "\n") @@ -3792,24 +3837,24 @@ func _format_mission_panel_text() -> String: var lines := [] var victory := str(state.objectives.get("victory", "")) if not victory.is_empty(): - lines.append("승리 조서") + lines.append("勝令") lines.append(" %s" % victory) var defeat := str(state.objectives.get("defeat", "")) if not defeat.is_empty(): - lines.append("패배 흉조") + lines.append("敗兆") lines.append(" %s" % defeat) var progress_lines := state.get_objective_progress_lines(false, false) if not progress_lines.is_empty(): - lines.append("전공 죽간") + lines.append("戰功 죽간") for progress_line in progress_lines: lines.append(" %s" % progress_line) var risk_lines := state.get_defeat_progress_lines() if not risk_lines.is_empty(): - lines.append("흉조 징후") + lines.append("凶兆 징후") for risk_line in risk_lines: lines.append(" %s" % risk_line) if lines.is_empty(): - return "아직 봉인된 조서가 없습니다." + return "아직 봉인된 군령이 없습니다." return _join_strings(lines, "\n") @@ -4254,18 +4299,18 @@ func _on_log_added(message: String) -> void: func _format_log_entry_text(message: String) -> String: 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:"): - return "흉조 개정:%s" % message.substr("Defeat condition updated:".length()) + return "敗兆 개정:%s" % message.substr("Defeat condition updated:".length()) return message func _on_objective_updated(victory: String, defeat: String) -> void: - var notice_lines := ["새 조서 봉인"] + var notice_lines := ["새 軍令 봉인"] if not victory.is_empty(): - notice_lines.append("승리 조서: %s" % victory) + notice_lines.append("勝令: %s" % victory) if not defeat.is_empty(): - notice_lines.append("패배 흉조: %s" % defeat) + notice_lines.append("敗兆: %s" % defeat) if objective_notice_label != null: objective_notice_label.text = "\n".join(notice_lines) if objective_notice_panel != null: @@ -4539,7 +4584,7 @@ func _normalized_dialogue_lines(lines: Array) -> Array: if text.is_empty(): continue 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({ "speaker": display_speaker, "text": text, @@ -4597,7 +4642,7 @@ func _render_dialogue_line() -> void: return var line: Dictionary = active_dialogue_lines[active_dialogue_index] 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")) _apply_dialogue_side(side) dialogue_speaker_label.text = display_speaker @@ -4612,10 +4657,10 @@ func _render_dialogue_line() -> void: func _update_dialogue_controls() -> void: 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: 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: @@ -4640,6 +4685,20 @@ func _update_dialogue_portrait(speaker: String, portrait_path: String) -> void: 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: return _load_portrait_texture(texture_path) @@ -4990,27 +5049,27 @@ func _format_briefing_objectives() -> String: var text := "" var victory := str(state.objectives.get("victory", "")) if not victory.is_empty(): - text = "승리 조서:\n %s" % victory + text = "勝令:\n %s" % victory var defeat := str(state.objectives.get("defeat", "")) if not defeat.is_empty(): if not text.is_empty(): text += "\n" - text += "패배 흉조:\n %s" % defeat + text += "敗兆:\n %s" % defeat var progress_lines := state.get_objective_progress_lines(false, false) if not progress_lines.is_empty(): if not text.is_empty(): text += "\n" - text += "전공 죽간:\n %s" % _join_strings(progress_lines, ", ") + text += "戰功 죽간:\n %s" % _join_strings(progress_lines, ", ") var risk_lines := state.get_defeat_progress_lines() if not risk_lines.is_empty(): if not text.is_empty(): text += "\n" - text += "흉조 징후:\n %s" % _join_strings(risk_lines, ", ") + text += "凶兆 징후:\n %s" % _join_strings(risk_lines, ", ") var rewards_text := _format_briefing_rewards() if not rewards_text.is_empty(): if not text.is_empty(): text += "\n" - text += "전리품 봉인:\n %s" % rewards_text + text += "戰利 봉인:\n %s" % rewards_text return text @@ -5512,7 +5571,7 @@ func _camp_conversation_entries() -> Array: func _format_camp_conversation_button_text(conversation: Dictionary) -> String: 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 group := _camp_conversation_group_text(str(conversation.get("group", "topic"))) var parts := [group, label] @@ -5563,13 +5622,13 @@ func _camp_conversation_group_text(group: String) -> String: func _camp_conversation_preview_speaker(conversation: Dictionary) -> String: var speaker := str(conversation.get("speaker", "")).strip_edges() if not speaker.is_empty(): - return speaker + return _localized_speaker_name(speaker) for line in conversation.get("lines", []): if typeof(line) != TYPE_DICTIONARY: continue speaker = str(line.get("speaker", "")).strip_edges() if not speaker.is_empty(): - return speaker + return _localized_speaker_name(speaker) return "" @@ -5600,7 +5659,7 @@ func _camp_conversation_preview_portrait_path(conversation: Dictionary) -> Strin return portrait var speaker := _camp_conversation_preview_speaker(conversation) 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 "" @@ -7372,7 +7431,7 @@ func _rebuild_post_move_item_picker(selected: Dictionary) -> void: _apply_item_button_icon(item_button, item) var disabled_reason := _post_move_item_disabled_reason(selected, item_id, count) 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) item_button.pressed.connect(_on_post_move_item_option_pressed.bind(item_id)) post_move_picker_list.add_child(item_button) @@ -7697,9 +7756,9 @@ func _update_end_turn_button() -> void: return var phase_block := _action_phase_block_reason() 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 - _set_action_button_state(end_turn_button, "차례 종료", false, "아군 차례를 마치고 적군이 움직입니다.") + _set_action_button_state(end_turn_button, "군령 종료", false, "아군 군령을 거두고 적군의 차례로 넘깁니다.") func _update_threat_button() -> void: diff --git a/tools/smoke_camp_conversation_rewards.gd b/tools/smoke_camp_conversation_rewards.gd index 36f2b58..d09f8e0 100644 --- a/tools/smoke_camp_conversation_rewards.gd +++ b/tools/smoke_camp_conversation_rewards.gd @@ -328,7 +328,7 @@ func _check_puyang_raid_camp_supplies(failures: Array[String]) -> void: scene.free() return 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") var merchant_lines: Array = merchant.get("lines", []) if merchant_lines.size() < 2: diff --git a/tools/smoke_objective_progress.gd b/tools/smoke_objective_progress.gd index 1872897..fa909d2 100644 --- a/tools/smoke_objective_progress.gd +++ b/tools/smoke_objective_progress.gd @@ -211,16 +211,16 @@ func _check_objective_notice(failures: Array[String]) -> void: scene._on_objective_updated("Defeat the new vanguard.", "") if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible: 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) scene._on_objective_updated("", "Supply train is lost.") if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible: 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) - 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") - 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") if scene.objective_notice_timer <= 0.0: failures.append("objective notice timer should be active") diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 7296d05..a417eb1 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -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_effect(failures, puyang_conversations[2], "bean", 1) 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") if (puyang_merchant.get("lines", []) as Array).size() < 2: failures.append("005 shop should expose merchant flavor lines") @@ -2672,14 +2672,14 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: scene.free() return var briefing_text := scene._format_briefing_objectives() - for expected in ["승리 조서:", "패배 흉조:", "전공 죽간:", "흉조 징후:", "전리품 봉인:"]: + for expected in ["勝令:", "敗兆:", "戰功 죽간:", "凶兆 징후:", "戰利 봉인:"]: if not briefing_text.contains(expected): failures.append("briefing objective parchment should use ancient heading `%s`: %s" % [expected, briefing_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) var mission_text := scene._format_mission_panel_text() - for expected in ["승리 조서", "패배 흉조", "전공 죽간", "흉조 징후"]: + for expected in ["勝令", "敗兆", "戰功 죽간", "凶兆 징후"]: if not mission_text.contains(expected): 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)) @@ -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_right_tassel, "dialogue right tassel") _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") - 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") 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", "")) != "조조": @@ -2739,7 +2739,7 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: 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"): 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") if scene.shop_button == null or scene.shop_button.text != "군상": 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._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]) scene.state.current_team = BattleState.TEAM_PLAYER