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