Localize Sishui gate and battle log tone
This commit is contained in:
@@ -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"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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": "제후들은 말다툼을 벌이는 동안 적은 달아날 것이다. 추격할지, 재정비할지 우리가 정해야 한다." }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user