Brogue-style tile colour offsets, and main menu

This commit is contained in:
Llywelwyn 2023-07-09 03:35:31 +01:00
parent d1b350cdc3
commit 7bf1c0b887
9 changed files with 256 additions and 45 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

View file

@ -1,4 +1,7 @@
use super::{gamelog::GameLog, CombatStats, InBackpack, Map, Name, Player, Point, Position, State};
use super::{
gamelog::GameLog, rex_assets::RexAssets, CombatStats, InBackpack, Map, Name, Player, Point, Position, RunState,
State,
};
use rltk::{Rltk, VirtualKeyCode, RGB};
use specs::prelude::*;
@ -201,3 +204,81 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option
},
}
}
#[derive(PartialEq, Copy, Clone)]
pub enum MainMenuSelection {
NewGame,
LoadGame,
Quit,
}
#[derive(PartialEq, Copy, Clone)]
pub enum MainMenuResult {
NoSelection { selected: MainMenuSelection },
Selected { selected: MainMenuSelection },
}
pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult {
let runstate = gs.ecs.fetch::<RunState>();
let assets = gs.ecs.fetch::<RexAssets>();
ctx.render_xp_sprite(&assets.menu, 0, 0);
ctx.print_color(38, 21, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "RUST-RL");
if let RunState::MainMenu { menu_selection: selection } = *runstate {
if selection == MainMenuSelection::NewGame {
ctx.print_color(34, 24, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "[");
ctx.print_color(36, 24, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "new game");
ctx.print_color(45, 24, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]");
} else {
ctx.print_color(36, 24, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "new game");
}
if selection == MainMenuSelection::LoadGame {
ctx.print_color(38, 26, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "[");
ctx.print_color(40, 26, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "load game");
ctx.print_color(50, 26, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]");
} else {
ctx.print_color(40, 26, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "load game");
}
if selection == MainMenuSelection::Quit {
ctx.print_color(34, 28, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "[");
ctx.print_color(36, 28, RGB::named(rltk::GREEN), RGB::from_f32(0.11, 0.11, 0.11), "goodbye!");
ctx.print_color(45, 28, RGB::named(rltk::YELLOW), RGB::from_f32(0.11, 0.11, 0.11), "]");
} else {
ctx.print_color(36, 28, RGB::named(rltk::WHITE), RGB::from_f32(0.11, 0.11, 0.11), "quit");
}
match ctx.key {
None => return MainMenuResult::NoSelection { selected: selection },
Some(key) => match key {
VirtualKeyCode::Escape | VirtualKeyCode::C => {
return MainMenuResult::NoSelection { selected: MainMenuSelection::Quit }
}
VirtualKeyCode::N => return MainMenuResult::NoSelection { selected: MainMenuSelection::NewGame },
VirtualKeyCode::L => return MainMenuResult::NoSelection { selected: MainMenuSelection::LoadGame },
VirtualKeyCode::Up => {
let new_selection;
match selection {
MainMenuSelection::NewGame => new_selection = MainMenuSelection::Quit,
MainMenuSelection::LoadGame => new_selection = MainMenuSelection::NewGame,
MainMenuSelection::Quit => new_selection = MainMenuSelection::LoadGame,
}
return MainMenuResult::NoSelection { selected: new_selection };
}
VirtualKeyCode::Down => {
let new_selection;
match selection {
MainMenuSelection::NewGame => new_selection = MainMenuSelection::LoadGame,
MainMenuSelection::LoadGame => new_selection = MainMenuSelection::Quit,
MainMenuSelection::Quit => new_selection = MainMenuSelection::NewGame,
}
return MainMenuResult::NoSelection { selected: new_selection };
}
VirtualKeyCode::Return => return MainMenuResult::Selected { selected: selection },
_ => return MainMenuResult::NoSelection { selected: selection },
},
}
}
MainMenuResult::NoSelection { selected: MainMenuSelection::NewGame }
}

View file

