Desktop self test: DC_SHOT starts the demo map or a savegame, takes a screenshot and quits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d7b6d6de7e
commit
04d9d719d7
1 file changed
+98
+98
@@ -945,6 +945,8 @@ async fn main() {
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
audio::native::start();
|
audio::native::start();
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let mut shot = ShotPlan::from_env();
|
||||||
loop {
|
loop {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
audio::native::pump();
|
audio::native::pump();
|
||||||
@@ -2160,12 +2162,108 @@ app.pending_briefing = briefing_of(&campaign, i);
|
|||||||
was_briefing = briefing;
|
was_briefing = briefing;
|
||||||
voice.update(now, if app.settings.muted { 0.0 } else { app.settings.volume });
|
voice.update(now, if app.settings.muted { 0.0 } else { app.settings.volume });
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let shot_now = shot.as_mut().and_then(|p| p.before_frame(&mut app, &mut ui, &mut menu, &mut r3d, now));
|
||||||
draw_frame(&mut app, &gfx, &mut r3d, &mut ui, &mut menu, &cloud, &inp, (mx, my), touch_mode, now);
|
draw_frame(&mut app, &gfx, &mut r3d, &mut ui, &mut menu, &cloud, &inp, (mx, my), touch_mode, now);
|
||||||
ui::end_frame();
|
ui::end_frame();
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
if let Some(path) = shot_now {
|
||||||
|
shot_capture(&path);
|
||||||
|
}
|
||||||
next_frame().await
|
next_frame().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- self test and screenshots (desktop): DC_SHOT=demo or a savegame file starts it right
|
||||||
|
// away, DC_SHOT_STYLE=hd|pixel picks the graphics style (not saved), DC_SHOT_AFTER seconds later
|
||||||
|
// (default 10) the screen goes to DC_SHOT_OUT (default /tmp/dc-shot.png) and the game ends;
|
||||||
|
// DC_SHOT_ZOOM sets the camera zoom, DC_SHOT_YAW its turn, DC_SHOT_AT=x,z where it looks
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
struct ShotPlan {
|
||||||
|
load: String,
|
||||||
|
out: String,
|
||||||
|
after: f64,
|
||||||
|
style: Option<u16>,
|
||||||
|
zoom: Option<f32>,
|
||||||
|
yaw: Option<f32>,
|
||||||
|
at: Option<(f32, f32)>,
|
||||||
|
started: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl ShotPlan {
|
||||||
|
fn from_env() -> Option<ShotPlan> {
|
||||||
|
let load = std::env::var("DC_SHOT").ok()?;
|
||||||
|
let num = |k: &str| std::env::var(k).ok().and_then(|v| v.parse::<f32>().ok());
|
||||||
|
Some(ShotPlan {
|
||||||
|
load,
|
||||||
|
out: std::env::var("DC_SHOT_OUT").unwrap_or_else(|_| "/tmp/dc-shot.png".into()),
|
||||||
|
after: num("DC_SHOT_AFTER").unwrap_or(10.0) as f64,
|
||||||
|
style: std::env::var("DC_SHOT_STYLE").ok().map(|v| if v == "pixel" { settings::PIXEL_ART_ROWS } else { settings::HIGH_RES_ROWS }),
|
||||||
|
zoom: num("DC_SHOT_ZOOM"),
|
||||||
|
yaw: num("DC_SHOT_YAW"),
|
||||||
|
at: std::env::var("DC_SHOT_AT").ok().and_then(|v| {
|
||||||
|
let (a, b) = v.split_once(',')?;
|
||||||
|
Some((a.trim().parse().ok()?, b.trim().parse().ok()?))
|
||||||
|
}),
|
||||||
|
started: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// starts the scene on the first frame; returns the file to write once the time has come
|
||||||
|
fn before_frame(&mut self, app: &mut App, ui: &mut Ui, menu: &mut Menu, r3d: &mut R3d, now: f64) -> Option<String> {
|
||||||
|
match self.started {
|
||||||
|
None => {
|
||||||
|
if let Some(rows) = self.style {
|
||||||
|
app.settings.pixel_rows = rows;
|
||||||
|
}
|
||||||
|
app.settings.hints = false;
|
||||||
|
apply_settings(&app.settings, r3d);
|
||||||
|
if self.load == "demo" {
|
||||||
|
app.seed = next_seed(app.seed);
|
||||||
|
(app.game, app.cam) = Game::new_demo_map(app.seed);
|
||||||
|
apply_game_settings(&mut app.game, &app.settings);
|
||||||
|
r3d.invalidate();
|
||||||
|
} else if let Ok(bytes) = std::fs::read(&self.load) {
|
||||||
|
app.load_bytes(&bytes, ui, menu, r3d, now);
|
||||||
|
}
|
||||||
|
menu.page = None;
|
||||||
|
ui.show_help = false;
|
||||||
|
if let Some(z) = self.zoom {
|
||||||
|
app.cam.zoom = z;
|
||||||
|
}
|
||||||
|
if let Some(y) = self.yaw {
|
||||||
|
app.cam.yaw = y;
|
||||||
|
}
|
||||||
|
if let Some((x, z)) = self.at {
|
||||||
|
(app.cam.tx, app.cam.tz, app.cam.rx, app.cam.rz) = (x, z, x, z);
|
||||||
|
}
|
||||||
|
self.started = Some(now);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Some(t0) if now - t0 >= self.after => Some(self.out.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writes the screen to a PNG file and ends the game
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn shot_capture(path: &str) {
|
||||||
|
let img = get_screen_data();
|
||||||
|
let (w, h) = (img.width as u32, img.height as u32);
|
||||||
|
let mut out = Image::gen_image_color(w as u16, h as u16, BLACK);
|
||||||
|
for y in 0..h {
|
||||||
|
for x in 0..w {
|
||||||
|
let mut p = img.get_pixel(x, y);
|
||||||
|
p.a = 1.0;
|
||||||
|
out.set_pixel(x, y, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.export_png(path);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
// pointer: CSS pixels of the mouse or finger; touch: hover highlight and information only
|
// pointer: CSS pixels of the mouse or finger; touch: hover highlight and information only
|
||||||
// while a finger is down or for a long-pressed point
|
// while a finger is down or for a long-pressed point
|
||||||
|
|||||||
Reference in new issue
Block a user