Style ancient UI and refine Guandu camp

This commit is contained in:
2026-06-19 00:48:27 +09:00
parent 0603d241d3
commit febba2baec
4 changed files with 714 additions and 35 deletions

View File

@@ -32,6 +32,102 @@
"Scouts from Yan Ford report unusually heavy traffic toward Wuchao. Yuan Shao's grain may be less protected than his banners."
]
}
],
"camp_conversations": [
{
"id": "guandu_main_line_council",
"group": "officer",
"officer_id": "cao_cao",
"label": "Cao Cao - Main Line",
"speaker": "Cao Cao",
"summary": "Set the rhythm for holding Guandu against Yuan Shao's full army.",
"lines": [
{
"speaker": "Cao Cao",
"side": "left",
"text": "Yuan Shao wants numbers to be a storm. A storm still breaks against a wall that knows why it stands."
},
{
"speaker": "Xiahou Dun",
"side": "right",
"text": "Then the front holds by count, not pride. I will make the line step back only when ordered."
}
]
},
{
"id": "guandu_guo_jia_supply_wound",
"group": "officer",
"officer_id": "guo_jia",
"label": "Guo Jia - Supply Wound",
"speaker": "Guo Jia",
"summary": "Watch Yuan Shao's assault for the supply weakness that can decide the campaign.",
"lines": [
{
"speaker": "Guo Jia",
"side": "right",
"text": "A large army has more than courage to protect. It has grain roads, signal fires, and arguments between commanders."
},
{
"speaker": "Cao Cao",
"side": "left",
"text": "Then Guandu is not only a wall. It is where we make Yuan Shao reveal what feeds him."
}
]
},
{
"id": "guandu_secured_line_medicine",
"campaign_flags": { "secured_guandu_line": true },
"group": "topic",
"label": "Secured Line Medicine",
"summary": "Open the prepared camp stores for field medicine before the main assault.",
"lines": [
{
"speaker": "Cao Ren",
"side": "left",
"text": "The camps secured after Yan Ford have dry medicine and enough order to send it where the line bends."
},
{
"speaker": "Cao Cao",
"side": "right",
"text": "Issue it before Yuan Shao's banners move. A prepared camp should save men before it praises them."
}
],
"effects": [
{
"type": "grant_item",
"item_id": "panacea",
"count": 1,
"text": "The secured line medicine adds a Panacea to the field supplies."
}
]
},
{
"id": "guandu_wuchao_scout_wine",
"campaign_flags": { "scouted_wuchao_depots": true },
"group": "topic",
"label": "Wuchao Scout Wine",
"summary": "Reward the scouts who traced Yuan Shao's grain traffic toward Wuchao.",
"lines": [
{
"speaker": "Xiahou Yuan",
"side": "right",
"text": "The scouts followed the grain marks far enough to name Wuchao. They came back with horses breathing hard."
},
{
"speaker": "Guo Jia",
"side": "left",
"text": "Give them wine and fresh orders. A tired scout who found the grain has already moved a whole army."
}
],
"effects": [
{
"type": "grant_item",
"item_id": "wine",
"count": 1,
"text": "The Wuchao scout wine adds Wine to the field supplies."
}
]
}
]
},
"shop": {
@@ -49,13 +145,20 @@
"conditional_items": [
{
"campaign_flags": { "secured_guandu_line": true },
"items": ["imperial_seal"]
"items": ["panacea"]
},
{
"campaign_flags": { "scouted_wuchao_depots": true },
"items": ["war_drum"]
"items": ["antidote"]
}
]
],
"merchant": {
"name": "Guandu Camp Sutler",
"lines": [
"Buy before Yuan Shao's banners blot out the road. Once the main assault starts, even a bean needs an escort.",
"No loose loads on the front line. Medicine, wine, fitted blades, and armor that can be buckled in one breath."
]
}
},
"roster": {
"max_units": 6,

View File

@@ -69,6 +69,15 @@ const POST_MOVE_PICKER_PANEL_SIZE := Vector2(320, 210)
const TARGETING_HINT_PANEL_SIZE := Vector2(240, 94)
const LOCAL_COMMAND_PANEL_GAP := 8.0
const LOCAL_COMMAND_PANEL_BOARD_PADDING := 4.0
const UI_RICE_PAPER := Color(0.77, 0.67, 0.48, 0.96)
const UI_RICE_PAPER_DARK := Color(0.48, 0.34, 0.18, 0.96)
const UI_AGED_INK := Color(0.10, 0.065, 0.04, 1.0)
const UI_DARK_LACQUER := Color(0.18, 0.055, 0.035, 0.96)
const UI_CINNABAR := Color(0.48, 0.055, 0.035, 1.0)
const UI_OLD_BRONZE := Color(0.72, 0.52, 0.22, 1.0)
const UI_MUTED_JADE := Color(0.20, 0.36, 0.28, 1.0)
const UI_PARCHMENT_TEXT := Color(0.94, 0.84, 0.62, 1.0)
const UI_DISABLED_TEXT := Color(0.48, 0.40, 0.29, 1.0)
var state: BattleState = BattleStateScript.new()
var campaign_state: CampaignState = CampaignStateScript.new()
@@ -273,6 +282,102 @@ func _draw() -> void:
_draw_floating_texts()
func _make_panel_style(fill: Color, border: Color, border_width: int, radius: int, margin: int, shadow_size: int) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = fill
style.border_color = border
style.set_border_width_all(border_width)
style.set_corner_radius_all(radius)
style.content_margin_left = margin
style.content_margin_right = margin
style.content_margin_top = margin
style.content_margin_bottom = margin
if shadow_size > 0:
style.shadow_color = Color(0.0, 0.0, 0.0, 0.38)
style.shadow_offset = Vector2(2, 3)
style.shadow_size = shadow_size
return style
func _apply_panel_style(panel: PanelContainer, variant: String) -> void:
if panel == null:
return
var fill := UI_DARK_LACQUER
var border := UI_OLD_BRONZE
var border_width := 2
var radius := 1
var margin := 10
var shadow_size := 6
match variant:
"paper":
fill = UI_RICE_PAPER
border = UI_RICE_PAPER_DARK
margin = 12
"seal":
fill = UI_CINNABAR
border = UI_OLD_BRONZE
margin = 8
"portrait":
fill = UI_AGED_INK
border = UI_OLD_BRONZE
margin = 3
"notice":
fill = Color(0.20, 0.09, 0.035, 0.96)
border = UI_CINNABAR
margin = 8
"map":
fill = UI_AGED_INK
border = UI_OLD_BRONZE
margin = 3
"compact":
fill = Color(0.16, 0.075, 0.04, 0.96)
border = UI_OLD_BRONZE
margin = 7
shadow_size = 4
_:
pass
panel.add_theme_stylebox_override("panel", _make_panel_style(fill, border, border_width, radius, margin, shadow_size))
func _make_button_style(fill: Color, border: Color, border_width: int) -> StyleBoxFlat:
return _make_panel_style(fill, border, border_width, 1, 8, 0)
func _apply_button_style(button: Button, important: bool = false) -> void:
if button == null:
return
var normal_fill := UI_CINNABAR if important else Color(0.21, 0.075, 0.04, 0.98)
var hover_fill := Color(0.58, 0.12, 0.055, 1.0) if important else Color(0.32, 0.12, 0.055, 1.0)
var pressed_fill := Color(0.36, 0.045, 0.03, 1.0)
button.add_theme_stylebox_override("normal", _make_button_style(normal_fill, UI_OLD_BRONZE, 1))
button.add_theme_stylebox_override("hover", _make_button_style(hover_fill, UI_OLD_BRONZE, 1))
button.add_theme_stylebox_override("pressed", _make_button_style(pressed_fill, UI_OLD_BRONZE, 1))
button.add_theme_stylebox_override("focus", _make_button_style(Color(0.20, 0.36, 0.28, 0.95), UI_OLD_BRONZE, 2))
button.add_theme_stylebox_override("disabled", _make_button_style(Color(0.13, 0.09, 0.065, 0.88), UI_RICE_PAPER_DARK, 1))
button.add_theme_color_override("font_color", UI_PARCHMENT_TEXT)
button.add_theme_color_override("font_hover_color", UI_PARCHMENT_TEXT)
button.add_theme_color_override("font_pressed_color", UI_PARCHMENT_TEXT)
button.add_theme_color_override("font_focus_color", UI_PARCHMENT_TEXT)
button.add_theme_color_override("font_disabled_color", UI_DISABLED_TEXT)
func _apply_label_style(label: Label, font_color: Color, font_size: int = 0) -> void:
if label == null:
return
label.add_theme_color_override("font_color", font_color)
if font_size > 0:
label.add_theme_font_size_override("font_size", font_size)
func _make_separator(width: float = 0.0, height: float = 2.0) -> ColorRect:
var separator := ColorRect.new()
separator.color = UI_OLD_BRONZE
separator.custom_minimum_size = Vector2(width, height)
separator.size_flags_horizontal = Control.SIZE_EXPAND_FILL
separator.mouse_filter = Control.MOUSE_FILTER_IGNORE
return separator
func _create_hud() -> void:
var layer := CanvasLayer.new()
add_child(layer)
@@ -285,6 +390,7 @@ func _create_hud() -> void:
var top_bar := PanelContainer.new()
top_bar.position = Vector2(24, 16)
top_bar.size = Vector2(1120, 64)
_apply_panel_style(top_bar, "compact")
root.add_child(top_bar)
var top_row := HBoxContainer.new()
@@ -293,15 +399,18 @@ func _create_hud() -> void:
status_label = Label.new()
status_label.custom_minimum_size = Vector2(180, 40)
_apply_label_style(status_label, UI_PARCHMENT_TEXT)
top_row.add_child(status_label)
objective_label = Label.new()
objective_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
objective_label.custom_minimum_size = Vector2(360, 48)
_apply_label_style(objective_label, UI_PARCHMENT_TEXT)
top_row.add_child(objective_label)
campaign_status_label = Label.new()
campaign_status_label.custom_minimum_size = Vector2(300, 40)
_apply_label_style(campaign_status_label, UI_PARCHMENT_TEXT)
top_row.add_child(campaign_status_label)
restart_button = Button.new()
@@ -319,6 +428,7 @@ func _create_hud() -> void:
objective_notice_panel.position = Vector2(360, 84)
objective_notice_panel.size = Vector2(560, 64)
objective_notice_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
_apply_panel_style(objective_notice_panel, "notice")
root.add_child(objective_notice_panel)
objective_notice_label = Label.new()
@@ -326,11 +436,13 @@ func _create_hud() -> void:
objective_notice_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
objective_notice_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
objective_notice_label.custom_minimum_size = Vector2(520, 48)
_apply_label_style(objective_notice_label, UI_PARCHMENT_TEXT, 16)
objective_notice_panel.add_child(objective_notice_label)
var side_panel := PanelContainer.new()
side_panel.position = Vector2(760, 104)
side_panel.size = Vector2(480, 610)
_apply_panel_style(side_panel, "compact")
root.add_child(side_panel)
var side_column := VBoxContainer.new()
@@ -345,6 +457,7 @@ func _create_hud() -> void:
hud_unit_portrait_panel = PanelContainer.new()
hud_unit_portrait_panel.custom_minimum_size = HUD_UNIT_PORTRAIT_SIZE
_apply_panel_style(hud_unit_portrait_panel, "portrait")
selected_row.add_child(hud_unit_portrait_panel)
var hud_unit_portrait_stack := Control.new()
@@ -362,37 +475,44 @@ func _create_hud() -> void:
hud_unit_portrait_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
hud_unit_portrait_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
hud_unit_portrait_label.add_theme_font_size_override("font_size", 24)
_apply_label_style(hud_unit_portrait_label, UI_PARCHMENT_TEXT)
hud_unit_portrait_stack.add_child(hud_unit_portrait_label)
selected_label = Label.new()
selected_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
selected_label.custom_minimum_size = Vector2(320, 126)
selected_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_apply_label_style(selected_label, UI_PARCHMENT_TEXT)
selected_row.add_child(selected_label)
mission_title_label = Label.new()
mission_title_label.text = "Objective"
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)
mission_detail_label = Label.new()
mission_detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
mission_detail_label.custom_minimum_size = Vector2(420, 126)
_apply_label_style(mission_detail_label, UI_PARCHMENT_TEXT)
side_column.add_child(mission_detail_label)
cell_info_label = Label.new()
cell_info_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
cell_info_label.custom_minimum_size = Vector2(420, 78)
_apply_label_style(cell_info_label, UI_PARCHMENT_TEXT)
side_column.add_child(cell_info_label)
forecast_label = Label.new()
forecast_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
forecast_label.custom_minimum_size = Vector2(420, 60)
_apply_label_style(forecast_label, UI_PARCHMENT_TEXT)
side_column.add_child(forecast_label)
inventory_label = Label.new()
inventory_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
inventory_label.custom_minimum_size = Vector2(420, 34)
_apply_label_style(inventory_label, UI_PARCHMENT_TEXT)
side_column.add_child(inventory_label)
var button_row := HBoxContainer.new()
@@ -437,6 +557,7 @@ func _create_hud() -> void:
var tactic_title := Label.new()
tactic_title.text = "Tactics"
_apply_label_style(tactic_title, UI_OLD_BRONZE, 14)
tactic_menu.add_child(tactic_title)
tactic_list = VBoxContainer.new()
@@ -450,6 +571,7 @@ func _create_hud() -> void:
var item_title := Label.new()
item_title.text = "Items"
_apply_label_style(item_title, UI_OLD_BRONZE, 14)
item_menu.add_child(item_title)
item_list = VBoxContainer.new()
@@ -463,6 +585,7 @@ func _create_hud() -> void:
var equip_title := Label.new()
equip_title.text = "Equipment"
_apply_label_style(equip_title, UI_OLD_BRONZE, 14)
equip_menu.add_child(equip_title)
equip_list = VBoxContainer.new()
@@ -474,6 +597,7 @@ func _create_hud() -> void:
post_move_menu.mouse_filter = Control.MOUSE_FILTER_STOP
post_move_menu.custom_minimum_size = POST_MOVE_MENU_SIZE
post_move_menu.size = POST_MOVE_MENU_SIZE
_apply_panel_style(post_move_menu, "compact")
root.add_child(post_move_menu)
var post_move_column := VBoxContainer.new()
@@ -483,6 +607,7 @@ func _create_hud() -> void:
post_move_title_label = Label.new()
post_move_title_label.text = "Choose Action"
post_move_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_apply_label_style(post_move_title_label, UI_OLD_BRONZE, 14)
post_move_column.add_child(post_move_title_label)
var post_move_grid := GridContainer.new()
@@ -526,6 +651,7 @@ func _create_hud() -> void:
post_move_picker_panel.mouse_filter = Control.MOUSE_FILTER_STOP
post_move_picker_panel.custom_minimum_size = POST_MOVE_PICKER_PANEL_SIZE
post_move_picker_panel.size = POST_MOVE_PICKER_PANEL_SIZE
_apply_panel_style(post_move_picker_panel, "compact")
root.add_child(post_move_picker_panel)
var picker_column := VBoxContainer.new()
@@ -535,11 +661,13 @@ func _create_hud() -> void:
post_move_picker_title_label = Label.new()
post_move_picker_title_label.text = "Choose"
post_move_picker_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_apply_label_style(post_move_picker_title_label, UI_OLD_BRONZE, 14)
picker_column.add_child(post_move_picker_title_label)
post_move_picker_detail_label = Label.new()
post_move_picker_detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
post_move_picker_detail_label.custom_minimum_size = Vector2(296, 32)
_apply_label_style(post_move_picker_detail_label, UI_PARCHMENT_TEXT)
picker_column.add_child(post_move_picker_detail_label)
var picker_scroll := ScrollContainer.new()
@@ -573,6 +701,7 @@ func _create_hud() -> void:
targeting_hint_panel.mouse_filter = Control.MOUSE_FILTER_STOP
targeting_hint_panel.custom_minimum_size = TARGETING_HINT_PANEL_SIZE
targeting_hint_panel.size = TARGETING_HINT_PANEL_SIZE
_apply_panel_style(targeting_hint_panel, "compact")
root.add_child(targeting_hint_panel)
var targeting_column := VBoxContainer.new()
@@ -582,11 +711,13 @@ func _create_hud() -> void:
targeting_hint_title_label = Label.new()
targeting_hint_title_label.text = "Choose Target"
targeting_hint_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_apply_label_style(targeting_hint_title_label, UI_OLD_BRONZE, 14)
targeting_column.add_child(targeting_hint_title_label)
targeting_hint_detail_label = Label.new()
targeting_hint_detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
targeting_hint_detail_label.custom_minimum_size = Vector2(220, 28)
_apply_label_style(targeting_hint_detail_label, UI_PARCHMENT_TEXT)
targeting_column.add_child(targeting_hint_detail_label)
var targeting_button_row := HBoxContainer.new()
@@ -608,41 +739,62 @@ func _create_hud() -> void:
log_box = RichTextLabel.new()
log_box.custom_minimum_size = Vector2(420, 70)
log_box.fit_content = false
log_box.add_theme_color_override("default_color", UI_PARCHMENT_TEXT)
log_box.add_theme_stylebox_override("normal", _make_panel_style(Color(0.11, 0.07, 0.045, 0.78), UI_RICE_PAPER_DARK, 1, 1, 6, 0))
side_column.add_child(log_box)
briefing_panel = PanelContainer.new()
briefing_panel.visible = false
briefing_panel.position = Vector2(300, 88)
briefing_panel.size = Vector2(680, 600)
briefing_panel.position = Vector2(280, 72)
briefing_panel.size = Vector2(720, 628)
_apply_panel_style(briefing_panel, "paper")
root.add_child(briefing_panel)
var briefing_column := VBoxContainer.new()
briefing_column.add_theme_constant_override("separation", 8)
briefing_column.add_theme_constant_override("separation", 7)
briefing_panel.add_child(briefing_column)
var briefing_title_panel := PanelContainer.new()
briefing_title_panel.custom_minimum_size = Vector2(680, 40)
_apply_panel_style(briefing_title_panel, "seal")
briefing_column.add_child(briefing_title_panel)
briefing_title_label = Label.new()
briefing_title_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
briefing_title_label.custom_minimum_size = Vector2(640, 28)
briefing_title_label.add_theme_font_size_override("font_size", 20)
briefing_column.add_child(briefing_title_label)
briefing_title_label.custom_minimum_size = Vector2(656, 28)
briefing_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
briefing_title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(briefing_title_label, UI_PARCHMENT_TEXT, 20)
briefing_title_panel.add_child(briefing_title_label)
briefing_location_label = Label.new()
briefing_location_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
briefing_location_label.custom_minimum_size = Vector2(640, 22)
briefing_location_label.custom_minimum_size = Vector2(680, 22)
briefing_location_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_apply_label_style(briefing_location_label, UI_MUTED_JADE, 14)
briefing_column.add_child(briefing_location_label)
briefing_column.add_child(_make_separator(680, 2))
var briefing_objective_panel := PanelContainer.new()
briefing_objective_panel.custom_minimum_size = Vector2(680, 92)
_apply_panel_style(briefing_objective_panel, "compact")
briefing_column.add_child(briefing_objective_panel)
briefing_objective_label = Label.new()
briefing_objective_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
briefing_objective_label.custom_minimum_size = Vector2(640, 48)
briefing_column.add_child(briefing_objective_label)
briefing_objective_label.custom_minimum_size = Vector2(656, 72)
_apply_label_style(briefing_objective_label, UI_PARCHMENT_TEXT, 15)
briefing_objective_panel.add_child(briefing_objective_label)
briefing_camp_overview_row = HBoxContainer.new()
briefing_camp_overview_row.custom_minimum_size = Vector2(640, 76)
briefing_camp_overview_row.add_theme_constant_override("separation", 8)
briefing_camp_overview_row.custom_minimum_size = Vector2(680, 84)
briefing_camp_overview_row.add_theme_constant_override("separation", 10)
briefing_column.add_child(briefing_camp_overview_row)
var camp_thumbnail_panel := PanelContainer.new()
camp_thumbnail_panel.custom_minimum_size = BRIEFING_CAMP_THUMBNAIL_SIZE
_apply_panel_style(camp_thumbnail_panel, "map")
briefing_camp_overview_row.add_child(camp_thumbnail_panel)
var camp_thumbnail_stack := Control.new()
@@ -660,21 +812,26 @@ func _create_hud() -> void:
briefing_camp_overview_fallback_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
briefing_camp_overview_fallback_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
briefing_camp_overview_fallback_label.add_theme_font_size_override("font_size", 18)
_apply_label_style(briefing_camp_overview_fallback_label, UI_OLD_BRONZE)
camp_thumbnail_stack.add_child(briefing_camp_overview_fallback_label)
briefing_camp_overview_label = Label.new()
briefing_camp_overview_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
briefing_camp_overview_label.custom_minimum_size = Vector2(490, 72)
briefing_camp_overview_label.custom_minimum_size = Vector2(520, 72)
briefing_camp_overview_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_apply_label_style(briefing_camp_overview_label, UI_AGED_INK, 14)
briefing_camp_overview_row.add_child(briefing_camp_overview_label)
briefing_column.add_child(_make_separator(680, 1))
var briefing_scroll := ScrollContainer.new()
briefing_scroll.custom_minimum_size = Vector2(640, 72)
briefing_scroll.custom_minimum_size = Vector2(680, 66)
briefing_column.add_child(briefing_scroll)
briefing_label = Label.new()
briefing_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
briefing_label.custom_minimum_size = Vector2(620, 0)
briefing_label.custom_minimum_size = Vector2(656, 0)
_apply_label_style(briefing_label, UI_AGED_INK, 14)
briefing_scroll.add_child(briefing_label)
var prep_button_row := HBoxContainer.new()
@@ -724,6 +881,7 @@ func _create_hud() -> void:
chapter_status_label = Label.new()
chapter_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
chapter_status_label.custom_minimum_size = Vector2(600, 22)
_apply_label_style(chapter_status_label, UI_AGED_INK, 14)
chapter_menu.add_child(chapter_status_label)
var chapter_scroll := ScrollContainer.new()
@@ -743,6 +901,7 @@ func _create_hud() -> void:
shop_status_label = Label.new()
shop_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
shop_status_label.custom_minimum_size = Vector2(600, 22)
_apply_label_style(shop_status_label, UI_AGED_INK, 14)
shop_menu.add_child(shop_status_label)
var shop_mode_row := HBoxContainer.new()
@@ -776,6 +935,7 @@ func _create_hud() -> void:
talk_status_label = Label.new()
talk_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
talk_status_label.custom_minimum_size = Vector2(600, 22)
_apply_label_style(talk_status_label, UI_AGED_INK, 14)
talk_menu.add_child(talk_status_label)
var talk_scroll := ScrollContainer.new()
@@ -795,6 +955,7 @@ func _create_hud() -> void:
armory_status_label = Label.new()
armory_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
armory_status_label.custom_minimum_size = Vector2(600, 22)
_apply_label_style(armory_status_label, UI_AGED_INK, 14)
armory_menu.add_child(armory_status_label)
var armory_scroll := ScrollContainer.new()
@@ -814,6 +975,7 @@ func _create_hud() -> void:
roster_status_label = Label.new()
roster_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
roster_status_label.custom_minimum_size = Vector2(600, 22)
_apply_label_style(roster_status_label, UI_AGED_INK, 14)
roster_menu.add_child(roster_status_label)
var roster_scroll := ScrollContainer.new()
@@ -833,6 +995,7 @@ func _create_hud() -> void:
formation_status_label = Label.new()
formation_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
formation_status_label.custom_minimum_size = Vector2(600, 22)
_apply_label_style(formation_status_label, UI_AGED_INK, 14)
formation_menu.add_child(formation_status_label)
var formation_scroll := ScrollContainer.new()
@@ -852,6 +1015,7 @@ func _create_hud() -> void:
save_status_label = Label.new()
save_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
save_status_label.custom_minimum_size = Vector2(600, 64)
_apply_label_style(save_status_label, UI_AGED_INK, 14)
save_menu.add_child(save_status_label)
var save_action_row := HBoxContainer.new()
@@ -878,6 +1042,7 @@ func _create_hud() -> void:
dialogue_panel.position = DIALOGUE_PANEL_POSITION
dialogue_panel.size = DIALOGUE_PANEL_SIZE
dialogue_panel.mouse_filter = Control.MOUSE_FILTER_STOP
_apply_panel_style(dialogue_panel, "compact")
root.add_child(dialogue_panel)
dialogue_row = HBoxContainer.new()
@@ -886,6 +1051,7 @@ func _create_hud() -> void:
dialogue_portrait_panel = PanelContainer.new()
dialogue_portrait_panel.custom_minimum_size = DIALOGUE_PORTRAIT_SIZE
_apply_panel_style(dialogue_portrait_panel, "portrait")
dialogue_row.add_child(dialogue_portrait_panel)
var dialogue_portrait_stack := Control.new()
@@ -903,6 +1069,7 @@ func _create_hud() -> void:
dialogue_portrait_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
dialogue_portrait_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
dialogue_portrait_label.add_theme_font_size_override("font_size", 30)
_apply_label_style(dialogue_portrait_label, UI_PARCHMENT_TEXT)
dialogue_portrait_stack.add_child(dialogue_portrait_label)
dialogue_column = VBoxContainer.new()
@@ -911,17 +1078,28 @@ func _create_hud() -> void:
dialogue_column.add_theme_constant_override("separation", 8)
dialogue_row.add_child(dialogue_column)
var dialogue_speaker_panel := PanelContainer.new()
dialogue_speaker_panel.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x, 32)
_apply_panel_style(dialogue_speaker_panel, "seal")
dialogue_column.add_child(dialogue_speaker_panel)
dialogue_speaker_label = Label.new()
dialogue_speaker_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x, 26)
dialogue_speaker_label.add_theme_font_size_override("font_size", 18)
dialogue_column.add_child(dialogue_speaker_label)
dialogue_speaker_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x - 24, 22)
dialogue_speaker_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(dialogue_speaker_label, UI_PARCHMENT_TEXT, 18)
dialogue_speaker_panel.add_child(dialogue_speaker_label)
var dialogue_text_panel := PanelContainer.new()
dialogue_text_panel.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x, 104)
_apply_panel_style(dialogue_text_panel, "paper")
dialogue_column.add_child(dialogue_text_panel)
dialogue_text_label = Label.new()
dialogue_text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
dialogue_text_label.custom_minimum_size = DIALOGUE_TEXT_SIZE
dialogue_text_label.custom_minimum_size = Vector2(DIALOGUE_TEXT_SIZE.x - 24, DIALOGUE_TEXT_SIZE.y - 20)
dialogue_text_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
dialogue_text_label.add_theme_font_size_override("font_size", 17)
dialogue_column.add_child(dialogue_text_label)
_apply_label_style(dialogue_text_label, UI_AGED_INK, 17)
dialogue_text_panel.add_child(dialogue_text_label)
var dialogue_control_row := HBoxContainer.new()
dialogue_control_row.add_theme_constant_override("separation", 8)
@@ -931,6 +1109,7 @@ func _create_hud() -> void:
dialogue_progress_label.custom_minimum_size = Vector2(520, 32)
dialogue_progress_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
dialogue_progress_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
_apply_label_style(dialogue_progress_label, UI_OLD_BRONZE)
dialogue_control_row.add_child(dialogue_progress_label)
dialogue_previous_button = Button.new()
@@ -950,6 +1129,7 @@ func _create_hud() -> void:
result_panel.visible = false
result_panel.position = Vector2(280, 156)
result_panel.size = Vector2(560, 420)
_apply_panel_style(result_panel, "paper")
root.add_child(result_panel)
var result_column := VBoxContainer.new()
@@ -961,6 +1141,7 @@ func _create_hud() -> void:
result_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP
result_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
result_label.custom_minimum_size = Vector2(520, 236)
_apply_label_style(result_label, UI_AGED_INK, 16)
result_column.add_child(result_label)
result_choice_list = VBoxContainer.new()
@@ -977,6 +1158,43 @@ func _create_hud() -> void:
next_battle_button.pressed.connect(_on_next_battle_pressed)
result_column.add_child(next_battle_button)
for hud_button in [
restart_button,
new_campaign_button,
wait_button,
tactic_button,
item_button,
equip_button,
threat_button,
end_turn_button,
post_move_attack_button,
post_move_tactic_button,
post_move_item_button,
post_move_wait_button,
post_move_cancel_button,
post_move_picker_back_button,
post_move_picker_cancel_move_button,
targeting_hint_back_button,
targeting_hint_cancel_move_button,
chapter_button,
shop_button,
talk_button,
armory_button,
roster_button,
formation_button,
save_button,
shop_buy_button,
shop_sell_button,
manual_save_button,
manual_load_button,
dialogue_previous_button,
dialogue_continue_button,
result_restart_button,
next_battle_button
]:
_apply_button_style(hud_button)
_apply_button_style(begin_button, true)
func _create_audio() -> void:
bgm_player = AudioStreamPlayer.new()
@@ -3202,7 +3420,7 @@ func _update_result_panel() -> void:
result_restart_button.visible = false
if next_battle_button != null:
next_battle_button.visible = false
result_label.text = "%s Complete\nAll current scenarios cleared.\nGold %d\nStart a new campaign to replay." % [
result_label.text = "%s Chronicle Complete\nAll current scenarios cleared.\nGold %d\nStart a new campaign to replay." % [
campaign_state.campaign_title,
campaign_state.gold
]
@@ -3222,7 +3440,7 @@ func _update_result_panel() -> void:
if result_restart_button != null:
result_restart_button.visible = true
_update_result_next_button_visibility()
result_label.text = "Victory\n%s\n%s" % [
result_label.text = "Victory Proclamation\n%s\n%s" % [
state.objectives.get("victory", ""),
_format_battle_result_summary()
]
@@ -3234,7 +3452,7 @@ func _update_result_panel() -> void:
result_restart_button.visible = true
if next_battle_button != null:
next_battle_button.visible = false
result_label.text = "Defeat\n%s" % state.objectives.get("defeat", "")
result_label.text = "Defeat Report\n%s" % state.objectives.get("defeat", "")
else:
_clear_result_choices()
result_panel.visible = false
@@ -3263,7 +3481,7 @@ func _on_log_added(message: String) -> void:
func _on_objective_updated(victory: String, defeat: String) -> void:
var notice_lines := ["Objective Updated"]
var notice_lines := ["New War Order"]
if not victory.is_empty():
notice_lines.append(victory)
if not defeat.is_empty():
@@ -3989,27 +4207,27 @@ func _format_briefing_objectives() -> String:
var text := ""
var victory := str(state.objectives.get("victory", ""))
if not victory.is_empty():
text = "Victory: %s" % victory
text = "War Edict: %s" % victory
var defeat := str(state.objectives.get("defeat", ""))
if not defeat.is_empty():
if not text.is_empty():
text += "\n"
text += "Defeat: %s" % defeat
text += "Ruin If: %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 += "Progress: %s" % _join_strings(progress_lines, ", ")
text += "War Report: %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 += "Risk: %s" % _join_strings(risk_lines, ", ")
text += "Ill Omen: %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 += "Rewards: %s" % rewards_text
text += "Spoils: %s" % rewards_text
return text
@@ -4250,6 +4468,7 @@ func _rebuild_chapter_menu() -> void:
if entries.is_empty():
var empty_label := Label.new()
empty_label.text = "No campaign chapters are defined."
_apply_label_style(empty_label, UI_AGED_INK)
chapter_list.add_child(empty_label)
return
for entry in entries:
@@ -4272,6 +4491,7 @@ func _rebuild_chapter_menu() -> void:
chapter_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
chapter_label.custom_minimum_size = Vector2(560, 0)
chapter_label.text = line
_apply_label_style(chapter_label, UI_AGED_INK)
chapter_list.add_child(chapter_label)
for scenario in entry.get("scenarios", []):
_add_chapter_scenario_entry(scenario)
@@ -4291,6 +4511,7 @@ func _add_chapter_scenario_entry(scenario: Dictionary) -> void:
var scenario_button := Button.new()
scenario_button.custom_minimum_size = Vector2(560, 0)
scenario_button.text = text
_apply_button_style(scenario_button)
scenario_button.pressed.connect(_on_chapter_scenario_pressed.bind(scenario_id))
chapter_list.add_child(scenario_button)
else:
@@ -4298,6 +4519,7 @@ func _add_chapter_scenario_entry(scenario: Dictionary) -> void:
scenario_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
scenario_label.custom_minimum_size = Vector2(560, 0)
scenario_label.text = text
_apply_label_style(scenario_label, UI_AGED_INK)
chapter_list.add_child(scenario_label)
@@ -4379,6 +4601,7 @@ func _rebuild_talk_menu() -> void:
if conversations.is_empty():
var empty_label := Label.new()
empty_label.text = "No camp conversations"
_apply_label_style(empty_label, UI_AGED_INK)
talk_list.add_child(empty_label)
return
for conversation in conversations:
@@ -4403,6 +4626,7 @@ func _add_camp_conversation_row(conversation: Dictionary) -> void:
button.text = _format_camp_conversation_row_title(conversation)
button.custom_minimum_size = Vector2(500, 32)
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_apply_button_style(button)
button.pressed.connect(_on_camp_conversation_pressed.bind(conversation_id))
column.add_child(button)
@@ -4410,12 +4634,14 @@ func _add_camp_conversation_row(conversation: Dictionary) -> void:
preview_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
preview_label.custom_minimum_size = Vector2(500, 24)
preview_label.text = _format_camp_conversation_preview_text(conversation)
_apply_label_style(preview_label, UI_AGED_INK)
column.add_child(preview_label)
func _make_camp_conversation_portrait(conversation: Dictionary) -> PanelContainer:
var portrait_panel := PanelContainer.new()
portrait_panel.custom_minimum_size = CAMP_TALK_PORTRAIT_SIZE
_apply_panel_style(portrait_panel, "portrait")
var stack := Control.new()
stack.custom_minimum_size = CAMP_TALK_PORTRAIT_SIZE
@@ -4432,6 +4658,7 @@ func _make_camp_conversation_portrait(conversation: Dictionary) -> PanelContaine
initials_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
initials_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
initials_label.add_theme_font_size_override("font_size", 18)
_apply_label_style(initials_label, UI_PARCHMENT_TEXT)
stack.add_child(initials_label)
var speaker := _camp_conversation_preview_speaker(conversation)
@@ -4914,6 +5141,7 @@ func _rebuild_roster_menu() -> void:
if units.is_empty():
var empty_label := Label.new()
empty_label.text = "No officers"
_apply_label_style(empty_label, UI_AGED_INK)
roster_list.add_child(empty_label)
return
@@ -4921,6 +5149,7 @@ func _rebuild_roster_menu() -> void:
var unit_id := str(unit.get("id", ""))
var roster_unit_button := Button.new()
roster_unit_button.text = _format_roster_unit_button_text(unit)
_apply_button_style(roster_unit_button)
var deployed := bool(unit.get("deployed", true))
roster_unit_button.disabled = (
bool(unit.get("required_deployment", false))
@@ -4987,6 +5216,7 @@ func _rebuild_formation_menu() -> void:
if units.is_empty():
var empty_label := Label.new()
empty_label.text = "No deployed officers"
_apply_label_style(empty_label, UI_AGED_INK)
formation_list.add_child(empty_label)
return
@@ -4994,6 +5224,7 @@ func _rebuild_formation_menu() -> void:
var unit_id := str(unit.get("id", ""))
var unit_button := Button.new()
unit_button.text = _format_formation_unit_button_text(unit)
_apply_button_style(unit_button)
unit_button.disabled = unit_id == formation_unit_id
unit_button.pressed.connect(_on_formation_unit_pressed.bind(unit_id))
formation_list.add_child(unit_button)
@@ -5055,17 +5286,20 @@ func _rebuild_armory_menu() -> void:
if units.is_empty():
var empty_label := Label.new()
empty_label.text = "No deployed officers"
_apply_label_style(empty_label, UI_AGED_INK)
armory_list.add_child(empty_label)
return
var unit_title := Label.new()
unit_title.text = "Officers"
_apply_label_style(unit_title, UI_CINNABAR, 14)
armory_list.add_child(unit_title)
for unit in units:
var unit_id := str(unit.get("id", ""))
var unit_button := Button.new()
unit_button.text = _format_armory_unit_button_text(unit)
_apply_button_style(unit_button)
unit_button.disabled = unit_id == armory_unit_id
unit_button.pressed.connect(_on_armory_unit_pressed.bind(unit_id))
armory_list.add_child(unit_button)
@@ -5077,12 +5311,14 @@ func _rebuild_armory_menu() -> void:
var equipment_label := Label.new()
equipment_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
equipment_label.text = "Equipped: %s" % _format_unit_equipment(selected)
_apply_label_style(equipment_label, UI_AGED_INK)
armory_list.add_child(equipment_label)
var equipped_slots := state.get_equipped_equipment_slots(armory_unit_id)
if not equipped_slots.is_empty():
var unequip_title := Label.new()
unequip_title.text = "Unequip"
_apply_label_style(unequip_title, UI_CINNABAR, 14)
armory_list.add_child(unequip_title)
var equipment := state.get_equipment_snapshot(armory_unit_id)
for slot in equipped_slots:
@@ -5091,6 +5327,7 @@ func _rebuild_armory_menu() -> void:
var item := state.get_item_def(item_id)
var unequip_button := Button.new()
unequip_button.text = _format_equipment_unequip_text(selected, normalized_slot, item_id, item)
_apply_button_style(unequip_button)
unequip_button.pressed.connect(_on_armory_unequip_pressed.bind(normalized_slot))
armory_list.add_child(unequip_button)
@@ -5098,6 +5335,7 @@ func _rebuild_armory_menu() -> void:
if item_ids.is_empty():
var empty_equipment_label := Label.new()
empty_equipment_label.text = "No compatible equipment in inventory"
_apply_label_style(empty_equipment_label, UI_AGED_INK)
armory_list.add_child(empty_equipment_label)
else:
var inventory := state.get_inventory_snapshot()
@@ -5111,6 +5349,7 @@ func _rebuild_armory_menu() -> void:
item,
int(inventory.get(normalized_id, 0))
)
_apply_button_style(armory_equip_button)
armory_equip_button.pressed.connect(_on_armory_equip_pressed.bind(normalized_id))
armory_list.add_child(armory_equip_button)
@@ -5214,6 +5453,7 @@ func _rebuild_shop_buy_list() -> void:
if item_ids.is_empty():
var empty_label := Label.new()
empty_label.text = "No shop stock"
_apply_label_style(empty_label, UI_AGED_INK)
shop_list.add_child(empty_label)
return
@@ -5233,6 +5473,7 @@ func _rebuild_shop_buy_list() -> void:
buy_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
buy_button.tooltip_text = _format_shop_item_tooltip_text(_format_shop_item_button_text(normalized_id, item, price, owned, stock_limit, remaining), detail_text)
buy_button.disabled = campaign_state.gold < price or (stock_limit >= 0 and remaining <= 0)
_apply_button_style(buy_button)
buy_button.pressed.connect(_on_shop_item_pressed.bind(normalized_id))
_add_shop_item_row(normalized_id, item, buy_button, detail_text)
@@ -5242,6 +5483,7 @@ func _rebuild_shop_sell_list() -> void:
if item_ids.is_empty():
var empty_label := Label.new()
empty_label.text = "No sellable stock"
_apply_label_style(empty_label, UI_AGED_INK)
shop_list.add_child(empty_label)
return
@@ -5257,6 +5499,7 @@ func _rebuild_shop_sell_list() -> void:
sell_button.custom_minimum_size = Vector2(500, 32)
sell_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
sell_button.tooltip_text = _format_shop_item_tooltip_text(_format_shop_sell_item_button_text(normalized_id, item, sale_price, owned), detail_text)
_apply_button_style(sell_button)
sell_button.pressed.connect(_on_shop_sell_item_pressed.bind(normalized_id))
_add_shop_item_row(normalized_id, item, sell_button, detail_text)
@@ -5284,6 +5527,7 @@ func _add_shop_merchant_notice() -> void:
merchant_label.custom_minimum_size = Vector2(500, 0)
merchant_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
merchant_label.text = notice_text
_apply_label_style(merchant_label, UI_AGED_INK)
row.add_child(merchant_label)
@@ -5306,11 +5550,13 @@ func _format_shop_merchant_notice_text(merchant: Dictionary) -> String:
func _make_initials_badge(label_text: String, badge_size: Vector2, font_size: int) -> PanelContainer:
var panel := PanelContainer.new()
panel.custom_minimum_size = badge_size
_apply_panel_style(panel, "portrait")
var label := Label.new()
label.set_anchors_preset(Control.PRESET_FULL_RECT)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.add_theme_font_size_override("font_size", font_size)
_apply_label_style(label, UI_PARCHMENT_TEXT)
label.text = _dialogue_portrait_initials(label_text)
panel.add_child(label)
return panel
@@ -5335,12 +5581,14 @@ func _add_shop_item_row(item_id: String, item: Dictionary, action_button: Button
detail_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
detail_label.custom_minimum_size = Vector2(500, 20)
detail_label.text = detail_text
_apply_label_style(detail_label, UI_AGED_INK)
column.add_child(detail_label)
func _make_shop_item_icon_panel(item_id: String, item: Dictionary) -> PanelContainer:
var icon_panel := PanelContainer.new()
icon_panel.custom_minimum_size = SHOP_ITEM_ICON_SIZE
_apply_panel_style(icon_panel, "portrait")
var stack := Control.new()
stack.custom_minimum_size = SHOP_ITEM_ICON_SIZE
@@ -5357,6 +5605,7 @@ func _make_shop_item_icon_panel(item_id: String, item: Dictionary) -> PanelConta
initials_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
initials_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
initials_label.add_theme_font_size_override("font_size", 16)
_apply_label_style(initials_label, UI_PARCHMENT_TEXT)
stack.add_child(initials_label)
var texture := _load_art_texture(str(item.get("icon", "")))
@@ -5596,6 +5845,7 @@ func _rebuild_result_choices() -> void:
continue
var choice_button := Button.new()
choice_button.text = _format_post_battle_choice_text(choice)
_apply_button_style(choice_button, true)
choice_button.pressed.connect(_on_post_battle_choice_pressed.bind(choice))
result_choice_list.add_child(choice_button)
@@ -5662,7 +5912,7 @@ func _on_post_battle_choice_pressed(choice: Dictionary) -> void:
_rebuild_result_choices()
_update_result_next_button_visibility()
if result_label != null and state.battle_status == BattleState.STATUS_VICTORY:
result_label.text = "Victory\n%s\n%s" % [
result_label.text = "Victory Proclamation\n%s\n%s" % [
state.objectives.get("victory", ""),
_format_battle_result_summary()
]
@@ -6222,6 +6472,7 @@ func _rebuild_post_move_tactic_picker(selected: Dictionary) -> void:
if skill_ids.is_empty():
var empty_label := Label.new()
empty_label.text = "No tactics"
_apply_label_style(empty_label, UI_PARCHMENT_TEXT)
post_move_picker_list.add_child(empty_label)
return
for skill_id_value in skill_ids:
@@ -6232,6 +6483,7 @@ func _rebuild_post_move_tactic_picker(selected: Dictionary) -> void:
var disabled_reason := _post_move_tactic_disabled_reason(selected, skill_id, skill)
skill_button.disabled = not disabled_reason.is_empty()
skill_button.tooltip_text = disabled_reason if not disabled_reason.is_empty() else "Choose this tactic."
_apply_button_style(skill_button)
skill_button.pressed.connect(_on_post_move_tactic_option_pressed.bind(skill_id))
post_move_picker_list.add_child(skill_button)
@@ -6241,6 +6493,7 @@ func _rebuild_post_move_item_picker(selected: Dictionary) -> void:
if item_ids.is_empty():
var empty_label := Label.new()
empty_label.text = "No items"
_apply_label_style(empty_label, UI_PARCHMENT_TEXT)
post_move_picker_list.add_child(empty_label)
return
var inventory := state.get_inventory_snapshot()
@@ -6254,6 +6507,7 @@ func _rebuild_post_move_item_picker(selected: Dictionary) -> void:
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 "Choose this item."
_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)
@@ -6652,12 +6906,14 @@ func _rebuild_tactic_menu(selected: Dictionary) -> void:
if skill_ids.is_empty():
var empty_label := Label.new()
empty_label.text = "No tactics"
_apply_label_style(empty_label, UI_PARCHMENT_TEXT)
tactic_list.add_child(empty_label)
return
var skill_sealed := state.is_unit_skill_sealed(selected["id"])
if skill_sealed:
var sealed_label := Label.new()
sealed_label.text = "Tactics sealed"
_apply_label_style(sealed_label, UI_PARCHMENT_TEXT)
tactic_list.add_child(sealed_label)
for skill_id in skill_ids:
@@ -6665,11 +6921,13 @@ func _rebuild_tactic_menu(selected: Dictionary) -> void:
var skill_button := Button.new()
skill_button.text = _format_tactic_button_text(str(skill_id), skill, selected)
skill_button.disabled = skill_sealed or selected.get("acted", false) or _is_input_locked() or int(selected.get("mp", 0)) < int(skill.get("mp_cost", 0))
_apply_button_style(skill_button)
skill_button.pressed.connect(_on_tactic_skill_pressed.bind(str(skill_id)))
tactic_list.add_child(skill_button)
var cancel_button := Button.new()
cancel_button.text = "Cancel"
_apply_button_style(cancel_button)
cancel_button.pressed.connect(_on_tactic_cancel_pressed)
tactic_list.add_child(cancel_button)
@@ -6832,6 +7090,7 @@ func _rebuild_item_menu(selected: Dictionary) -> void:
if item_ids.is_empty():
var empty_label := Label.new()
empty_label.text = "No items"
_apply_label_style(empty_label, UI_PARCHMENT_TEXT)
item_list.add_child(empty_label)
return
@@ -6844,11 +7103,13 @@ func _rebuild_item_menu(selected: Dictionary) -> void:
use_button.text = _format_item_button_text(normalized_id, item, count)
_apply_item_button_icon(use_button, item)
use_button.disabled = selected.get("acted", false) or _is_input_locked() or count <= 0
_apply_button_style(use_button)
use_button.pressed.connect(_on_item_selected_pressed.bind(normalized_id))
item_list.add_child(use_button)
var cancel_button := Button.new()
cancel_button.text = "Cancel"
_apply_button_style(cancel_button)
cancel_button.pressed.connect(_on_item_cancel_pressed)
item_list.add_child(cancel_button)
@@ -6954,12 +7215,14 @@ func _rebuild_equip_menu(selected: Dictionary) -> void:
var equipment_label := Label.new()
equipment_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
equipment_label.text = "Equipped: %s" % _format_unit_equipment(selected)
_apply_label_style(equipment_label, UI_PARCHMENT_TEXT)
equip_list.add_child(equipment_label)
var equipped_slots := state.get_equipped_equipment_slots(str(selected["id"]))
if not equipped_slots.is_empty():
var unequip_title := Label.new()
unequip_title.text = "Unequip"
_apply_label_style(unequip_title, UI_OLD_BRONZE, 14)
equip_list.add_child(unequip_title)
var equipment := state.get_equipment_snapshot(str(selected["id"]))
for slot in equipped_slots:
@@ -6970,6 +7233,7 @@ func _rebuild_equip_menu(selected: Dictionary) -> void:
unequip_button.text = _format_equipment_unequip_text(selected, normalized_slot, item_id, item)
_apply_item_button_icon(unequip_button, item)
unequip_button.disabled = _is_input_locked()
_apply_button_style(unequip_button)
unequip_button.pressed.connect(_on_equip_unequip_pressed.bind(normalized_slot))
equip_list.add_child(unequip_button)
@@ -6977,6 +7241,7 @@ func _rebuild_equip_menu(selected: Dictionary) -> void:
if item_ids.is_empty():
var empty_label := Label.new()
empty_label.text = "No compatible equipment"
_apply_label_style(empty_label, UI_PARCHMENT_TEXT)
equip_list.add_child(empty_label)
else:
var inventory := state.get_inventory_snapshot()
@@ -6992,11 +7257,13 @@ func _rebuild_equip_menu(selected: Dictionary) -> void:
)
_apply_item_button_icon(equip_item_button, item)
equip_item_button.disabled = _is_input_locked()
_apply_button_style(equip_item_button)
equip_item_button.pressed.connect(_on_equip_item_pressed.bind(normalized_id))
equip_list.add_child(equip_item_button)
var cancel_button := Button.new()
cancel_button.text = "Cancel"
_apply_button_style(cancel_button)
cancel_button.pressed.connect(_on_equip_cancel_pressed)
equip_list.add_child(cancel_button)

View File

@@ -42,6 +42,10 @@ const YAN_FORD_SCENARIO_ID := "011_yan_ford_pursuit"
const YAN_FORD_SCENARIO_PATH := "res://data/scenarios/011_yan_ford_pursuit.json"
const YAN_FORD_FORTIFIED_CONVERSATION_ID := "yan_ford_fortified_crossing_medicine"
const YAN_FORD_RAIDED_CONVERSATION_ID := "yan_ford_raided_supply_beans"
const GUANDU_SCENARIO_ID := "012_guandu_showdown"
const GUANDU_SCENARIO_PATH := "res://data/scenarios/012_guandu_showdown.json"
const GUANDU_SECURED_CONVERSATION_ID := "guandu_secured_line_medicine"
const GUANDU_SCOUTED_CONVERSATION_ID := "guandu_wuchao_scout_wine"
const WUCHAO_SCENARIO_ID := "013_wuchao_raid"
const WUCHAO_SCENARIO_PATH := "res://data/scenarios/013_wuchao_raid.json"
const SAVE_PATH := "user://campaign_save.json"
@@ -63,6 +67,8 @@ func _init() -> void:
_check_xiapi_branch_camp_conversations(failures)
_check_white_horse_branch_camp_conversations(failures)
_check_yan_ford_branch_camp_conversations(failures)
_check_guandu_branch_camp_conversations(failures)
_check_guandu_post_battle_choice_bridges_to_wuchao(failures)
_check_manual_checkpoint_reverts_claim(failures)
_check_completed_replay_cannot_claim(failures)
_check_conditional_camp_conversations(failures)
@@ -760,6 +766,181 @@ func _check_yan_ford_branch_camp_conversations(failures: Array[String]) -> void:
raided_scene.free()
func _check_guandu_branch_camp_conversations(failures: Array[String]) -> void:
var base_scene = _new_prebattle_scene_for(failures, "Guandu base camp", GUANDU_SCENARIO_ID, GUANDU_SCENARIO_PATH)
if base_scene != null:
if base_scene._camp_conversation_by_id("guandu_main_line_council").is_empty():
failures.append("Guandu should expose the main line council conversation")
if base_scene._camp_conversation_by_id("guandu_guo_jia_supply_wound").is_empty():
failures.append("Guandu should expose Guo Jia's supply wound conversation")
if not base_scene._camp_conversation_by_id(GUANDU_SECURED_CONVERSATION_ID).is_empty():
failures.append("Guandu secured line medicine should be hidden without secured line flag")
if not base_scene._camp_conversation_by_id(GUANDU_SCOUTED_CONVERSATION_ID).is_empty():
failures.append("Guandu Wuchao scout wine should be hidden without scouted Wuchao flag")
var merchant: Dictionary = base_scene.state.get_shop_merchant()
if str(merchant.get("name", "")) != "Guandu Camp Sutler":
failures.append("Guandu shop merchant name should be present")
var merchant_lines: Array = merchant.get("lines", [])
if merchant_lines.size() < 2:
failures.append("Guandu shop merchant should expose flavor lines")
if base_scene._format_talk_status_text(base_scene._camp_conversation_entries()) != "Camp conversations | 2 topics":
failures.append("Guandu base talk status should summarize two non-supply topics")
base_scene.free()
var secured_scene = _new_prebattle_scene_for(
failures,
"Guandu secured line camp",
GUANDU_SCENARIO_ID,
GUANDU_SCENARIO_PATH,
{"secured_guandu_line": true}
)
if secured_scene != null:
var medicine: Dictionary = secured_scene._camp_conversation_by_id(GUANDU_SECURED_CONVERSATION_ID)
if medicine.is_empty():
failures.append("secured Guandu line flag should expose Guandu secured line medicine")
if not secured_scene._camp_conversation_by_id(GUANDU_SCOUTED_CONVERSATION_ID).is_empty():
failures.append("secured Guandu line flag should not expose Guandu Wuchao scout wine")
if not medicine.is_empty():
if secured_scene._format_talk_status_text(secured_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready":
failures.append("Guandu secured line talk status should show one ready supply")
if not secured_scene._format_camp_conversation_button_text(medicine).contains("Supply Panacea"):
failures.append("Guandu secured line medicine button should preview Panacea supply")
var before_panacea := int(secured_scene.campaign_state.get_inventory_snapshot().get("panacea", 0))
secured_scene._apply_camp_conversation_effects(medicine)
if int(secured_scene.campaign_state.get_inventory_snapshot().get("panacea", 0)) != before_panacea + 1:
failures.append("Guandu secured line medicine should add Panacea to campaign inventory")
if int(secured_scene.state.get_inventory_snapshot().get("panacea", 0)) != before_panacea + 1:
failures.append("Guandu secured line medicine should refresh battle Panacea inventory")
if not secured_scene.campaign_state.has_claimed_camp_conversation_effects(GUANDU_SCENARIO_ID, GUANDU_SECURED_CONVERSATION_ID):
failures.append("Guandu secured line medicine claim should be saved")
if secured_scene._format_talk_status_text(secured_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed":
failures.append("Guandu secured line talk status should show claimed supply after claim")
secured_scene.free()
var scouted_scene = _new_prebattle_scene_for(
failures,
"Guandu scouted Wuchao camp",
GUANDU_SCENARIO_ID,
GUANDU_SCENARIO_PATH,
{"scouted_wuchao_depots": true}
)
if scouted_scene != null:
var wine: Dictionary = scouted_scene._camp_conversation_by_id(GUANDU_SCOUTED_CONVERSATION_ID)
if wine.is_empty():
failures.append("scouted Wuchao flag should expose Guandu Wuchao scout wine")
if not scouted_scene._camp_conversation_by_id(GUANDU_SECURED_CONVERSATION_ID).is_empty():
failures.append("scouted Wuchao flag should not expose Guandu secured line medicine")
if not wine.is_empty():
if scouted_scene._format_talk_status_text(scouted_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready":
failures.append("Guandu scouted Wuchao talk status should show one ready supply")
if not scouted_scene._format_camp_conversation_button_text(wine).contains("Supply Wine"):
failures.append("Guandu Wuchao scout wine button should preview Wine supply")
var before_wine := int(scouted_scene.campaign_state.get_inventory_snapshot().get("wine", 0))
scouted_scene._apply_camp_conversation_effects(wine)
if int(scouted_scene.campaign_state.get_inventory_snapshot().get("wine", 0)) != before_wine + 1:
failures.append("Guandu Wuchao scout wine should add Wine to campaign inventory")
if int(scouted_scene.state.get_inventory_snapshot().get("wine", 0)) != before_wine + 1:
failures.append("Guandu Wuchao scout wine should refresh battle Wine inventory")
if not scouted_scene.campaign_state.has_claimed_camp_conversation_effects(GUANDU_SCENARIO_ID, GUANDU_SCOUTED_CONVERSATION_ID):
failures.append("Guandu Wuchao scout wine claim should be saved")
if scouted_scene._format_talk_status_text(scouted_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed":
failures.append("Guandu scouted Wuchao talk status should show claimed supply after claim")
scouted_scene.free()
func _check_guandu_post_battle_choice_bridges_to_wuchao(failures: Array[String]) -> void:
_check_guandu_choice_branch_to_wuchao(
failures,
"prepare_wuchao_raid",
"prepared_wuchao_raid",
"strengthened_guandu_defense",
"wuchao_fast_raid_supplies",
"wuchao_guandu_reserve_wagon",
"war_drum",
"imperial_seal"
)
_check_guandu_choice_branch_to_wuchao(
failures,
"strengthen_guandu_defense",
"strengthened_guandu_defense",
"prepared_wuchao_raid",
"wuchao_guandu_reserve_wagon",
"wuchao_fast_raid_supplies",
"imperial_seal",
"war_drum"
)
func _check_guandu_choice_branch_to_wuchao(
failures: Array[String],
choice_id: String,
expected_flag: String,
cleared_flag: String,
expected_conversation_id: String,
hidden_conversation_id: String,
expected_shop_item_id: String,
hidden_shop_item_id: String
) -> void:
var scene = _new_prebattle_scene_for(failures, "Guandu choice bridge %s" % choice_id, GUANDU_SCENARIO_ID, GUANDU_SCENARIO_PATH)
if scene == null:
return
scene.state.battle_status = "victory"
var result: Dictionary = scene.campaign_state.apply_battle_result(scene.state)
if not bool(result.get("saved", false)):
failures.append("Guandu choice bridge %s should save 012 victory" % choice_id)
scene.free()
return
if bool(result.get("choice_applied", true)):
failures.append("Guandu choice bridge %s should wait for a selected post-battle choice" % choice_id)
if scene.campaign_state.pending_post_battle_choice_scenario_id != GUANDU_SCENARIO_ID:
failures.append("Guandu choice bridge %s should mark Guandu choice pending" % choice_id)
var choice := _find_choice_by_id(result.get("post_battle_choices", []), choice_id)
if choice.is_empty():
failures.append("Guandu choice bridge missing choice: %s" % choice_id)
scene.free()
return
if not scene.campaign_state.try_apply_post_battle_choice(choice, GUANDU_SCENARIO_ID):
failures.append("Guandu choice bridge %s should save selected choice" % choice_id)
scene.free()
return
var flags: Dictionary = scene.campaign_state.get_flags_snapshot()
if flags.get(expected_flag, false) != true:
failures.append("Guandu choice bridge %s should set %s" % [choice_id, expected_flag])
if flags.get(cleared_flag, true) != false:
failures.append("Guandu choice bridge %s should clear %s" % [choice_id, cleared_flag])
if scene.campaign_state.current_scenario_id != WUCHAO_SCENARIO_ID:
failures.append("Guandu choice bridge %s should advance to Wuchao, got %s" % [choice_id, scene.campaign_state.current_scenario_id])
if not scene.state.load_battle(
WUCHAO_SCENARIO_PATH,
scene.campaign_state.get_roster_overrides(),
scene.campaign_state.get_inventory_snapshot(),
flags,
scene.campaign_state.get_joined_officers_snapshot()
):
failures.append("Guandu choice bridge %s could not load Wuchao" % choice_id)
scene.free()
return
if scene._camp_conversation_by_id(expected_conversation_id).is_empty():
failures.append("Guandu choice bridge %s should expose %s" % [choice_id, expected_conversation_id])
if not scene._camp_conversation_by_id(hidden_conversation_id).is_empty():
failures.append("Guandu choice bridge %s should hide %s" % [choice_id, hidden_conversation_id])
var shop_items: Array = scene.state.get_shop_item_ids()
if not shop_items.has(expected_shop_item_id):
failures.append("Guandu choice bridge %s should expose shop item %s" % [choice_id, expected_shop_item_id])
if shop_items.has(hidden_shop_item_id):
failures.append("Guandu choice bridge %s should hide shop item %s" % [choice_id, hidden_shop_item_id])
scene.free()
func _find_choice_by_id(choices, choice_id: String) -> Dictionary:
if typeof(choices) != TYPE_ARRAY:
return {}
for choice in choices:
if typeof(choice) == TYPE_DICTIONARY and str(choice.get("id", "")) == choice_id:
return choice
return {}
func _check_manual_checkpoint_reverts_claim(failures: Array[String]) -> void:
var scene = _new_prebattle_scene(failures, "manual checkpoint")
if scene == null:

View File

@@ -43,6 +43,7 @@ func _init() -> void:
_check_battle_visual_data(failures)
_check_scene_texture_loading(failures)
_check_hud_focus_text(failures)
_check_ancient_ui_theme(failures)
_check_hover_intent_badges(failures)
_check_action_button_disabled_reasons(failures)
_check_terrain_and_unit_presentation(failures)
@@ -599,24 +600,112 @@ func _check_battle_visual_data(failures: Array[String]) -> void:
failures.append("could not load Guandu Showdown shop data")
else:
_check_shop_items_unique(failures, guandu_state, "012 base")
var guandu_conversations: Array = guandu_state.get_briefing().get("camp_conversations", [])
if guandu_conversations.size() != 2:
failures.append("012 base briefing should expose exactly two camp conversations")
else:
_check_camp_conversation(failures, guandu_conversations[0], "guandu_main_line_council", "officer", "cao_cao")
_check_camp_conversation(failures, guandu_conversations[1], "guandu_guo_jia_supply_wound", "officer", "guo_jia")
var guandu_merchant := guandu_state.get_shop_merchant()
if str(guandu_merchant.get("name", "")) != "Guandu Camp Sutler":
failures.append("012 shop should expose Guandu merchant name")
if (guandu_merchant.get("lines", []) as Array).size() < 2:
failures.append("012 shop should expose merchant flavor lines")
_check_shop_item_visibility(failures, guandu_state, "imperial_seal", false, "012 base")
_check_shop_item_visibility(failures, guandu_state, "war_drum", false, "012 base")
_check_shop_item_visibility(failures, guandu_state, "yitian_sword", false, "012 base")
_check_shop_item_visibility(failures, guandu_state, "panacea", false, "012 base")
_check_shop_item_visibility(failures, guandu_state, "antidote", false, "012 base")
var guandu_secured_state = BattleStateScript.new()
if not guandu_secured_state.load_battle("res://data/scenarios/012_guandu_showdown.json", {}, {}, {"secured_guandu_line": true}):
failures.append("could not load Guandu secured line shop data")
else:
_check_shop_items_unique(failures, guandu_secured_state, "012 secured Guandu line")
_check_shop_item_visibility(failures, guandu_secured_state, "imperial_seal", true, "012 secured Guandu line")
var line_medicine: Dictionary = _find_camp_conversation(
guandu_secured_state.get_briefing().get("camp_conversations", []),
"guandu_secured_line_medicine"
)
if line_medicine.is_empty():
failures.append("012 secured Guandu line flag should expose secured line medicine conversation")
else:
_check_camp_conversation_effect(failures, line_medicine, "panacea", 1)
if not _find_camp_conversation(
guandu_secured_state.get_briefing().get("camp_conversations", []),
"guandu_wuchao_scout_wine"
).is_empty():
failures.append("012 secured Guandu line flag should not expose Wuchao scout wine conversation")
_check_shop_item_visibility(failures, guandu_secured_state, "imperial_seal", false, "012 secured Guandu line")
_check_shop_item_visibility(failures, guandu_secured_state, "war_drum", false, "012 secured Guandu line")
_check_shop_item_visibility(failures, guandu_secured_state, "yitian_sword", false, "012 secured Guandu line")
_check_shop_item_visibility(failures, guandu_secured_state, "panacea", true, "012 secured Guandu line")
_check_shop_item_visibility(failures, guandu_secured_state, "antidote", false, "012 secured Guandu line")
var guandu_scouted_state = BattleStateScript.new()
if not guandu_scouted_state.load_battle("res://data/scenarios/012_guandu_showdown.json", {}, {}, {"scouted_wuchao_depots": true}):
failures.append("could not load Guandu scouted Wuchao shop data")
else:
_check_shop_items_unique(failures, guandu_scouted_state, "012 scouted Wuchao")
var scout_wine: Dictionary = _find_camp_conversation(
guandu_scouted_state.get_briefing().get("camp_conversations", []),
"guandu_wuchao_scout_wine"
)
if scout_wine.is_empty():
failures.append("012 scouted Wuchao flag should expose Wuchao scout wine conversation")
else:
_check_camp_conversation_effect(failures, scout_wine, "wine", 1)
if not _find_camp_conversation(
guandu_scouted_state.get_briefing().get("camp_conversations", []),
"guandu_secured_line_medicine"
).is_empty():
failures.append("012 scouted Wuchao flag should not expose secured line medicine conversation")
_check_shop_item_visibility(failures, guandu_scouted_state, "imperial_seal", false, "012 scouted Wuchao")
_check_shop_item_visibility(failures, guandu_scouted_state, "war_drum", true, "012 scouted Wuchao")
_check_shop_item_visibility(failures, guandu_scouted_state, "war_drum", false, "012 scouted Wuchao")
_check_shop_item_visibility(failures, guandu_scouted_state, "yitian_sword", false, "012 scouted Wuchao")
_check_shop_item_visibility(failures, guandu_scouted_state, "panacea", false, "012 scouted Wuchao")
_check_shop_item_visibility(failures, guandu_scouted_state, "antidote", true, "012 scouted Wuchao")
var wuchao_state = BattleStateScript.new()
if not wuchao_state.load_battle("res://data/scenarios/013_wuchao_raid.json"):
failures.append("could not load Wuchao Raid shop data")
else:
_check_shop_items_unique(failures, wuchao_state, "013 base")
_check_shop_item_visibility(failures, wuchao_state, "war_drum", false, "013 base")
_check_shop_item_visibility(failures, wuchao_state, "imperial_seal", false, "013 base")
var wuchao_prepared_state = BattleStateScript.new()
if not wuchao_prepared_state.load_battle("res://data/scenarios/013_wuchao_raid.json", {}, {}, {"prepared_wuchao_raid": true}):
failures.append("could not load Wuchao prepared raid shop data")
else:
_check_shop_items_unique(failures, wuchao_prepared_state, "013 prepared Wuchao raid")
_check_shop_item_visibility(failures, wuchao_prepared_state, "war_drum", true, "013 prepared Wuchao raid")
_check_shop_item_visibility(failures, wuchao_prepared_state, "imperial_seal", false, "013 prepared Wuchao raid")
var wuchao_strengthened_state = BattleStateScript.new()
if not wuchao_strengthened_state.load_battle("res://data/scenarios/013_wuchao_raid.json", {}, {}, {"strengthened_guandu_defense": true}):
failures.append("could not load Wuchao strengthened Guandu shop data")
else:
_check_shop_items_unique(failures, wuchao_strengthened_state, "013 strengthened Guandu defense")
_check_shop_item_visibility(failures, wuchao_strengthened_state, "war_drum", false, "013 strengthened Guandu defense")
_check_shop_item_visibility(failures, wuchao_strengthened_state, "imperial_seal", true, "013 strengthened Guandu defense")
var wuchao_old_flags_state = BattleStateScript.new()
if not wuchao_old_flags_state.load_battle("res://data/scenarios/013_wuchao_raid.json", {}, {}, {"secured_guandu_line": true, "scouted_wuchao_depots": true}):
failures.append("could not load Wuchao old Guandu flag shop data")
else:
_check_shop_items_unique(failures, wuchao_old_flags_state, "013 old Guandu flags")
_check_shop_item_visibility(failures, wuchao_old_flags_state, "war_drum", false, "013 old Guandu flags")
_check_shop_item_visibility(failures, wuchao_old_flags_state, "imperial_seal", false, "013 old Guandu flags")
if not _find_camp_conversation(
wuchao_old_flags_state.get_briefing().get("camp_conversations", []),
"wuchao_fast_raid_supplies"
).is_empty():
failures.append("013 old Guandu flags should not expose fast raid supplies")
if not _find_camp_conversation(
wuchao_old_flags_state.get_briefing().get("camp_conversations", []),
"wuchao_guandu_reserve_wagon"
).is_empty():
failures.append("013 old Guandu flags should not expose reserve wagon supplies")
for item_id in ["bronze_sword", "training_spear", "short_bow", "hand_axe", "iron_armor", "panacea"]:
var item := state.get_item_def(item_id)
@@ -782,6 +871,45 @@ func _check_hud_focus_text(failures: Array[String]) -> void:
scene.free()
func _check_ancient_ui_theme(failures: Array[String]) -> void:
var scene = BattleSceneScript.new()
scene._create_hud()
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
failures.append("could not load battle scene state for ancient UI theme")
scene.free()
return
var briefing_text := scene._format_briefing_objectives()
for expected in ["War Edict:", "Ruin If:", "War Report:", "Ill Omen:", "Spoils:"]:
if not briefing_text.contains(expected):
failures.append("briefing objective parchment should use ancient heading `%s`: %s" % [expected, briefing_text])
_check_panel_style_fill(failures, scene.briefing_panel, "briefing panel", Color(0.77, 0.67, 0.48, 0.96))
_check_panel_style_fill(failures, scene.dialogue_panel, "dialogue panel", Color(0.16, 0.075, 0.04, 0.96))
_check_panel_style_fill(failures, scene.dialogue_portrait_panel, "dialogue portrait panel", Color(0.10, 0.065, 0.04, 1.0))
_check_panel_style_fill(failures, scene.result_panel, "result panel", Color(0.77, 0.67, 0.48, 0.96))
scene.free()
func _check_panel_style_fill(failures: Array[String], panel: PanelContainer, label: String, expected: Color) -> void:
if panel == null:
failures.append("%s missing" % label)
return
var style := panel.get_theme_stylebox("panel") as StyleBoxFlat
if style == null:
failures.append("%s should use a flat styled panel" % label)
return
if not _colors_nearly_equal(style.bg_color, expected):
failures.append("%s fill mismatch: %s" % [label, str(style.bg_color)])
func _colors_nearly_equal(left: Color, right: Color) -> bool:
return (
absf(left.r - right.r) < 0.01
and absf(left.g - right.g) < 0.01
and absf(left.b - right.b) < 0.01
and absf(left.a - right.a) < 0.01
)
func _check_action_button_disabled_reasons(failures: Array[String]) -> void:
var scene = BattleSceneScript.new()
scene.wait_button = Button.new()