Pointer in the editor; new screenshots and an editor section in the README

Scenes for the screenshots come from export_scenes (src/scenes.rs) and are
taken with tools/screenshots.py in the Playwright image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 18:31:10 +01:00
1 parent f3ee6296db
commit ef7add2846
16 files changed
+257 -2

No files matched your search

+165
View File
@@ -0,0 +1,165 @@
// Scenes for the screenshots in docs/screenshots: savegames with a fitting camera, written by
// `cargo test --release export_scenes -- --ignored` to /tmp/scenes (see tools/screenshots.py).
use crate::game::*;
use crate::map::*;
use crate::render3d::Cam;
use crate::settings::Settings;
fn open(g: &mut Game, x0: i32, y0: i32, x1: i32, y1: i32) {
for y in y0..=y1 {
for x in x0..=x1 {
if inb(x, y) {
let t = &mut g.map.t[idx(x, y)];
if !matches!(t.kind, Kind::Core | Kind::Portal) {
t.kind = Kind::Floor;
t.owner = g.me;
t.gold = 0;
}
}
}
}
g.recount();
}
fn tower(g: &mut Game, kind: TowerKind, x: i32, y: i32, hero: bool, level: u32) {
let i = idx(x, y);
g.map.t[i].kind = Kind::Tower;
g.map.t[i].owner = if hero { Owner::Hero } else { g.me };
let id = g.next_id;
g.next_id += 1;
let mut tw = Tower { id, kind, tile: i, level, hp: 1.0, cd: 1.0, aim: 0.0, flash: 0.0, hero, rune_final: false };
tw.hp = tw.max_hp();
g.towers.push(tw);
}
fn creatures(g: &mut Game, kinds: &[(EK, u32)], x: f32, y: f32) {
for (k, &(kind, level)) in kinds.iter().enumerate() {
let a = k as f32 * 1.3;
let id = g.spawn(kind, false, x + a.cos() * 2.0, y + a.sin() * 2.0, level);
if let Some(e) = g.ents.iter_mut().find(|e| e.id == id) {
e.happy = 1.0;
e.settled = true;
}
}
}
fn save(name: &str, g: &mut Game, x: f32, z: f32, zoom: f32, yaw: f32, fog: bool) {
if !fog {
g.map.explored.clear();
g.map.visible.clear();
}
let mut cam = Cam::new(x, z);
cam.zoom = zoom;
cam.yaw += yaw;
let mut s = Settings::default();
s.language = "en".into();
s.hints = false;
std::fs::write(format!("/tmp/scenes/{}.json", name), crate::save::to_json(g, &cam, &s)).unwrap();
}
fn run(g: &mut Game, secs: f32) {
for _ in 0..(secs * 20.0) as usize {
g.update(0.05);
}
}
#[test]
#[ignore]
fn export_scenes() {
std::fs::create_dir_all("/tmp/scenes").unwrap();
// the dungeon's heart: the crystal core, dark towers, a demon gate, rooms around
let mut g = Game::new(11, 80, 64);
let (cx, cy) = g.map.core;
g.gold = 50000.0;
open(&mut g, cx - 9, cy - 8, cx + 11, cy + 9);
g.build_rect(1, cx - 8, cy - 7, cx - 4, cy - 3);
g.build_rect(0, cx + 5, cy - 7, cx + 9, cy - 4);
g.build_rect(3, cx - 8, cy + 4, cx - 3, cy + 8);
g.build_rect(4, cx + 5, cy + 4, cx + 10, cy + 8);
tower(&mut g, TowerKind::Arrow, cx - 2, cy - 3, false, 3);
tower(&mut g, TowerKind::Cannon, cx + 4, cy - 2, false, 2);
tower(&mut g, TowerKind::Magic, cx - 2, cy + 4, false, 3);
creatures(&mut g, &[(EK::Ogre, 6), (EK::Mage, 4), (EK::Spider, 3), (EK::Kobold, 5), (EK::Priestess, 7), (EK::Jailer, 3)], cx as f32 + 1.5, cy as f32 + 6.0);
g.place_all_torches();
run(&mut g, 6.0);
save("dungeon-heart", &mut g, cx as f32 + 1.5, cy as f32 + 1.5, 20.0, 0.0, false);
// forge and traps: coglings at work, every trap and door
let mut g = Game::new(12, 80, 64);
let (cx, cy) = g.map.core;
g.gold = 50000.0;
open(&mut g, cx - 4, cy - 10, cx + 16, cy + 8);
g.build_rect(8, cx + 6, cy - 9, cx + 13, cy - 4);
creatures(&mut g, &[(EK::Cogling, 4), (EK::Cogling, 6), (EK::Cogling, 2)], cx as f32 + 9.5, cy as f32 - 6.5);
use crate::traps::*;
let kinds = [TrapKind::Spikes, TrapKind::Poison, TrapKind::Fire, TrapKind::Roller, TrapKind::Alarm, TrapKind::Mine, TrapKind::Frost, TrapKind::WoodDoor, TrapKind::IronDoor, TrapKind::MagicDoor];
for (k, kind) in kinds.iter().enumerate() {
let i = idx(cx + 5 + k as i32, cy + 4);
if kind.door() {
g.map.t[i].kind = Kind::Door;
}
let id = g.next_id;
g.next_id += 1;
g.traps.push(Trap { id, kind: *kind, tile: i, stage: ARMED, crate_tile: 0, progress: 0.0, cd: 0.0, hp: kind.hp(), locked: false, carrier: 0, fired: if k % 3 == 0 { 0.2 } else { 99.0 }, aim: 0.0, revealed: false, sprung: true });
}
g.place_all_torches();
run(&mut g, 4.0);
save("forge-and-traps", &mut g, cx as f32 + 9.5, cy as f32 - 1.0, 18.0, 0.0, false);
// the dark rooms: prison, graveyard, arena, altar and guard posts
let mut g = Game::new(13, 80, 64);
let (cx, cy) = g.map.core;
g.gold = 60000.0;
open(&mut g, cx - 4, cy - 10, cx + 18, cy + 10);
g.build_rect(9, cx + 5, cy - 9, cx + 10, cy - 5);
g.build_rect(10, cx + 12, cy - 9, cx + 17, cy - 4);
g.build_rect(12, cx + 5, cy + 4, cx + 11, cy + 9);
g.build_rect(13, cx + 13, cy + 4, cx + 16, cy + 7);
g.build_rect(11, cx + 13, cy - 1, cx + 14, cy + 1);
creatures(&mut g, &[(EK::Revenant, 4), (EK::Ogre, 5), (EK::Kobold, 3), (EK::Jailer, 6)], cx as f32 + 9.0, cy as f32 + 2.0);
g.place_all_torches();
run(&mut g, 5.0);
save("dark-rooms", &mut g, cx as f32 + 11.0, cy as f32, 22.0, 0.0, false);
// a dwarf cave with its hoard
let mut g = Game::new(14, 96, 96);
if let Some(c) = g.dwarf_caves.first().map(|c| c.at) {
run(&mut g, 2.0);
save("dwarf-cave", &mut g, c.0 as f32 + 0.5, c.1 as f32 + 0.5, 16.0, 0.0, false);
}
// the start of a game in the fog of war
let mut g = Game::new(15, 96, 96);
let (cx, cy) = g.map.core;
run(&mut g, 3.0);
save("fog-of-war", &mut g, cx as f32 + 1.5, cy as f32 + 1.5, 34.0, 0.0, true);
// a battle between lava and water (the title demo's map, saved as a game)
let (mut g, _) = Game::new_demo(21);
run(&mut g, 25.0);
g.demo = None;
// a mission without goals: the battle goes on (a free game without opponents would be won)
g.mission = Some(MissionState { index: 0, goals: Vec::new(), tools: vec![true; crate::ui::TOOLS.len()], waves: false, wave_limit: 0, gate_defenders: false, heart: false, time_limit: 0.0, shards: 0 });
let (x, z) = (35.5, 23.5);
save("bridge-battle", &mut g, x, z, 16.0, 0.0, false);
// lava light, torches on stands and in the walls
let mut g = Game::new(16, 80, 64);
let (cx, cy) = g.map.core;
g.gold = 50000.0;
open(&mut g, cx - 6, cy - 9, cx + 16, cy + 9);
for y in cy - 9..=cy + 9 {
for x in cx + 9..cx + 12 {
let t = &mut g.map.t[idx(x, y)];
t.kind = Kind::Lava;
t.owner = Owner::None;
}
}
g.build_rect(3, cx - 5, cy + 4, cx + 2, cy + 8);
creatures(&mut g, &[(EK::Wyrm, 6), (EK::Bat, 3), (EK::Gargoyle, 4)], cx as f32 + 6.0, cy as f32);
g.place_all_torches();
run(&mut g, 4.0);
save("lava-light", &mut g, cx as f32 + 5.0, cy as f32, 22.0, 0.0, false);
}