@ -1,5 +1,6 @@
use rltk::{GameState, Point, Rltk, RGB};
use specs::prelude::*;
use std::ops::Add;
mod components;
pub use components::*;
@ -26,6 +27,7 @@ mod inventory_system;
use inventory_system::*;
mod particle_system;
use particle_system::{ParticleBuilder, DEFAULT_PARTICLE_LIFETIME};
mod rex_assets;
// Embedded resources for use in wasm build
rltk::embedded_resource!(TERMINAL8X8, "../resources/terminal8x8.jpg");
@ -40,6 +42,7 @@ pub enum RunState {
MonsterTurn,
ShowInventory,
ShowDropItem,
MainMenu { menu_selection: gui::MainMenuSelection },
}
pub struct State {
@ -72,10 +75,18 @@ impl State {
impl GameState for State {
fn tick(&mut self, ctx: &mut Rltk) {
let mut new_runstate;
{
let runstate = self.ecs.fetch::<RunState>();
new_runstate = *runstate;
}
// Clear screen
ctx.cls();
particle_system::cull_dead_particles(&mut self.ecs, ctx);
match new_runstate {
RunState::MainMenu { .. } => {}
_ => {
// Draw map and ui
draw_map(&self.ecs, ctx);
{
@ -87,7 +98,9 @@ impl GameState for State {
data.sort_by(|&a, &b| b.1.render_order.cmp(&a.1.render_order));
for (pos, render) in data.iter() {
let idx = map.xy_idx(pos.x, pos.y);
let mut bg = render.bg;
let offsets = RGB::from_u8(map.red_offset[idx], map.green_offset[idx], map.blue_offset[idx]);
let mut bg = render.bg.add(RGB::from_u8(26, 45, 45)).add(offsets);
//bg = bg.add(offsets);
if map.bloodstains.contains(&idx) {
bg = RGB::from_f32(0.4, 0., 0.);
}
@ -97,11 +110,7 @@ impl GameState for State {
}
gui::draw_ui(&self.ecs, ctx);
}
let mut new_runstate;
{
let runstate = self.ecs.fetch::<RunState>();
new_runstate = *runstate;
}
}
match new_runstate {
@ -153,6 +162,28 @@ impl GameState for State {
}
}
}
RunState::MainMenu { .. } => {
let result = gui::main_menu(self, ctx);
match result {
gui::MainMenuResult::NoSelection { selected } => {
new_runstate = RunState::MainMenu { menu_selection: selected }
}
gui::MainMenuResult::Selected { selected } => {
match selected {
gui::MainMenuSelection::NewGame => new_runstate = RunState::PreRun,
gui::MainMenuSelection::LoadGame => {
new_runstate = RunState::PreRun;
//saveload_system::load_game(&mut self.ecs);
//rew_runstate = RunState::AwaitingInput;
//saveload_system::delete_save();
}
gui::MainMenuSelection::Quit => {
::std::process::exit(0);
}
}
}
}
}
}
{
@ -164,14 +195,22 @@ impl GameState for State {
}
}
const DISPLAYWIDTH: i32 = 80;
const DISPLAYHEIGHT: i32 = 50;
fn main() -> rltk::BError {
use rltk::RltkBuilder;
let mut context = RltkBuilder::simple80x50()
.with_tile_dimensions(16, 16)
//.with_fitscreen(true)
let mut context = RltkBuilder::new()
.with_title("rust-rl")
.with_dimensions(DISPLAYWIDTH, DISPLAYHEIGHT)
.with_tile_dimensions(16, 16)
.with_resource_path("resources/")
.with_font("terminal8x8.jpg", 8, 8)
.with_simple_console(DISPLAYWIDTH, DISPLAYHEIGHT, "terminal8x8.jpg")
.with_simple_console_no_bg(DISPLAYWIDTH, DISPLAYHEIGHT, "terminal8x8.jpg")
.build()?;
context.with_post_scanlines(true);
context.with_post_scanlines(false);
//context.screen_burn_color(RGB::named((150, 255, 255)));
let mut gs = State { ecs: World::new() };
gs.ecs.register::<Position>();
@ -207,8 +246,9 @@ fn main() -> rltk::BError {
gs.ecs.insert(Point::new(player_x, player_y));
gs.ecs.insert(player_entity);
gs.ecs.insert(gamelog::GameLog { entries: vec!["Here's your welcome message.".to_string()] });
gs.ecs.insert(RunState::PreRun);
gs.ecs.insert(RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame });
gs.ecs.insert(particle_system::ParticleBuilder::new());
gs.ecs.insert(rex_assets::RexAssets::new());
rltk::main_loop(context, gs)
}

View file

@ -3,6 +3,7 @@ use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB};
use specs::prelude::*;
use std::cmp::{max, min};
use std::collections::HashSet;
use std::ops::Add;
#[derive(PartialEq, Copy, Clone)]
pub enum TileType {
@ -12,6 +13,7 @@ pub enum TileType {
pub const MAPWIDTH: usize = 80;
pub const MAPHEIGHT: usize = 43;
const MAX_OFFSET: u8 = 32;
const MAPCOUNT: usize = MAPHEIGHT * MAPWIDTH;
#[derive(Default)]
@ -22,6 +24,9 @@ pub struct Map {
pub height: i32,
pub revealed_tiles: Vec<bool>,
pub visible_tiles: Vec<bool>,
pub red_offset: Vec<u8>,
pub green_offset: Vec<u8>,
pub blue_offset: Vec<u8>,
pub blocked: Vec<bool>,
pub tile_content: Vec<Vec<Entity>>,
pub bloodstains: HashSet<usize>,
@ -89,6 +94,9 @@ impl Map {
height: MAPHEIGHT as i32,
revealed_tiles: vec![false; MAPCOUNT],
visible_tiles: vec![false; MAPCOUNT],
red_offset: vec![0; MAPCOUNT],
green_offset: vec![0; MAPCOUNT],
blue_offset: vec![0; MAPCOUNT],
blocked: vec![false; MAPCOUNT],
tile_content: vec![Vec::new(); MAPCOUNT],
bloodstains: HashSet::new(),
@ -100,6 +108,19 @@ impl Map {
let mut rng = RandomNumberGenerator::new();
for idx in 0..map.red_offset.len() {
let roll = rng.roll_dice(1, MAX_OFFSET as i32);
map.red_offset[idx] = roll as u8;
}
for idx in 0..map.green_offset.len() {
let roll = rng.roll_dice(1, MAX_OFFSET as i32);
map.green_offset[idx] = roll as u8;
}
for idx in 0..map.blue_offset.len() {
let roll = rng.roll_dice(1, MAX_OFFSET as i32);
map.blue_offset[idx] = roll as u8;
}
for _i in 0..MAX_ROOMS {
let w = rng.range(MIN_SIZE, MAX_SIZE);
let h = rng.range(MIN_SIZE, MAX_SIZE);
@ -198,23 +219,28 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
let mut y = 0;
let mut x = 0;
for (idx, tile) in map.tiles.iter().enumerate() {
// Get our colour offsets. Credit to Brogue for the inspiration here.
let offsets = RGB::from_u8(map.red_offset[idx], map.green_offset[idx], map.blue_offset[idx]);
if map.revealed_tiles[idx] {
let mut fg = offsets;
// Right now, everything always has the same background. It's a
// very dark green, just to distinguish it slightly from the
// black that is tiles we've *never* seen.
let mut bg = offsets.add(RGB::from_u8(26, 45, 45));
let glyph;
let mut fg;
let mut bg = RGB::from_f32(0., 0., 0.);
match tile {
TileType::Floor => {
glyph = rltk::to_cp437('.');
fg = RGB::from_f32(0.0, 1.0, 0.5);
fg = fg.add(RGB::from_f32(0.1, 0.8, 0.5));
}
TileType::Wall => {
glyph = wall_glyph(&*map, x, y);
fg = RGB::from_f32(0.0, 1.0, 0.0);
fg = fg.add(RGB::from_f32(0.1, 0.8, 0.1));
}
}
let mut bloody = false;
if map.bloodstains.contains(&idx) {
bg = RGB::from_f32(0.4, 0., 0.);
bg = bg.add(RGB::from_f32(0.4, 0., 0.));
bloody = true;
}
if !map.visible_tiles[idx] {
@ -223,7 +249,9 @@ pub fn draw_map(ecs: &World, ctx: &mut Rltk) {
// since desaturate is an expensive function. If this stops being the case,
// will need to switch to using desaturate
if bloody {
bg = RGB::from_f32(0.4, 0.4, 0.4)
bg = RGB::from_f32(0.4, 0.4, 0.4);
} else {
bg = bg.to_greyscale();
}
}
ctx.set(x, y, fg, bg, glyph);

View file

@ -1,5 +1,5 @@
use super::{
gamelog::GameLog, CombatStats, Item, Map, Player, Position, RunState, State, Viewshed, WantsToMelee,
gamelog::GameLog, gui, CombatStats, Item, Map, Player, Position, RunState, State, Viewshed, WantsToMelee,
WantsToPickupItem, MAPHEIGHT, MAPWIDTH,
};
use rltk::{Point, Rltk, VirtualKeyCode};
@ -98,6 +98,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
VirtualKeyCode::G => get_item(&mut gs.ecs),
VirtualKeyCode::I => return RunState::ShowInventory,
VirtualKeyCode::D => return RunState::ShowDropItem,
VirtualKeyCode::Escape => return RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame },
_ => {
return RunState::AwaitingInput;
}
@ -105,3 +106,48 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
}
RunState::PlayerTurn
}
/* Playing around with autoexplore, without having read how to do it.
pub fn auto_explore(ecs: &mut World) {
let player_pos = ecs.fetch::<Point>();
let positions = ecs.read_storage::<Position>();
let entities = ecs.entities();
let map = ecs.fetch::<Map>();
let mut viewsheds = ecs.write_storage::<Viewshed>();
let mut unexplored_tiles: Vec<usize> = vec![];
for (idx, _tile) in map.tiles.iter().enumerate() {
if !map.revealed_tiles[idx] {
unexplored_tiles.push(idx);
}
}
let mut unexplored_tile = (0, 0.0f32);
let flow_map = DijkstraMap::new_empty(MAPWIDTH, MAPHEIGHT, 200.0);
DijkstraMap::build(&mut flow_map, &unexplored_tiles, &map);
for (i, tile) in map.tiles.iter().enumerate() {
if *tile == TileType::Floor {
let distance_to_start = flow_map.map[i];
if distance_to_start > unexplored_tile.1 {
unexplored_tile.0 = i;
unexplored_tile.1 = distance_to_start;
}
}
}
let path = rltk::a_star_search(map.xy_idx(player_pos.x, player_pos.y), unexplored_tile.0, &*map);
if path.success && path.steps.len() > 1 {
let mut idx = map.xy_idx(player_pos.x, player_pos.y);
map.blocked[idx] = false;
player_pos.x = (path.steps[1] as i32) % map.width;
player_pos.y = (path.steps[1] as i32) / map.width;
idx = map.xy_idx(player_pos.x, player_pos.y);
map.blocked[idx] = true;
for (ent, viewshed, pos) in (&entities, &mut viewsheds, &positions).join() {
viewshed.dirty = true;
}
}
}
*/

16
src/rex_assets.rs Normal file
View file

@ -0,0 +1,16 @@
use rltk::rex::XpFile;
rltk::embedded_resource!(CAVE_TUNNEL, "../resources/cave_tunnel80x50.xp");
pub struct RexAssets {
pub menu: XpFile,
}
impl RexAssets {
#[allow(clippy::new_without_default)]
pub fn new() -> RexAssets {
rltk::link_resource!(CAVE_TUNNEL, "../resources/cave_tunnel80x50.xp");
RexAssets { menu: XpFile::from_resource("../resources/cave_tunnel80x50.xp").unwrap() }
}
}

View file

@ -801,16 +801,16 @@ function __wbg_get_imports() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper194 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 31, __wbg_adapter_20);
imports.wbg.__wbindgen_closure_wrapper187 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 7, __wbg_adapter_20);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper525 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 136, __wbg_adapter_23);
imports.wbg.__wbindgen_closure_wrapper648 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_23);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper527 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 136, __wbg_adapter_23);
imports.wbg.__wbindgen_closure_wrapper650 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 158, __wbg_adapter_23);
return addHeapObject(ret);
};

Binary file not shown.