Editor texts (EN/DE), whole-campaign play and briefings for editor missions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
30dd8441b9
commit
985c34759d
6 files changed
+705
-14
No files matched your search
+9
-7
@@ -43,6 +43,7 @@ pub enum EdAction {
|
||||
None,
|
||||
Close,
|
||||
Test(usize),
|
||||
Play, // the whole campaign from its first mission, following the branches
|
||||
SaveFile,
|
||||
OpenFile,
|
||||
ServerSave,
|
||||
@@ -280,31 +281,32 @@ impl Editor {
|
||||
self.doc.language = langs[k].0.clone();
|
||||
}
|
||||
// buttons on the right
|
||||
let labels = [tr("ed.new"), tr("ed.open"), tr("ed.save_file"), tr("ed.save_server"), tr("ed.close")];
|
||||
let labels = [tr("ed.play"), tr("ed.new"), tr("ed.open"), tr("ed.save_file"), tr("ed.save_server"), tr("ed.close")];
|
||||
let bw = labels.iter().map(|l| gfx.measure(l, S())).fold(0.0, f32::max) + CW() * 2.0;
|
||||
let mut bx = w - CW() - bw;
|
||||
for k in (0..labels.len()).rev() {
|
||||
let r = Rect::new(bx, CH() * 0.6, bw, CH() * 1.7);
|
||||
if im.button(gfx, r, &labels[k]) {
|
||||
match k {
|
||||
0 => {
|
||||
0 => act = EdAction::Play,
|
||||
1 => {
|
||||
self.doc = CampaignDoc { name: tr("ed.new_campaign"), language: crate::i18n::language(), ..CampaignDoc::default() };
|
||||
self.doc.add_mission();
|
||||
self.server_id = None;
|
||||
}
|
||||
1 => {
|
||||
2 => {
|
||||
self.open_dialog = true;
|
||||
act = EdAction::ServerRefresh;
|
||||
}
|
||||
2 => act = EdAction::SaveFile,
|
||||
3 => act = EdAction::ServerSave,
|
||||
3 => act = EdAction::SaveFile,
|
||||
4 => act = EdAction::ServerSave,
|
||||
_ => act = EdAction::Close,
|
||||
}
|
||||
}
|
||||
bx -= bw + CW() * 0.5;
|
||||
}
|
||||
let mut publ = self.published;
|
||||
if self.server_id.is_some() && im.toggle(gfx, w - CW() * 2.0 - bw * 3.0, CH() * 3.1, &tr("ed.published"), &mut publ) {
|
||||
if self.server_id.is_some() && im.toggle(gfx, w - CW() * 2.0 - bw * 3.5, CH() * 3.1, &tr("ed.published"), &mut publ) {
|
||||
self.published = publ;
|
||||
act = EdAction::Publish(publ);
|
||||
}
|
||||
@@ -748,7 +750,7 @@ impl Editor {
|
||||
(tr("ed.lava_rivers"), m.gen.lava.to_string(), 4),
|
||||
(tr("ed.forts"), m.gen.forts.to_string(), 5),
|
||||
(tr("ed.rivals"), m.gen.rivals.to_string(), 6),
|
||||
(tr("ed.difficulty"), tr(["diff.easy", "diff.medium", "diff.hard"][m.gen.difficulty.min(2) as usize]), 7),
|
||||
(tr("ed.difficulty"), tr(["diff.0", "diff.1", "diff.2"][m.gen.difficulty.min(2) as usize]), 7),
|
||||
(tr("ed.portals"), m.portals.to_string(), 8),
|
||||
];
|
||||
if fixed {
|
||||
|
||||
@@ -88,7 +88,7 @@ pub fn demo_campaign() -> CampaignDoc {
|
||||
let mut m = MissionDoc {
|
||||
key: "demo1".into(),
|
||||
title: tr("demo.m1.title"),
|
||||
story: tr("demo.m1.story"),
|
||||
story: tr("demo.m1.story").replace("\\n", "\n"),
|
||||
size: (80, 64),
|
||||
seed: 4242,
|
||||
gold: 4000.0,
|
||||
@@ -181,7 +181,7 @@ pub fn demo_campaign() -> CampaignDoc {
|
||||
let mut m2 = MissionDoc {
|
||||
key: "demo2".into(),
|
||||
title: tr("demo.m2.title"),
|
||||
story: tr("demo.m2.story"),
|
||||
story: tr("demo.m2.story").replace("\\n", "\n"),
|
||||
size: (72, 64),
|
||||
seed: 77,
|
||||
gold: 3000.0,
|
||||
|
||||
+31
-4
@@ -196,6 +196,12 @@ fn sanitize_campaign_key(value: &str) -> String {
|
||||
value.trim().chars().filter(|c| c.is_ascii_alphanumeric() || *c == '_' || *c == '-').take(48).collect()
|
||||
}
|
||||
|
||||
// the briefing of an editor mission (None: it has no text)
|
||||
fn briefing_of(doc: &campaign_doc::CampaignDoc, i: usize) -> Option<(String, String)> {
|
||||
let m = doc.missions.get(i)?;
|
||||
(!m.story.trim().is_empty()).then(|| (m.title.clone(), m.story.clone()))
|
||||
}
|
||||
|
||||
// a mission of an editor campaign (its map is painted, or rolled from the run's seed)
|
||||
fn custom_mission_game(doc: &campaign_doc::CampaignDoc, i: usize, run_seed: u32) -> (Game, Cam) {
|
||||
let g = campaign::start_editor_mission(&doc.missions[i], i, run_seed);
|
||||
@@ -438,9 +444,11 @@ struct App {
|
||||
frame_hist: [f32; FRAME_HIST],
|
||||
frame_hist_pos: usize,
|
||||
editor: Option<editor::Editor>, // the campaign editor (takes the whole frame while open)
|
||||
editor_testing: bool, // a mission of the editor is being played
|
||||
editor_testing: bool, // the editor waits while one of its missions is played
|
||||
editor_test_only: bool, // only that mission (test play); else the campaign goes on
|
||||
editor_public_pending: bool, // the public campaign list still has to be fetched
|
||||
editor_loading: Option<String>, // server id of the own campaign being loaded
|
||||
pending_briefing: Option<(String, String)>, // an editor mission began: show its briefing
|
||||
custom_campaign: Option<campaign_doc::CampaignDoc>,
|
||||
custom_progress: Option<campaign_doc::CampaignProgress>,
|
||||
custom_mission_index: Option<usize>,
|
||||
@@ -652,7 +660,7 @@ async fn main() {
|
||||
let seed = (miniquad::date::now() * 1000.0) as u32 | 1;
|
||||
// behind the title menu: the endless bridge battle
|
||||
let (game, cam) = Game::new_demo(seed);
|
||||
let mut app = App { game, cam, settings, seed, status: None, recorded: false, client: MatchClient::new(), ws_id: 0, match_mode: false, coop: false, fps: 0.0, renderer: platform::renderer(), frame_ms: 0.0, frame_hist: [0.0; FRAME_HIST], frame_hist_pos: 0, custom_campaign: None, custom_progress: None, custom_mission_index: None, custom_next_index: None, editor: None, editor_testing: false, editor_public_pending: false, editor_loading: None };
|
||||
let mut app = App { game, cam, settings, seed, status: None, recorded: false, client: MatchClient::new(), ws_id: 0, match_mode: false, coop: false, fps: 0.0, renderer: platform::renderer(), frame_ms: 0.0, frame_hist: [0.0; FRAME_HIST], frame_hist_pos: 0, custom_campaign: None, custom_progress: None, custom_mission_index: None, custom_next_index: None, editor: None, editor_testing: false, editor_test_only: false, editor_public_pending: false, editor_loading: None, pending_briefing: None };
|
||||
menu.progress = platform::storage_get(PROGRESS_KEY).and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default();
|
||||
apply_game_settings(&mut app.game, &app.settings);
|
||||
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 };
|
||||
@@ -684,6 +692,16 @@ async fn main() {
|
||||
app.editor = Some(editor::Editor::new(menu.campaign_doc.clone()));
|
||||
menu.page = Some(Page::Title);
|
||||
}
|
||||
// an editor mission began: its briefing first
|
||||
if let Some(b) = app.pending_briefing.take() {
|
||||
menu.custom_briefing = b;
|
||||
menu.page = Some(Page::CustomBriefing);
|
||||
menu.scroll_lines(-1000);
|
||||
}
|
||||
// a played editor campaign is over: back into the editor
|
||||
if menu.page == Some(Page::CampaignEditor) && app.editor_testing {
|
||||
back_to_editor(&mut app, &mut menu, &mut r3d);
|
||||
}
|
||||
if app.editor.is_some() && !app.editor_testing {
|
||||
editor_frame(&mut app, &mut menu, &mut cloud, &mut r3d, &gfx, &mut ui, dt, now);
|
||||
next_frame().await;
|
||||
@@ -1311,6 +1329,7 @@ async fn main() {
|
||||
let _ = mission_doc;
|
||||
let progress = campaign_doc::CampaignProgress::for_doc(&menu.campaign_doc);
|
||||
(app.game, app.cam) = custom_mission_game(&menu.campaign_doc, i, progress.run_seed);
|
||||
app.pending_briefing = briefing_of(&menu.campaign_doc, i);
|
||||
apply_game_settings(&mut app.game, &app.settings);
|
||||
app.game.coop = false;
|
||||
app.coop = false;
|
||||
@@ -1391,7 +1410,7 @@ async fn main() {
|
||||
app.coop = false;
|
||||
}
|
||||
// end of a mission: next mission (won) or try again (lost)
|
||||
UiAction::EndNext | UiAction::EndCampaign if app.editor_testing => back_to_editor(&mut app, &mut menu, &mut r3d),
|
||||
UiAction::EndNext | UiAction::EndCampaign if app.editor_testing && app.editor_test_only => back_to_editor(&mut app, &mut menu, &mut r3d),
|
||||
UiAction::EndNext => {
|
||||
if app.custom_campaign.is_some() {
|
||||
let campaign = app.custom_campaign.clone();
|
||||
@@ -1401,6 +1420,7 @@ async fn main() {
|
||||
if let Some(campaign) = campaign {
|
||||
if next < campaign.missions.len() {
|
||||
(app.game, app.cam) = custom_mission_game(&campaign, next, progress.as_ref().map_or(1, |p| p.run_seed));
|
||||
app.pending_briefing = briefing_of(&campaign, next);
|
||||
apply_game_settings(&mut app.game, &app.settings);
|
||||
app.game.coop = false;
|
||||
app.coop = false;
|
||||
@@ -1424,6 +1444,7 @@ async fn main() {
|
||||
} else if let (Some(campaign), Some(i)) = (campaign, app.custom_mission_index) {
|
||||
if i < campaign.missions.len() {
|
||||
(app.game, app.cam) = custom_mission_game(&campaign, i, progress.as_ref().map_or(1, |p| p.run_seed));
|
||||
app.pending_briefing = briefing_of(&campaign, i);
|
||||
apply_game_settings(&mut app.game, &app.settings);
|
||||
app.game.coop = false;
|
||||
app.coop = false;
|
||||
@@ -1841,9 +1862,14 @@ fn editor_frame(app: &mut App, menu: &mut Menu, cloud: &mut Cloud, r3d: &mut R3d
|
||||
menu.page = Some(Page::Title);
|
||||
return; // the editor is gone
|
||||
}
|
||||
editor::EdAction::Test(i) => {
|
||||
editor::EdAction::Test(_) | editor::EdAction::Play => {
|
||||
let (i, only) = match act {
|
||||
editor::EdAction::Test(i) => (i, true),
|
||||
_ => (0, false),
|
||||
};
|
||||
let progress = campaign_doc::CampaignProgress::for_doc(&ed.doc);
|
||||
(app.game, app.cam) = custom_mission_game(&ed.doc, i, progress.run_seed);
|
||||
app.pending_briefing = briefing_of(&ed.doc, i);
|
||||
apply_game_settings(&mut app.game, &app.settings);
|
||||
app.game.coop = false;
|
||||
app.coop = false;
|
||||
@@ -1853,6 +1879,7 @@ fn editor_frame(app: &mut App, menu: &mut Menu, cloud: &mut Cloud, r3d: &mut R3d
|
||||
app.custom_mission_index = Some(i);
|
||||
app.custom_next_index = None;
|
||||
app.editor_testing = true;
|
||||
app.editor_test_only = only;
|
||||
menu.editor_test = true;
|
||||
menu.page = None;
|
||||
ui.log.clear();
|
||||
|
||||
+33
-1
@@ -28,6 +28,7 @@ pub enum Page {
|
||||
Campaign, // list of the campaign missions
|
||||
CampaignEditor, // editable shareable .dccamp campaign file
|
||||
Briefing { mission: usize, ingame: bool }, // story and goals of a mission
|
||||
CustomBriefing, // title and briefing of an editor mission
|
||||
Login, // login to the savegame service (before the title)
|
||||
Account, // password, logout, users (admin)
|
||||
Match { from_title: bool }, // multiplayer lobby and match status
|
||||
@@ -415,6 +416,7 @@ pub struct Menu {
|
||||
pub coop_mission: usize, // co-op campaign mission selected on the Match page
|
||||
pub in_match: bool, // a match is being played; the pause menu changes its shape
|
||||
pub editor_test: bool, // a mission of the editor is being tested: back to the editor
|
||||
pub custom_briefing: (String, String), // title and text of an editor mission's briefing
|
||||
pub campaign_doc: CampaignDoc,
|
||||
pub campaign_selected: usize,
|
||||
pub campaign_pending: bool,
|
||||
@@ -451,6 +453,7 @@ impl Menu {
|
||||
coop_mission: crate::campaign::coop_mission_indexes().first().copied().unwrap_or(0),
|
||||
in_match: false,
|
||||
editor_test: false,
|
||||
custom_briefing: (String::new(), String::new()),
|
||||
campaign_doc: CampaignDoc::default(),
|
||||
campaign_selected: 0,
|
||||
campaign_pending: false,
|
||||
@@ -489,6 +492,7 @@ impl Menu {
|
||||
Some(Page::Bestiary { from_title: false, .. }) => Some(Page::Pause),
|
||||
Some(Page::Briefing { ingame: true, .. }) => Some(Page::Pause),
|
||||
Some(Page::Briefing { ingame: false, .. }) => Some(Page::Campaign),
|
||||
Some(Page::CustomBriefing) => None,
|
||||
Some(Page::Pause) => None,
|
||||
Some(Page::Title) => Some(Page::Title),
|
||||
None => Some(Page::Pause),
|
||||
@@ -865,6 +869,7 @@ impl Menu {
|
||||
Page::Account => self.draw_account(gfx, cloud),
|
||||
Page::Match { .. } => self.draw_match(gfx, cloud),
|
||||
Page::Briefing { mission, ingame } => self.draw_briefing(gfx, mission, ingame),
|
||||
Page::CustomBriefing => self.draw_custom_briefing(gfx),
|
||||
Page::Bestiary { tab, sel, level, .. } => self.draw_bestiary(gfx, tab, sel, level),
|
||||
Page::KeyCapture { action, .. } => {
|
||||
self.draw_settings(gfx, settings, 5);
|
||||
@@ -1632,10 +1637,37 @@ impl Menu {
|
||||
}
|
||||
|
||||
// scrolling on pages with lists (mouse wheel)
|
||||
// the briefing of an editor mission: its text (scrolls) and a button to begin
|
||||
fn draw_custom_briefing(&mut self, gfx: &Gfx) {
|
||||
let (sw, sh) = (sw(), sh());
|
||||
let cols = ((sw / CW()) as i32 - 2).min(64);
|
||||
let rows = ((sh / CH()) as i32 - 2).min(28);
|
||||
let x = grid_center(sw, cols as f32 * CW()).max(0.0);
|
||||
let y = grid_center_y(sh, rows as f32 * CH()).max(0.0);
|
||||
let (title, story) = self.custom_briefing.clone();
|
||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &title, FRAME_HI);
|
||||
let maxc = (cols - 6) as usize;
|
||||
let mut lines: Vec<String> = Vec::new();
|
||||
for p in story.split('\n') {
|
||||
lines.extend(wrap(p, maxc));
|
||||
if wrap(p, maxc).is_empty() {
|
||||
lines.push(String::new());
|
||||
}
|
||||
}
|
||||
self.rows_visible = (rows as usize).saturating_sub(7).max(1);
|
||||
self.scroll = self.scroll.min(lines.len().saturating_sub(self.rows_visible));
|
||||
for (k, l) in lines.iter().skip(self.scroll).take(self.rows_visible).enumerate() {
|
||||
gfx.text(l, x + CW() * 3.0, y + CH() * (2.0 + k as f32), S(), TXT);
|
||||
}
|
||||
let label = tr("camp.start");
|
||||
let w = gfx.measure(&label, S()) + CW() * 6.0;
|
||||
self.column(gfx, x + (cols as f32 * CW() - w) * 0.5, y + (rows - 4) as f32 * CH(), w, &[(Btn::Resume, label)]);
|
||||
}
|
||||
|
||||
pub fn scrollable(&self) -> bool {
|
||||
matches!(
|
||||
self.page,
|
||||
Some(Page::Saves { .. }) | Some(Page::Campaign) | Some(Page::CampaignEditor) | Some(Page::Briefing { .. }) | Some(Page::Settings { tab: 5, .. }) | Some(Page::Match { .. }) | Some(Page::Account)
|
||||
Some(Page::Saves { .. }) | Some(Page::Campaign) | Some(Page::CampaignEditor) | Some(Page::Briefing { .. }) | Some(Page::CustomBriefing) | Some(Page::Settings { tab: 5, .. }) | Some(Page::Match { .. }) | Some(Page::Account)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user