// 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_with_camera(g, &cam)).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); // bridges: a raised and a lowered drawbridge over water, a burning bridge, obsidian on lava let mut g = Game::new(17, 80, 64); let (cx, cy) = g.map.core; g.gold = 50000.0; open(&mut g, cx - 6, cy - 9, cx + 18, cy + 9); for y in cy - 9..=cy + 9 { for x in cx + 6..cx + 9 { let t = &mut g.map.t[idx(x, y)]; t.kind = Kind::Water; t.owner = Owner::None; } for x in cx + 12..cx + 15 { let t = &mut g.map.t[idx(x, y)]; t.kind = Kind::Lava; t.owner = Owner::None; } } g.drawbridge_order(cx + 5, cy - 5, cx + 9, cy - 5); g.drawbridge_order(cx + 5, cy - 1, cx + 9, cy - 1); g.drawbridge_order(cx + 6, cy - 1, cx + 6, cy - 1); // raised g.build_bridges(cx + 5, cy + 4, cx + 9, cy + 4); g.ignite_at(cx as f32 + 7.5, cy as f32 + 4.5); g.cool_lava_around(cx as f32 + 13.5, cy as f32 + 1.5, 2.5, 60.0); g.place_all_torches(); run(&mut g, 2.5); save("bridges", &mut g, cx as f32 + 9.0, cy as f32, 18.0, 0.0, false); // wood: giant mushrooms in a cavern, an imp felling one; planned walls let mut g = Game::new(9, 96, 96); let m = g.map.t.iter().position(|t| t.kind == Kind::Mushroom).unwrap(); let (mx, my) = xy(m); let (cx, cy) = g.map.core; g.gold = 5000.0; open(&mut g, cx - 2, cy - 2, cx + 6, cy + 4); g.plan_walls(cx + 5, cy - 1, cx + 5, cy + 2); run(&mut g, 2.0); save("wood", &mut g, mx as f32 + 0.5, my as f32 + 0.5, 14.0, 0.0, false); // above ground: the cliff with a cave mouth, grass, a forest let mut g = Game::new(23, 128, 96); g.add_surface(23, 0.45, 3); g.init_fog(); let mid = (mh() / 2) as i32; let ex = (0..mw() as i32).find(|&x| g.map.sky(idx(x, mid))).unwrap_or(60); run(&mut g, 1.0); save("surface", &mut g, ex as f32 + 6.0, mid as f32, 24.0, 0.0, false); // room levels: a lair at level 3, a treasury at level 2 next to plain rooms 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(2, cx - 8, cy + 4, cx - 3, cy + 8); g.set_room_level(1, 3); g.set_room_level(0, 2); g.place_all_torches(); run(&mut g, 1.0); save("room-levels", &mut g, cx as f32 + 1.5, cy as f32 - 2.0, 16.0, 0.0, false); } // a mushroom grove: moss, grown (glowing) and young mushrooms #[test] #[ignore] fn export_grove_scene() { std::fs::create_dir_all("/tmp/scenes").unwrap(); let mut g = Game::new(9, 96, 96); // the grove tile with the most grove around it let best = (0..g.map.t.len()) .filter(|&i| g.map.t[i].kind == Kind::Grove) .max_by_key(|&i| { let (x, y) = xy(i); (-3..=3).flat_map(|dy| (-3..=3).map(move |dx| (dx, dy))).filter(|&(dx, dy)| g.map.get(x + dx, y + dy).map_or(false, |t| matches!(t.kind, Kind::Grove | Kind::Mushroom))).count() }) .unwrap(); let (x, y) = xy(best); run(&mut g, 1.0); save("mushroom-grove", &mut g, x as f32 + 0.5, y as f32 + 0.5, 12.0, 0.0, false); } // the rogues of the later waves in a dungeon: thief, monk, ranger, inquisitor and a snare #[test] #[ignore] fn export_rogues_scene() { std::fs::create_dir_all("/tmp/scenes").unwrap(); let mut g = Game::new(11, 80, 64); let (cx, cy) = g.map.core; open(&mut g, cx - 9, cy - 8, cx + 11, cy + 9); g.place_all_torches(); for (k, kind) in [EK::Thief, EK::Monk, EK::Ranger, EK::Inquisitor].into_iter().enumerate() { let id = g.spawn(kind, true, cx as f32 + 4.5 + k as f32 * 1.2, cy as f32 + 5.5, 3); if let Some(e) = g.ents.iter_mut().find(|e| e.id == id) { e.guard = true; e.home = Some((e.x, e.y)); } } g.snares.push(idx(cx + 5, cy + 3)); g.snares.push(idx(cx + 7, cy + 3)); save("rogues", &mut g, cx as f32 + 6.5, cy as f32 + 4.5, 8.0, 0.0, false); } // weather above ground: rain and fog over the land #[test] #[ignore] fn export_weather_scenes() { std::fs::create_dir_all("/tmp/scenes").unwrap(); for (name, kind) in [("weather-rain", crate::weather::RAIN), ("weather-fog", crate::weather::FOG), ("weather-storm", crate::weather::STORM)] { let mut g = Game::new(7, 96, 96); g.add_surface(7, 0.5, 3); g.weather = crate::weather::Weather { kind, shown: kind, level: 1.0, left: 900.0, ..Default::default() }; // the camera on the land above ground, near the edge of the dungeon side let i = (0..g.map.t.len()).filter(|&i| g.map.sky(i)).min_by_key(|&i| { let (x, y) = xy(i); (x - 52).abs() + (y - 48).abs() }).unwrap(); let (x, y) = xy(i); save(name, &mut g, x as f32 + 4.5, y as f32 + 0.5, 18.0, 0.0, false); } } // named creatures and memorial stones of the fallen #[test] #[ignore] fn export_names_scene() { std::fs::create_dir_all("/tmp/scenes").unwrap(); let mut g = Game::new(11, 80, 64); let (cx, cy) = g.map.core; open(&mut g, cx - 9, cy - 8, cx + 11, cy + 9); g.place_all_torches(); creatures(&mut g, &[(EK::Ogre, 9), (EK::Mage, 5), (EK::Kobold, 2)], cx as f32 + 6.5, cy as f32 + 5.0); for (k, &(kind, lv)) in [(EK::Spider, 4), (EK::Ogre, 8), (EK::Mage, 6)].iter().enumerate() { let id = g.spawn(kind, false, cx as f32 + 2.5 + k as f32 * 2.0, cy as f32 + 6.5, lv); let j = g.ents.iter().position(|e| e.id == id).unwrap(); g.kill(j); } save("names", &mut g, cx as f32 + 5.0, cy as f32 + 5.5, 9.0, 0.0, false); }