Antique briefing UI and Puyang localization

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

View File

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