Localize Sishui gate and battle log tone

This commit is contained in:
2026-06-19 06:06:14 +09:00
parent 30ac2479c4
commit 0b599b39be
12 changed files with 314 additions and 190 deletions

View File

@@ -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: