summaryrefslogtreecommitdiff
path: root/src/scenes/mod.rs
diff options
context:
space:
mode:
authorskylar <me@casper>2026-11-11 15:24:37 -0800
committerskylar <me@casper>2026-11-11 15:24:37 -0800
commit9ccf951473e21a8b90c25161156fa09a9b227a7b (patch)
treee7bd9858a4cdbc5ae50069db8ca0cf868e436dec /src/scenes/mod.rs
parentd36c54d996f3c14ae811312dbbe621e12a39edde (diff)
Orrery 0.1.0: the galaxy, flight, cities, inventory and saves
Grows the derelict crawler into the whole game, and gets it ready to publish. - A galaxy of star systems joined by lanes, split between four peoples' territories, with a star map and a system map. Systems are modelled with Kepler orbits, moons inside their Hill spheres, belts past the frost line and stations at Lagrange points. - Flight around one place at a time: arcade flight with a throttle fallback where held keys can't be detected, mining rocks for ore, landing, docking at stations, and boarding wrecks that come and go by the calendar. - Your ship to walk about, and capital cities to land at: generated plans with walls, quarters and streets, building interiors, shops, taverns, scholars and armourers. - Fighting with a weapon and a gun ready, six damage types and how each monster takes them, and armour that guards, wears and can be mended. - Everything you own has a weight: a vim-keyed inventory, a pack and the ship's lockers, and trade in ore, salvage, arms and fuel. - Characters with a people and a ship of their own, saved as versioned JSON on every scene change and on quit, with a character sheet, a saves list and a pause menu wherever you are. - `orrery weapons`, `armour`, `galaxy`, `system` and `town` print the generators' output without the TUI. - Release: README install steps for macOS and Linux, a Makefile whose install also works with macOS's install, an MIT licence, and Cargo metadata with rust-version 1.88. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/scenes/mod.rs')
-rw-r--r--src/scenes/mod.rs159
1 files changed, 153 insertions, 6 deletions
diff --git a/src/scenes/mod.rs b/src/scenes/mod.rs
index 60db647..1c20edf 100644
--- a/src/scenes/mod.rs
+++ b/src/scenes/mod.rs
@@ -1,9 +1,25 @@
-//! Scenes: the title screen, the name forge, and derelicts to board;
-//! flight and ports later.
+//! Scenes: the title screen, characters and their saves, the name forge,
+//! derelicts to board, flight (around a place, across the system map, and
+//! between systems on the star map), and what's on the ground: cities to
+//! walk, and ports.
+pub mod aboard;
+pub mod character;
pub mod derelict;
+pub mod flight;
pub mod forge;
+pub mod galaxy_map;
+pub mod inventory;
+pub mod keys;
+pub mod pause;
+pub mod port;
+pub mod saves;
+pub mod sky;
+pub mod system_map;
pub mod title;
+pub mod town;
+#[cfg(test)]
+mod zz_dump;
use ratatui::Frame;
use ratatui::crossterm::event::KeyEvent;
@@ -15,8 +31,14 @@ pub enum Transition {
None,
/// Open a scene on top of this one. Popping it comes back here.
Push(Box<dyn Scene>),
+ /// Open several scenes at once, each on top of the one before.
+ Open(Vec<Box<dyn Scene>>),
/// Close this scene and return to the one beneath it.
Pop,
+ /// Close this scene and open these in its place.
+ Replace(Vec<Box<dyn Scene>>),
+ /// Close every scene but the title screen, at the bottom of the stack.
+ Title,
Quit,
}
@@ -33,10 +55,15 @@ pub trait Scene {
}
fn render(&self, frame: &mut Frame, ctx: &Ctx);
+
+ /// This scene has come to the top of the stack: just opened, or
+ /// uncovered when the ones above it closed. What it shows may need
+ /// bringing up to date with what happened up there.
+ fn on_top(&mut self, _ctx: &Ctx) {}
}
#[cfg(test)]
-mod tests {
+pub(crate) mod tests {
use ratatui::Terminal;
use ratatui::backend::TestBackend;
use ratatui::crossterm::event::{KeyCode, KeyModifiers};
@@ -53,13 +80,130 @@ mod tests {
#[test]
fn scenes_render_at_any_size() {
let mut ctx = test_ctx();
+ let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
+ let mut pausing = derelict::Derelict::new(2);
+ pausing.key(esc, &mut ctx);
let mut abandoning = derelict::Derelict::new(2);
- abandoning.key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE), &mut ctx);
- let scenes: [Box<dyn Scene>; 4] = [
- Box::new(title::Title::new(&ctx)),
+ abandoning.key(esc, &mut ctx);
+ abandoning.key(KeyEvent::new(KeyCode::Char('2'), KeyModifiers::NONE), &mut ctx);
+ let mut aiming = derelict::Derelict::new(2);
+ aiming.key(KeyEvent::new(KeyCode::Char('f'), KeyModifiers::NONE), &mut ctx);
+ aiming.key(KeyEvent::new(KeyCode::Char('l'), KeyModifiers::NONE), &mut ctx);
+ let mut packing = derelict::Derelict::new(2);
+ packing.key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE), &mut ctx);
+ // Something of every kind, on you and in the lockers.
+ {
+ use crate::dungeon::armour;
+ use crate::dungeon::arms::Source;
+ use crate::items::{Food, Item, Salvage, Slot, Stack};
+ let mut rng = crate::seed::rng(8);
+ let mut armor = |slot| {
+ let mut a = armour::find(&mut rng, Source::Armoury, false);
+ a.slot = slot;
+ a.durability = 1;
+ a
+ };
+ let food = Food {
+ title: "ration brick".into(),
+ description: "dense and grey".into(),
+ nourishment: 10,
+ mass: 400,
+ };
+ let scrap = Salvage { title: "coil of superconductor".into(), value: 30, mass: 6000 };
+ let g = &mut ctx.hold.gear;
+ g.worn.insert(Slot::Body, armor(Slot::Body));
+ g.add(Stack::one(Item::Armor(armor(Slot::Head))));
+ g.add(Stack { item: Item::Food(food), count: 3 });
+ g.add(Stack { item: Item::Medpatch, count: 2 });
+ ctx.hold.lockers.push(Stack { item: Item::Salvage(scrap.clone()), count: 4 });
+ ctx.hold.deck.push((crate::dungeon::Pos::new(0, 0), Stack::one(Item::Salvage(scrap))));
+ }
+ let mut rummaging = derelict::Derelict::board(2, &mut ctx);
+ for code in [KeyCode::Char('i'), KeyCode::Char('j'), KeyCode::Enter] {
+ rummaging.key(KeyEvent::new(code, KeyModifiers::NONE), &mut ctx);
+ }
+ let home = ctx.system.home_place();
+ let mut stowing = aboard::Aboard::landed(&ctx, home);
+ for code in [KeyCode::Char('i'), KeyCode::Char('l'), KeyCode::Char('/')] {
+ stowing.key(KeyEvent::new(code, KeyModifiers::NONE), &mut ctx);
+ }
+ let mut strolling = town::TownScene::new(&ctx, home);
+ strolling.key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE), &mut ctx);
+ let mut leaving = town::TownScene::new(&ctx, home);
+ leaving.key(esc, &mut ctx);
+ let mut resting = aboard::Aboard::landed(&ctx, home);
+ resting.key(esc, &mut ctx);
+ let mut flying = flight::FlightScene::new(&ctx);
+ flying.key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE), &mut ctx);
+ let mut holding = flight::FlightScene::new(&ctx);
+ holding.key(esc, &mut ctx);
+ let mut docked = port::Port::new(&ctx, 0);
+ docked.key(esc, &mut ctx);
+ let belt = ctx.system.places.len() - 1;
+ let mut trading = town::TownScene::new(&ctx, home);
+ for _ in 0..12 {
+ trading.key(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE), &mut ctx);
+ }
+ let wreck = ctx.system.wrecks_on(ctx.day).pop();
+ let mut renaming = character::Sheet::new();
+ renaming.key(KeyEvent::new(KeyCode::Char('r'), KeyModifiers::NONE), &mut ctx);
+ let mut creating = character::Create::new(&ctx);
+ creating.key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE), &mut ctx);
+ let dir = crate::save::scratch("render");
+ ctx.saves = Some(dir.clone());
+ ctx.slot = Some(crate::save::new_slot(&dir, &ctx.character, ctx.seed));
+ ctx.record.slain.insert("rust-mite".into(), 2);
+ ctx.bestiary.insert(("rust-mite", crate::dungeon::arms::Dmg::Blunt));
+ ctx.autosave();
+ std::fs::write(dir.join("broken.json"), "{").unwrap();
+ let playing = title::Title::new(&ctx);
+ let mut plotting = galaxy_map::GalaxyMap::new(&ctx);
+ plotting.key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE), &mut ctx);
+ let mut far = galaxy_map::GalaxyMap::new(&ctx);
+ for _ in 0..6 {
+ far.key(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE), &mut ctx);
+ }
+ let mut stranded = test_ctx();
+ let dead = (0..stranded.galaxy.sites.len())
+ .find(|&i| !stranded.galaxy.sites[i].kind.sells_fuel())
+ .unwrap();
+ stranded.enter_system(dead);
+ stranded.hold.fuel = 0;
+ stranded.hold.debt = 120;
+ let scenes: [Box<dyn Scene>; 33] = [
+ Box::new(title::Title::new(&test_ctx())),
+ Box::new(playing),
+ Box::new(character::Create::new(&ctx)),
+ Box::new(creating),
+ Box::new(character::Sheet::new()),
+ Box::new(renaming),
+ Box::new(saves::Saves::new(&ctx)),
Box::new(forge::Forge::new(&ctx)),
Box::new(derelict::Derelict::new(1)),
+ Box::new(pausing),
Box::new(abandoning),
+ Box::new(holding),
+ Box::new(docked),
+ Box::new(leaving),
+ Box::new(resting),
+ Box::new(aiming),
+ Box::new(packing),
+ Box::new(rummaging),
+ Box::new(stowing),
+ Box::new(strolling),
+ Box::new(flying),
+ Box::new(system_map::SystemMap::new(&ctx)),
+ Box::new(galaxy_map::GalaxyMap::new(&ctx)),
+ Box::new(plotting),
+ Box::new(far),
+ Box::new(port::Port::new(&ctx, ctx.system.home_place())),
+ Box::new(port::Port::new(&ctx, belt)),
+ Box::new(port::Port::new(&ctx, 0)),
+ Box::new(town::TownScene::new(&ctx, home)),
+ Box::new(trading),
+ Box::new(aboard::Aboard::landed(&ctx, home)),
+ Box::new(aboard::Aboard::resumed(&ctx, home)),
+ Box::new(aboard::Aboard::adrift("in orbit around nowhere", wreck)),
];
for (width, height) in [(160, 50), (80, 24), (40, 12), (5, 3), (1, 1)] {
let mut terminal = Terminal::new(TestBackend::new(width, height)).unwrap();
@@ -69,6 +213,9 @@ mod tests {
terminal.draw(|frame| scene.render(frame, &ctx)).unwrap();
}
}
+ let map = galaxy_map::GalaxyMap::new(&stranded);
+ terminal.draw(|frame| map.render(frame, &stranded)).unwrap();
}
+ let _ = std::fs::remove_dir_all(dir);
}
}