Campaign menu and story: mission list, briefings, goals panel, mission end flow
- Title screen: Campaign with the list of the 20 missions (done / open / locked) - Briefing per mission: story told by old Crookfinger (EN/DE), goals, new tools, creatures and gold carried over; start button - In the game: goals panel with progress (click/tap opens the briefing again), the pause menu has a Briefing entry to read the whole briefing again - Mission end: next mission / try again and back to the campaign; won missions and the carry-over are stored in the browser; epilogue after the last mission Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
46755d68ac
commit
09e22b009e
5 files changed
+523
-39
No files matched your search
+64
-5
@@ -122,6 +122,20 @@ fn new_game(seed: u32, s: &Settings) -> (Game, Cam) {
|
||||
(g, cam)
|
||||
}
|
||||
|
||||
// a campaign mission with what the last mission handed over
|
||||
fn mission_game(i: usize, progress: &campaign::Progress) -> (Game, Cam) {
|
||||
let g = campaign::start_mission(i, progress.carry_into(i));
|
||||
let (cx, cy) = g.map.core;
|
||||
let cam = Cam::new(cx as f32 + 1.5, cy as f32 + 1.5);
|
||||
(g, cam)
|
||||
}
|
||||
|
||||
const PROGRESS_KEY: &str = "dc.campaign";
|
||||
|
||||
fn store_progress(p: &campaign::Progress) {
|
||||
platform::storage_set(PROGRESS_KEY, &serde_json::to_string(p).unwrap_or_default());
|
||||
}
|
||||
|
||||
fn next_seed(seed: u32) -> u32 {
|
||||
seed.wrapping_mul(1103515245).wrapping_add(12345) | 1
|
||||
}
|
||||
@@ -223,6 +237,7 @@ struct App {
|
||||
settings: Settings,
|
||||
seed: u32,
|
||||
status: Option<(String, f64)>,
|
||||
recorded: bool, // the end of the current mission has been recorded in the campaign progress
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -286,7 +301,8 @@ async fn main() {
|
||||
|
||||
let seed = (miniquad::date::now() * 1000.0) as u32 | 1;
|
||||
let (game, cam) = new_game(seed, &settings);
|
||||
let mut app = App { game, cam, settings, seed, status: None };
|
||||
let mut app = App { game, cam, settings, seed, status: None, recorded: false };
|
||||
menu.progress = platform::storage_get(PROGRESS_KEY).and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default();
|
||||
app.game.hints = app.settings.hints;
|
||||
let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, mlast: None, minimap_drag: false, tlast: None, touch_world: false, inspect: None };
|
||||
let mut touch = TouchInput::new();
|
||||
@@ -361,10 +377,10 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches!(menu.page, Some(Page::Saves { .. })) {
|
||||
if menu.scrollable() {
|
||||
let (_, wheel) = mouse_wheel();
|
||||
if wheel != 0.0 {
|
||||
menu.scroll_by(if wheel > 0.0 { -1 } else { 1 }, cloud.list.len());
|
||||
menu.scroll_lines(if wheel > 0.0 { -1 } else { 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +649,15 @@ async fn main() {
|
||||
MenuAction::CloudDelete(id) => {
|
||||
cloud.delete(&id);
|
||||
}
|
||||
MenuAction::StartMission(i) => {
|
||||
(app.game, app.cam) = mission_game(i, &menu.progress);
|
||||
app.game.hints = app.settings.hints;
|
||||
app.recorded = false;
|
||||
menu.page = None;
|
||||
ui.log.clear();
|
||||
ui.show_help = false;
|
||||
autosave_t = 0.0;
|
||||
}
|
||||
MenuAction::EditName => {
|
||||
if let Some(n) = platform::prompt(&i18n::tr("saves.name"), &menu.save_name) {
|
||||
menu.save_name = n.chars().filter(|c| !c.is_control()).take(40).collect();
|
||||
@@ -664,6 +689,25 @@ async fn main() {
|
||||
(app.game, app.cam) = new_game(app.seed, &app.settings);
|
||||
app.game.hints = app.settings.hints;
|
||||
}
|
||||
// end of a mission: next mission (won) or try again (lost)
|
||||
UiAction::EndNext => {
|
||||
if let Some(i) = app.game.mission.as_ref().map(|m| m.index) {
|
||||
if app.game.state == 1 {
|
||||
menu.page = Some(if i + 1 < campaign::missions().len() { Page::Briefing { mission: i + 1, ingame: false } } else { Page::Campaign });
|
||||
} else {
|
||||
(app.game, app.cam) = mission_game(i, &menu.progress);
|
||||
app.game.hints = app.settings.hints;
|
||||
app.recorded = false;
|
||||
ui.log.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
UiAction::EndCampaign => menu.page = Some(Page::Campaign),
|
||||
UiAction::Briefing => {
|
||||
if let Some(i) = app.game.mission.as_ref().map(|m| m.index) {
|
||||
menu.open_briefing(i);
|
||||
}
|
||||
}
|
||||
UiAction::OpenMenu => menu.page = Some(Page::Pause),
|
||||
UiAction::Locked => {
|
||||
app.game.msg("msg.tool_locked", vec![]);
|
||||
@@ -785,7 +829,22 @@ async fn main() {
|
||||
for ev in app.game.events.drain(..) {
|
||||
sounds.play(ev, vec2(app.cam.tx, app.cam.tz), right, hear, now);
|
||||
}
|
||||
let on_title = menu.page == Some(Page::Title) || matches!(menu.page, Some(Page::Settings { from_title: true, .. }) | Some(Page::KeyCapture { from_title: true, .. }) | Some(Page::NewGame));
|
||||
// a won mission goes into the campaign progress (with the carry-over)
|
||||
if let (Some(i), false) = (app.game.mission.as_ref().map(|m| m.index), app.recorded) {
|
||||
if app.game.state != 0 {
|
||||
app.recorded = true;
|
||||
if app.game.state == 1 {
|
||||
menu.progress.set_done(i, campaign::carry_from(&app.game));
|
||||
store_progress(&menu.progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
menu.mission = app.game.mission.as_ref().map(|m| m.index);
|
||||
let on_title = menu.page == Some(Page::Title)
|
||||
|| matches!(
|
||||
menu.page,
|
||||
Some(Page::Settings { from_title: true, .. }) | Some(Page::KeyCapture { from_title: true, .. }) | Some(Page::NewGame) | Some(Page::Campaign) | Some(Page::Briefing { ingame: false, .. })
|
||||
);
|
||||
music.update(&app.game, on_title, (app.cam.tx, app.cam.tz));
|
||||
for m in app.game.msgs.drain(..) {
|
||||
ui.log.push((m, now));
|
||||
@@ -832,7 +891,7 @@ fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut M
|
||||
r3d.draw(&app.game, gfx, &app.cam, now, &ov);
|
||||
let title_screen = matches!(
|
||||
menu.page,
|
||||
Some(Page::Title) | Some(Page::Settings { from_title: true, .. }) | Some(Page::KeyCapture { from_title: true, .. })
|
||||
Some(Page::Title) | Some(Page::Settings { from_title: true, .. }) | Some(Page::KeyCapture { from_title: true, .. }) | Some(Page::Campaign) | Some(Page::Briefing { ingame: false, .. })
|
||||
);
|
||||
if !title_screen {
|
||||
let info = if over_ui || (touch && inp.inspect.is_none()) {
|
||||
|
||||
Reference in new issue
Block a user