| @@ -11,28 +11,12 @@ use ggez::{ | |||
| graphics, Context, ContextBuilder, GameResult, | |||
| }; | |||
| use std::collections::VecDeque; | |||
| pub use util::{Error, Result}; | |||
| use world::World; | |||
| /// lazy result type | |||
| pub type Result<T> = std::result::Result<T, Error>; | |||
| // error types | |||
| #[derive(Debug, thiserror::Error)] | |||
| pub enum Error { | |||
| // external errors | |||
| #[error(transparent)] | |||
| Stdio(#[from] std::io::Error), | |||
| #[error(transparent)] | |||
| Toml(#[from] toml::de::Error), | |||
| #[error(transparent)] | |||
| Ggez(#[from] ggez::error::GameError), | |||
| // internal errors | |||
| #[error("could not find {expected} MasterTile on TileBuilder")] | |||
| NoMasterTile { expected: String }, | |||
| } | |||
| // primary game state | |||
| pub struct MainState { | |||
| struct MainState { | |||
| debug_mode: bool, | |||
| world: World, | |||
| action_buffer: Vec<Box<dyn Action>>, | |||
| action_history: VecDeque<Box<dyn Action>>, | |||
| @@ -45,6 +29,7 @@ impl MainState { | |||
| let world = World::new(ctx, "/data/tile_conf.toml".into(), 20, 20, 1)?; | |||
| Ok(MainState { | |||
| world, | |||
| debug_mode: true, | |||
| action_history_max_length: 100, | |||
| action_history: VecDeque::new(), | |||
| action_buffer: Vec::new(), | |||
| @@ -56,7 +41,13 @@ impl EventHandler for MainState { | |||
| fn update(&mut self, ctx: &mut Context) -> GameResult<()> { | |||
| // check through the input buffer | |||
| while let Some(action) = self.action_buffer.pop() { | |||
| action.execute(ctx, self).expect("failed to execute action"); | |||
| if self.debug_mode { | |||
| action | |||
| .execute_debug(ctx, self) | |||
| .expect("fialed to execute action"); | |||
| } else { | |||
| action.execute(ctx, self).expect("failed to execute action"); | |||
| } | |||
| if self.action_history.len() < self.action_history_max_length { | |||
| self.action_history.push_back(action); | |||
| @@ -69,6 +60,17 @@ impl EventHandler for MainState { | |||
| Ok(()) | |||
| } | |||
| fn mouse_wheel_event(&mut self, _ctx: &mut Context, _x: f32, y: f32) { | |||
| use world::ZoomAction; | |||
| if y > 0.0 { | |||
| self.action_buffer | |||
| .push(Box::new(ZoomAction::new([0.1, 0.1].into()))); | |||
| } else if y < 0.0 { | |||
| self.action_buffer | |||
| .push(Box::new(ZoomAction::new([-0.1, -0.1].into()))); | |||
| } | |||
| } | |||
| fn key_down_event( | |||
| &mut self, | |||
| _ctx: &mut Context, | |||
| @@ -88,7 +90,7 @@ impl EventHandler for MainState { | |||
| // zoom key codes | |||
| KeyCode::Equals => Some(Box::new(ZoomAction::new([0.1, 0.1].into()))), | |||
| KeyCode::Minus => Some(Box::new(ZoomAction::new([-0.1, -0.1].into()))), | |||
| KeyCode::Subtract => Some(Box::new(ZoomAction::new([-0.1, -0.1].into()))), | |||
| // if escape, quit program (for now) | |||
| // !! isabelle please remove this eventually | |||
| @@ -118,6 +120,16 @@ impl EventHandler for MainState { | |||
| trait Action: std::fmt::Debug { | |||
| fn execute(&self, ctx: &mut Context, state: &mut MainState) -> Result<()>; | |||
| fn undo(&self, ctx: &mut Context, state: &mut MainState) -> Result<()>; | |||
| fn execute_debug(&self, ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| println!("executing: {:?}", self); | |||
| self.execute(ctx, state) | |||
| } | |||
| fn undo_debug(&self, ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| println!("undoing: {:?}", self); | |||
| self.execute(ctx, state) | |||
| } | |||
| } | |||
| /// struct to wrap quitting the game | |||
| @@ -1,5 +1,22 @@ | |||
| use mint::Point2; | |||
| /// lazy result type | |||
| pub type Result<T> = std::result::Result<T, Error>; | |||
| // error types | |||
| #[derive(Debug, thiserror::Error)] | |||
| pub enum Error { | |||
| // external errors | |||
| #[error(transparent)] | |||
| Stdio(#[from] std::io::Error), | |||
| #[error(transparent)] | |||
| Toml(#[from] toml::de::Error), | |||
| #[error(transparent)] | |||
| Ggez(#[from] ggez::error::GameError), | |||
| // internal errors | |||
| #[error("could not find {expected} MasterTile on TileBuilder")] | |||
| NoMasterTile { expected: String }, | |||
| } | |||
| pub struct IsometricVector2(Point2<f32>); | |||
| impl IsometricVector2 { | |||
| @@ -1,8 +1,10 @@ | |||
| // modules | |||
| mod actions; | |||
| mod tile; | |||
| // namespacing | |||
| use crate::{util::*, MainState, Result}; | |||
| pub use actions::*; | |||
| use ggez::{ | |||
| graphics::{self, DrawParam}, | |||
| Context, | |||
| @@ -34,7 +36,7 @@ impl World { | |||
| let builder = TileBuilder::new(ctx, tile_config)?; | |||
| let mut data: HashMap<Vector3<isize>, Tile> = HashMap::new(); | |||
| let offset: Point2<f32> = [350.0f32, 100.0f32].into(); | |||
| let zoom: Vector2<f32> = [1.0f32, 1.0f32].into(); | |||
| let zoom: Vector2<f32> = [2.0f32, 2.0f32].into(); | |||
| for x in 0..width { | |||
| for y in 0..depth { | |||
| @@ -49,7 +51,7 @@ impl World { | |||
| height, | |||
| depth, | |||
| data, | |||
| builder: builder, | |||
| builder, | |||
| offset, | |||
| zoom, | |||
| }) | |||
| @@ -82,55 +84,3 @@ impl World { | |||
| Ok(()) | |||
| } | |||
| } | |||
| /// action struct for wrapping offsetting input | |||
| #[derive(Debug)] | |||
| pub struct OffsetAction(Vector2<f32>); | |||
| impl OffsetAction { | |||
| /// construct a new offset action | |||
| pub fn new(offset: Vector2<f32>) -> OffsetAction { | |||
| OffsetAction(offset) | |||
| } | |||
| } | |||
| impl crate::Action for OffsetAction { | |||
| fn execute(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| state.world.offset.x = | |||
| self.0.x * (state.world.builder.tile_width() / 2.0) + state.world.offset.x; | |||
| state.world.offset.y = | |||
| self.0.y * (state.world.builder.tile_height() / 2.0) + state.world.offset.y; | |||
| Ok(()) | |||
| } | |||
| fn undo(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| state.world.offset.x = state.world.offset.x - self.0.x; | |||
| state.world.offset.y = state.world.offset.y - self.0.y; | |||
| Ok(()) | |||
| } | |||
| } | |||
| /// action struct for wrapping zoom input | |||
| #[derive(Debug)] | |||
| pub struct ZoomAction(Vector2<f32>); | |||
| impl ZoomAction { | |||
| /// construct a new scale action | |||
| pub fn new(scale: Vector2<f32>) -> ZoomAction { | |||
| ZoomAction(scale) | |||
| } | |||
| } | |||
| impl crate::Action for ZoomAction { | |||
| fn execute(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| state.world.zoom.x = self.0.x + state.world.zoom.x; | |||
| state.world.zoom.y = self.0.y + state.world.zoom.y; | |||
| Ok(()) | |||
| } | |||
| fn undo(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| state.world.zoom.x = state.world.zoom.x - self.0.x; | |||
| state.world.zoom.y = state.world.zoom.y - self.0.y; | |||
| Ok(()) | |||
| } | |||
| } | |||
| @@ -0,0 +1,57 @@ | |||
| use crate::{MainState, Result}; | |||
| use ggez::Context; | |||
| use mint::Vector2; | |||
| /// action struct for wrapping offsetting input | |||
| #[derive(Debug)] | |||
| pub struct OffsetAction(Vector2<f32>); | |||
| impl OffsetAction { | |||
| /// construct a new offset action | |||
| pub fn new(offset: Vector2<f32>) -> OffsetAction { | |||
| OffsetAction(offset) | |||
| } | |||
| } | |||
| impl crate::Action for OffsetAction { | |||
| fn execute(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| let x: f32 = self.0.x * (state.world.builder.tile_width() / 2.0) + state.world.offset.x; | |||
| let y: f32 = self.0.y * (state.world.builder.tile_height() / 2.0) + state.world.offset.y; | |||
| state.world.offset = [x, y].into(); | |||
| Ok(()) | |||
| } | |||
| fn undo(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| let x: f32 = state.world.offset.x - self.0.x * (state.world.builder.tile_width() / 2.0); | |||
| let y: f32 = state.world.offset.y - self.0.y * (state.world.builder.tile_height() / 2.0); | |||
| state.world.offset = [x, y].into(); | |||
| Ok(()) | |||
| } | |||
| } | |||
| /// action struct for wrapping zoom input | |||
| #[derive(Debug)] | |||
| pub struct ZoomAction(Vector2<f32>); | |||
| impl ZoomAction { | |||
| /// construct a new scale action | |||
| pub fn new(scale: Vector2<f32>) -> ZoomAction { | |||
| ZoomAction(scale) | |||
| } | |||
| } | |||
| impl crate::Action for ZoomAction { | |||
| fn execute(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| let x: f32 = state.world.zoom.x + (self.0.x * (state.world.builder.tile_width() / 2.0)); | |||
| let y: f32 = state.world.zoom.y + (self.0.y * (state.world.builder.tile_height() / 2.0)); | |||
| state.world.zoom = [x, y].into(); | |||
| Ok(()) | |||
| } | |||
| fn undo(&self, _ctx: &mut Context, state: &mut MainState) -> Result<()> { | |||
| let x: f32 = state.world.zoom.x - self.0.x * (state.world.builder.tile_width() / 2.0); | |||
| let y: f32 = state.world.zoom.y - self.0.y * (state.world.builder.tile_height() / 2.0); | |||
| state.world.zoom = [x, y].into(); | |||
| Ok(()) | |||
| } | |||
| } | |||
| @@ -1,5 +1,8 @@ | |||
| use crate::{Error, Result}; | |||
| use ggez::{graphics::Image, Context}; | |||
| use ggez::{ | |||
| graphics::{FilterMode, Image}, | |||
| Context, | |||
| }; | |||
| use mint::Vector2; | |||
| use serde::Deserialize; | |||
| use std::io::Read; | |||
| @@ -34,7 +37,11 @@ impl TileBuilder { | |||
| raw_map.iter().for_each(|(kind, raw_tile)| { | |||
| let tile = MasterTile { | |||
| texture: Rc::new(Image::new(ctx, &raw_tile.path).expect("failed to load image")), | |||
| texture: Rc::new({ | |||
| let mut image = Image::new(ctx, &raw_tile.path).expect("failed to load image"); | |||
| image.set_filter(FilterMode::Nearest); | |||
| image | |||
| }), | |||
| kind: kind.to_owned(), | |||
| blocking: raw_tile.blocking, | |||
| }; | |||
| @@ -42,7 +49,7 @@ impl TileBuilder { | |||
| }); | |||
| Ok(TileBuilder { | |||
| textures: textures, | |||
| textures, | |||
| tile_dimensions, | |||
| }) | |||
| } | |||