diff --git a/data/campaign/campaign.json b/data/campaign/campaign.json index 6671d8e..5b60374 100644 --- a/data/campaign/campaign.json +++ b/data/campaign/campaign.json @@ -1,12 +1,12 @@ { "id": "cao_cao_campaign", - "title": "Cao Cao Campaign", + "title": "조조전", "start_scenario": "001_yellow_turbans", "initial_joined_officers": ["cao_cao", "xiahou_dun"], "chapters": [ { "id": "rise_through_turmoil", - "title": "Rise Through Turmoil", + "title": "난세의 기치", "start_scenario": "001_yellow_turbans", "end_scenario": "009_xiapi_siege" }, @@ -56,12 +56,12 @@ "scenarios": [ { "id": "001_yellow_turbans", - "title": "Yingchuan Skirmish", + "title": "영천 소전", "path": "res://data/scenarios/001_yellow_turbans.json" }, { "id": "002_sishui_gate", - "title": "Sishui Gate Vanguard", + "title": "사수관 선봉전", "path": "res://data/scenarios/002_sishui_gate.json" }, { diff --git a/data/scenarios/002_sishui_gate.json b/data/scenarios/002_sishui_gate.json index c603532..f66d649 100644 --- a/data/scenarios/002_sishui_gate.json +++ b/data/scenarios/002_sishui_gate.json @@ -1,9 +1,9 @@ { "id": "002_sishui_gate", - "name": "Sishui Gate Vanguard", + "name": "사수관 선봉전", "objectives": { - "victory": "Defeat Hua Xiong's vanguard.", - "defeat": "Cao Cao is defeated or the battle reaches Turn 9." + "victory": "화웅의 선봉대를 격파하라.", + "defeat": "조조가 퇴각하거나 제9턴에 이르면 패한다." }, "conditions": { "victory": { "type": "all_units_defeated", "team": "enemy", "after_event": "turn_2_warning" }, @@ -13,58 +13,60 @@ ] }, "briefing": { - "title": "Sishui Gate Vanguard", - "location": "Sishui Gate, 190 CE", + "title": "사수관 선봉전", + "location": "사수관, 초평 원년", "lines": [ - "The coalition gathers to challenge Dong Zhuo.", - "Cao Cao leads a small vanguard to test Hua Xiong's defenses." + "동탁을 토벌하기 위해 제후의 군기가 사수관 아래 모였다.", + "조조는 작은 선봉을 이끌고 화웅의 진을 먼저 시험하려 한다." ], "camp_conversations": [ { "id": "sishui_coalition_reading", "group": "officer", "officer_id": "cao_cao", - "label": "Cao Cao - Coalition Nerve", + "label": "조조 - 연합의 담력", "speaker": "Cao Cao", - "summary": "Measure the coalition before challenging Hua Xiong.", + "display_speaker": "조조", + "summary": "화웅을 치기 전에 제후 연합의 속내를 헤아린다.", "lines": [ - { "speaker": "Cao Cao", "side": "left", "text": "The coalition has banners enough to shade the pass, yet every lord waits for another to move first." }, - { "speaker": "Xiahou Dun", "side": "right", "text": "Then let them watch us move. Hua Xiong will learn which banner has teeth." } + { "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "제후의 깃발은 관문을 덮을 만큼 많으나, 누구도 먼저 나서려 하지 않는다." }, + { "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "그렇다면 우리가 움직이는 모습을 보이겠습니다. 화웅도 어느 깃발에 이빨이 있는지 알게 될 것입니다." } ] }, { "id": "sishui_xiahou_yuan_ridge", "group": "officer", "officer_id": "xiahou_yuan", - "label": "Xiahou Yuan - Ridge Line", + "label": "하후연 - 산등성이", "speaker": "Xiahou Yuan", - "summary": "Discuss the archers watching the road.", + "display_speaker": "하후연", + "summary": "길 위를 내려다보는 궁병 진을 논한다.", "lines": [ - { "speaker": "Xiahou Yuan", "side": "right", "text": "Their archers favor the ridge above the road. If we let them settle, the vanguard will bleed before touching Hua Xiong." }, - { "speaker": "Cao Cao", "side": "left", "text": "Keep their eyes busy. A single opened flank will make the pass feel wider than it is." } + { "speaker": "Xiahou Yuan", "display_speaker": "하후연", "side": "right", "text": "적 궁병은 길 위의 능선을 좋아합니다. 자리를 굳히게 두면 화웅에게 닿기도 전에 선봉이 피를 흘릴 것입니다." }, + { "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "그 눈을 흔들어 두어라. 한쪽 옆구리만 열어도 좁은 관문이 넓어 보일 것이다." } ] }, { "id": "sishui_quartermaster_antidote", "group": "topic", - "label": "Quartermaster's Bundle", - "summary": "Check the last useful supplies before the gate.", + "label": "군수관의 약낭", + "summary": "관문 앞에서 마지막 쓸 만한 보급을 확인한다.", "lines": [ - { "speaker": "Coalition Quartermaster", "side": "left", "text": "My lord, the western stores are crowded with borrowed seals and empty promises. I found one antidote bundle that is actually useful." }, - { "speaker": "Cao Ren", "side": "right", "text": "Take it. A single clean cure can keep a line from folding." } + { "speaker": "Coalition Quartermaster", "display_speaker": "연합 군수관", "side": "left", "text": "장군, 서쪽 보급고에는 빌린 인장과 빈 약속만 가득합니다. 그래도 쓸 만한 해독약 한 꾸러미를 찾았습니다." }, + { "speaker": "Cao Ren", "display_speaker": "조인", "side": "right", "text": "가져오게. 제대로 된 치료 한 번이 전열 하나를 무너지지 않게 한다." } ], "effects": [ - { "type": "grant_item", "item_id": "antidote", "count": 1, "text": "The quartermaster adds an Antidote to the field supplies." } + { "type": "grant_item", "item_id": "antidote", "count": 1, "text": "군수관이 해독약 한 꾸러미를 군수에 보탰다." } ] } ] }, "shop": { "merchant": { - "name": "Coalition Merchant", + "name": "연합 상인", "lines": [ - "Every lord at Sishui wants credit, but only the first vanguard buys enough bandages.", - "I have robes, armor, bows, and spears. If Hua Xiong breaks through, nobody will ask where they came from." + "사수관의 제후들은 공을 탐하지만, 붕대를 넉넉히 사는 이는 늘 선봉뿐입니다.", + "도포와 갑옷, 활과 창을 갖췄습니다. 화웅이 뚫고 나오면 물건의 출처를 묻는 이는 없을 겁니다." ] }, "items": [ @@ -134,7 +136,7 @@ }, { "unit_id": "hua_xiong_vanguard", - "name": "Hua Xiong Vanguard", + "name": "화웅 선봉대", "class_id": "cavalry", "team": "enemy", "level": 2, @@ -143,7 +145,7 @@ }, { "unit_id": "liang_spearman_1", - "name": "Liang Spearman", + "name": "양주 창병", "class_id": "infantry", "team": "enemy", "level": 2, @@ -152,7 +154,7 @@ }, { "unit_id": "liang_spearman_2", - "name": "Liang Spearman", + "name": "양주 창병", "class_id": "infantry", "team": "enemy", "level": 2, @@ -161,7 +163,7 @@ }, { "unit_id": "liang_archer", - "name": "Liang Archer", + "name": "양주 궁병", "class_id": "archer", "team": "enemy", "level": 2, @@ -175,7 +177,7 @@ "once": true, "when": { "type": "battle_start" }, "actions": [ - { "type": "log", "text": "The coalition vanguard moves toward Sishui Gate." } + { "type": "log", "text": "연합 선봉이 사수관을 향해 움직였다." } ] }, { @@ -186,8 +188,8 @@ { "type": "dialogue", "lines": [ - { "speaker": "Cao Cao", "text": "Hua Xiong's vanguard is testing the coalition's nerve. We cannot give them ground." }, - { "speaker": "Xiahou Yuan", "text": "Their archers hold the ridge. I will keep pressure on their flank." } + { "speaker": "Cao Cao", "display_speaker": "조조", "text": "화웅의 선봉은 제후 연합의 담력을 시험하고 있다. 한 치도 땅을 내줄 수 없다." }, + { "speaker": "Xiahou Yuan", "display_speaker": "하후연", "text": "적 궁병이 능선을 잡았습니다. 제가 옆구리를 눌러 두겠습니다." } ] } ] @@ -197,18 +199,18 @@ "once": true, "when": { "type": "turn_start", "team": "enemy", "turn": 2 }, "actions": [ - { "type": "log", "text": "Hua Xiong's men tighten their formation." }, + { "type": "log", "text": "화웅의 병사들이 진형을 조여 왔다." }, { "type": "set_objective", - "victory": "Defeat Hua Xiong's vanguard and reinforcements.", - "defeat": "Cao Cao is defeated or the battle reaches Turn 9." + "victory": "화웅의 선봉대와 원군을 모두 격파하라.", + "defeat": "조조가 퇴각하거나 제9턴에 이르면 패한다." }, { "type": "spawn_deployments", "deployments": [ { "unit_id": "liang_reinforcement_1", - "name": "Liang Rider", + "name": "양주 기병", "class_id": "cavalry", "team": "enemy", "level": 2, @@ -217,7 +219,7 @@ }, { "unit_id": "liang_reinforcement_2", - "name": "Liang Guard", + "name": "양주 호위병", "class_id": "infantry", "team": "enemy", "level": 2, @@ -232,15 +234,15 @@ "post_battle_choices": [ { "id": "pursue_dong_zhuo", - "label": "Pursue Dong Zhuo", - "description": "Cao Cao presses the retreating army while Cao Ren secures Sishui.", + "label": "동탁 추격", + "description": "조조가 물러나는 적을 몰아붙이고 조인이 사수관을 지킨다.", "leave_officers": ["cao_ren"], "set_flags": { "pursued_dong_zhuo": true, "regrouped_after_sishui": false } }, { "id": "regroup_coalition", - "label": "Regroup", - "description": "The vanguard pauses to gather supplies.", + "label": "연합 재정비", + "description": "선봉이 잠시 멈추어 보급을 모은다.", "set_flags": { "pursued_dong_zhuo": false, "regrouped_after_sishui": true } } ], @@ -249,7 +251,7 @@ "items": ["bean"] }, "post_battle_dialogue": [ - { "speaker": "Xiahou Yuan", "text": "Sishui Gate is broken, but Dong Zhuo's main force is already moving west." }, - { "speaker": "Cao Cao", "text": "The coalition will argue while the enemy escapes. We must decide whether to pursue or regroup." } + { "speaker": "Xiahou Yuan", "display_speaker": "하후연", "text": "사수관은 무너졌으나 동탁의 본대는 이미 서쪽으로 움직이고 있습니다." }, + { "speaker": "Cao Cao", "display_speaker": "조조", "text": "제후들은 말다툼을 벌이는 동안 적은 달아날 것이다. 추격할지, 재정비할지 우리가 정해야 한다." } ] } diff --git a/scripts/core/battle_state.gd b/scripts/core/battle_state.gd index 2635b81..d0b3563 100644 --- a/scripts/core/battle_state.gd +++ b/scripts/core/battle_state.gd @@ -735,12 +735,12 @@ func try_cast_selected_skill(skill_id: String, target_cell: Vector2i) -> bool: if caster.get("acted", false): return false if _unit_has_action_lock(caster, "skill"): - _emit_log("%s cannot use tactics while sealed." % caster["name"]) + _emit_log("%s는 봉인되어 책략을 펼치지 못한다." % caster["name"]) return false if not _unit_has_skill(caster, skill_id): return false if int(caster.get("mp", 0)) < int(skill.get("mp_cost", 0)): - _emit_log("%s does not have enough MP." % caster["name"]) + _emit_log("%s의 기력이 모자라다." % caster["name"]) return false if not _skill_cells_from(caster, skill).has(target_cell): return false @@ -749,9 +749,9 @@ func try_cast_selected_skill(skill_id: String, target_cell: Vector2i) -> bool: var targets := _skill_targets_for_cast(caster, skill_id, skill, target_cell) if targets.is_empty(): if skill_kind == "heal": - _emit_log("%s has no injured targets in range." % skill.get("name", "Skill")) + _emit_log("%s의 범위 안에 다친 아군이 없다." % skill.get("name", "책략")) else: - _emit_log("%s has no valid targets." % skill.get("name", "Skill")) + _emit_log("%s의 표적이 없다." % skill.get("name", "책략")) return false if skill_kind == "support" and not _skill_has_support_effects(skill): return false @@ -795,7 +795,7 @@ func try_use_selected_item_on_cell(item_id: String, target_cell: Vector2i) -> bo if user.get("acted", false): return false if int(battle_inventory.get(item_id, 0)) <= 0: - _emit_log("No %s remains." % item.get("name", item_id)) + _emit_log("%s은 이미 다하였다." % _item_display_name(item_id)) return false if str(item.get("kind", "")) != "consumable": return false @@ -814,7 +814,7 @@ func try_use_selected_item_on_cell(item_id: String, target_cell: Vector2i) -> bo user["acted"] = true user["moved"] = true selected_unit_id = "" - _emit_log("%s uses %s on %s." % [user["name"], item.get("name", item_id), target["name"]]) + _emit_log("%s 사용: %s -> %s." % [user["name"], _item_display_name(item_id), target["name"]]) _check_battle_status() _notify_changed() return true @@ -1126,9 +1126,9 @@ func _resolve_skill_support(caster: Dictionary, target: Dictionary, skill_id: St "remaining_phases": max(1, int(effect.get("duration_turns", 1))) }) target["status_effects"] = status_effects - _emit_log("%s casts %s on %s. %s" % [ + _emit_log("%s 시전 %s -> %s. %s" % [ caster["name"], - skill.get("name", "Skill"), + skill.get("name", "책략"), target["name"], _format_support_effects(skill) ]) @@ -1159,25 +1159,25 @@ func _format_support_effects(skill: Dictionary) -> String: continue var effect_type := str(effect.get("type", "")) var duration: int = maxi(1, int(effect.get("duration_turns", 1))) - var duration_text := "next phase" if duration == 1 else "%d phases" % duration + var duration_text := _support_duration_text(duration) if effect_type == "stat_bonus": var stat := str(effect.get("stat", "")) var amount := int(effect.get("amount", 0)) if not _is_supported_status_stat(stat) or amount == 0: continue var sign := "+" if amount > 0 else "" - parts.append("%s %s%d until %s" % [stat.to_upper(), sign, amount, duration_text]) + parts.append("%s %s%d, %s 지속" % [_status_stat_display_name(stat), sign, amount, duration_text]) elif effect_type == "damage_over_time": var damage := int(effect.get("amount", 0)) if damage <= 0: continue var status_name := _status_display_name(str(effect.get("status", "poison"))) - parts.append("%s -%d HP for %s" % [status_name, damage, duration_text]) + parts.append("%s -%d 병력, %s 지속" % [status_name, damage, duration_text]) elif effect_type == "action_lock": var status_name := _status_display_name(str(effect.get("status", "status"))) - parts.append("%s %s for %s" % [status_name, _action_lock_display_name(str(effect.get("action", ""))), duration_text]) + parts.append("%s %s, %s 지속" % [status_name, _action_lock_display_name(str(effect.get("action", ""))), duration_text]) if parts.is_empty(): - return "No support effect." + return "효험 없음" return _join_strings(parts, ", ") @@ -1193,15 +1193,15 @@ func _format_support_popup(skill: Dictionary) -> String: if not _is_supported_status_stat(stat) or amount == 0: continue var sign := "+" if amount > 0 else "" - parts.append("%s %s%d" % [stat.to_upper(), sign, amount]) + parts.append("%s %s%d" % [_status_stat_display_name(stat), sign, amount]) elif effect_type == "damage_over_time": var damage := int(effect.get("amount", 0)) if damage > 0: - parts.append(_status_display_name(str(effect.get("status", "poison"))).to_upper()) + parts.append(_status_display_name(str(effect.get("status", "poison")))) elif effect_type == "action_lock": - parts.append(_status_display_name(str(effect.get("status", "status"))).to_upper()) + parts.append(_status_display_name(str(effect.get("status", "status")))) if parts.is_empty(): - return "Support" + return "지원" return _join_strings(parts, ", ") @@ -1232,7 +1232,7 @@ func _format_active_status_effects(unit: Dictionary) -> String: var duration_text := _format_status_remaining_phases(effect) if effect_type == "damage_over_time": if amount > 0: - parts.append("%s -%d HP %s" % [ + parts.append("%s -%d 병력 %s" % [ _status_display_name(str(effect.get("status", "poison"))), amount, duration_text @@ -1249,23 +1249,53 @@ func _format_active_status_effects(unit: Dictionary) -> String: if not _is_supported_status_stat(stat) or amount == 0: continue var sign := "+" if amount > 0 else "" - parts.append("%s %s%d %s" % [stat.to_upper(), sign, amount, duration_text]) + parts.append("%s %s%d %s" % [_status_stat_display_name(stat), sign, amount, duration_text]) return _join_strings(parts, ", ") func _format_status_remaining_phases(effect: Dictionary) -> String: var remaining: int = maxi(1, int(effect.get("remaining_phases", 1))) - var label := "phase" if remaining == 1 else "phases" - return "(%d %s)" % [remaining, label] + return "(%d회)" % remaining func _status_display_name(status: String) -> String: - var normalized := status.strip_edges() + var normalized := status.strip_edges().to_lower() if normalized.is_empty(): - return "Status" + return "상태" + if normalized == "poison": + return "독" + if normalized == "seal": + return "봉인" + if normalized == "snare": + return "발묶임" + if normalized == "disarm": + return "무장해제" + if normalized == "debuff": + return "약화" return normalized.replace("_", " ").capitalize() +func _status_stat_display_name(stat: String) -> String: + var normalized := stat.strip_edges().to_lower() + if normalized == "atk": + return "무력" + if normalized == "def": + return "방비" + if normalized == "int": + return "지략" + if normalized == "agi": + return "순발" + if normalized == "move": + return "행군" + return normalized.to_upper() + + +func _support_duration_text(duration: int) -> String: + if duration <= 1: + return "다음 차례" + return "%d차례" % duration + + func _is_supported_status_stat(stat: String) -> bool: return stat == "atk" or stat == "def" or stat == "int" or stat == "agi" or stat == "move" @@ -1282,12 +1312,12 @@ func _unit_has_action_lock(unit: Dictionary, action: String) -> bool: func _action_lock_display_name(action: String) -> String: if action == "skill": - return "no tactics" + return "책략 봉인" if action == "move": - return "no movement" + return "행군 봉인" if action == "attack": - return "no attacks" - return "no action" + return "타격 봉인" + return "행동 봉인" func try_wait_selected() -> bool: @@ -1300,7 +1330,7 @@ func try_wait_selected() -> bool: unit["acted"] = true unit["moved"] = true selected_unit_id = "" - _emit_log("%s waits." % unit["name"]) + _emit_log("%s 대기." % unit["name"]) _notify_changed() return true @@ -2290,12 +2320,12 @@ func _format_skill_threat_effect_summary(skill: Dictionary) -> String: if not _is_supported_status_stat(stat) or amount == 0: continue var sign := "+" if amount > 0 else "" - parts.append("%s %s%d" % [stat.to_upper(), sign, amount]) + parts.append("%s %s%d" % [_status_stat_display_name(stat), sign, amount]) elif effect_type == "damage_over_time": var damage := int(effect.get("amount", 0)) if damage <= 0: continue - parts.append("%s -%d HP" % [_status_display_name(str(effect.get("status", "poison"))), damage]) + parts.append("%s -%d 병력" % [_status_display_name(str(effect.get("status", "poison"))), damage]) elif effect_type == "action_lock": var status_name := _status_display_name(str(effect.get("status", "status"))) parts.append("%s %s" % [status_name, _action_lock_display_name(str(effect.get("action", "")))]) @@ -2518,28 +2548,28 @@ func _apply_item_effects(user: Dictionary, target: Dictionary, item: Dictionary) var healed := _apply_item_heal(target, int(effect.get("amount", 0))) if healed > 0: applied = true - _emit_log("%s recovers %d HP." % [target["name"], healed]) - _emit_combat_feedback(target, "+%d HP" % healed, "heal") + _emit_log("%s의 병력 %d 회복." % [target["name"], healed]) + _emit_combat_feedback(target, "+%d 병력" % healed, "heal") elif effect_type == "heal_mp": var restored := _apply_item_mp_heal(target, int(effect.get("amount", 0))) if restored > 0: applied = true - _emit_log("%s recovers %d MP." % [target["name"], restored]) - _emit_combat_feedback(target, "+%d MP" % restored, "mp") + _emit_log("%s의 기력 %d 회복." % [target["name"], restored]) + _emit_combat_feedback(target, "+%d 기력" % restored, "mp") elif effect_type == "cure_status": var cured_statuses := _apply_item_status_cure(target, str(effect.get("status", ""))) if not cured_statuses.is_empty(): applied = true - _emit_log("%s is cured of %s." % [target["name"], _join_strings(cured_statuses, ", ")]) - _emit_combat_feedback(target, "CURE", "support") + _emit_log("%s의 %s 해소." % [target["name"], _join_strings(cured_statuses, ", ")]) + _emit_combat_feedback(target, "해소", "support") elif effect_type == "cleanse_debuffs": var cleansed_debuffs := _apply_item_debuff_cleanse(target) if not cleansed_debuffs.is_empty(): applied = true - _emit_log("%s is cleansed of %s." % [target["name"], _join_strings(cleansed_debuffs, ", ")]) - _emit_combat_feedback(target, "CLEANSE", "support") + _emit_log("%s의 약화 해소: %s." % [target["name"], _join_strings(cleansed_debuffs, ", ")]) + _emit_combat_feedback(target, "정화", "support") if not applied: - _emit_log("%s has no effect on %s." % [item.get("name", "Item"), target["name"]]) + _emit_log("%s은 %s에게 효험이 없었다." % [str(item.get("name", "도구")), target["name"]]) return applied @@ -2670,8 +2700,8 @@ func _is_negative_stat_bonus_effect(effect: Dictionary) -> bool: func _stat_debuff_display_name(effect: Dictionary) -> String: var stat := str(effect.get("stat", "stat")) if not _is_supported_status_stat(stat): - return "Debuff" - return "%s down" % stat.to_upper() + return "약화" + return "%s 저하" % _status_stat_display_name(stat) func _equipment_slot_for_item(item: Dictionary) -> String: @@ -3184,7 +3214,7 @@ func _execute_ai_skill_action(caster: Dictionary, action: Dictionary) -> bool: if _resolve_skill_damage(caster, area_target, skill): defeated_count += 1 _grant_experience(caster, EXP_SKILL + defeated_count * EXP_DEFEAT_BONUS) - _emit_log("%s spends %d MP." % [caster["name"], int(skill.get("mp_cost", 0))]) + _emit_log("%s 기력 %d 소모." % [caster["name"], int(skill.get("mp_cost", 0))]) return true @@ -3230,7 +3260,7 @@ func _resolve_combat(attacker: Dictionary, target: Dictionary) -> void: func _resolve_attack(attacker: Dictionary, target: Dictionary, is_counter := false) -> Dictionary: var hit_chance := calculate_hit_chance(attacker, target) - var verb := "counterattacks" if is_counter else "attacks" + var verb := "반격" if is_counter else "공격" unit_action_motion_requested.emit( str(attacker.get("id", "")), attacker.get("pos", Vector2i(-1, -1)), @@ -3238,22 +3268,22 @@ func _resolve_attack(attacker: Dictionary, target: Dictionary, is_counter := fal "counter" if is_counter else "attack" ) if rng.randi_range(1, 100) > hit_chance: - _emit_log("%s %s %s but misses. Hit %d%%." % [attacker["name"], verb, target["name"], hit_chance]) - _emit_combat_feedback(target, "MISS", "miss") + _emit_log("%s %s %s, 헛쳤다. 명중 %d%%." % [attacker["name"], verb, target["name"], hit_chance]) + _emit_combat_feedback(target, "헛침", "miss") return {"hit": false, "defeated": false} var damage := calculate_damage(attacker, target) target["hp"] = max(0, int(target["hp"]) - damage) var effective_bonus := _weapon_effectiveness_bonus(attacker, target) var effective_text := "" if effective_bonus > 0: - effective_text = " Effective +%d vs %s." % [effective_bonus, _format_move_type(_weapon_effectiveness_target_type(target))] - _emit_log("%s %s %s for %d damage. Hit %d%%.%s" % [attacker["name"], verb, target["name"], damage, hit_chance, effective_text]) + effective_text = " %s 특효 +%d." % [_format_move_type(_weapon_effectiveness_target_type(target)), effective_bonus] + _emit_log("%s %s %s, %d 피해. 명중 %d%%.%s" % [attacker["name"], verb, target["name"], damage, hit_chance, effective_text]) _emit_combat_feedback(target, "-%d" % damage, "damage") if int(target["hp"]) <= 0: target["alive"] = false - _emit_log("%s is defeated." % target["name"]) - _emit_combat_feedback(target, "DOWN", "defeat") + _emit_log("%s 퇴각." % target["name"]) + _emit_combat_feedback(target, "퇴각", "defeat") _handle_unit_defeated(target) return {"hit": true, "defeated": true} return {"hit": true, "defeated": false} @@ -3262,9 +3292,9 @@ func _resolve_attack(attacker: Dictionary, target: Dictionary, is_counter := fal func _resolve_skill_damage(caster: Dictionary, target: Dictionary, skill: Dictionary) -> bool: var damage := calculate_skill_damage(caster, target, skill) target["hp"] = max(0, int(target["hp"]) - damage) - _emit_log("%s casts %s on %s for %d damage." % [ + _emit_log("%s 시전 %s -> %s, %d 피해." % [ caster["name"], - skill.get("name", "Skill"), + skill.get("name", "책략"), target["name"], damage ]) @@ -3272,8 +3302,8 @@ func _resolve_skill_damage(caster: Dictionary, target: Dictionary, skill: Dictio if int(target["hp"]) <= 0: target["alive"] = false - _emit_log("%s is defeated." % target["name"]) - _emit_combat_feedback(target, "DOWN", "defeat") + _emit_log("%s 퇴각." % target["name"]) + _emit_combat_feedback(target, "퇴각", "defeat") _handle_unit_defeated(target) return true return false @@ -3299,13 +3329,13 @@ func _handle_unit_defeated(unit: Dictionary) -> void: func _resolve_skill_heal(caster: Dictionary, target: Dictionary, skill: Dictionary) -> int: var healed := calculate_skill_heal(caster, target, skill) target["hp"] = min(int(target["max_hp"]), int(target["hp"]) + healed) - _emit_log("%s casts %s on %s, restoring %d HP." % [ + _emit_log("%s 시전 %s -> %s, 병력 %d 회복." % [ caster["name"], - skill.get("name", "Skill"), + skill.get("name", "책략"), target["name"], healed ]) - _emit_combat_feedback(target, "+%d HP" % healed, "heal") + _emit_combat_feedback(target, "+%d 병력" % healed, "heal") return healed @@ -3313,7 +3343,7 @@ func _grant_experience(unit: Dictionary, amount: int) -> void: if unit.is_empty() or not unit.get("alive", false) or amount <= 0: return unit["exp"] = int(unit.get("exp", 0)) + amount - _emit_log("%s gains %d EXP." % [unit["name"], amount]) + _emit_log("%s 공적 +%d." % [unit["name"], amount]) while int(unit["exp"]) >= EXP_LEVEL: unit["exp"] = int(unit["exp"]) - EXP_LEVEL _level_up(unit) @@ -3330,7 +3360,7 @@ func _level_up(unit: Dictionary) -> void: unit["mp"] = min(int(unit["max_mp"]), int(unit.get("mp", 0)) + mp_gain) for stat in ["atk", "def", "int", "agi"]: unit[stat] = int(unit.get(stat, 0)) + _growth_gain(unit, stat, 1) - _emit_log("%s reaches Lv.%d." % [unit["name"], unit["level"]]) + _emit_log("%s Lv.%d에 이르렀다." % [unit["name"], unit["level"]]) _emit_combat_feedback(unit, "Lv.%d" % int(unit["level"]), "progress") if str(unit.get("team", "")) == TEAM_PLAYER: _record_progression_event({ @@ -3414,12 +3444,12 @@ func _check_battle_status() -> void: battle_status = STATUS_DEFEAT current_team = TEAM_PLAYER selected_unit_id = "" - _emit_log("Defeat condition met.") + _emit_log("패전의 조짐이 굳어졌다.") elif _is_condition_group_met(battle_conditions.get("victory", {})): battle_status = STATUS_VICTORY current_team = TEAM_PLAYER selected_unit_id = "" - _emit_log("Victory condition met.") + _emit_log("승전의 군령이 이루어졌다.") func _is_condition_group_met(condition_group) -> bool: @@ -4051,9 +4081,9 @@ func _grant_event_item(action: Dictionary, trigger_unit: Dictionary = {}) -> boo return false var count: int = maxi(1, int(action.get("count", 1))) battle_inventory[item_id] = int(battle_inventory.get(item_id, 0)) + count - var item_name: String = str(item.get("name", item_id)) + var item_name: String = _item_display_name(item_id) var count_text: String = "" if count == 1 else " x%d" % count - _emit_log("Received %s%s." % [item_name, count_text]) + _emit_log("전리품: %s%s." % [item_name, count_text]) if not trigger_unit.is_empty() and bool(trigger_unit.get("alive", false)): _emit_combat_feedback(trigger_unit, "+%s" % item_name, "item") return true @@ -4067,7 +4097,7 @@ func _grant_event_gold(action: Dictionary, trigger_unit: Dictionary = {}) -> boo battle_gold_reward += amount var text := str(action.get("text", "")).strip_edges() if text.is_empty(): - text = "Received %d gold." % amount + text = "군자금: %dG." % amount _emit_log(text) if not trigger_unit.is_empty() and bool(trigger_unit.get("alive", false)): _emit_combat_feedback(trigger_unit, "+%dG" % amount, "gold") @@ -4230,11 +4260,11 @@ func _withdraw_event_unit(unit_id: String) -> bool: return false if selected_unit_id == normalized_id: selected_unit_id = "" - _emit_combat_feedback(unit, "WITHDRAW", "fade") + _emit_combat_feedback(unit, "퇴각", "fade") unit["deployed"] = false unit["acted"] = true unit["moved"] = true - _emit_log("%s withdraws from the battlefield." % unit.get("name", normalized_id)) + _emit_log("%s 전장을 물러났다." % unit.get("name", normalized_id)) _notify_changed() return true @@ -4263,12 +4293,13 @@ func _apply_phase_status_effects_for_team(team: String) -> void: continue unit["hp"] = max(0, int(unit.get("hp", 0)) - damage) var status := str(effect.get("status", "poison")) - _emit_log("%s suffers %d %s damage." % [unit["name"], damage, status.replace("_", " ")]) + var status_name := _status_display_name(status) + _emit_log("%s, %s으로 병력 %d 손실." % [unit["name"], status_name, damage]) _emit_combat_feedback(unit, "-%d" % damage, status) if int(unit.get("hp", 0)) <= 0: unit["alive"] = false - _emit_log("%s is defeated by %s." % [unit["name"], status.replace("_", " ")]) - _emit_combat_feedback(unit, "DOWN", "defeat") + _emit_log("%s, %s으로 퇴각." % [unit["name"], status_name]) + _emit_combat_feedback(unit, "퇴각", "defeat") _handle_unit_defeated(unit) break @@ -4288,13 +4319,13 @@ func _expire_status_effects_for_team(team: String) -> void: kept_effect["remaining_phases"] = next_remaining kept_effects.append(kept_effect) else: - var effect_name := str(effect.get("name", "Support")) + var effect_name := str(effect.get("name", "지원")) if not expired_names.has(effect_name): expired_names.append(effect_name) unit["status_effects"] = kept_effects for expired_effect_name in expired_names: - _emit_log("%s's %s fades." % [unit["name"], expired_effect_name]) - _emit_combat_feedback(unit, "FADES", "fade") + _emit_log("%s의 %s이 사라졌다." % [unit["name"], expired_effect_name]) + _emit_combat_feedback(unit, "해제", "fade") func _manhattan(a: Vector2i, b: Vector2i) -> int: @@ -4316,6 +4347,47 @@ func _format_cells(cells: Array[Vector2i]) -> String: return _join_strings(labels, ", ") +func _item_display_name(item_id: String) -> String: + match item_id: + "bronze_sword": + return "청동검" + "iron_sword": + return "철검" + "training_spear": + return "연습창" + "short_bow": + return "단궁" + "hand_axe": + return "손도끼" + "war_axe": + return "전부" + "war_drum": + return "전고" + "imperial_seal": + return "옥새" + "yitian_sword": + return "의천검" + "dragon_spear": + return "용담창" + "wind_chaser_bow": + return "추풍궁" + "black_tortoise_robe": + return "현무도포" + "bean": + return "콩" + "antidote": + return "해독약" + "panacea": + return "만능약" + "wine": + return "술" + _: + var item := get_item_def(item_id) + if item.is_empty(): + return item_id + return str(item.get("name", item_id)) + + func _join_strings(values: Array, delimiter: String) -> String: var text := "" for value in values: diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 487f763..83892e1 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -1645,25 +1645,25 @@ func _play_ui_cancel() -> void: func _play_log_sfx(message: String) -> void: - if message.contains(" but misses."): + if message.contains(" but misses.") or message.contains("헛쳤다"): _play_sfx("guard_block") - elif message.begins_with("Objective updated:") or message.begins_with("Defeat condition updated:"): + elif message.begins_with("Objective updated:") or message.begins_with("Defeat condition updated:") or message.begins_with("군령 갱신:") or message.begins_with("패전 군령 갱신:"): _play_ui_confirm() - elif message.ends_with(" withdraws from the battlefield."): + elif message.ends_with(" withdraws from the battlefield.") or message.ends_with("전장을 물러났다."): _play_ui_confirm() - elif message.contains(" attacks ") or message.contains(" counterattacks "): - if message.contains(" for "): + elif message.contains(" attacks ") or message.contains(" counterattacks ") or message.contains(" 공격 ") or message.contains(" 반격 "): + if message.contains(" for ") or message.contains("피해"): _play_sfx("hit_heavy") - elif message.contains(" casts "): - if message.contains("restoring") or message.contains(" until "): + elif message.contains(" casts ") or message.contains(" 시전 "): + if message.contains("restoring") or message.contains(" until ") or message.contains("회복") or message.contains("지원"): _play_sfx("skill_cast") else: _play_sfx("fire_skill") elif message.contains(" moved to ") or message.contains(" advances to ") or message.contains(" takes formation at ") or message.contains(" arrives at "): _play_sfx("footstep") - elif message.contains(" uses ") or message.contains(" equips ") or message.contains(" 매입:") or message.contains(" 매각:") or message.begins_with("Received "): + elif message.contains(" uses ") or message.contains(" equips ") or message.contains(" 사용:") or message.contains(" 장착:") or message.contains(" 매입:") or message.contains(" 매각:") or message.begins_with("Received ") or message.begins_with("전리품:") or message.begins_with("군자금:"): _play_sfx("item_pickup") - elif message.contains(" reaches Lv.") or message.contains(" promotes to "): + elif message.contains(" reaches Lv.") or message.contains(" promotes to ") or message.contains("공적") or message.contains("승급"): _play_sfx("menu_confirm") @@ -3056,9 +3056,9 @@ func _skill_target_preview_badge_for_cell(selected: Dictionary, target: Dictiona return _make_target_preview_badge_at("가득", "invalid", cell) if bool(preview.get("has_area", false)): return _make_target_preview_badge_at("+%d x%d" % [total_heal, target_count], "heal", cell) - return _make_target_preview_badge_at("+%d HP" % heal, "heal", cell) + return _make_target_preview_badge_at("+%d 병력" % heal, "heal", cell) if kind == "support": - var effect_text := str(preview.get("effect_text", "Support")) + var effect_text := str(preview.get("effect_text", "지원")) var badge_kind := _support_preview_badge_kind(effect_text) if bool(preview.get("has_area", false)): return _make_target_preview_badge_at("%s x%d" % [_compact_support_preview_text(effect_text), target_count], badge_kind, cell) @@ -3099,8 +3099,8 @@ func _item_target_preview_badge_for_cell(selected: Dictionary, target: Dictionar if hp_heal > 0 and mp_heal > 0: return _make_target_preview_badge_at("+%d/%d" % [hp_heal, mp_heal], "heal", cell) if hp_heal > 0: - return _make_target_preview_badge_at("+%d HP" % hp_heal, "heal", cell) - return _make_target_preview_badge_at("+%d MP" % mp_heal, "mp", cell) + return _make_target_preview_badge_at("+%d 병력" % hp_heal, "heal", cell) + return _make_target_preview_badge_at("+%d 기력" % mp_heal, "mp", cell) func _hover_intent_preview_badge(selected: Dictionary, target: Dictionary) -> Dictionary: @@ -3145,7 +3145,10 @@ func _compact_support_preview_text(effect_text: String) -> String: var for_index := part.find(" for ") if for_index >= 0: part = part.substr(0, for_index) - if not part.is_empty() and part != "No support effect.": + var duration_index := part.find(", ") + if duration_index >= 0: + part = part.substr(0, duration_index) + if not part.is_empty() and part != "No support effect." and part != "효험 없음": parts.append(_compact_support_badge_text(part)) if parts.is_empty(): return "지원" @@ -3155,27 +3158,27 @@ func _compact_support_preview_text(effect_text: String) -> String: func _compact_support_badge_text(text: String) -> String: - if text.contains("Disarm"): + if text.contains("Disarm") or text.contains("무장해제"): return "무장해제" - if text.contains("Snare"): + if text.contains("Snare") or text.contains("발묶임"): return "속박" - if text.contains("Seal"): + if text.contains("Seal") or text.contains("봉인"): return "봉인" - if text.contains("Poison"): + if text.contains("Poison") or text.contains("독"): return "독" - if text.contains("Cleanse"): + if text.contains("Cleanse") or text.contains("해소"): return "해소" return text func _support_preview_badge_kind(effect_text: String) -> String: - if effect_text.contains("Disarm"): + if effect_text.contains("Disarm") or effect_text.contains("무장해제"): return "disarm" - if effect_text.contains("Snare"): + if effect_text.contains("Snare") or effect_text.contains("발묶임"): return "snare" - if effect_text.contains("Seal"): + if effect_text.contains("Seal") or effect_text.contains("봉인"): return "seal" - if effect_text.contains("Poison"): + if effect_text.contains("Poison") or effect_text.contains("독"): return "poison" if effect_text.contains("-"): return "debuff" @@ -3805,7 +3808,7 @@ func _update_skill_forecast(selected: Dictionary) -> void: preview["mp_cost"], mp_text, area_text, - preview.get("effect_text", "Support effect"), + preview.get("effect_text", "지원"), refresh_text ] else: @@ -4617,7 +4620,7 @@ func _load_pending_post_battle_choice() -> void: if briefing_panel != null: briefing_panel.visible = false if log_box != null: - log_box.append_text("Campaign choice pending.\n") + log_box.append_text("군의 논의가 이어지고 있다.\n") func _load_scenario(scenario_id: String) -> void: @@ -6426,10 +6429,10 @@ func _on_post_battle_choice_pressed(choice: Dictionary) -> void: _append_result_items(choice.get("items", [])) _append_unique_result_values("joined_officers", choice.get("join_officers", [])) _append_unique_result_values("left_officers", choice.get("leave_officers", [])) - _on_log_added("Campaign choice saved: %s." % battle_result_summary["choice_label"]) + _on_log_added("군령 채택: %s." % battle_result_summary["choice_label"]) else: _play_ui_cancel() - _on_log_added("Could not save campaign choice.") + _on_log_added("군령을 봉하지 못했다.") _rebuild_result_choices() _update_result_next_button_visibility() if result_label != null and state.battle_status == BattleState.STATUS_VICTORY: @@ -6825,7 +6828,7 @@ func _show_campaign_complete() -> void: if briefing_panel != null: briefing_panel.visible = false if log_box != null: - log_box.append_text("Campaign complete.\n") + log_box.append_text("전기가 여기서 매듭지어졌다.\n") func _on_new_campaign_pressed() -> void: @@ -7109,7 +7112,7 @@ func _format_post_move_tactic_button_text(skill_id: String, skill: Dictionary, s func _format_post_move_item_button_text(item_id: String, item: Dictionary, count: int) -> String: var marker := "> " if selected_item_id == item_id else "" - var item_name := str(item.get("name", item_id)) + var item_name := _item_display_name(item_id) return "%s%s x%d %s" % [marker, item_name, count, _format_post_move_item_effect_text(item)] @@ -7545,13 +7548,13 @@ func _format_tactic_effect_text(skill: Dictionary) -> String: if amount == 0: continue var sign := "+" if amount > 0 else "" - parts.append("%s %s%d" % [str(effect.get("stat", "")).to_upper(), sign, amount]) + parts.append("%s %s%d" % [_battle_stat_display_name(str(effect.get("stat", ""))), sign, amount]) elif effect_type == "damage_over_time" and amount > 0: - var status_name := str(effect.get("status", "status")).replace("_", " ").capitalize() + var status_name := _battle_status_display_name(str(effect.get("status", "status"))) parts.append("%s -%d" % [status_name, amount]) elif effect_type == "action_lock": parts.append("%s %s" % [ - str(effect.get("status", "status")).replace("_", " ").capitalize(), + _battle_status_display_name(str(effect.get("status", "status"))), _action_lock_effect_label(str(effect.get("action", ""))) ]) if not parts.is_empty(): @@ -7592,6 +7595,42 @@ func _action_lock_effect_label(action: String) -> String: return "군령 봉인" +func _battle_stat_display_name(stat: String) -> String: + var normalized := stat.strip_edges().to_lower() + if normalized == "hp": + return "병력" + if normalized == "mp": + return "기력" + if normalized == "atk": + return "무력" + if normalized == "def": + return "방비" + if normalized == "int": + return "지략" + if normalized == "agi": + return "순발" + if normalized == "move": + return "행군" + return _format_identifier_label(stat) + + +func _battle_status_display_name(status: String) -> String: + var normalized := status.strip_edges().to_lower() + if normalized == "poison": + return "독" + if normalized == "seal": + return "봉인" + if normalized == "snare": + return "발묶임" + if normalized == "disarm": + return "무장해제" + if normalized == "debuff": + return "약화" + if normalized.is_empty(): + return "상태" + return _format_identifier_label(status) + + func _skill_kind_text(kind: String) -> String: var normalized := kind.strip_edges().to_lower() if normalized == "damage": @@ -7714,7 +7753,7 @@ func _clear_item_list() -> void: func _format_item_button_text(item_id: String, item: Dictionary, count: int) -> String: var marker := "> " if selected_item_id == item_id else "" - var item_name := str(item.get("name", item_id)) + var item_name := _item_display_name(item_id) return "%s%s x%d %s" % [marker, item_name, count, _format_item_effect_text(item)] @@ -7728,7 +7767,7 @@ func _format_item_effect_text(item: Dictionary) -> String: elif str(effect.get("type", "")) == "heal_mp": parts.append("기력 +%d" % int(effect.get("amount", 0))) elif str(effect.get("type", "")) == "cure_status": - parts.append("치료 %s" % str(effect.get("status", "status")).replace("_", " ").capitalize()) + parts.append("치료 %s" % _battle_status_display_name(str(effect.get("status", "status")))) elif str(effect.get("type", "")) == "cleanse_debuffs": parts.append("약화 해소") if parts.is_empty(): @@ -7872,9 +7911,9 @@ func _format_unit_equipment(unit: Dictionary) -> String: for slot in ["weapon", "armor", "accessory"]: var item_id := _equipment_item_id(equipment, slot) if item_id.is_empty(): - parts.append("%s -" % slot.capitalize()) + parts.append("%s -" % _equipment_slot_display_name(slot)) else: - parts.append("%s %s" % [slot.capitalize(), _item_display_name(item_id)]) + parts.append("%s %s" % [_equipment_slot_display_name(slot), _item_display_name(item_id)]) return _join_strings(parts, ", ") @@ -7911,7 +7950,7 @@ func _format_equipment_option_text(unit: Dictionary, item_id: String, item: Dict func _format_equipment_unequip_text(unit: Dictionary, slot: String, item_id: String, item: Dictionary) -> String: var parts := [ - "%s 해제:" % slot.capitalize(), + "%s 해제:" % _equipment_slot_display_name(slot), _item_display_name(item_id) ] var change_text := _format_equipment_unequip_change_text(unit, slot, item) @@ -7929,7 +7968,7 @@ func _format_equipment_unequip_change_text(unit: Dictionary, slot: String, item: var delta := -int(current_bonuses.get(stat, 0)) if delta == 0: continue - parts.append("%s %d" % [stat.to_upper(), delta]) + parts.append("%s %d" % [_battle_stat_display_name(stat), delta]) if slot == "weapon": var current_range := _format_equipment_item_range_text(item) if not current_range.is_empty(): @@ -7960,7 +7999,7 @@ func _format_equipment_change_text(unit: Dictionary, item: Dictionary) -> String if delta == 0: continue var sign := "+" if delta > 0 else "" - parts.append("%s %s%d" % [stat.to_upper(), sign, delta]) + parts.append("%s %s%d" % [_battle_stat_display_name(stat), sign, delta]) var range_text := _format_equipment_range_change_text(current_item, item) if not range_text.is_empty(): @@ -7980,6 +8019,17 @@ func _equipment_slot_for_display(item: Dictionary) -> String: return "" +func _equipment_slot_display_name(slot: String) -> String: + var normalized := slot.strip_edges().to_lower() + if normalized == "weapon": + return "병장" + if normalized == "armor": + return "갑주" + if normalized == "accessory": + return "보물" + return _format_identifier_label(slot) + + func _equipment_bonus_map(item: Dictionary) -> Dictionary: if item.is_empty() or typeof(item.get("bonuses", {})) != TYPE_DICTIONARY: return {} @@ -7995,7 +8045,7 @@ func _format_equipment_range_change_text(current_item: Dictionary, candidate_ite return "" if current_range.is_empty(): current_range = "-" - return "RNG %s->%s" % [current_range, candidate_range] + return "사거리 %s->%s" % [current_range, candidate_range] func _format_equipment_item_range_text(item: Dictionary) -> String: @@ -8021,10 +8071,10 @@ func _format_equipment_effectiveness_change_text(current_item: Dictionary, candi if candidate_text == current_text: return "" if current_text.is_empty(): - return "Gains %s" % candidate_text + return "특효 획득 %s" % candidate_text if candidate_text.is_empty(): - return "Loses %s" % current_text - return "Effect changes" + return "특효 상실 %s" % current_text + return "특효 변화" func _format_equipment_bonus_text(item: Dictionary) -> String: @@ -8038,12 +8088,12 @@ func _format_equipment_bonus_text(item: Dictionary) -> String: if amount == 0: continue var sign := "+" if amount > 0 else "" - parts.append("%s %s%d" % [stat.to_upper(), sign, amount]) + parts.append("%s %s%d" % [_battle_stat_display_name(stat), sign, amount]) var effective_text := _format_equipment_effectiveness_text(item) if not effective_text.is_empty(): parts.append(effective_text) if parts.is_empty(): - return str(item.get("kind", "equipment")).capitalize() + return _item_kind_text(str(item.get("kind", "item"))) return _join_strings(parts, ", ") @@ -8054,7 +8104,7 @@ func _format_equipment_rarity_text(item: Dictionary) -> String: func _format_item_rarity_text(item: Dictionary) -> String: var rarity := str(item.get("rarity", "")) if rarity == "named": - return "Named" + return "명품" return "" @@ -8074,7 +8124,7 @@ func _format_equipment_effectiveness_text(item: Dictionary) -> String: labels.append(label) if labels.is_empty(): return "" - return "Effective +%d vs %s" % [bonus, _join_strings(labels, "/")] + return "%s 특효 +%d" % [_join_strings(labels, "/"), bonus] func _format_move_type(move_type: String) -> String: diff --git a/tools/smoke_equipment_unequip.gd b/tools/smoke_equipment_unequip.gd index fe2d22f..0c75200 100644 --- a/tools/smoke_equipment_unequip.gd +++ b/tools/smoke_equipment_unequip.gd @@ -186,12 +186,12 @@ func _check_unequip_ui_entries(failures: Array[String]) -> void: var selected: Dictionary = scene.state.get_unit("cao_cao") scene._rebuild_equip_menu(selected) - if not _container_has_button_text(scene.equip_list, "Unequip Weapon:"): + if not _container_has_button_text(scene.equip_list, "병장 해제:"): failures.append("battle equipment menu should include an Unequip Weapon button") scene.armory_unit_id = "cao_cao" scene._rebuild_armory_menu() - if not _container_has_button_text(scene.armory_list, "Unequip Weapon:"): + if not _container_has_button_text(scene.armory_list, "병장 해제:"): failures.append("pre-battle Armory should include an Unequip Weapon button") scene.free() diff --git a/tools/smoke_event_item_pickups.gd b/tools/smoke_event_item_pickups.gd index bccd772..12a72ba 100644 --- a/tools/smoke_event_item_pickups.gd +++ b/tools/smoke_event_item_pickups.gd @@ -48,13 +48,13 @@ func _check_wuchao_depot_pickup(failures: Array[String]) -> void: failures.append("Wuchao depot should grant Wine x1: before %s after %s" % [str(before_inventory), str(after_inventory)]) if not logs.has("Usable Wuchao stores are seized before the granaries burn."): failures.append("Wuchao depot pickup log missing") - if not logs.has("Received Bean x2."): + if not logs.has("전리품: 콩 x2."): failures.append("Bean pickup receipt log missing") - if not logs.has("Received Wine."): + if not logs.has("전리품: 술."): failures.append("Wine pickup receipt log missing") - if not feedback.has("cao_cao_ch13|+Bean|item"): + if not feedback.has("cao_cao_ch13|+콩|item"): failures.append("Bean pickup feedback missing") - if not feedback.has("cao_cao_ch13|+Wine|item"): + if not feedback.has("cao_cao_ch13|+술|item"): failures.append("Wine pickup feedback missing") if not state.fired_event_ids.has("wuchao_depot_burned"): failures.append("Wuchao depot event should be marked fired") diff --git a/tools/smoke_event_withdraw.gd b/tools/smoke_event_withdraw.gd index c26aaa2..a56e37f 100644 --- a/tools/smoke_event_withdraw.gd +++ b/tools/smoke_event_withdraw.gd @@ -51,8 +51,8 @@ func _check_single_withdraw(state, failures: Array[String]) -> void: failures.append("single withdraw unit should no longer occupy its map cell") if state.get_living_units("enemy").size() != before_living - 1: failures.append("single withdraw should remove one living enemy from battle counts") - _check_log_contains(failures, logs, "Smoke Raider withdraws from the battlefield.") - _check_feedback_contains(failures, feedback, "withdraw_smoke_enemy", "WITHDRAW", "fade") + _check_log_contains(failures, logs, "Smoke Raider 전장을 물러났다.") + _check_feedback_contains(failures, feedback, "withdraw_smoke_enemy", "퇴각", "fade") func _check_multi_withdraw(state, failures: Array[String]) -> void: diff --git a/tools/smoke_item_debuff_cleanse.gd b/tools/smoke_item_debuff_cleanse.gd index dea7f73..941526a 100644 --- a/tools/smoke_item_debuff_cleanse.gd +++ b/tools/smoke_item_debuff_cleanse.gd @@ -35,7 +35,7 @@ func _check_panacea_cleanses_debuffs(failures: Array[String]) -> void: var preview: Dictionary = state.get_item_preview("cao_cao", "panacea", cao_cao["pos"]) var cure_names: Array = preview.get("cure_statuses", []) - for expected in ["Poison", "Seal", "ATK down", "MOVE down"]: + for expected in ["독", "봉인", "무력 저하", "행군 저하"]: if not cure_names.has(expected): failures.append("Panacea preview should include %s in %s" % [expected, str(cure_names)]) @@ -58,15 +58,15 @@ func _check_panacea_cleanses_debuffs(failures: Array[String]) -> void: if int(state.get_inventory_snapshot().get("panacea", 0)) != 0: failures.append("Panacea should be consumed after cleansing") var summary := state.get_status_effect_summary("cao_cao") - if not summary.contains("DEF +4"): + if not summary.contains("방비 +4"): failures.append("Panacea should keep positive buffs: %s" % summary) - for removed_text in ["ATK -3", "MOVE -1", "Poison", "Seal"]: + for removed_text in ["무력 -3", "행군 -1", "독", "봉인"]: if summary.contains(removed_text): failures.append("Panacea should remove %s from summary: %s" % [removed_text, summary]) - _check_log_contains(failures, logs, "Cao Cao is cured of Poison.") - _check_log_contains(failures, logs, "Cao Cao is cured of Seal.") - _check_log_contains(failures, logs, "Cao Cao is cleansed of ATK down, MOVE down.") - if not feedback.has("cao_cao|CLEANSE|support"): + _check_log_contains(failures, logs, "Cao Cao의 독 해소.") + _check_log_contains(failures, logs, "Cao Cao의 봉인 해소.") + _check_log_contains(failures, logs, "Cao Cao의 약화 해소: 무력 저하, 행군 저하.") + if not feedback.has("cao_cao|정화|support"): failures.append("Panacea cleanse feedback missing: %s" % str(feedback)) @@ -102,7 +102,7 @@ func _check_panacea_menu_text(failures: Array[String]) -> void: scene.free() return var text := scene._format_item_effect_text(scene.state.get_item_def("panacea")) - if not text.contains("Cleanse debuffs"): + if not text.contains("약화 해소"): failures.append("Panacea menu text should mention debuff cleanse: %s" % text) scene.free() diff --git a/tools/smoke_post_move_action_flow.gd b/tools/smoke_post_move_action_flow.gd index 96a6be1..6facca3 100644 --- a/tools/smoke_post_move_action_flow.gd +++ b/tools/smoke_post_move_action_flow.gd @@ -422,7 +422,7 @@ func _check_scene_post_move_item_picker_flow(failures: Array[String]) -> void: if item_markers.size() != 1 or item_markers[0].get("cell", Vector2i.ZERO) != Vector2i(2, 4): failures.append("item target markers should include only the Bean target: %s" % str(item_markers)) else: - if str(item_markers[0].get("text", "")) != "+20 HP": + if str(item_markers[0].get("text", "")) != "+20 병력": failures.append("item target marker should preview healing: %s" % str(item_markers[0])) if str(item_markers[0].get("kind", "")) != "heal": failures.append("item target marker should use heal color: %s" % str(item_markers[0])) diff --git a/tools/smoke_result_summary.gd b/tools/smoke_result_summary.gd index fb29539..81508f1 100644 --- a/tools/smoke_result_summary.gd +++ b/tools/smoke_result_summary.gd @@ -30,7 +30,7 @@ func _init() -> void: {"type": "level_up", "name": "Cao Cao", "to_level": 2} ], "next_scenario_id": "002_sishui_gate", - "next_scenario_title": "Sishui Gate", + "next_scenario_title": "사수관 선봉전", "choice_applied": true, "choice_label": "" } @@ -40,9 +40,9 @@ func _init() -> void: _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, "campaign section", result_text, "다음 행군: 사수관 선봉전") _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 location", result_text, "사수관, 초평 원년") _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") @@ -58,7 +58,7 @@ func _init() -> void: "joined_officers": [], "left_officers": [], "progression_events": [], - "next_scenario_title": "Sishui Gate", + "next_scenario_title": "사수관 선봉전", "choice_applied": true, "choice_label": "" } diff --git a/tools/smoke_status_effect_summary.gd b/tools/smoke_status_effect_summary.gd index ec797e4..004f9c7 100644 --- a/tools/smoke_status_effect_summary.gd +++ b/tools/smoke_status_effect_summary.gd @@ -45,9 +45,9 @@ func _check_status_summary_duration_text(failures: Array[String]) -> void: } ] var summary: String = state.get_status_effect_summary("cao_cao") - _check_contains(failures, "stat duration", summary, "ATK +3 (2 phases)") - _check_contains(failures, "poison duration", summary, "Poison -4 HP (1 phase)") - _check_contains(failures, "seal duration", summary, "Seal no tactics (3 phases)") + _check_contains(failures, "stat duration", summary, "무력 +3 (2회)") + _check_contains(failures, "poison duration", summary, "독 -4 병력 (1회)") + _check_contains(failures, "seal duration", summary, "봉인 책략 봉인 (3회)") func _check_hud_reuses_status_summary(failures: Array[String]) -> void: @@ -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, "상태: DEF -2 (2 phases)") - _check_contains(failures, "hover tile status", scene.cell_info_label.text, "상태: DEF -2 (2 phases)") + _check_contains(failures, "selected HUD status", scene.selected_label.text, "상태: 방비 -2 (2회)") + _check_contains(failures, "hover tile status", scene.cell_info_label.text, "상태: 방비 -2 (2회)") scene.free() diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 47dcf03..49c88fa 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -133,7 +133,7 @@ func _check_battle_visual_data(failures: Array[String]) -> void: _check_camp_conversation(failures, gate_conversations[2], "sishui_quartermaster_antidote", "topic", "") _check_camp_conversation_effect(failures, gate_conversations[2], "antidote", 1) var gate_merchant := gate_state.get_shop_merchant() - if str(gate_merchant.get("name", "")) != "Coalition Merchant": + if str(gate_merchant.get("name", "")) != "연합 상인": failures.append("002 shop should expose coalition merchant name") if (gate_merchant.get("lines", []) as Array).size() < 2: failures.append("002 shop should expose merchant flavor lines")