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

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)
}