@@ -11,28 +11,12 @@ use ggez::{ | |||||
graphics, Context, ContextBuilder, GameResult, | graphics, Context, ContextBuilder, GameResult, | ||||
}; | }; | ||||
use std::collections::VecDeque; | use std::collections::VecDeque; | ||||
pub use util::{Error, Result}; | |||||
use world::World; | 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 | // primary game state | ||||
pub struct MainState { | |||||
struct MainState { | |||||
debug_mode: bool, | |||||
world: World, | world: World, | ||||
action_buffer: Vec<Box<dyn Action>>, | action_buffer: Vec<Box<dyn Action>>, | ||||
action_history: VecDeque<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)?; | let world = World::new(ctx, "/data/tile_conf.toml".into(), 20, 20, 1)?; | ||||
Ok(MainState { | Ok(MainState { | ||||
world, | world, | ||||
debug_mode: true, | |||||
action_history_max_length: 100, | action_history_max_length: 100, | ||||
action_history: VecDeque::new(), | action_history: VecDeque::new(), | ||||
action_buffer: Vec::new(), | action_buffer: Vec::new(), | ||||
@@ -56,7 +41,13 @@ impl EventHandler for MainState { | |||||
fn update(&mut self, ctx: &mut Context) -> GameResult<()> { | fn update(&mut self, ctx: &mut Context) -> GameResult<()> { | ||||
// check through the input buffer | // check through the input buffer | ||||
while let Some(action) = self.action_buffer.pop() { | 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 { | if self.action_history.len() < self.action_history_max_length { | ||||
self.action_history.push_back(action); | self.action_history.push_back(action); | ||||
@@ -69,6 +60,17 @@ impl EventHandler for MainState { | |||||
Ok(()) | 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( | fn key_down_event( | ||||
&mut self, | &mut self, | ||||
_ctx: &mut Context, | _ctx: &mut Context, | ||||
@@ -88,7 +90,7 @@ impl EventHandler for MainState { | |||||
// zoom key codes | // zoom key codes | ||||
KeyCode::Equals => Some(Box::new(ZoomAction::new([0.1, 0.1].into()))), | 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) | // if escape, quit program (for now) | ||||
// !! isabelle please remove this eventually | // !! isabelle please remove this eventually | ||||
@@ -118,6 +120,16 @@ impl EventHandler for MainState { | |||||
trait Action: std::fmt::Debug { | trait Action: std::fmt::Debug { | ||||
fn execute(&self, ctx: &mut Context, state: &mut MainState) -> Result<()>; | fn execute(&self, ctx: &mut Context, state: &mut MainState) -> Result<()>; | ||||
fn undo(&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 | /// struct to wrap quitting the game | ||||
@@ -1,5 +1,22 @@ | |||||
use mint::Point2; | 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>); | pub struct IsometricVector2(Point2<f32>); | ||||
impl IsometricVector2 { | impl IsometricVector2 { | ||||
@@ -1,8 +1,10 @@ | |||||
// modules | // modules | ||||
mod actions; | |||||
mod tile; | mod tile; | ||||
// namespacing | // namespacing | ||||
use crate::{util::*, MainState, Result}; | use crate::{util::*, MainState, Result}; | ||||
pub use actions::*; | |||||
use ggez::{ | use ggez::{ | ||||
graphics::{self, DrawParam}, | graphics::{self, DrawParam}, | ||||
Context, | Context, | ||||
@@ -34,7 +36,7 @@ impl World { | |||||
let builder = TileBuilder::new(ctx, tile_config)?; | let builder = TileBuilder::new(ctx, tile_config)?; | ||||
let mut data: HashMap<Vector3<isize>, Tile> = HashMap::new(); | let mut data: HashMap<Vector3<isize>, Tile> = HashMap::new(); | ||||
let offset: Point2<f32> = [350.0f32, 100.0f32].into(); | 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 x in 0..width { | ||||
for y in 0..depth { | for y in 0..depth { | ||||
@@ -49,7 +51,7 @@ impl World { | |||||
height, | height, | ||||
depth, | depth, | ||||
data, | data, | ||||
builder: builder, | |||||
builder, | |||||
offset, | offset, | ||||
zoom, | zoom, | ||||
}) | }) | ||||
@@ -82,55 +84,3 @@ impl World { | |||||
Ok(()) | 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 crate::{Error, Result}; | ||||
use ggez::{graphics::Image, Context}; | |||||
use ggez::{ | |||||
graphics::{FilterMode, Image}, | |||||
Context, | |||||
}; | |||||
use mint::Vector2; | use mint::Vector2; | ||||
use serde::Deserialize; | use serde::Deserialize; | ||||
use std::io::Read; | use std::io::Read; | ||||
@@ -34,7 +37,11 @@ impl TileBuilder { | |||||
raw_map.iter().for_each(|(kind, raw_tile)| { | raw_map.iter().for_each(|(kind, raw_tile)| { | ||||
let tile = MasterTile { | 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(), | kind: kind.to_owned(), | ||||
blocking: raw_tile.blocking, | blocking: raw_tile.blocking, | ||||
}; | }; | ||||
@@ -42,7 +49,7 @@ impl TileBuilder { | |||||
}); | }); | ||||
Ok(TileBuilder { | Ok(TileBuilder { | ||||
textures: textures, | |||||
textures, | |||||
tile_dimensions, | tile_dimensions, | ||||
}) | }) | ||||
} | } | ||||