mirror of https://git.sr.ht/~garritfra/midgard
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
8.8 KiB
244 lines
8.8 KiB
extern crate rltk; |
|
|
|
mod crypto; |
|
mod inventory; |
|
mod map; |
|
mod traits; |
|
use crate::inventory::Inventory; |
|
use crate::inventory::InventoryView; |
|
use crate::map::Chunk; |
|
use crate::traits::Render; |
|
use map::TileType; |
|
use rltk::prelude::*; |
|
use std::collections::HashMap; |
|
|
|
#[derive(Clone)] |
|
enum DisplayMode { |
|
Game, |
|
Inventory, |
|
} |
|
|
|
#[derive(Clone)] |
|
pub struct State { |
|
mode: DisplayMode, |
|
view: Chunk, |
|
loaded_chunks: Box<HashMap<(i32, i32), Chunk>>, |
|
world_pos: (i32, i32), |
|
seed: u64, |
|
// CLEANUP: Move to player struct |
|
player_inventory: InventoryView, |
|
player_position: usize, |
|
selected_object: Option<TileType>, |
|
visible: Vec<bool>, |
|
} |
|
|
|
impl State { |
|
pub fn new() -> State { |
|
let seed = RandomNumberGenerator::new().rand::<u64>(); |
|
Self::seeded(seed) |
|
} |
|
pub fn seeded(seed: u64) -> State { |
|
let mut chunks: HashMap<(i32, i32), Chunk> = HashMap::new(); |
|
|
|
// Coordinates of the current and surrounding chunks |
|
let keys: Vec<(i32, i32)> = [ |
|
(-1, 1), |
|
(0, 1), |
|
(1, 1), |
|
(-1, 0), |
|
(0, 0), |
|
(1, 0), |
|
(-1, -1), |
|
(0, -1), |
|
(1, -1), |
|
] |
|
.into(); |
|
for key in keys { |
|
let chunk = map::new_chunk(seed, Point::from(key)); |
|
chunks.insert(key, chunk); |
|
} |
|
|
|
let mut player_inventory = Inventory::new(); |
|
player_inventory.push(inventory::Item { |
|
name: "Axe".to_string(), |
|
}); |
|
|
|
player_inventory.push(inventory::Item { |
|
name: "Water".to_string(), |
|
}); |
|
|
|
player_inventory.push(inventory::Item { |
|
name: "Wood".to_string(), |
|
}); |
|
|
|
let state = State { |
|
mode: DisplayMode::Game, |
|
world_pos: (0, 0), |
|
view: chunks.get(&(0, 0)).unwrap().to_vec(), |
|
loaded_chunks: Box::new(chunks), |
|
seed, |
|
player_inventory: InventoryView::new(player_inventory), |
|
player_position: (25 * 80) + 40, // Equivalent to point2d_to_index(40, 25) but we haven't initialized it yet |
|
visible: vec![false; 80 * 50], |
|
selected_object: None, |
|
}; |
|
|
|
state |
|
} |
|
|
|
fn move_to_chunk(&mut self, delta: (i32, i32)) { |
|
// Move world position by delta |
|
self.world_pos = (self.world_pos.0 + delta.0, self.world_pos.1 + delta.1); |
|
|
|
// Update loaded chunks |
|
let chunk = match self.loaded_chunks.get(&self.world_pos) { |
|
Some(chunk) => chunk, |
|
None => { |
|
let new_chunk = map::new_chunk(self.seed, Point::from(self.world_pos)); |
|
self.loaded_chunks.insert(self.world_pos, new_chunk); |
|
self.loaded_chunks |
|
.get(&self.world_pos) |
|
.expect("Unexpectedly failed finding loaded chunk.") |
|
} |
|
}; |
|
|
|
self.view = chunk.to_vec(); |
|
|
|
dbg!("Loaded chunks: {:?}", &self.loaded_chunks.keys()); |
|
dbg!("Current position: {:?}", &self.world_pos); |
|
dbg!("Number of loaded chunks: {:?}", &self.loaded_chunks.len()); |
|
} |
|
|
|
pub fn move_player(&mut self, delta: Point) { |
|
let current_position = self.index_to_point2d(self.player_position); |
|
let new_position = current_position + delta; |
|
|
|
// Check if the user has left the chunk to either side. |
|
// If that's the case, the current chunk is updated |
|
// and the player position is updated |
|
if new_position.x >= 80 { |
|
self.move_to_chunk((1, 0)); |
|
self.move_player(Point::new(-79, 0)); |
|
} else if new_position.x < 0 { |
|
self.move_to_chunk((-1, 0)); |
|
self.move_player(Point::new(79, 0)); |
|
} else if new_position.y >= 50 { |
|
self.move_to_chunk((0, 1)); |
|
self.move_player(Point::new(0, -49)); |
|
} else if new_position.y < 0 { |
|
self.move_to_chunk((0, -1)); |
|
self.move_player(Point::new(0, 49)); |
|
} else { |
|
let new_idx = self.point2d_to_index(new_position); |
|
if self.view[new_idx] == TileType::Floor { |
|
self.player_position = new_idx; |
|
} |
|
} |
|
} |
|
|
|
fn select_object(&mut self, pos: Point) { |
|
let idx = self.point2d_to_index(pos); |
|
|
|
match self.view.get(idx) { |
|
Some(tile) => self.selected_object = Some(*tile), |
|
None => {} |
|
} |
|
} |
|
} |
|
|
|
// Implement the game loop |
|
impl GameState for State { |
|
#[allow(non_snake_case)] |
|
fn tick(&mut self, ctx: &mut Rltk) { |
|
match ctx.key { |
|
None => {} // Nothing happened |
|
Some(key) => { |
|
// A key is pressed or held |
|
// CLEANUP: Move to update trait |
|
match self.mode { |
|
DisplayMode::Game => { |
|
match key { |
|
// Numpad |
|
VirtualKeyCode::Numpad8 => self.move_player(Point::new(0, -1)), |
|
VirtualKeyCode::Numpad4 => self.move_player(Point::new(-1, 0)), |
|
VirtualKeyCode::Numpad6 => self.move_player(Point::new(1, 0)), |
|
VirtualKeyCode::Numpad2 => self.move_player(Point::new(0, 1)), |
|
// Numpad diagonals |
|
VirtualKeyCode::Numpad7 => self.move_player(Point::new(-1, -1)), |
|
VirtualKeyCode::Numpad9 => self.move_player(Point::new(1, -1)), |
|
VirtualKeyCode::Numpad1 => self.move_player(Point::new(-1, 1)), |
|
VirtualKeyCode::Numpad3 => self.move_player(Point::new(1, 1)), |
|
// Cursors |
|
VirtualKeyCode::Up => self.move_player(Point::new(0, -1)), |
|
VirtualKeyCode::Down => self.move_player(Point::new(0, 1)), |
|
VirtualKeyCode::Left => self.move_player(Point::new(-1, 0)), |
|
VirtualKeyCode::Right => self.move_player(Point::new(1, 0)), |
|
// L33t keys |
|
VirtualKeyCode::W => self.move_player(Point::new(0, -1)), |
|
VirtualKeyCode::S => self.move_player(Point::new(0, 1)), |
|
VirtualKeyCode::A => self.move_player(Point::new(-1, 0)), |
|
VirtualKeyCode::D => self.move_player(Point::new(1, 0)), |
|
// Views |
|
VirtualKeyCode::I => { |
|
self.mode = DisplayMode::Inventory; |
|
// CLEANUP: This really isn't the right place to reset the inventory view. Also: Cloning! |
|
self.player_inventory = |
|
InventoryView::new(self.player_inventory.inv.clone()) |
|
} |
|
_ => {} // Ignore all the other possibilities |
|
} |
|
} |
|
DisplayMode::Inventory => { |
|
match key { |
|
// Numpad |
|
VirtualKeyCode::Numpad8 => self.player_inventory.selection_up(), |
|
VirtualKeyCode::Numpad2 => self.player_inventory.selection_down(), |
|
// Cursors |
|
VirtualKeyCode::Up => self.player_inventory.selection_up(), |
|
VirtualKeyCode::Down => self.player_inventory.selection_down(), |
|
// Views |
|
VirtualKeyCode::Escape => self.mode = DisplayMode::Game, |
|
VirtualKeyCode::Return => self.player_inventory.select(), |
|
_ => {} // Ignore all the other possibilities |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ctx.left_click { |
|
let mouse_pos = ctx.mouse_pos(); |
|
self.select_object(Point::from(mouse_pos)); |
|
} |
|
|
|
// CLEANUP: We shouldn't be cloning the state on each frame |
|
match self.mode { |
|
DisplayMode::Game => self.view.render(&mut self.clone(), ctx), |
|
DisplayMode::Inventory => self.player_inventory.render(&mut self.clone(), ctx), |
|
} |
|
} |
|
} |
|
|
|
// To work with RLTK's algorithm features, we need to implement some the Algorithm2D trait for our map. |
|
|
|
// First, default implementations of some we aren't using yet (more on these later!) |
|
impl BaseMap for State { |
|
// We'll use this one - if its a wall, we can't see through it |
|
fn is_opaque(&self, idx: usize) -> bool { |
|
self.view[idx as usize] == TileType::Wall |
|
} |
|
} |
|
|
|
impl Algorithm2D for State { |
|
fn dimensions(&self) -> Point { |
|
Point::new(80, 50) |
|
} |
|
} |
|
|
|
fn main() -> RltkError { |
|
let context = RltkBuilder::simple(80, 70)? |
|
.with_title("Legends of Midgard") |
|
.build()?; |
|
let gs = State::new(); |
|
rltk::main_loop(context, gs) |
|
}
|
|
|