Add briefing campaign header
This commit is contained in:
@@ -20,6 +20,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
|
|||||||
- Pre-battle Armory equipment changes save immediately and sync roster equipment plus inventory stock.
|
- Pre-battle Armory equipment changes save immediately and sync roster equipment plus inventory stock.
|
||||||
- Pre-battle Roster can move optional officers between sortie and reserve within scenario limits.
|
- Pre-battle Roster can move optional officers between sortie and reserve within scenario limits.
|
||||||
- Pre-battle Formation lets deployed officers swap starting positions inside scenario-defined cells.
|
- Pre-battle Formation lets deployed officers swap starting positions inside scenario-defined cells.
|
||||||
|
- Pre-battle briefing shows the campaign battle number, location, and victory/defeat summary.
|
||||||
- Escort scenarios can include required, non-controllable protected units.
|
- Escort scenarios can include required, non-controllable protected units.
|
||||||
- Campaign saves joined officers, future deployments can require prior recruitment, and choices can make officers leave.
|
- Campaign saves joined officers, future deployments can require prior recruitment, and choices can make officers leave.
|
||||||
- Completed saves reconcile officer join/leave rewards on load without replaying gold or items.
|
- Completed saves reconcile officer join/leave rewards on load without replaying gold or items.
|
||||||
@@ -93,7 +94,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
|
|||||||
- Basic battle EXP, level-ups, and core first-tier class promotion routes.
|
- Basic battle EXP, level-ups, and core first-tier class promotion routes.
|
||||||
- Victory results summarize saved player level-ups and promotions.
|
- Victory results summarize saved player level-ups and promotions.
|
||||||
- Scenario events for battle start, battle-begin dialogue, turn start, movement-triggered ambushes, objective updates, and reinforcements.
|
- Scenario events for battle start, battle-begin dialogue, turn start, movement-triggered ambushes, objective updates, and reinforcements.
|
||||||
- Scenario briefing before battle.
|
- Scenario briefing before battle with a campaign battle header and objective summary.
|
||||||
- Victory rewards and consumed inventory saved into a campaign state.
|
- Victory rewards and consumed inventory saved into a campaign state.
|
||||||
- Scenario-defined victory and defeat conditions.
|
- Scenario-defined victory and defeat conditions.
|
||||||
- Destination-style victory conditions with objective map markers.
|
- Destination-style victory conditions with objective map markers.
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
1. Load battle data.
|
1. Load battle data.
|
||||||
2. Hydrate deployments through `DataCatalog`.
|
2. Hydrate deployments through `DataCatalog`.
|
||||||
3. Dispatch battle-start scenario events.
|
3. Dispatch battle-start scenario events.
|
||||||
4. Show scenario briefing, including the optional pre-battle shop, Armory, Roster, and Formation setup.
|
4. Show scenario briefing with the campaign battle header, objective summary, and optional pre-battle shop, Armory, Roster, and Formation setup.
|
||||||
5. Player selects a unit.
|
5. Player selects a unit.
|
||||||
6. Briefing completion dispatches battle-begin events, including opening dialogue.
|
6. Briefing completion dispatches battle-begin events, including opening dialogue.
|
||||||
7. Unit moves, attacks, chooses a tactic or consumable item from the side menu, changes equipment, counters, gains EXP, levels up, promotes at class thresholds, or waits. Tactic buttons show MP cost, kind, range, and power or support effect. Physical attacks roll hit chance from unit agility and target terrain avoid; misses give reduced EXP. The scene plays placeholder SFX, a short movement slide, low-HP warning rings, hover target preview badges, and floating combat text for core action and UI feedback.
|
7. Unit moves, attacks, chooses a tactic or consumable item from the side menu, changes equipment, counters, gains EXP, levels up, promotes at class thresholds, or waits. Tactic buttons show MP cost, kind, range, and power or support effect. Physical attacks roll hit chance from unit agility and target terrain avoid; misses give reduced EXP. The scene plays placeholder SFX, a short movement slide, low-HP warning rings, hover target preview badges, and floating combat text for core action and UI feedback.
|
||||||
@@ -58,6 +58,6 @@ Joined officers gate whether `requires_joined` player deployments are loaded at
|
|||||||
|
|
||||||
Inventory and campaign flags are copied from `CampaignState` into `BattleState` when a scenario starts. Pre-battle shop stock comes from the loaded scenario's `shop.items` list plus matching `shop.conditional_items` blocks. Shop purchases and 50% sell-back are campaign transactions: they update saved gold and unequipped inventory immediately, write the save file, and refresh the already-loaded battle inventory before the player begins the battle. Pre-battle Armory equipment changes use the same equipment rules as battle HUD equipment changes, then immediately save merged roster equipment/stat snapshots and inventory stock. Pre-battle Roster uses scenario `roster.max_units`, `roster.required_officers`, and `roster.required_units` to mark optional player deployments as sortie or reserve; reserve units remain in the candidate list but are excluded from board occupancy, drawing, selection, AI targeting, and living-unit victory checks. Non-controllable player units can act as protected escort targets: enemies can attack them and conditions can reference them, but they are skipped by player selection, Armory, Formation, counterattacks, and campaign roster progression snapshots when `persist_progression` is false. Scenario `ai_target_priority` nudges enemy movement and damage-skill scoring toward important targets such as envoys without overriding defeat bonuses or range checks. Pre-battle Formation uses the loaded scenario's `formation.cells` and only mutates current battle unit positions before battle-begin events fire. Destination victory cells from `unit_reaches_tile` conditions are exposed to the scene for objective overlays and tile info. Movement-triggered `unit_reaches_tile` events receive the unit that just moved, so ambushes fire on entry rather than from units already standing on a marker. Briefing data can append matching `briefing.conditional_lines`, and scenario events can use `when.campaign_flags` to branch dialogue, objective changes, and reinforcements from saved campaign choices. Consumable use can restore HP or MP, and in-battle item use plus equipment swaps mutate only the battle copy until victory; defeats and restarts do not spend saved items or preserve in-battle gear changes. Post-battle choices write campaign `flags` only after the result-panel button is pressed; if the player reloads after rewards are saved but before selecting, the pending scenario id restores the victory choice panel without replaying rewards. The next battle button stays locked until the campaign choice save succeeds. Equipment rewards share the same `item_id -> count` inventory stock, cover weapon, armor, and accessory slots, can be equipped from the side HUD before the selected unit moves or acts, and do not appear in the consumable action menu. Already completed scenarios can be replayed without granting duplicate rewards, choices, or replay inventory consumption; pre-battle Shop, Armory, Roster, and Formation are disabled on completed-scenario replays to avoid side effects.
|
Inventory and campaign flags are copied from `CampaignState` into `BattleState` when a scenario starts. Pre-battle shop stock comes from the loaded scenario's `shop.items` list plus matching `shop.conditional_items` blocks. Shop purchases and 50% sell-back are campaign transactions: they update saved gold and unequipped inventory immediately, write the save file, and refresh the already-loaded battle inventory before the player begins the battle. Pre-battle Armory equipment changes use the same equipment rules as battle HUD equipment changes, then immediately save merged roster equipment/stat snapshots and inventory stock. Pre-battle Roster uses scenario `roster.max_units`, `roster.required_officers`, and `roster.required_units` to mark optional player deployments as sortie or reserve; reserve units remain in the candidate list but are excluded from board occupancy, drawing, selection, AI targeting, and living-unit victory checks. Non-controllable player units can act as protected escort targets: enemies can attack them and conditions can reference them, but they are skipped by player selection, Armory, Formation, counterattacks, and campaign roster progression snapshots when `persist_progression` is false. Scenario `ai_target_priority` nudges enemy movement and damage-skill scoring toward important targets such as envoys without overriding defeat bonuses or range checks. Pre-battle Formation uses the loaded scenario's `formation.cells` and only mutates current battle unit positions before battle-begin events fire. Destination victory cells from `unit_reaches_tile` conditions are exposed to the scene for objective overlays and tile info. Movement-triggered `unit_reaches_tile` events receive the unit that just moved, so ambushes fire on entry rather than from units already standing on a marker. Briefing data can append matching `briefing.conditional_lines`, and scenario events can use `when.campaign_flags` to branch dialogue, objective changes, and reinforcements from saved campaign choices. Consumable use can restore HP or MP, and in-battle item use plus equipment swaps mutate only the battle copy until victory; defeats and restarts do not spend saved items or preserve in-battle gear changes. Post-battle choices write campaign `flags` only after the result-panel button is pressed; if the player reloads after rewards are saved but before selecting, the pending scenario id restores the victory choice panel without replaying rewards. The next battle button stays locked until the campaign choice save succeeds. Equipment rewards share the same `item_id -> count` inventory stock, cover weapon, armor, and accessory slots, can be equipped from the side HUD before the selected unit moves or acts, and do not appear in the consumable action menu. Already completed scenarios can be replayed without granting duplicate rewards, choices, or replay inventory consumption; pre-battle Shop, Armory, Roster, and Formation are disabled on completed-scenario replays to avoid side effects.
|
||||||
|
|
||||||
The battle scene owns presentation feedback: briefing and result states use menu BGM, active battles use battle BGM, dialogue lines can show optional portrait textures or speaker-initial fallback panels, board units render low-HP warning rings and HP bar color states from battle state fields, hover previews are rendered as target badges from existing `BattleState` forecast APIs, log/result hooks trigger placeholder SFX, `BattleState.unit_motion_requested` asks the scene to interpolate a unit's draw position during movement, and `BattleState.combat_feedback_requested` asks the scene to draw transient floating combat text without changing battle rules.
|
The battle scene owns presentation feedback: briefing and result states use menu BGM, briefing headers combine `CampaignState` campaign order and title data with `BattleState` briefing and objective text, active battles use battle BGM, dialogue lines can show optional portrait textures or speaker-initial fallback panels, board units render low-HP warning rings and HP bar color states from battle state fields, hover previews are rendered as target badges from existing `BattleState` forecast APIs, log/result hooks trigger placeholder SFX, `BattleState.unit_motion_requested` asks the scene to interpolate a unit's draw position during movement, and `BattleState.combat_feedback_requested` asks the scene to draw transient floating combat text without changing battle rules.
|
||||||
|
|
||||||
The `New Campaign` button clears `user://campaign_save.json`, resets campaign state to the first scenario, and reloads the opening briefing.
|
The `New Campaign` button clears `user://campaign_save.json`, resets campaign state to the first scenario, and reloads the opening briefing.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
## Milestone 1: Playable Battle Prototype
|
## Milestone 1: Playable Battle Prototype
|
||||||
|
|
||||||
- Scenario briefing before battle.
|
- Scenario briefing before battle with a campaign battle header and objective summary.
|
||||||
- Select player units.
|
- Select player units.
|
||||||
- Show move and attack ranges.
|
- Show move and attack ranges.
|
||||||
- Show hover tile and unit information.
|
- Show hover tile and unit information.
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
## Milestone 3: Scenario Layer
|
## Milestone 3: Scenario Layer
|
||||||
|
|
||||||
- Pre-battle briefing exists. Full dialogue scenes are still planned.
|
- Pre-battle briefing exists with a campaign battle header and objective summary. Full dialogue scenes are still planned.
|
||||||
- Scenario events exist for battle start, battle begin, turn start, and movement-triggered map tiles.
|
- Scenario events exist for battle start, battle begin, turn start, and movement-triggered map tiles.
|
||||||
- Mid-battle map/action events have a first action system with movement ambushes.
|
- Mid-battle map/action events have a first action system with movement ambushes.
|
||||||
- Opening battle dialogue exists through event actions.
|
- Opening battle dialogue exists through event actions.
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
- Unit sprites and animations. Board movement now has a short slide animation, and board units show low-HP warning rings.
|
- Unit sprites and animations. Board movement now has a short slide animation, and board units show low-HP warning rings.
|
||||||
- Battle effects. First floating combat text now covers damage, recovery, misses, support effects, level-ups, and promotions. Hover target preview badges summarize attacks, tactics, and items.
|
- Battle effects. First floating combat text now covers damage, recovery, misses, support effects, level-ups, and promotions. Hover target preview badges summarize attacks, tactics, and items.
|
||||||
- Music and sound. Basic placeholder BGM and SFX now play for menus, battle flow, unit actions, tactics, items, and result states.
|
- Music and sound. Basic placeholder BGM and SFX now play for menus, battle flow, unit actions, tactics, items, and result states.
|
||||||
- Chapter UI and visual novel scenes.
|
- Chapter UI and visual novel scenes. Pre-battle briefing now has a first campaign battle header; chapter-aware numbering remains planned once chapter data exists.
|
||||||
|
|
||||||
## Milestone 6: Content Expansion
|
## Milestone 6: Content Expansion
|
||||||
|
|
||||||
|
|||||||
@@ -549,6 +549,17 @@ func get_scenario_title(scenario_id: String) -> String:
|
|||||||
return str(scenario_titles.get(scenario_id, scenario_id))
|
return str(scenario_titles.get(scenario_id, scenario_id))
|
||||||
|
|
||||||
|
|
||||||
|
func get_scenario_position(scenario_id: String) -> int:
|
||||||
|
var index := scenario_order.find(scenario_id)
|
||||||
|
if index == -1:
|
||||||
|
return 0
|
||||||
|
return index + 1
|
||||||
|
|
||||||
|
|
||||||
|
func get_scenario_count() -> int:
|
||||||
|
return scenario_order.size()
|
||||||
|
|
||||||
|
|
||||||
func get_next_scenario_id(scenario_id: String) -> String:
|
func get_next_scenario_id(scenario_id: String) -> String:
|
||||||
var index := scenario_order.find(scenario_id)
|
var index := scenario_order.find(scenario_id)
|
||||||
if index == -1 or index + 1 >= scenario_order.size():
|
if index == -1 or index + 1 >= scenario_order.size():
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ var cell_info_label: Label
|
|||||||
var forecast_label: Label
|
var forecast_label: Label
|
||||||
var inventory_label: Label
|
var inventory_label: Label
|
||||||
var briefing_panel: PanelContainer
|
var briefing_panel: PanelContainer
|
||||||
|
var briefing_title_label: Label
|
||||||
|
var briefing_location_label: Label
|
||||||
|
var briefing_objective_label: Label
|
||||||
var briefing_label: Label
|
var briefing_label: Label
|
||||||
var shop_button: Button
|
var shop_button: Button
|
||||||
var shop_menu: VBoxContainer
|
var shop_menu: VBoxContainer
|
||||||
@@ -312,18 +315,38 @@ func _create_hud() -> void:
|
|||||||
|
|
||||||
briefing_panel = PanelContainer.new()
|
briefing_panel = PanelContainer.new()
|
||||||
briefing_panel.visible = false
|
briefing_panel.visible = false
|
||||||
briefing_panel.position = Vector2(284, 88)
|
briefing_panel.position = Vector2(300, 88)
|
||||||
briefing_panel.size = Vector2(640, 500)
|
briefing_panel.size = Vector2(680, 600)
|
||||||
root.add_child(briefing_panel)
|
root.add_child(briefing_panel)
|
||||||
|
|
||||||
var briefing_column := VBoxContainer.new()
|
var briefing_column := VBoxContainer.new()
|
||||||
briefing_column.add_theme_constant_override("separation", 10)
|
briefing_column.add_theme_constant_override("separation", 8)
|
||||||
briefing_panel.add_child(briefing_column)
|
briefing_panel.add_child(briefing_column)
|
||||||
|
|
||||||
|
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_location_label = Label.new()
|
||||||
|
briefing_location_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
briefing_location_label.custom_minimum_size = Vector2(640, 22)
|
||||||
|
briefing_column.add_child(briefing_location_label)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
var briefing_scroll := ScrollContainer.new()
|
||||||
|
briefing_scroll.custom_minimum_size = Vector2(640, 96)
|
||||||
|
briefing_column.add_child(briefing_scroll)
|
||||||
|
|
||||||
briefing_label = Label.new()
|
briefing_label = Label.new()
|
||||||
briefing_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
briefing_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
briefing_label.custom_minimum_size = Vector2(600, 170)
|
briefing_label.custom_minimum_size = Vector2(620, 0)
|
||||||
briefing_column.add_child(briefing_label)
|
briefing_scroll.add_child(briefing_label)
|
||||||
|
|
||||||
var prep_button_row := HBoxContainer.new()
|
var prep_button_row := HBoxContainer.new()
|
||||||
prep_button_row.add_theme_constant_override("separation", 8)
|
prep_button_row.add_theme_constant_override("separation", 8)
|
||||||
@@ -1793,6 +1816,39 @@ func _load_scenario(scenario_id: String) -> void:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _format_briefing_title(briefing: Dictionary) -> String:
|
||||||
|
var title := str(briefing.get("title", state.battle_name))
|
||||||
|
if title.is_empty():
|
||||||
|
title = campaign_state.get_scenario_title(active_scenario_id)
|
||||||
|
var position := campaign_state.get_scenario_position(active_scenario_id)
|
||||||
|
var count := campaign_state.get_scenario_count()
|
||||||
|
if position > 0 and count > 0:
|
||||||
|
return "Battle %02d/%02d - %s" % [position, count, title]
|
||||||
|
return title
|
||||||
|
|
||||||
|
|
||||||
|
func _format_briefing_location(briefing: Dictionary) -> String:
|
||||||
|
var location := str(briefing.get("location", ""))
|
||||||
|
if campaign_state.campaign_title.is_empty():
|
||||||
|
return location
|
||||||
|
if location.is_empty():
|
||||||
|
return campaign_state.campaign_title
|
||||||
|
return "%s | %s" % [campaign_state.campaign_title, location]
|
||||||
|
|
||||||
|
|
||||||
|
func _format_briefing_objectives() -> String:
|
||||||
|
var text := ""
|
||||||
|
var victory := str(state.objectives.get("victory", ""))
|
||||||
|
if not victory.is_empty():
|
||||||
|
text = "Victory: %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
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
func _show_briefing() -> void:
|
func _show_briefing() -> void:
|
||||||
_play_bgm("menu")
|
_play_bgm("menu")
|
||||||
var briefing := state.get_briefing()
|
var briefing := state.get_briefing()
|
||||||
@@ -1806,12 +1862,12 @@ func _show_briefing() -> void:
|
|||||||
body += "\n"
|
body += "\n"
|
||||||
body += str(line)
|
body += str(line)
|
||||||
|
|
||||||
briefing_label.text = "%s\n%s\n\n%s\n\nObjective: %s" % [
|
briefing_title_label.text = _format_briefing_title(briefing)
|
||||||
briefing.get("title", state.battle_name),
|
briefing_location_label.text = _format_briefing_location(briefing)
|
||||||
briefing.get("location", ""),
|
briefing_location_label.visible = not briefing_location_label.text.is_empty()
|
||||||
body,
|
briefing_objective_label.text = _format_briefing_objectives()
|
||||||
state.objectives.get("victory", "")
|
briefing_objective_label.visible = not briefing_objective_label.text.is_empty()
|
||||||
]
|
briefing_label.text = body
|
||||||
briefing_panel.visible = true
|
briefing_panel.visible = true
|
||||||
battle_started = false
|
battle_started = false
|
||||||
_hide_shop_menu()
|
_hide_shop_menu()
|
||||||
|
|||||||
Reference in New Issue
Block a user