From a5e5ddc31090e2fa73e63e6753f594eef60d5a3b Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sat, 20 Jun 2026 17:39:57 +0900 Subject: [PATCH] Improve Korean text readability checks --- README.md | 4 +- scripts/scenes/battle_scene.gd | 81 ++++++++++++++++++++++++++++--- tools/smoke_chapter_one_polish.gd | 64 ++++++++++++++++++++++++ tools/smoke_visual_assets.gd | 11 +++-- tools/validate_data.ps1 | 49 +++++++++++++++++++ 5 files changed, 196 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4522433..5bfa76a 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr - Pre-battle Armory equipment changes, including unequipping gear back into stock, save immediately and sync roster equipment plus inventory stock, with portrait and item-icon rows plus hover change details. - Pre-battle Roster can move optional officers between sortie and reserve within scenario limits, using portrait rows, compact status badges, and hover details. - Pre-battle Formation lets deployed officers swap starting positions inside scenario-defined cells, with portrait rows, readable zone badges, and hover placement instructions. -- Pre-battle briefing shows the campaign chapter, chapter battle number, location, victory/defeat summary, first-clear reward preview, generated battlefield thumbnail, allied portrait/enemy unit force markers, and image-first tactical marker cards for lure lines and supply points with compact tactical summaries, full hover hints, and click-to-focus map guidance. +- Pre-battle briefing shows the campaign chapter, chapter battle number, location, victory/defeat summary, first-clear reward preview, generated battlefield thumbnail, allied portrait/enemy unit force markers, a compact three-line battlefield/camp summary with dense details moved to hover text, and image-first tactical marker cards for lure lines and supply points with compact tactical summaries, full hover hints, and click-to-focus map guidance. - Pre-battle prep commands use compact icon buttons with active selection state and Korean hover labels for chapters, shop, talks, armory, roster, formation, and save records. - Post-move command boards, tactic pickers, and item pickers use generated command/item icons, compact option rows, and richer hover guidance for move-attack, stratagem, supply, wait, and cancel decisions instead of dense inline text. - Target preview badges, minimap hover badges, and battlefield target markers include generated jade panel surfaces plus action icons for move, attack, threat, support, objective, terrain, and item outcomes. -- Story, briefing, dialogue, mission-detail, and result scroll/ribbon/binding/bamboo/ink/tassel ornaments keep generated seal and panel art around the frame, while text-bearing surfaces use high-contrast flat paper/ink panels so Korean copy remains readable. +- Story, briefing, dialogue, mission-detail, and result scroll/ribbon/binding/bamboo/ink/tassel ornaments keep generated seal and panel art around the frame, while text-bearing surfaces use high-contrast flat paper/ink panels and fitted Korean text checks so copy remains readable. - Battlefield hover info badges use generated jade panel texture plus terrain, supply, tactic, objective, and threat icons, keeping raw grid coordinates out of visible play text. - Battle, top-toolbar, and pre-battle command buttons now prefer generated high-resolution bronze/parchment icon art under `art/ui/icons`, wide generated lacquer/jade/cinnabar menu button surfaces, and compact generated bronze/jade icon-button surfaces under `art/ui/buttons`, with procedural icon drawing and flat button styles kept only as fallbacks. - Battle maps blend generated terrain texture tiles for plains, forests, hills, wasteland, roads, water, villages, and castles over the high-resolution battlefield backdrop, with generated transparent feature overlays for road connections, water, villages, castles, and wasteland accents. diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 09bf8c3..6150864 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -148,6 +148,10 @@ const OBJECTIVE_NOTICE_FONT_SIZE := 16 const OBJECTIVE_NOTICE_MIN_FONT_SIZE := 14 const BRIEFING_OBJECTIVE_FONT_SIZE := 17 const BRIEFING_OBJECTIVE_MIN_FONT_SIZE := 14 +const BRIEFING_BODY_FONT_SIZE := 17 +const BRIEFING_BODY_MIN_FONT_SIZE := 15 +const BRIEFING_OVERVIEW_FONT_SIZE := 16 +const BRIEFING_OVERVIEW_MIN_FONT_SIZE := 13 const DIALOGUE_SPEAKER_FONT_SIZE := 20 const DIALOGUE_SPEAKER_MIN_FONT_SIZE := 16 const DIALOGUE_TEXT_FONT_SIZE := 19 @@ -4061,6 +4065,7 @@ func _create_hud() -> void: opening_prologue_title_label.custom_minimum_size = Vector2(220, 42) opening_prologue_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_prologue_title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + opening_prologue_title_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _apply_opening_caption_text_style(opening_prologue_title_label, UI_OPENING_CAPTION_TITLE, 26) opening_title_column.add_child(opening_prologue_title_label) @@ -4068,6 +4073,7 @@ func _create_hud() -> void: opening_prologue_step_label.custom_minimum_size = Vector2(220, 24) opening_prologue_step_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_prologue_step_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + opening_prologue_step_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _apply_opening_caption_text_style(opening_prologue_step_label, UI_OPENING_CAPTION_MUTED, 16) opening_title_column.add_child(opening_prologue_step_label) @@ -4255,7 +4261,7 @@ func _create_hud() -> void: briefing_camp_overview_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART briefing_camp_overview_label.custom_minimum_size = Vector2(286, 86) briefing_camp_overview_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL - _apply_label_style(briefing_camp_overview_label, UI_AGED_INK, 16) + _apply_label_style(briefing_camp_overview_label, UI_AGED_INK, BRIEFING_OVERVIEW_FONT_SIZE) briefing_camp_overview_row.add_child(briefing_camp_overview_label) briefing_tactical_marker_list = VBoxContainer.new() @@ -4271,8 +4277,10 @@ func _create_hud() -> void: briefing_label = Label.new() briefing_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - briefing_label.custom_minimum_size = Vector2(656, 0) - _apply_label_style(briefing_label, UI_AGED_INK, 17) + briefing_label.custom_minimum_size = Vector2(656, 92) + briefing_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + briefing_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP + _apply_label_style(briefing_label, UI_AGED_INK, BRIEFING_BODY_FONT_SIZE) briefing_scroll.add_child(briefing_label) var prep_button_row := HBoxContainer.new() @@ -4516,7 +4524,7 @@ func _create_hud() -> void: dialogue_column.add_child(dialogue_seal_ribbon) dialogue_speaker_panel = PanelContainer.new() - dialogue_speaker_panel.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x, 34) + dialogue_speaker_panel.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x, 40) _apply_panel_style(dialogue_speaker_panel, "speaker_seal") dialogue_column.add_child(dialogue_speaker_panel) @@ -4524,7 +4532,7 @@ func _create_hud() -> void: dialogue_speaker_row.add_theme_constant_override("separation", 8) dialogue_speaker_panel.add_child(dialogue_speaker_row) dialogue_speaker_label = Label.new() - dialogue_speaker_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x - 24, 24) + dialogue_speaker_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x - 24, 30) dialogue_speaker_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL dialogue_speaker_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _apply_label_style(dialogue_speaker_label, UI_PARCHMENT_TEXT, DIALOGUE_SPEAKER_FONT_SIZE) @@ -12774,8 +12782,16 @@ func _update_briefing_camp_overview(briefing: Dictionary, prep_locked: bool) -> briefing_camp_overview_row.visible = has_overview if briefing_camp_overview_panel != null: briefing_camp_overview_panel.visible = has_overview + briefing_camp_overview_panel.tooltip_text = _format_briefing_camp_overview_tooltip_text(briefing, prep_locked) if briefing_camp_overview_label != null: - briefing_camp_overview_label.text = overview_text + briefing_camp_overview_label.tooltip_text = _format_briefing_camp_overview_tooltip_text(briefing, prep_locked) + _set_fitted_label_text( + briefing_camp_overview_label, + overview_text, + BRIEFING_OVERVIEW_FONT_SIZE, + BRIEFING_OVERVIEW_MIN_FONT_SIZE, + briefing_camp_overview_label.custom_minimum_size + ) var texture := _current_battle_background_texture() var has_texture := texture != null if briefing_camp_overview_texture != null: @@ -13096,6 +13112,43 @@ func _briefing_marker_summary_from_hint(hint: String) -> String: func _format_briefing_camp_overview_text(briefing: Dictionary, prep_locked: bool) -> String: + var lines := [] + var location := str(briefing.get("location", "")).strip_edges() + if not location.is_empty(): + lines.append("위치: %s" % location) + var battlefield_parts := [] + var scale_text := _format_battlefield_scale_text(state.get_map_size()) + if not scale_text.is_empty(): + battlefield_parts.append(scale_text) + var enemy_count := state.get_living_units(BattleState.TEAM_ENEMY).size() + if enemy_count > 0: + battlefield_parts.append("적세 %d명" % enemy_count) + var recovery_count := state.get_recovery_terrain_summaries().size() + if recovery_count > 0: + battlefield_parts.append("회복지 %d곳" % recovery_count) + if not battlefield_parts.is_empty(): + lines.append("전장: %s" % _join_strings(battlefield_parts, " · ")) + var camp_parts := [] + var talk_count := _camp_conversation_entries().size() + if talk_count > 0: + camp_parts.append("회의 %d건" % talk_count) + var buy_count := state.get_shop_item_ids().size() + if buy_count > 0: + camp_parts.append("물품 %d종" % buy_count) + var deployed_count := state.get_deployed_player_count() + var max_units := state.get_deployment_max_units() + if max_units > 0: + camp_parts.append("출진 %d/%d명" % [deployed_count, max_units]) + elif deployed_count > 0: + camp_parts.append("출진 %d명" % deployed_count) + if prep_locked: + camp_parts.append("회상 전투") + if not camp_parts.is_empty(): + lines.append("군막: %s" % _join_strings(camp_parts, " · ")) + return _join_strings(lines, "\n") + + +func _format_briefing_camp_overview_tooltip_text(briefing: Dictionary, prep_locked: bool) -> String: var lines := [] var location := str(briefing.get("location", "")).strip_edges() if not location.is_empty(): @@ -13286,7 +13339,13 @@ func _show_briefing() -> void: ) briefing_objective_label.visible = not briefing_objective_label.text.is_empty() _update_briefing_objective_visibility() - briefing_label.text = body + _set_fitted_label_text( + briefing_label, + body, + BRIEFING_BODY_FONT_SIZE, + BRIEFING_BODY_MIN_FONT_SIZE, + briefing_label.custom_minimum_size + ) briefing_panel.visible = true _refresh_screen_backdrop_visibility() battle_started = false @@ -14808,7 +14867,13 @@ func _update_opening_prologue_page() -> void: ) _apply_opening_caption_text_style(opening_prologue_body_label, UI_OPENING_CAPTION_BODY) if opening_prologue_step_label != null: - opening_prologue_step_label.text = _opening_story_record_text(opening_prologue_index, page_count) + _set_fitted_label_text( + opening_prologue_step_label, + _opening_story_record_text(opening_prologue_index, page_count), + 16, + 12, + opening_prologue_step_label.custom_minimum_size + ) _apply_opening_caption_text_style(opening_prologue_step_label, UI_OPENING_CAPTION_MUTED) if opening_prologue_next_button != null: var is_last_page := opening_prologue_index >= page_count - 1 diff --git a/tools/smoke_chapter_one_polish.gd b/tools/smoke_chapter_one_polish.gd index c096ec5..9a72f49 100644 --- a/tools/smoke_chapter_one_polish.gd +++ b/tools/smoke_chapter_one_polish.gd @@ -25,6 +25,7 @@ func _init() -> void: _check_battle_presentation_sequence_contract(failures) _check_battle_start_notice_sequence(failures) _check_readability_contract(failures) + _check_first_battle_rendered_text_fit(failures) _check_korean_fallback_labels(failures) _check_dialogue_localization_and_side(failures) _check_event_dialogue_side_passthrough(failures) @@ -731,6 +732,69 @@ func _check_text_fit_contract(scene, failures: Array[String]) -> void: failures.append("fitted briefing objective text should stay within its panel") +func _check_first_battle_rendered_text_fit(failures: Array[String]) -> void: + var scene = BattleSceneScript.new() + scene._create_hud() + scene._show_opening_prologue() + for index in range(BattleSceneScript.OPENING_PROLOGUE_PAGES.size()): + scene.opening_prologue_index = index + scene._update_opening_prologue_page() + _check_label_fits_visible_area(scene, failures, scene.opening_prologue_title_label, "opening prologue title page %d" % (index + 1)) + _check_label_fits_visible_area(scene, failures, scene.opening_prologue_step_label, "opening prologue step page %d" % (index + 1)) + _check_label_fits_visible_area(scene, failures, scene.opening_prologue_body_label, "opening prologue body page %d" % (index + 1)) + + if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"): + failures.append("could not load opening battle for rendered text fit") + scene.free() + return + scene._show_briefing() + _check_label_fits_visible_area(scene, failures, scene.briefing_title_label, "opening briefing title") + _check_label_fits_visible_area(scene, failures, scene.briefing_location_label, "opening briefing location") + _check_label_fits_visible_area(scene, failures, scene.briefing_objective_label, "opening briefing objective") + _check_label_fits_visible_area(scene, failures, scene.briefing_camp_overview_label, "opening briefing map overview") + _check_label_fits_visible_area(scene, failures, scene.briefing_label, "opening briefing body") + + var briefing: Dictionary = scene.state.get_briefing() + var dialogue_lines: Array = [] + dialogue_lines.append_array(briefing.get("camp_dialogue", [])) + for conversation in briefing.get("camp_conversations", []): + if typeof(conversation) != TYPE_DICTIONARY: + continue + dialogue_lines.append_array((conversation as Dictionary).get("lines", [])) + for index in range(dialogue_lines.size()): + var normalized_lines := scene._normalized_dialogue_lines([dialogue_lines[index]]) + if normalized_lines.is_empty(): + continue + scene.active_dialogue_lines = normalized_lines + scene.active_dialogue_index = 0 + scene._render_dialogue_line() + _check_label_fits_visible_area(scene, failures, scene.dialogue_speaker_label, "opening dialogue speaker %d" % (index + 1)) + _check_label_fits_visible_area(scene, failures, scene.dialogue_text_label, "opening dialogue body %d" % (index + 1)) + scene.free() + + +func _check_label_fits_visible_area(scene, failures: Array[String], label: Label, context: String) -> void: + if label == null: + failures.append("%s missing" % context) + return + var text := str(label.text).strip_edges() + if text.is_empty(): + failures.append("%s should render visible text" % context) + return + var area := label.custom_minimum_size + if area.x <= 0.0 or area.y <= 0.0: + failures.append("%s should reserve a stable text area, got %s" % [context, str(area)]) + return + var font_size := label.get_theme_font_size("font_size") + var fitted_height := float(scene._estimated_wrapped_text_height(text, area.x, font_size)) + if fitted_height > area.y + 2.0: + failures.append("%s text should fit its visible area: height %.1f > %.1f at font %d text=`%s`" % [context, fitted_height, area.y, font_size, text]) + if font_size < 12: + failures.append("%s should not shrink below readable Korean text size: %d" % [context, font_size]) + if label.clip_text: + failures.append("%s should not hard-clip Korean text" % context) + + func _check_dialogue_localization_and_side(failures: Array[String]) -> void: var scene = BattleSceneScript.new() var lines := scene._normalized_dialogue_lines([ diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index debf8cd..2cf3ac9 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3129,11 +3129,16 @@ func _check_scene_texture_loading(failures: Array[String]) -> void: if not merchant_notice.contains("군막 상인") or not merchant_notice.contains("길가의 물자"): failures.append("shop merchant notice should include name and flavor lines") var camp_overview := scene._format_briefing_camp_overview_text(scene.state.get_briefing(), false) - for expected in ["위치: 영천, 중평 원년", "군막:", "준비:", "군자금 0냥", "군막 회의 · 4건", "보급 대기 1건", "군상 물품 · 2종", "출진 명부 2명 중 2명", "진형 배치 6칸", "비축 물자"]: + for expected in ["위치: 영천, 중평 원년", "전장:", "적세 16명", "회복지 2곳", "군막:", "회의 4건", "물품 2종", "출진 2/2명"]: if not camp_overview.contains(expected): failures.append("camp overview should include %s: %s" % [expected, camp_overview]) - if not camp_overview.contains("전장:") or not camp_overview.contains("전장"): - failures.append("camp overview should include a readable battlefield scale label: %s" % camp_overview) + for dense_text in ["군자금 0냥", "보급 대기", "진형 배치", "비축 물자"]: + if camp_overview.contains(dense_text): + failures.append("camp overview should move dense detail to tooltip: %s" % camp_overview) + var camp_overview_tooltip := scene._format_briefing_camp_overview_tooltip_text(scene.state.get_briefing(), false) + for expected_detail in ["군자금 0냥", "군막 회의 · 4건", "보급 대기 1건", "군상 물품 · 2종", "출진 명부 2명 중 2명", "진형 배치 6칸", "비축 물자"]: + if not camp_overview_tooltip.contains(expected_detail): + failures.append("camp overview tooltip should retain %s: %s" % [expected_detail, camp_overview_tooltip]) if camp_overview.contains("칸 x") or camp_overview.contains("칸 ×") or camp_overview.contains("칸x"): failures.append("camp overview should not expose raw map dimensions: %s" % camp_overview) if camp_overview.contains("칸 +"): diff --git a/tools/validate_data.ps1 b/tools/validate_data.ps1 index a44e186..9b0a896 100644 --- a/tools/validate_data.ps1 +++ b/tools/validate_data.ps1 @@ -32,6 +32,23 @@ function Fail($Message) { throw $Message } +function Check-Readable-Korean-Text([string]$Text, [string]$Context, [switch]$RequireHangul) { + if ([string]::IsNullOrWhiteSpace($Text)) { + Fail "$Context must not be empty." + } + $replacementCharacter = [string][char]0xFFFD + if ($Text.Contains($replacementCharacter)) { + Fail "$Context contains a replacement character; check UTF-8 encoding." + } + if ($Text -match '\p{IsCJKUnifiedIdeographs}') { + Fail "$Context contains Hanja/CJK ideographs; use Korean-facing Hangul text." + } + $needsHangul = $RequireHangul -or $script:CurrentScenarioRequiresHangul + if ($needsHangul -and $Text -notmatch '\p{IsHangulSyllables}') { + Fail "$Context should contain readable Hangul text." + } +} + function Check-Raw-Skills-Arrays() { $relativePaths = @( "data\defs\classes.json", @@ -80,6 +97,8 @@ $officerIds = Get-Names $officers $skillIds = Get-Names $skills $terrainKeys = Get-Names $terrain +$script:CurrentScenarioRequiresHangul = $false + $validConditionTypes = @( "all_units_defeated", "all_enemies_defeated", @@ -171,6 +190,7 @@ foreach ($scenario in @($campaign.scenarios)) { if ([string]::IsNullOrWhiteSpace($scenarioTitle)) { Fail "Campaign scenario $scenarioId has an empty title." } + Check-Readable-Korean-Text $scenarioTitle "Campaign scenario $scenarioId title" -RequireHangul if ([string]::IsNullOrWhiteSpace($scenarioPath)) { Fail "Campaign scenario $scenarioId has an empty path." } @@ -216,6 +236,7 @@ if (Has-Prop $campaign "chapters") { if ([string]::IsNullOrWhiteSpace($chapterTitle)) { Fail "Campaign chapter $chapterId has an empty title." } + Check-Readable-Korean-Text $chapterTitle "Campaign chapter $chapterId title" -RequireHangul if (-not $campaignScenarioIdSet.Contains($startScenario)) { Fail "Campaign chapter $chapterId references unknown start_scenario: $startScenario" } @@ -1325,6 +1346,7 @@ function Check-Shop($Shop, [string]$ScenarioId) { if ([string]::IsNullOrWhiteSpace($merchantName)) { Fail "Scenario $ScenarioId shop merchant has an empty name." } + Check-Readable-Korean-Text $merchantName "Scenario $ScenarioId shop merchant name" $merchantLines = Get-Prop $merchant "lines" $null if ($null -eq $merchantLines -or -not ($merchantLines -is [System.Array])) { Fail "Scenario $ScenarioId shop merchant must have line array." @@ -1333,6 +1355,7 @@ function Check-Shop($Shop, [string]$ScenarioId) { if ([string]::IsNullOrWhiteSpace([string]$merchantLine)) { Fail "Scenario $ScenarioId shop merchant has an empty line." } + Check-Readable-Korean-Text ([string]$merchantLine) "Scenario $ScenarioId shop merchant line" } } @@ -1802,11 +1825,18 @@ function Check-Briefing($Briefing, [string]$ScenarioId) { if ($Briefing -is [string] -or $Briefing -is [System.Array]) { Fail "Scenario $ScenarioId briefing must be an object." } + if (Has-Prop $Briefing "title") { + Check-Readable-Korean-Text ([string](Get-Prop $Briefing "title" "")) "Scenario $ScenarioId briefing title" + } + if (Has-Prop $Briefing "location") { + Check-Readable-Korean-Text ([string](Get-Prop $Briefing "location" "")) "Scenario $ScenarioId briefing location" + } $lines = Get-Prop $Briefing "lines" @() foreach ($line in @($lines)) { if ([string]::IsNullOrWhiteSpace([string]$line)) { Fail "Scenario $ScenarioId briefing has an empty line." } + Check-Readable-Korean-Text ([string]$line) "Scenario $ScenarioId briefing line" } foreach ($line in @((Get-Prop $Briefing "camp_dialogue" @()))) { Check-Dialogue-Line $line "Scenario $ScenarioId briefing camp_dialogue" @@ -1831,6 +1861,7 @@ function Check-Briefing($Briefing, [string]$ScenarioId) { if ([string]::IsNullOrWhiteSpace([string]$line)) { Fail "Scenario $ScenarioId briefing conditional_lines has an empty line." } + Check-Readable-Korean-Text ([string]$line) "Scenario $ScenarioId briefing conditional line" } } } @@ -1861,6 +1892,10 @@ function Check-Camp-Conversations($Conversations, [string]$ScenarioId) { if ([string]::IsNullOrWhiteSpace($label)) { Fail "Scenario $ScenarioId briefing camp_conversations $conversationId has an empty label." } + Check-Readable-Korean-Text $label "Scenario $ScenarioId briefing camp_conversations $conversationId label" + if (Has-Prop $conversation "summary") { + Check-Readable-Korean-Text ([string](Get-Prop $conversation "summary" "")) "Scenario $ScenarioId briefing camp_conversations $conversationId summary" + } $group = ([string](Get-Prop $conversation "group" "topic")).Trim().ToLowerInvariant() if (-not $validCampConversationGroups.Contains($group)) { Fail "Scenario $ScenarioId briefing camp_conversations $conversationId has unknown group: $group" @@ -1908,6 +1943,9 @@ function Check-Camp-Conversations($Conversations, [string]$ScenarioId) { if ((Has-Prop $effect "text") -and [string]::IsNullOrWhiteSpace([string](Get-Prop $effect "text" ""))) { Fail "Scenario $ScenarioId briefing camp_conversations $conversationId effect text must not be empty." } + if (Has-Prop $effect "text") { + Check-Readable-Korean-Text ([string](Get-Prop $effect "text" "")) "Scenario $ScenarioId briefing camp_conversations $conversationId effect text" + } } } } @@ -1918,6 +1956,7 @@ function Check-Dialogue-Line($Line, [string]$Context) { if ([string]::IsNullOrWhiteSpace([string]$Line)) { Fail "$Context has an empty dialogue line." } + Check-Readable-Korean-Text ([string]$Line) "$Context dialogue line" return } if ($Line -is [System.Array] -or -not (Has-Prop $Line "text")) { @@ -1926,6 +1965,10 @@ function Check-Dialogue-Line($Line, [string]$Context) { if ([string]::IsNullOrWhiteSpace([string](Get-Prop $Line "text" ""))) { Fail "$Context has an empty dialogue text." } + Check-Readable-Korean-Text ([string](Get-Prop $Line "text" "")) "$Context dialogue text" + if (Has-Prop $Line "display_speaker") { + Check-Readable-Korean-Text ([string](Get-Prop $Line "display_speaker" "")) "$Context display speaker" + } if (Has-Prop $Line "portrait") { Check-Portrait-Path (Get-Prop $Line "portrait" "") "$Context portrait" } @@ -2115,9 +2158,15 @@ foreach ($scenario in $campaign.scenarios) { $scenarioPath = ([string]$scenario.path).Replace("res://", "") $scenarioData = Read-Json $scenarioPath $scenarioId = [string]$scenarioData.id + $script:CurrentScenarioRequiresHangul = $scenarioId -match '^00[1-9]_' if ($scenarioId -ne [string]$scenario.id) { Fail "Campaign id $($scenario.id) does not match scenario file id $scenarioId." } + Check-Readable-Korean-Text ([string](Get-Prop $scenarioData "name" "")) "Scenario $scenarioId name" + if (Has-Prop $scenarioData "objectives") { + Check-Readable-Korean-Text ([string](Get-Prop $scenarioData.objectives "victory" "")) "Scenario $scenarioId victory objective" + Check-Readable-Korean-Text ([string](Get-Prop $scenarioData.objectives "defeat" "")) "Scenario $scenarioId defeat objective" + } $map = $scenarioData.map if (Has-Prop $map "background") {