diff --git a/.gitignore b/.gitignore index 9b5b643..d5ca841 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,9 @@ docs/gifs/* .rustfmt.toml .prettierignore -# Savegame -savegame.json \ No newline at end of file +# Save files, morgue files +savegame.json +morgue + +# A default user config will be written on first run, if not present +config.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 15084c6..73868b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2284,6 +2284,7 @@ dependencies = [ "serde_json", "specs", "specs-derive", + "toml", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 743ea45..2febef8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ specs = { version = "0.16.1", features = ["serde"] } specs-derive = "0.4.1" serde = { version = "1.0.93", features = ["derive"]} serde_json = "1.0.39" +toml = "0.5" lazy_static = "1.4.0" [dev-dependencies] diff --git a/src/ai/energy_system.rs b/src/ai/energy_system.rs index c24c7c7..b09c6fe 100644 --- a/src/ai/energy_system.rs +++ b/src/ai/energy_system.rs @@ -1,7 +1,8 @@ use crate::config::entity::*; -use crate::{ Burden, BurdenLevel, Clock, Energy, Name, Position, RunState, TakingTurn, LOG_TICKS }; +use crate::{ Burden, BurdenLevel, Clock, Energy, Name, Position, RunState, TakingTurn }; use rltk::prelude::*; use specs::prelude::*; +use crate::config::CONFIG; pub struct EnergySystem {} @@ -51,7 +52,7 @@ impl<'a> System<'a> for EnergySystem { energy.current -= TURN_COST; crate::gamelog::record_event("turns", 1); // Handle spawning mobs each turn - if LOG_TICKS { + if CONFIG.logging.log_ticks { console::log(format!("===== TURN {} =====", crate::gamelog::get_event_count("turns"))); } } @@ -101,7 +102,7 @@ impl<'a> System<'a> for EnergySystem { } if my_turn { turns.insert(entity, TakingTurn {}).expect("Unable to insert turn."); - if LOG_TICKS { + if CONFIG.logging.log_ticks { let name = if let Some(name) = names.get(entity) { &name.name } else { "Unknown entity" }; console::log( format!("ENERGY SYSTEM: {} granted a turn. [leftover energy: {}].", name, energy.current) diff --git a/src/config/load.rs b/src/config/load.rs new file mode 100644 index 0000000..b86a5d2 --- /dev/null +++ b/src/config/load.rs @@ -0,0 +1,2 @@ +use std::error::Error; +use super::Config; diff --git a/src/config/mod.rs b/src/config/mod.rs index 5dc7bf6..ac22321 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,8 +3,105 @@ pub mod visuals; pub mod glyphs; pub mod messages; pub mod char_create; +mod load; -// DEBUG/LOGGING -pub const SHOW_MAPGEN: bool = false; // Shows the step-by-step map gen process. -pub const LOG_SPAWNING: bool = true; // Logs spawning of entities. -pub const LOG_TICKS: bool = false; // Logs hunger/energy ticks. +use rltk::prelude::*; +use toml::de::Error as TomlError; +use serde::{ Serialize, Deserialize }; + +lazy_static! { + pub static ref CONFIG: Config = try_load_configuration(); +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + pub logging: LogConfig, + pub visuals: VisualConfig, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct VisualConfig { + pub with_scanlines: bool, + pub with_screen_burn: bool, + pub with_darken_by_distance: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LogConfig { + pub show_mapgen: bool, + pub log_spawning: bool, + pub log_ticks: bool, +} + +impl Default for Config { + fn default() -> Self { + Config { + logging: LogConfig { + show_mapgen: false, + log_spawning: false, + log_ticks: false, + }, + visuals: VisualConfig { + with_scanlines: false, + with_screen_burn: false, + with_darken_by_distance: true, + }, + } + } +} + +#[derive(Debug)] +pub enum ReadError { + Io(std::io::Error), + Toml(TomlError), +} + +impl From for ReadError { + fn from(error: std::io::Error) -> Self { + ReadError::Io(error) + } +} + +impl From for ReadError { + fn from(error: TomlError) -> Self { + ReadError::Toml(error) + } +} + +impl Config { + pub fn load_from_file(filename: &str) -> Result { + let contents = std::fs::read_to_string(filename).map_err(|e| ReadError::Io(e))?; + let config: Config = toml::from_str(&contents).map_err(|e| ReadError::Toml(e))?; + return Ok(config); + } + pub fn save_to_file(&self, filename: &str) -> Result<(), Box> { + let toml_string = toml::to_string(self)?; + std::fs::write(filename, toml_string)?; + Ok(()) + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn try_load_configuration() -> Config { + let config: Config = match Config::load_from_file("config.toml") { + Ok(config) => { + console::log(format!("Successfully loaded config: {:?}", config)); + config + } + Err(e) => { + console::log(format!("Error loading config: {:?}", e)); + let config = Config::default(); + if let Err(write_err) = config.save_to_file("config.toml") { + eprintln!("Error writing default config: {:?}", write_err); + } + config + } + }; + + return config; +} + +#[cfg(target_arch = "wasm32")] +pub fn try_load_configuration() -> Config { + let config = Config::default(); +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index bd2da48..6663a99 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1094,6 +1094,7 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { pub enum YesNoResult { NoSelection, Yes, + No, } pub fn game_over(ctx: &mut Rltk) -> YesNoResult { @@ -1103,7 +1104,13 @@ pub fn game_over(ctx: &mut Rltk) -> YesNoResult { let height = 20; ctx.draw_box(x, y, width, height, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK)); ctx.print_color(x + 3, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "You died!"); - ctx.print_color(x + 3, y + height, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "ESC to close"); + ctx.print_color( + x + 3, + y + height, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + " Write a morgue file? [y/n] " + ); x += 2; y += 2; ctx.print_color( @@ -1164,7 +1171,8 @@ pub fn game_over(ctx: &mut Rltk) -> YesNoResult { None => YesNoResult::NoSelection, Some(key) => match key { - VirtualKeyCode::Escape => YesNoResult::Yes, + VirtualKeyCode::N => YesNoResult::No, + VirtualKeyCode::Y => YesNoResult::Yes, _ => YesNoResult::NoSelection, } } diff --git a/src/hunger_system.rs b/src/hunger_system.rs index 8c2b4ec..7670554 100644 --- a/src/hunger_system.rs +++ b/src/hunger_system.rs @@ -1,14 +1,7 @@ -use super::{ - effects::{ add_effect, EffectType, Targets }, - gamelog, - Clock, - HungerClock, - HungerState, - TakingTurn, - LOG_TICKS, -}; +use super::{ effects::{ add_effect, EffectType, Targets }, gamelog, Clock, HungerClock, HungerState, TakingTurn }; use rltk::prelude::*; use specs::prelude::*; +use crate::config::CONFIG; /// HungerSystem is in charge of ticking down the hunger clock for entities with a hunger clock, /// every time the turn clock ticks. @@ -78,7 +71,7 @@ impl<'a> System<'a> for HungerSystem { if hunger_clock.state == HungerState::Starving { add_effect(None, EffectType::Damage { amount: 1 }, Targets::Entity { target: entity }); } - if LOG_TICKS && entity == *player_entity { + if CONFIG.logging.log_ticks && entity == *player_entity { rltk::console::log( format!("HUNGER SYSTEM: Ticked for player entity. [clock: {}]", hunger_clock.duration) ); diff --git a/src/main.rs b/src/main.rs index c4238ba..dd1dc88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,13 +35,11 @@ mod gamesystem; mod random_table; mod rex_assets; mod spatial; +mod morgue; #[macro_use] extern crate lazy_static; -//Consts -pub use config::{ SHOW_MAPGEN, LOG_SPAWNING, LOG_TICKS }; - #[derive(PartialEq, Copy, Clone)] pub enum RunState { AwaitingInput, // Player's turn @@ -509,15 +507,20 @@ impl GameState for State { } RunState::GameOver => { let result = gui::game_over(ctx); - match result { - gui::YesNoResult::NoSelection => {} - gui::YesNoResult::Yes => { - self.game_over_cleanup(); - new_runstate = RunState::MapGeneration; - self.mapgen_next_state = Some(RunState::MainMenu { - menu_selection: gui::MainMenuSelection::NewGame, - }); + let write_to_morgue: Option = match result { + gui::YesNoResult::NoSelection => None, + gui::YesNoResult::No => Some(false), + gui::YesNoResult::Yes => Some(true), + }; + if let Some(response) = write_to_morgue { + if response { + morgue::create_morgue_file(&self.ecs); } + self.game_over_cleanup(); + new_runstate = RunState::MapGeneration; + self.mapgen_next_state = Some(RunState::MainMenu { + menu_selection: gui::MainMenuSelection::NewGame, + }); } } RunState::NextLevel => { @@ -533,11 +536,11 @@ impl GameState for State { RunState::HelpScreen => { let result = gui::show_help(ctx); match result { - gui::YesNoResult::NoSelection => {} gui::YesNoResult::Yes => { gamelog::record_event("looked_for_help", 1); new_runstate = RunState::AwaitingInput; } + _ => {} } } RunState::MagicMapReveal { row, cursed } => { @@ -576,7 +579,7 @@ impl GameState for State { } } RunState::MapGeneration => { - if !SHOW_MAPGEN { + if !config::CONFIG.logging.show_mapgen { new_runstate = self.mapgen_next_state.unwrap(); } if self.mapgen_history.len() != 0 { @@ -624,7 +627,7 @@ fn main() -> rltk::BError { .with_tile_dimensions(14, 16) .with_simple_console(DISPLAYWIDTH, DISPLAYHEIGHT, "curses14x16.png") .build()?; - if config::visuals::WITH_SCANLINES { + if config::CONFIG.visuals.with_scanlines { context.with_post_scanlines(config::visuals::WITH_SCREEN_BURN); } diff --git a/src/map/interval_spawning_system.rs b/src/map/interval_spawning_system.rs index 07c6976..72e59ad 100644 --- a/src/map/interval_spawning_system.rs +++ b/src/map/interval_spawning_system.rs @@ -1,4 +1,4 @@ -use crate::{ gamelog, raws, spawner, Clock, Map, RandomNumberGenerator, TakingTurn, LOG_SPAWNING }; +use crate::{ config::CONFIG, gamelog, raws, spawner, Clock, Map, RandomNumberGenerator, TakingTurn }; use specs::prelude::*; const TRY_SPAWN_CHANCE: i32 = 70; @@ -22,7 +22,7 @@ pub fn try_spawn_interval(ecs: &mut World) { } } if try_spawn { - if LOG_SPAWNING { + if CONFIG.logging.log_spawning { rltk::console::log("SPAWNINFO: Trying spawn."); } spawn_random_mob_in_free_nonvisible_tile(ecs); @@ -36,7 +36,7 @@ fn spawn_random_mob_in_free_nonvisible_tile(ecs: &mut World) { rltk::console::log(player_level); let difficulty = (map.difficulty + player_level) / 2; if available_tiles.len() == 0 { - if LOG_SPAWNING { + if CONFIG.logging.log_spawning { rltk::console::log("SPAWNINFO: No free tiles; not spawning anything.."); } return; @@ -55,7 +55,7 @@ fn spawn_random_mob_in_free_nonvisible_tile(ecs: &mut World) { std::mem::drop(rng); // For every idx in the spawn list, spawn mob. for idx in spawn_locations { - if LOG_SPAWNING { + if CONFIG.logging.log_spawning { rltk::console::log(format!("SPAWNINFO: Spawning {} at {}, {}.", key, idx.0, idx.1)); } raws::spawn_named_entity( diff --git a/src/map/themes.rs b/src/map/themes.rs index 533518e..87a7d18 100644 --- a/src/map/themes.rs +++ b/src/map/themes.rs @@ -1,6 +1,7 @@ use super::{ Map, Point, TileType }; use crate::config::glyphs::*; use crate::config::visuals::*; +use crate::config::CONFIG; use rltk::prelude::*; use std::ops::{ Add, Mul }; @@ -19,13 +20,17 @@ pub fn get_tile_renderables_for_id(idx: usize, map: &Map, other_pos: Option 0.0 { + if CONFIG.visuals.with_scanlines && WITH_SCANLINES_BRIGHTEN_AMOUNT > 0.0 { (fg, bg) = brighten_by(fg, bg, WITH_SCANLINES_BRIGHTEN_AMOUNT); } bg = apply_bloodstain_if_necessary(bg, map, idx); let (mut multiplier, mut nonvisible, mut darken) = (1.0, false, false); if !map.visible_tiles[idx] { - multiplier = if WITH_SCANLINES { NON_VISIBLE_MULTIPLIER_IF_SCANLINES } else { NON_VISIBLE_MULTIPLIER }; + multiplier = if CONFIG.visuals.with_scanlines { + NON_VISIBLE_MULTIPLIER_IF_SCANLINES + } else { + NON_VISIBLE_MULTIPLIER + }; nonvisible = true; } if other_pos.is_some() && WITH_DARKEN_BY_DISTANCE && !nonvisible { @@ -34,7 +39,7 @@ pub fn get_tile_renderables_for_id(idx: usize, map: &Map, other_pos: Option f32 { (distance - START_DARKEN_AT_N_TILES) / ((crate::config::entity::DEFAULT_VIEWSHED_STANDARD as f32) - START_DARKEN_AT_N_TILES); let interp_factor = interp_factor.max(0.0).min(1.0); // Clamp [0-1] - return 1.0 - interp_factor * (1.0 - (if WITH_SCANLINES { MAX_DARKENING_IF_SCANLINES } else { MAX_DARKENING })); + return ( + 1.0 - + interp_factor * (1.0 - (if CONFIG.visuals.with_scanlines { MAX_DARKENING_IF_SCANLINES } else { MAX_DARKENING })) + ); } fn brighten_by(mut fg: RGB, mut bg: RGB, amount: f32) -> (RGB, RGB) { diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 7349eba..7357ac5 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -1,4 +1,4 @@ -use super::{ spawner, Map, Position, Rect, TileType, SHOW_MAPGEN }; +use super::{ spawner, Map, Position, Rect, TileType }; mod bsp_dungeon; use bsp_dungeon::BspDungeonBuilder; mod bsp_interior; @@ -35,6 +35,7 @@ mod voronoi_spawning; use common::*; use specs::prelude::*; use voronoi_spawning::VoronoiSpawning; +use super::config::CONFIG; //use wfc::WaveFunctionCollapseBuilder; mod room_exploder; use room_exploder::RoomExploder; @@ -78,7 +79,7 @@ pub struct BuilderMap { impl BuilderMap { fn take_snapshot(&mut self) { - if SHOW_MAPGEN { + if CONFIG.logging.show_mapgen { let mut snapshot = self.map.clone(); for v in snapshot.revealed_tiles.iter_mut() { *v = true; @@ -154,7 +155,9 @@ impl BuilderChain { spawned_entities.push(&entity.1); spawner::spawn_entity(ecs, &(&entity.0, &entity.1)); } - rltk::console::log(format!("DEBUGINFO: SPAWNED ENTITIES = {:?}", spawned_entities)); + if CONFIG.logging.log_spawning { + rltk::console::log(format!("DEBUGINFO: SPAWNED ENTITIES = {:?}", spawned_entities)); + } } } diff --git a/src/morgue.rs b/src/morgue.rs new file mode 100644 index 0000000..5da74d5 --- /dev/null +++ b/src/morgue.rs @@ -0,0 +1,32 @@ +use std::fs::{ File, create_dir_all }; +use std::io::{ self, Write }; +use std::time::SystemTime; +use specs::prelude::*; +use rltk::prelude::*; + +#[cfg(target_arch = "wasm32")] +pub fn create_morgue_file(_ecs: &World) {} + +#[cfg(not(target_arch = "wasm32"))] +pub fn create_morgue_file(ecs: &World) { + let morgue_dir = "morgue"; + if let Err(err) = create_dir_all(&morgue_dir) { + console::log(format!("Unable to create the directory (/{}): {}", morgue_dir, err)); + } + if let Err(err) = write_morgue_file(ecs, &morgue_dir) { + console::log(format!("Unable to write the morgue file: {}", err)); + } +} + +fn write_morgue_file(ecs: &World, morgue_dir: &str) -> Result<(), io::Error> { + let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + let morgue_info = format!( + r#"╔══════════════════════════════════════════════════════════════╗ +║ Level 1, human wizard ║ +╚══════════════════════════════════════════════════════════════╝"# + ); + let file_name = format!("{}/{}-{}-{}.txt", morgue_dir, "human", "wizard", timestamp); + let mut file = File::create(&file_name)?; // Open/create morgue file + file.write_all(morgue_info.as_bytes())?; + Ok(()) +} diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 9285e7b..3bd5df7 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -3,7 +3,7 @@ use crate::components::*; use crate::gamesystem::*; use crate::gui::Ancestry; use crate::random_table::RandomTable; -use crate::LOG_SPAWNING; +use crate::config::CONFIG; use regex::Regex; use rltk::prelude::*; use specs::prelude::*; @@ -543,7 +543,7 @@ pub fn spawn_named_mob( eb = eb.with(LootTable { table: loot.table.clone(), chance: loot.chance }); } - if LOG_SPAWNING { + if CONFIG.logging.log_spawning { rltk::console::log( format!( "SPAWNLOG: {} ({}HP, {}MANA, {}BAC) spawned at level {} ({}[base], {}[map difficulty], {}[player level]), worth {} XP", diff --git a/wasm/index.html b/wasm/index.html index a237547..429aada 100644 --- a/wasm/index.html +++ b/wasm/index.html @@ -9,10 +9,10 @@
- + diff --git a/wasm/rust-llyrl.js b/wasm/rust-rl.js similarity index 98% rename from wasm/rust-llyrl.js rename to wasm/rust-rl.js index fb46a5a..6d2e080 100644 --- a/wasm/rust-llyrl.js +++ b/wasm/rust-rl.js @@ -208,7 +208,7 @@ function makeMutClosure(arg0, arg1, dtor, f) { return real; } function __wbg_adapter_20(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h648b303b06146952(arg0, arg1); + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__he878b5719967bfcb(arg0, arg1); } function __wbg_adapter_23(arg0, arg1, arg2) { @@ -801,16 +801,16 @@ function __wbg_get_imports() { const ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper699 = function(arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper698 = function(arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 125, __wbg_adapter_20); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2728 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 516, __wbg_adapter_23); + imports.wbg.__wbindgen_closure_wrapper2729 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 519, __wbg_adapter_23); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper2730 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 516, __wbg_adapter_23); + imports.wbg.__wbindgen_closure_wrapper2731 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 519, __wbg_adapter_23); return addHeapObject(ret); }; diff --git a/wasm/rust-llyrl_bg.wasm b/wasm/rust-rl_bg.wasm similarity index 53% rename from wasm/rust-llyrl_bg.wasm rename to wasm/rust-rl_bg.wasm index a18a09b..477a4e0 100644 Binary files a/wasm/rust-llyrl_bg.wasm and b/wasm/rust-rl_bg.wasm differ