diff --git a/Cargo.toml b/Cargo.toml index 9bc6c2d..ff66501 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,12 @@ opengles_version = [3, 1] fullscreen = true orientation = "sensorLandscape" permission = [ - {name = "android.permission.READ_EXTERNAL_STORAGE"}, - {name = "android.permission.WRITE_EXTERNAL_STORAGE"} + { name = "android.permission.READ_EXTERNAL_STORAGE" }, + { name = "android.permission.WRITE_EXTERNAL_STORAGE" } ] application_metadatas = [ - {name = "android:hardwareAccelerated", value = "true"}, - {name = "android:requestLegacyExternalStorage", value = "true"} + { name = "android:hardwareAccelerated", value = "true" }, + { name = "android:requestLegacyExternalStorage", value = "true" } ] [profile.release] @@ -40,42 +40,34 @@ opt-level = 1 opt-level = 1 [features] -default = ["scripting"] +default = ["scripting", "backend-sdl"] +backend-sdl = ["sdl2"] +backend-gfx = ["winit", "imgui-gfx-renderer", "imgui-winit-support"] scripting = ["lua-ffi"] editor = [] [dependencies] -#cpal = {path = "./3rdparty/cpal"} -#gfx_device_gl = {path = "./3rdparty/gfx/src/backend/gl"} -#ggez = {path = "./3rdparty/ggez"} -#glutin = {path = "./3rdparty/glutin/glutin"} -#lua-ffi = {path = "./3rdparty/luajit-rs", optional = true} - bitvec = "0.17.4" byteorder = "1.3" case_insensitive_hashmap = "1.0.0" chrono = "0.4" -cpal = {git = "https://github.com/doukutsu-rs/cpal.git", branch = "android-support"} -directories = "2" -gfx = "0.18" -gfx_core = "0.9" -gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"} -ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "43631b0401271d4bc8fe4a5afba8aad63976dba1"} -glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "master"} -imgui = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"} -imgui-gfx-renderer = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"} -imgui-winit-support = {git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"} -image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"]} +cpal = { git = "https://github.com/doukutsu-rs/cpal.git", branch = "android-support" } +directories = "3" +imgui = { git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785" } +imgui-gfx-renderer = { git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785", optional = true } +imgui-winit-support = { git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785", optional = true } +image = { version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"] } itertools = "0.9.0" lazy_static = "1.4.0" log = "0.4" -lua-ffi = {git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "1ef3caf772d72068297ddf75df06fd2ef8c1daab", optional = true} +lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "1ef3caf772d72068297ddf75df06fd2ef8c1daab", optional = true } lru = "0.6.0" num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" pretty_env_logger = "0.4.0" -serde = {version = "1", features = ["derive"]} +sdl2 = { version = "0.34.3", optional = true } +serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_yaml = "0.8" strum = "0.18.0" @@ -83,7 +75,10 @@ strum_macros = "0.18.0" # remove and replace when drain_filter is in stable vec_mut_scan = "0.3.0" webbrowser = "0.5.5" -winit = {version = "0.24.0", features = ["serde"]} +winit = { version = "0.24.0", features = ["serde"], optional = true } + +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3", features = ["winuser"] } [target.'cfg(target_os = "android")'.dependencies] ndk = "0.2" diff --git a/src/bmfont.rs b/src/bmfont.rs index a7e877e..59d8a5f 100644 --- a/src/bmfont.rs +++ b/src/bmfont.rs @@ -3,8 +3,9 @@ use std::io; use byteorder::{LE, ReadBytesExt}; -use ggez::GameError::ResourceLoadError; -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameError::ResourceLoadError; +use crate::framework::error::GameResult; use crate::str; #[derive(Debug)] diff --git a/src/bmfont_renderer.rs b/src/bmfont_renderer.rs index f4bd960..a4c68f6 100644 --- a/src/bmfont_renderer.rs +++ b/src/bmfont_renderer.rs @@ -4,10 +4,13 @@ use std::path::PathBuf; use crate::bmfont::BMFont; use crate::common::{FILE_TYPES, Rect}; use crate::engine_constants::EngineConstants; -use ggez::{Context, filesystem, GameResult}; -use ggez::GameError::ResourceLoadError; + use crate::str; use crate::texture_set::TextureSet; +use crate::framework::error::GameError::ResourceLoadError; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::filesystem; pub struct BMFontRenderer { font: BMFont, diff --git a/src/builtin_fs.rs b/src/builtin_fs.rs index 5a2a0cc..da7adc2 100644 --- a/src/builtin_fs.rs +++ b/src/builtin_fs.rs @@ -5,9 +5,9 @@ use std::io::ErrorKind; use std::io::SeekFrom; use std::path::{Component, Path, PathBuf}; -use ggez::GameResult; -use ggez::GameError::FilesystemError; -use ggez::vfs::{OpenOptions, VFile, VFS, VMetadata}; +use crate::framework::error::GameError::FilesystemError; +use crate::framework::error::GameResult; +use crate::framework::vfs::{OpenOptions, VFile, VMetadata, VFS}; #[derive(Debug)] pub struct BuiltinFile(Cursor<&'static [u8]>); @@ -59,7 +59,7 @@ impl VMetadata for BuiltinMetadata { } } -#[derive(Clone)] +#[derive(Clone, Debug)] enum FSNode { File(&'static str, &'static [u8]), Directory(&'static str, Vec), @@ -232,7 +232,7 @@ impl VFS for BuiltinFS { } } -/*#[test] +#[test] fn test_builtin_fs() { let fs = BuiltinFS { root: vec![ @@ -240,7 +240,7 @@ fn test_builtin_fs() { FSNode::Directory("memes", vec![ FSNode::File("nothing.txt", &[]), FSNode::Directory("secret stuff", vec![ - FSNode::File("passwords.txt", &[b'1', b'2', b'3', b'4', b'5', b'6',]), + FSNode::File("passwords.txt", &b"12345678"), ]), ]), FSNode::File("test2.txt", &[]), @@ -252,4 +252,4 @@ fn test_builtin_fs() { println!("{:?}", fs.get_node(Path::new("/memes")).unwrap()); println!("{:?}", fs.get_node(Path::new("/memes/nothing.txt")).unwrap()); println!("{:?}", fs.get_node(Path::new("/memes/secret stuff/passwords.txt")).unwrap()); -}*/ +} diff --git a/src/common.rs b/src/common.rs index 6ee0ff0..41f2d05 100644 --- a/src/common.rs +++ b/src/common.rs @@ -260,16 +260,6 @@ impl Rect { bottom: y.add(height), } } - - pub fn from(rect: ggez::graphics::Rect) -> Rect { - Rect { - left: rect.x, - top: rect.y, - right: (rect.x + rect.w), - bottom: (rect.y + rect.h), - } - } - pub fn width(&self) -> T { if let Some(Ordering::Greater) = self.left.partial_cmp(&self.right) { self.left.sub(self.right) @@ -287,15 +277,6 @@ impl Rect { } } -impl> Into for Rect { - fn into(self) -> ggez::graphics::Rect { - ggez::graphics::Rect::new(self.left.as_(), - self.top.as_(), - self.width().as_(), - self.height().as_()) - } -} - impl Serialize for Rect { fn serialize(&self, serializer: S) -> Result where @@ -371,3 +352,167 @@ pub fn interpolate_fix9_scale(old_val: i32, val: i32, frame_delta: f64) -> f32 { (lerp_f64(old_val as f64, val as f64, frame_delta) / 512.0) as f32 } + + +/// A RGBA color in the `sRGB` color space represented as `f32`'s in the range `[0.0-1.0]` +/// +/// For convenience, [`WHITE`](constant.WHITE.html) and [`BLACK`](constant.BLACK.html) are provided. +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Color { + /// Red component + pub r: f32, + /// Green component + pub g: f32, + /// Blue component + pub b: f32, + /// Alpha component + pub a: f32, +} + +/// White +pub const WHITE: Color = Color { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, +}; + +/// Black +pub const BLACK: Color = Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, +}; + +impl Color { + /// Create a new `Color` from four `f32`'s in the range `[0.0-1.0]` + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Color { r, g, b, a } + } + + /// Create a new `Color` from four `u8`'s in the range `[0-255]` + pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Color { + Color::from((r, g, b, a)) + } + + /// Create a new `Color` from three u8's in the range `[0-255]`, + /// with the alpha component fixed to 255 (opaque) + pub const fn from_rgb(r: u8, g: u8, b: u8) -> Color { + Color::from((r, g, b)) + } + + /// Return a tuple of four `u8`'s in the range `[0-255]` with the `Color`'s + /// components. + pub const fn to_rgba(self) -> (u8, u8, u8, u8) { + self.into() + } + + /// Return a tuple of three `u8`'s in the range `[0-255]` with the `Color`'s + /// components. + pub const fn to_rgb(self) -> (u8, u8, u8) { + self.into() + } + + /// Convert a packed `u32` containing `0xRRGGBBAA` into a `Color` + pub const fn from_rgba_u32(c: u32) -> Color { + let c = c.to_be_bytes(); + + Color::from((c[0], c[1], c[2], c[3])) + } + + /// Convert a packed `u32` containing `0x00RRGGBB` into a `Color`. + /// This lets you do things like `Color::from_rgb_u32(0xCD09AA)` easily if you want. + pub const fn from_rgb_u32(c: u32) -> Color { + let c = c.to_be_bytes(); + + Color::from((c[1], c[2], c[3])) + } + + /// Convert a `Color` into a packed `u32`, containing `0xRRGGBBAA` as bytes. + pub const fn to_rgba_u32(self) -> u32 { + let (r, g, b, a): (u8, u8, u8, u8) = self.into(); + + u32::from_be_bytes([r, g, b, a]) + } + + /// Convert a `Color` into a packed `u32`, containing `0x00RRGGBB` as bytes. + pub const fn to_rgb_u32(self) -> u32 { + let (r, g, b, _a): (u8, u8, u8, u8) = self.into(); + + u32::from_be_bytes([0, r, g, b]) + } +} + +impl From<(u8, u8, u8, u8)> for Color { + /// Convert a `(R, G, B, A)` tuple of `u8`'s in the range `[0-255]` into a `Color` + fn from(val: (u8, u8, u8, u8)) -> Self { + let (r, g, b, a) = val; + let rf = (f32::from(r)) / 255.0; + let gf = (f32::from(g)) / 255.0; + let bf = (f32::from(b)) / 255.0; + let af = (f32::from(a)) / 255.0; + Color::new(rf, gf, bf, af) + } +} + +impl From<(u8, u8, u8)> for Color { + /// Convert a `(R, G, B)` tuple of `u8`'s in the range `[0-255]` into a `Color`, + /// with a value of 255 for the alpha element (i.e., no transparency.) + fn from(val: (u8, u8, u8)) -> Self { + let (r, g, b) = val; + Color::from((r, g, b, 255)) + } +} + +impl From<[f32; 4]> for Color { + /// Turns an `[R, G, B, A] array of `f32`'s into a `Color` with no format changes. + /// All inputs should be in the range `[0.0-1.0]`. + fn from(val: [f32; 4]) -> Self { + Color::new(val[0], val[1], val[2], val[3]) + } +} + +impl From<(f32, f32, f32)> for Color { + /// Convert a `(R, G, B)` tuple of `f32`'s in the range `[0.0-1.0]` into a `Color`, + /// with a value of 1.0 to for the alpha element (ie, no transparency.) + fn from(val: (f32, f32, f32)) -> Self { + let (r, g, b) = val; + Color::new(r, g, b, 1.0) + } +} + +impl From<(f32, f32, f32, f32)> for Color { + /// Convert a `(R, G, B, A)` tuple of `f32`'s in the range `[0.0-1.0]` into a `Color` + fn from(val: (f32, f32, f32, f32)) -> Self { + let (r, g, b, a) = val; + Color::new(r, g, b, a) + } +} + +impl From for (u8, u8, u8, u8) { + /// Convert a `Color` into a `(R, G, B, A)` tuple of `u8`'s in the range of `[0-255]`. + fn from(color: Color) -> Self { + let r = (color.r * 255.0) as u8; + let g = (color.g * 255.0) as u8; + let b = (color.b * 255.0) as u8; + let a = (color.a * 255.0) as u8; + (r, g, b, a) + } +} + +impl From for (u8, u8, u8) { + /// Convert a `Color` into a `(R, G, B)` tuple of `u8`'s in the range of `[0-255]`, + /// ignoring the alpha term. + fn from(color: Color) -> Self { + let (r, g, b, _) = color.into(); + (r, g, b) + } +} + +impl From for [f32; 4] { + /// Convert a `Color` into an `[R, G, B, A]` array of `f32`'s in the range of `[0.0-1.0]`. + fn from(color: Color) -> Self { + [color.r, color.g, color.b, color.a] + } +} \ No newline at end of file diff --git a/src/components/boss_life_bar.rs b/src/components/boss_life_bar.rs index 50c33f9..0c27d44 100644 --- a/src/components/boss_life_bar.rs +++ b/src/components/boss_life_bar.rs @@ -1,8 +1,8 @@ -use ggez::{Context, GameResult}; - use crate::common::Rect; use crate::entity::GameEntity; use crate::frame::Frame; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::npc::boss::BossNPC; use crate::npc::list::NPCList; use crate::shared_game_state::SharedGameState; @@ -52,7 +52,7 @@ impl BossLifeBar { } impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar { - fn tick(&mut self, _state: &mut SharedGameState, (npc_list, boss): (&NPCList, &BossNPC)) -> GameResult<> { + fn tick(&mut self, _state: &mut SharedGameState, (npc_list, boss): (&NPCList, &BossNPC)) -> GameResult<()> { match self.target { BossLifeTarget::NPC(npc_id) => { if let Some(npc) = npc_list.get_npc(npc_id as usize) { diff --git a/src/components/draw_common.rs b/src/components/draw_common.rs index a7594f8..07417a1 100644 --- a/src/components/draw_common.rs +++ b/src/components/draw_common.rs @@ -1,7 +1,7 @@ -use ggez::{Context, GameResult}; - use crate::common::Rect; use crate::shared_game_state::SharedGameState; +use crate::framework::context::Context; +use crate::framework::error::GameResult; #[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)] pub enum Alignment { diff --git a/src/components/hud.rs b/src/components/hud.rs index e30018a..1957c48 100644 --- a/src/components/hud.rs +++ b/src/components/hud.rs @@ -1,4 +1,5 @@ -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::common::Rect; use crate::components::draw_common::{Alignment, draw_number}; diff --git a/src/components/stage_select.rs b/src/components/stage_select.rs index 504863a..9c6b015 100644 --- a/src/components/stage_select.rs +++ b/src/components/stage_select.rs @@ -1,4 +1,5 @@ -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::common::Rect; use crate::entity::GameEntity; diff --git a/src/entity.rs b/src/entity.rs index 080a641..461f454 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -1,4 +1,5 @@ -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::frame::Frame; use crate::shared_game_state::SharedGameState; diff --git a/src/framework/backend.rs b/src/framework/backend.rs new file mode 100644 index 0000000..c20c7da --- /dev/null +++ b/src/framework/backend.rs @@ -0,0 +1,9 @@ +use crate::Game; + +pub(crate) trait Backend { + fn create_event_loop(&self) -> Box; +} + +pub(crate) trait BackendEventLoop { + fn run(&self, game: &mut Game); +} \ No newline at end of file diff --git a/src/render/mod.rs b/src/framework/backend_null.rs similarity index 100% rename from src/render/mod.rs rename to src/framework/backend_null.rs diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/framework/context.rs b/src/framework/context.rs new file mode 100644 index 0000000..1ee64f9 --- /dev/null +++ b/src/framework/context.rs @@ -0,0 +1,18 @@ +use crate::framework::filesystem::Filesystem; +use crate::Game; + +pub struct Context { + pub(crate) filesystem: Filesystem, +} + +impl Context { + pub fn new() -> Context { + Context { + filesystem: Filesystem::new(), + } + } + + pub fn run(&mut self, game: &mut Game) { + loop {} + } +} \ No newline at end of file diff --git a/src/framework/error.rs b/src/framework/error.rs new file mode 100644 index 0000000..88f53e7 --- /dev/null +++ b/src/framework/error.rs @@ -0,0 +1,145 @@ +//! Error types and conversion functions. + + +use std::error::Error; +use std::fmt; +use std::string::FromUtf8Error; +use std::sync::{Arc, PoisonError}; +use std::sync::mpsc::SendError; + +/// An enum containing all kinds of game framework errors. +#[derive(Debug, Clone)] +pub enum GameError { + /// An error in the filesystem layout + FilesystemError(String), + /// An error in the config file + ConfigError(String), + /// Happens when an `winit::EventsLoopProxy` attempts to + /// wake up an `winit::EventsLoop` that no longer exists. + EventLoopError(String), + /// An error trying to load a resource, such as getting an invalid image file. + ResourceLoadError(String), + /// Unable to find a resource; the `Vec` is the paths it searched for and associated errors + ResourceNotFound(String, Vec<(std::path::PathBuf, GameError)>), + /// Something went wrong in the renderer + RenderError(String), + /// Something went wrong in the audio playback + AudioError(String), + /// Something went wrong trying to set or get window properties. + WindowError(String), + /// Something went wrong trying to read from a file + IOError(Arc), + /// Something went wrong trying to load/render a font + FontError(String), + /// Something went wrong applying video settings. + VideoError(String), + /// Something went wrong compiling shaders + ShaderProgramError(String), + /// Something went wrong with the `gilrs` gamepad-input library. + GamepadError(String), + /// Something went wrong with the `lyon` shape-tesselation library. + LyonError(String), + /// Something went wrong while parsing something. + ParseError(String), + /// Something went wrong while converting a value. + InvalidValue(String), +} + +impl fmt::Display for GameError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + GameError::ConfigError(ref s) => write!(f, "Config error: {}", s), + GameError::ResourceLoadError(ref s) => write!(f, "Error loading resource: {}", s), + GameError::ResourceNotFound(ref s, ref paths) => write!( + f, + "Resource not found: {}, searched in paths {:?}", + s, paths + ), + GameError::WindowError(ref e) => write!(f, "Window creation error: {}", e), + _ => write!(f, "GameError {:?}", self), + } + } +} + +impl Error for GameError { + fn cause(&self) -> Option<&dyn Error> { + match *self { + GameError::WindowCreationError(ref e) => Some(&**e), + GameError::IOError(ref e) => Some(&**e), + GameError::ShaderProgramError(ref e) => Some(e), + _ => None, + } + } +} + +/// A convenient result type consisting of a return type and a `GameError` +pub type GameResult = Result; + +impl From for GameError { + fn from(e: std::io::Error) -> GameError { + GameError::IOError(Arc::new(e)) + } +} + +impl From for GameError { + fn from(e: image::ImageError) -> GameError { + let errstr = format!("Image load error: {}", e); + GameError::ResourceLoadError(errstr) + } +} + +impl From for GameError { + fn from(e: FromUtf8Error) -> Self { + let errstr = format!("UTF-8 decoding error: {:?}", e); + GameError::ConfigError(errstr) + } +} + +#[cfg(target_os = "android")] +impl From for GameError { + fn from(e: jni::errors::Error) -> GameError { + GameError::WindowError(e.to_string()) + } +} + +impl From for GameError { + fn from(s: strum::ParseError) -> GameError { + let errstr = format!("Strum parse error: {}", s); + GameError::ParseError(errstr) + } +} + +impl From for GameError { + fn from(s: cpal::DefaultStreamConfigError) -> GameError { + let errstr = format!("Default stream config error: {}", s); + GameError::AudioError(errstr) + } +} + +impl From for GameError { + fn from(s: cpal::PlayStreamError) -> GameError { + let errstr = format!("Play stream error: {}", s); + GameError::AudioError(errstr) + } +} + +impl From for GameError { + fn from(s: cpal::BuildStreamError) -> GameError { + let errstr = format!("Build stream error: {}", s); + GameError::AudioError(errstr) + } +} + +impl From> for GameError { + fn from(s: PoisonError) -> GameError { + let errstr = format!("Poison error: {}", s); + GameError::EventLoopError(errstr) + } +} + +impl From> for GameError { + fn from(s: SendError) -> GameError { + let errstr = format!("Send error: {}", s); + GameError::EventLoopError(errstr) + } +} diff --git a/src/framework/filesystem.rs b/src/framework/filesystem.rs new file mode 100644 index 0000000..306f10e --- /dev/null +++ b/src/framework/filesystem.rs @@ -0,0 +1,373 @@ +use std::env; +use std::fmt; +use std::io; +use std::io::SeekFrom; +use std::path; +use std::path::PathBuf; + +use directories::ProjectDirs; +use crate::framework::vfs; +use crate::framework::vfs::{VFS, OpenOptions}; +use crate::framework::error::{GameResult, GameError}; +use crate::framework::context::Context; + +/// A structure that contains the filesystem state and cache. +#[derive(Debug)] +pub struct Filesystem { + vfs: vfs::OverlayFS, + user_vfs: vfs::OverlayFS, +} + +/// Represents a file, either in the filesystem, or in the resources zip file, +/// or whatever. +pub enum File { + /// A wrapper for a VFile trait object. + VfsFile(Box), +} + +impl fmt::Debug for File { + // Make this more useful? + // But we can't seem to get a filename out of a file, + // soooooo. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + File::VfsFile(ref _file) => write!(f, "VfsFile"), + } + } +} + +impl io::Read for File { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match *self { + File::VfsFile(ref mut f) => f.read(buf), + } + } +} + +impl io::Write for File { + fn write(&mut self, buf: &[u8]) -> io::Result { + match *self { + File::VfsFile(ref mut f) => f.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match *self { + File::VfsFile(ref mut f) => f.flush(), + } + } +} + +impl io::Seek for File { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match *self { + File::VfsFile(ref mut f) => f.seek(pos), + } + } +} + +impl Filesystem { + pub fn new() -> Filesystem { + // Set up VFS to merge resource path, root path, and zip path. + let overlay = vfs::OverlayFS::new(); + // User data VFS. + let mut user_overlay = vfs::OverlayFS::new(); + + Filesystem { + vfs: overlay, + user_vfs: user_overlay, + } + } + + /// Opens the given `path` and returns the resulting `File` + /// in read-only mode. + pub(crate) fn open>(&mut self, path: P) -> GameResult { + self.vfs.open(path.as_ref()).map(|f| File::VfsFile(f)) + } + + /// Opens the given `path` from user directory and returns the resulting `File` + /// in read-only mode. + pub(crate) fn user_open>(&mut self, path: P) -> GameResult { + self.user_vfs.open(path.as_ref()).map(|f| File::VfsFile(f)) + } + + /// Opens a file in the user directory with the given + /// [`filesystem::OpenOptions`](struct.OpenOptions.html). + /// Note that even if you open a file read-write, it can only + /// write to files in the "user" directory. + pub(crate) fn open_options>( + &mut self, + path: P, + options: OpenOptions, + ) -> GameResult { + self.user_vfs + .open_options(path.as_ref(), options) + .map(|f| File::VfsFile(f)) + .map_err(|e| { + GameError::ResourceLoadError(format!( + "Tried to open {:?} but got error: {:?}", + path.as_ref(), + e + )) + }) + } + + /// Creates a new file in the user directory and opens it + /// to be written to, truncating it if it already exists. + pub(crate) fn user_create>(&mut self, path: P) -> GameResult { + self.user_vfs.create(path.as_ref()).map(|f| File::VfsFile(f)) + } + + /// Create an empty directory in the user dir + /// with the given name. Any parents to that directory + /// that do not exist will be created. + pub(crate) fn user_create_dir>(&mut self, path: P) -> GameResult<()> { + self.user_vfs.mkdir(path.as_ref()) + } + + /// Deletes the specified file in the user dir. + pub(crate) fn user_delete>(&mut self, path: P) -> GameResult<()> { + self.user_vfs.rm(path.as_ref()) + } + + /// Deletes the specified directory in the user dir, + /// and all its contents! + pub(crate) fn user_delete_dir>(&mut self, path: P) -> GameResult<()> { + self.user_vfs.rmrf(path.as_ref()) + } + + /// Check whether a file or directory in the user directory exists. + pub(crate) fn user_exists>(&self, path: P) -> bool { + self.user_vfs.exists(path.as_ref()) + } + + /// Check whether a file or directory exists. + pub(crate) fn exists>(&self, path: P) -> bool { + self.vfs.exists(path.as_ref()) + } + + /// Check whether a path points at a file. + pub(crate) fn user_is_file>(&self, path: P) -> bool { + self.user_vfs + .metadata(path.as_ref()) + .map(|m| m.is_file()) + .unwrap_or(false) + } + + /// Check whether a path points at a file. + pub(crate) fn is_file>(&self, path: P) -> bool { + self.vfs + .metadata(path.as_ref()) + .map(|m| m.is_file()) + .unwrap_or(false) + } + + /// Check whether a path points at a directory. + pub(crate) fn user_is_dir>(&self, path: P) -> bool { + self.user_vfs + .metadata(path.as_ref()) + .map(|m| m.is_dir()) + .unwrap_or(false) + } + + /// Check whether a path points at a directory. + pub(crate) fn is_dir>(&self, path: P) -> bool { + self.vfs + .metadata(path.as_ref()) + .map(|m| m.is_dir()) + .unwrap_or(false) + } + + /// Returns a list of all files and directories in the user directory, + /// in no particular order. + /// + /// Lists the base directory if an empty path is given. + pub(crate) fn user_read_dir>( + &mut self, + path: P, + ) -> GameResult>> { + let itr = self.user_vfs.read_dir(path.as_ref())?.map(|fname| { + fname.expect("Could not read file in read_dir()? Should never happen, I hope!") + }); + Ok(Box::new(itr)) + } + + /// Returns a list of all files and directories in the resource directory, + /// in no particular order. + /// + /// Lists the base directory if an empty path is given. + pub(crate) fn read_dir>( + &mut self, + path: P, + ) -> GameResult>> { + let itr = self.vfs.read_dir(path.as_ref())?.map(|fname| { + fname.expect("Could not read file in read_dir()? Should never happen, I hope!") + }); + Ok(Box::new(itr)) + } + + fn write_to_string(&mut self) -> String { + use std::fmt::Write; + let mut s = String::new(); + for vfs in self.vfs.roots() { + write!(s, "Source {:?}", vfs).expect("Could not write to string; should never happen?"); + match vfs.read_dir(path::Path::new("/")) { + Ok(files) => { + for itm in files { + write!(s, " {:?}", itm) + .expect("Could not write to string; should never happen?"); + } + } + Err(e) => write!(s, " Could not read source: {:?}", e) + .expect("Could not write to string; should never happen?"), + } + } + s + } + + /// Adds the given (absolute) path to the list of directories + /// it will search to look for resources. + /// + /// You probably shouldn't use this in the general case, since it is + /// harder than it looks to make it bulletproof across platforms. + /// But it can be very nice for debugging and dev purposes, such as + /// by pushing `$CARGO_MANIFEST_DIR/resources` to it + pub fn mount(&mut self, path: &path::Path, readonly: bool) { + let physfs = vfs::PhysicalFS::new(path, readonly); + trace!("Mounting new path: {:?}", physfs); + self.vfs.push_back(Box::new(physfs)); + } + + pub fn mount_vfs(&mut self, vfs: Box) { + self.vfs.push_back(vfs); + } +} + +/// Opens the given path and returns the resulting `File` +/// in read-only mode. +pub fn open>(ctx: &mut Context, path: P) -> GameResult { + ctx.filesystem.open(path) +} + +/// Opens the given path in the user directory and returns the resulting `File` +/// in read-only mode. +pub fn user_open>(ctx: &mut Context, path: P) -> GameResult { + ctx.filesystem.user_open(path) +} + +/// Opens a file in the user directory with the given `filesystem::OpenOptions`. +pub fn open_options>( + ctx: &mut Context, + path: P, + options: OpenOptions, +) -> GameResult { + ctx.filesystem.open_options(path, options) +} + +/// Creates a new file in the user directory and opens it +/// to be written to, truncating it if it already exists. +pub fn user_create>(ctx: &mut Context, path: P) -> GameResult { + ctx.filesystem.user_create(path) +} + +/// Create an empty directory in the user dir +/// with the given name. Any parents to that directory +/// that do not exist will be created. +pub fn user_create_dir>(ctx: &mut Context, path: P) -> GameResult { + ctx.filesystem.user_create_dir(path.as_ref()) +} + +/// Deletes the specified file in the user dir. +pub fn user_delete>(ctx: &mut Context, path: P) -> GameResult { + ctx.filesystem.user_delete(path.as_ref()) +} + +/// Deletes the specified directory in the user dir, +/// and all its contents! +pub fn user_delete_dir>(ctx: &mut Context, path: P) -> GameResult { + ctx.filesystem.user_delete_dir(path.as_ref()) +} + +/// Check whether a file or directory exists. +pub fn user_exists>(ctx: &Context, path: P) -> bool { + ctx.filesystem.user_exists(path.as_ref()) +} + +/// Check whether a path points at a file. +pub fn user_is_file>(ctx: &Context, path: P) -> bool { + ctx.filesystem.user_is_file(path) +} + +/// Check whether a path points at a directory. +pub fn user_is_dir>(ctx: &Context, path: P) -> bool { + ctx.filesystem.user_is_dir(path) +} + +/// Returns a list of all files and directories in the user directory, +/// in no particular order. +/// +/// Lists the base directory if an empty path is given. +pub fn user_read_dir>( + ctx: &mut Context, + path: P, +) -> GameResult>> { + ctx.filesystem.user_read_dir(path) +} + +/// Check whether a file or directory exists. +pub fn exists>(ctx: &Context, path: P) -> bool { + ctx.filesystem.exists(path.as_ref()) +} + +/// Check whether a path points at a file. +pub fn is_file>(ctx: &Context, path: P) -> bool { + ctx.filesystem.is_file(path) +} + +/// Check whether a path points at a directory. +pub fn is_dir>(ctx: &Context, path: P) -> bool { + ctx.filesystem.is_dir(path) +} + +/// Returns a list of all files and directories in the resource directory, +/// in no particular order. +/// +/// Lists the base directory if an empty path is given. +pub fn read_dir>( + ctx: &mut Context, + path: P, +) -> GameResult>> { + ctx.filesystem.read_dir(path) +} + +/// Prints the contents of all data directories. +/// Useful for debugging. +pub fn print_all(ctx: &mut Context) { + ctx.filesystem.print_all() +} + +/// Outputs the contents of all data directories, +/// using the "info" log level of the `log` crate. +/// Useful for debugging. +/// +/// See the [`logging` example](https://github.com/ggez/ggez/blob/master/examples/eventloop.rs) +/// for how to collect log information. +pub fn log_all(ctx: &mut Context) { + ctx.filesystem.log_all() +} + +/// Adds the given (absolute) path to the list of directories +/// it will search to look for resources. +/// +/// You probably shouldn't use this in the general case, since it is +/// harder than it looks to make it bulletproof across platforms. +/// But it can be very nice for debugging and dev purposes, such as +/// by pushing `$CARGO_MANIFEST_DIR/resources` to it +pub fn mount(ctx: &mut Context, path: &path::Path, readonly: bool) { + ctx.filesystem.mount(path, readonly) +} + +/// Adds a VFS to the list of resource search locations. +pub fn mount_vfs(ctx: &mut Context, vfs: Box) { + ctx.filesystem.mount_vfs(vfs) +} \ No newline at end of file diff --git a/src/framework/graphics.rs b/src/framework/graphics.rs new file mode 100644 index 0000000..0c08fd8 --- /dev/null +++ b/src/framework/graphics.rs @@ -0,0 +1,24 @@ +use crate::common::Color; +use crate::framework::context::Context; +use crate::framework::error::GameResult; + +pub enum FilterMode { + Nearest, + Linear, +} + +pub struct Canvas {} + +impl Canvas { + +} + +pub fn clear(ctx: &mut Context, color: Color) {} + +pub fn present(ctx: &mut Context) -> GameResult<()> { + Ok(()) +} + +pub fn drawable_size(ctx: &mut Context) -> (f32, f32) { + (320.0, 240.0) +} \ No newline at end of file diff --git a/src/framework/image.rs b/src/framework/image.rs new file mode 100644 index 0000000..84d14f1 --- /dev/null +++ b/src/framework/image.rs @@ -0,0 +1,15 @@ +use crate::framework::context::Context; +use crate::framework::error::GameResult; + +pub struct Image {} + +impl Image { + pub fn from_rgba8( + context: &mut Context, + width: u16, + height: u16, + rgba: &[u8], + ) -> GameResult { + Ok(Image {}) + } +} \ No newline at end of file diff --git a/src/framework/keyboard.rs b/src/framework/keyboard.rs new file mode 100644 index 0000000..427808a --- /dev/null +++ b/src/framework/keyboard.rs @@ -0,0 +1,205 @@ +use crate::framework::context::Context; + +#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] +#[repr(u32)] +pub enum ScanCode { + /// The '1' key over the letters. + Key1, + /// The '2' key over the letters. + Key2, + /// The '3' key over the letters. + Key3, + /// The '4' key over the letters. + Key4, + /// The '5' key over the letters. + Key5, + /// The '6' key over the letters. + Key6, + /// The '7' key over the letters. + Key7, + /// The '8' key over the letters. + Key8, + /// The '9' key over the letters. + Key9, + /// The '0' key over the 'O' and 'P' keys. + Key0, + + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + + /// The Escape key, next to F1. + Escape, + + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + + /// Print Screen/SysRq. + Snapshot, + /// Scroll Lock. + Scroll, + /// Pause/Break key, next to Scroll lock. + Pause, + + /// `Insert`, next to Backspace. + Insert, + Home, + Delete, + End, + PageDown, + PageUp, + + Left, + Up, + Right, + Down, + + /// The Backspace key, right over Enter. + Backspace, + /// The Enter key. + Return, + /// The space bar. + Space, + + /// The "Compose" key on Linux. + Compose, + + Caret, + + Numlock, + Numpad0, + Numpad1, + Numpad2, + Numpad3, + Numpad4, + Numpad5, + Numpad6, + Numpad7, + Numpad8, + Numpad9, + NumpadAdd, + NumpadDivide, + NumpadDecimal, + NumpadComma, + NumpadEnter, + NumpadEquals, + NumpadMultiply, + NumpadSubtract, + + AbntC1, + AbntC2, + Apostrophe, + Apps, + Asterisk, + At, + Ax, + Backslash, + Calculator, + Capital, + Colon, + Comma, + Convert, + Equals, + Grave, + Kana, + Kanji, + LAlt, + LBracket, + LControl, + LShift, + LWin, + Mail, + MediaSelect, + MediaStop, + Minus, + Mute, + MyComputer, + // also called "Next" + NavigateForward, + // also called "Prior" + NavigateBackward, + NextTrack, + NoConvert, + OEM102, + Period, + PlayPause, + Plus, + Power, + PrevTrack, + RAlt, + RBracket, + RControl, + RShift, + RWin, + Semicolon, + Slash, + Sleep, + Stop, + Sysrq, + Tab, + Underline, + Unlabeled, + VolumeDown, + VolumeUp, + Wake, + WebBack, + WebFavorites, + WebForward, + WebHome, + WebRefresh, + WebSearch, + WebStop, + Yen, + Copy, + Paste, + Cut, +} + +pub fn is_key_pressed(ctx: &mut Context, code: ScanCode) -> bool { + false +} \ No newline at end of file diff --git a/src/framework/mod.rs b/src/framework/mod.rs new file mode 100644 index 0000000..bad713e --- /dev/null +++ b/src/framework/mod.rs @@ -0,0 +1,10 @@ +pub mod backend; +pub mod backend_sdl2; +pub mod context; +pub mod error; +pub mod filesystem; +pub mod vfs; +pub mod image; +pub mod graphics; +pub mod keyboard; +pub mod backend_null; \ No newline at end of file diff --git a/src/framework/vfs.rs b/src/framework/vfs.rs new file mode 100644 index 0000000..459e78a --- /dev/null +++ b/src/framework/vfs.rs @@ -0,0 +1,678 @@ +//! A virtual file system layer that lets us define multiple +//! "file systems" with various backing stores, then merge them +//! together. +//! +//! Basically a re-implementation of the C library `PhysFS`. The +//! `vfs` crate does something similar but has a couple design +//! decisions that make it kind of incompatible with this use case: +//! the relevant trait for it has generic methods so we can't use it +//! as a trait object, and its path abstraction is not the most +//! convenient. + + +use std::collections::VecDeque; +use std::fmt::{self, Debug}; +use std::fs; +use std::io::{Read, Seek, Write, BufRead}; +use std::path::{self, Path, PathBuf}; +use crate::framework::error::{GameResult, GameError}; + +fn convenient_path_to_str(path: &path::Path) -> GameResult<&str> { + path.to_str().ok_or_else(|| { + let errmessage = format!("Invalid path format for resource: {:?}", path); + GameError::FilesystemError(errmessage) + }) +} + +/// Virtual file +pub trait VFile: Read + Write + Seek + Debug {} + +impl VFile for T where T: Read + Write + Seek + Debug {} + +/// Options for opening files +/// +/// We need our own version of this structure because the one in +/// `std` annoyingly doesn't let you read the read/write/create/etc +/// state out of it. +#[must_use] +#[allow(missing_docs)] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct OpenOptions { + pub read: bool, + pub write: bool, + pub create: bool, + pub append: bool, + pub truncate: bool, +} + +impl OpenOptions { + /// Create a new instance + pub fn new() -> OpenOptions { + Default::default() + } + + /// Open for reading + pub fn read(mut self, read: bool) -> OpenOptions { + self.read = read; + self + } + + /// Open for writing + pub fn write(mut self, write: bool) -> OpenOptions { + self.write = write; + self + } + + /// Create the file if it does not exist yet + pub fn create(mut self, create: bool) -> OpenOptions { + self.create = create; + self + } + + /// Append at the end of the file + pub fn append(mut self, append: bool) -> OpenOptions { + self.append = append; + self + } + + /// Truncate the file to 0 bytes after opening + pub fn truncate(mut self, truncate: bool) -> OpenOptions { + self.truncate = truncate; + self + } + + fn to_fs_openoptions(self) -> fs::OpenOptions { + let mut opt = fs::OpenOptions::new(); + let _ = opt + .read(self.read) + .write(self.write) + .create(self.create) + .append(self.append) + .truncate(self.truncate) + .create(self.create); + opt + } +} + +/// Virtual filesystem +pub trait VFS: Debug { + /// Open the file at this path with the given options + fn open_options(&self, path: &Path, open_options: OpenOptions) -> GameResult>; + /// Open the file at this path for reading + fn open(&self, path: &Path) -> GameResult> { + self.open_options(path, OpenOptions::new().read(true)) + } + /// Open the file at this path for writing, truncating it if it exists already + fn create(&self, path: &Path) -> GameResult> { + self.open_options( + path, + OpenOptions::new().write(true).create(true).truncate(true), + ) + } + /// Open the file at this path for appending, creating it if necessary + fn append(&self, path: &Path) -> GameResult> { + self.open_options( + path, + OpenOptions::new().write(true).create(true).append(true), + ) + } + /// Create a directory at the location by this path + fn mkdir(&self, path: &Path) -> GameResult; + + /// Remove a file or an empty directory. + fn rm(&self, path: &Path) -> GameResult; + + /// Remove a file or directory and all its contents + fn rmrf(&self, path: &Path) -> GameResult; + + /// Check if the file exists + fn exists(&self, path: &Path) -> bool; + + /// Get the file's metadata + fn metadata(&self, path: &Path) -> GameResult>; + + /// Retrieve all file and directory entries in the given directory. + fn read_dir(&self, path: &Path) -> GameResult>>>; + + /// Retrieve the actual location of the VFS root, if available. + fn to_path_buf(&self) -> Option; +} + +/// VFS metadata +pub trait VMetadata { + /// Returns whether or not it is a directory. + /// Note that zip files don't actually have directories, awkwardly, + /// just files with very long names. + fn is_dir(&self) -> bool; + /// Returns whether or not it is a file. + fn is_file(&self) -> bool; + /// Returns the length of the thing. If it is a directory, + /// the result of this is undefined/platform dependent. + fn len(&self) -> u64; +} + +/// A VFS that points to a directory and uses it as the root of its +/// file hierarchy. +/// +/// It IS allowed to have symlinks in it! They're surprisingly +/// difficult to get rid of. +#[derive(Clone)] +pub struct PhysicalFS { + root: PathBuf, + readonly: bool, +} + +#[derive(Debug, Clone)] +/// Physical FS metadata +pub struct PhysicalMetadata(fs::Metadata); + +impl VMetadata for PhysicalMetadata { + fn is_dir(&self) -> bool { + self.0.is_dir() + } + fn is_file(&self) -> bool { + self.0.is_file() + } + fn len(&self) -> u64 { + self.0.len() + } +} + +/// This takes an absolute path and returns either a sanitized relative +/// version of it, or None if there's something bad in it. +/// +/// What we want is an absolute path with no `..`'s in it, so, something +/// like "/foo" or "/foo/bar.txt". This means a path with components +/// starting with a `RootDir`, and zero or more `Normal` components. +/// +/// We gotta return a new path because there's apparently no real good way +/// to turn an absolute path into a relative path with the same +/// components (other than the first), and pushing an absolute `Path` +/// onto a `PathBuf` just completely nukes its existing contents. +fn sanitize_path(path: &path::Path) -> Option { + let mut c = path.components(); + match c.next() { + Some(path::Component::RootDir) => (), + _ => return None, + } + + fn is_normal_component(comp: path::Component) -> Option<&str> { + match comp { + path::Component::Normal(s) => s.to_str(), + _ => None, + } + } + + // This could be done more cleverly but meh + let mut accm = PathBuf::new(); + for component in c { + if let Some(s) = is_normal_component(component) { + accm.push(s) + } else { + return None; + } + } + Some(accm) +} + +impl PhysicalFS { + /// Creates a new PhysicalFS + pub fn new(root: &Path, readonly: bool) -> Self { + PhysicalFS { + root: root.into(), + readonly, + } + } + + /// Takes a given path (&str) and returns + /// a new PathBuf containing the canonical + /// absolute path you get when appending it + /// to this filesystem's root. + fn to_absolute(&self, p: &Path) -> GameResult { + if let Some(safe_path) = sanitize_path(p) { + let mut root_path = self.root.clone(); + root_path.push(safe_path); + Ok(root_path) + } else { + let msg = format!( + "Path {:?} is not valid: must be an absolute path with no \ + references to parent directories", + p + ); + Err(GameError::FilesystemError(msg)) + } + } + + /// Creates the PhysicalFS's root directory if necessary. + /// Idempotent. + /// This way we can not create the directory until it's + /// actually used, though it IS a tiny bit of a performance + /// malus. + fn create_root(&self) -> GameResult { + if !self.root.exists() { + fs::create_dir_all(&self.root).map_err(GameError::from) + } else { + Ok(()) + } + } +} + +impl Debug for PhysicalFS { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "", self.root.display()) + } +} + +impl VFS for PhysicalFS { + /// Open the file at this path with the given options + fn open_options(&self, path: &Path, open_options: OpenOptions) -> GameResult> { + if self.readonly + && (open_options.write + || open_options.create + || open_options.append + || open_options.truncate) + { + let msg = format!( + "Cannot alter file {:?} in root {:?}, filesystem read-only", + path, self + ); + return Err(GameError::FilesystemError(msg)); + } + self.create_root()?; + let p = self.to_absolute(path)?; + open_options + .to_fs_openoptions() + .open(p) + .map(|x| Box::new(x) as Box) + .map_err(GameError::from) + } + + /// Create a directory at the location by this path + fn mkdir(&self, path: &Path) -> GameResult { + if self.readonly { + return Err(GameError::FilesystemError( + "Tried to make directory {} but FS is \ + read-only" + .to_string(), + )); + } + self.create_root()?; + let p = self.to_absolute(path)?; + //println!("Creating {:?}", p); + fs::DirBuilder::new() + .recursive(true) + .create(p) + .map_err(GameError::from) + } + + /// Remove a file + fn rm(&self, path: &Path) -> GameResult { + if self.readonly { + return Err(GameError::FilesystemError( + "Tried to remove file {} but FS is read-only".to_string(), + )); + } + + self.create_root()?; + let p = self.to_absolute(path)?; + if p.is_dir() { + fs::remove_dir(p).map_err(GameError::from) + } else { + fs::remove_file(p).map_err(GameError::from) + } + } + + /// Remove a file or directory and all its contents + fn rmrf(&self, path: &Path) -> GameResult { + if self.readonly { + return Err(GameError::FilesystemError( + "Tried to remove file/dir {} but FS is \ + read-only" + .to_string(), + )); + } + + self.create_root()?; + let p = self.to_absolute(path)?; + if p.is_dir() { + fs::remove_dir_all(p).map_err(GameError::from) + } else { + fs::remove_file(p).map_err(GameError::from) + } + } + + /// Check if the file exists + fn exists(&self, path: &Path) -> bool { + match self.to_absolute(path) { + Ok(p) => p.exists(), + _ => false, + } + } + + /// Get the file's metadata + fn metadata(&self, path: &Path) -> GameResult> { + self.create_root()?; + let p = self.to_absolute(path)?; + p.metadata() + .map(|m| Box::new(PhysicalMetadata(m)) as Box) + .map_err(GameError::from) + } + + /// Retrieve the path entries in this path + fn read_dir(&self, path: &Path) -> GameResult>>> { + self.create_root()?; + let p = self.to_absolute(path)?; + // This is inconvenient because path() returns the full absolute + // path of the bloody file, which is NOT what we want! + // But if we use file_name() to just get the name then it is ALSO not what we want! + // what we WANT is the full absolute file path, *relative to the resources dir*. + // So that we can do read_dir("/foobar/"), and for each file, open it and query + // it and such by name. + // So we build the paths ourself. + let direntry_to_path = |entry: &fs::DirEntry| -> GameResult { + let fname = entry + .file_name() + .into_string() + .expect("Non-unicode char in file path? Should never happen, I hope!"); + let mut pathbuf = PathBuf::from(path); + pathbuf.push(fname); + Ok(pathbuf) + }; + let itr = fs::read_dir(p)? + .map(|entry| direntry_to_path(&entry?)) + .collect::>() + .into_iter(); + Ok(Box::new(itr)) + } + + /// Retrieve the actual location of the VFS root, if available. + fn to_path_buf(&self) -> Option { + Some(self.root.clone()) + } +} + +/// A structure that joins several VFS's together in order. +#[derive(Debug)] +pub struct OverlayFS { + roots: VecDeque>, +} + +impl OverlayFS { + /// Creates a new OverlayFS + pub fn new() -> Self { + Self { + roots: VecDeque::new(), + } + } + + /// Adds a new VFS to the front of the list. + /// Currently unused, I suppose, but good to + /// have at least for tests. + #[allow(dead_code)] + pub fn push_front(&mut self, fs: Box) { + self.roots.push_front(fs); + } + + /// Adds a new VFS to the end of the list. + pub fn push_back(&mut self, fs: Box) { + self.roots.push_back(fs); + } + + /// Returns a list of registered VFS roots. + pub fn roots(&self) -> &VecDeque> { + &self.roots + } +} + +impl VFS for OverlayFS { + /// Open the file at this path with the given options + fn open_options(&self, path: &Path, open_options: OpenOptions) -> GameResult> { + let mut tried: Vec<(PathBuf, GameError)> = vec![]; + + for vfs in &self.roots { + match vfs.open_options(path, open_options) { + Err(e) => { + if let Some(vfs_path) = vfs.to_path_buf() { + tried.push((vfs_path, e)); + } else { + tried.push((PathBuf::from(""), e)); + } + } + f => return f, + } + } + let errmessage = String::from(convenient_path_to_str(path)?); + Err(GameError::ResourceNotFound(errmessage, tried)) + } + + /// Create a directory at the location by this path + fn mkdir(&self, path: &Path) -> GameResult { + for vfs in &self.roots { + match vfs.mkdir(path) { + Err(_) => (), + f => return f, + } + } + Err(GameError::FilesystemError(format!( + "Could not find anywhere writeable to make dir {:?}", + path + ))) + } + + /// Remove a file + fn rm(&self, path: &Path) -> GameResult { + for vfs in &self.roots { + match vfs.rm(path) { + Err(_) => (), + f => return f, + } + } + Err(GameError::FilesystemError(format!( + "Could not remove file {:?}", + path + ))) + } + + /// Remove a file or directory and all its contents + fn rmrf(&self, path: &Path) -> GameResult { + for vfs in &self.roots { + match vfs.rmrf(path) { + Err(_) => (), + f => return f, + } + } + Err(GameError::FilesystemError(format!( + "Could not remove file/dir {:?}", + path + ))) + } + + /// Check if the file exists + fn exists(&self, path: &Path) -> bool { + for vfs in &self.roots { + if vfs.exists(path) { + return true; + } + } + + false + } + + /// Get the file's metadata + fn metadata(&self, path: &Path) -> GameResult> { + for vfs in &self.roots { + match vfs.metadata(path) { + Err(_) => (), + f => return f, + } + } + Err(GameError::FilesystemError(format!( + "Could not get metadata for file/dir {:?}", + path + ))) + } + + /// Retrieve the path entries in this path + fn read_dir(&self, path: &Path) -> GameResult>>> { + // This is tricky 'cause we have to actually merge iterators together... + // Doing it the simple and stupid way works though. + let mut v = Vec::new(); + for fs in &self.roots { + if let Ok(rddir) = fs.read_dir(path) { + v.extend(rddir) + } + } + Ok(Box::new(v.into_iter())) + } + + /// Retrieve the actual location of the VFS root, if available. + fn to_path_buf(&self) -> Option { + None + } +} + +#[cfg(test)] +mod tests { + use std::io::{self, BufRead}; + + use super::*; + + #[test] + fn headless_test_path_filtering() { + // Valid pahts + let p = path::Path::new("/foo"); + assert!(sanitize_path(p).is_some()); + + let p = path::Path::new("/foo/"); + assert!(sanitize_path(p).is_some()); + + let p = path::Path::new("/foo/bar.txt"); + assert!(sanitize_path(p).is_some()); + + let p = path::Path::new("/"); + assert!(sanitize_path(p).is_some()); + + // Invalid paths + let p = path::Path::new("../foo"); + assert!(sanitize_path(p).is_none()); + + let p = path::Path::new("foo"); + assert!(sanitize_path(p).is_none()); + + let p = path::Path::new("/foo/../../"); + assert!(sanitize_path(p).is_none()); + + let p = path::Path::new("/foo/../bop"); + assert!(sanitize_path(p).is_none()); + + let p = path::Path::new("/../bar"); + assert!(sanitize_path(p).is_none()); + + let p = path::Path::new(""); + assert!(sanitize_path(p).is_none()); + } + + #[test] + fn headless_test_read() { + let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR")); + let fs = PhysicalFS::new(cargo_path, true); + let f = fs.open(Path::new("/Cargo.toml")).unwrap(); + let mut bf = io::BufReader::new(f); + let mut s = String::new(); + let _ = bf.read_line(&mut s).unwrap(); + // Trim whitespace from string 'cause it will + // potentially be different on Windows and Unix. + let trimmed_string = s.trim(); + assert_eq!(trimmed_string, "[package]"); + } + + #[test] + fn headless_test_read_overlay() { + let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR")); + let fs1 = PhysicalFS::new(cargo_path, true); + let mut f2path = PathBuf::from(cargo_path); + f2path.push("src"); + let fs2 = PhysicalFS::new(&f2path, true); + let mut ofs = OverlayFS::new(); + ofs.push_back(Box::new(fs1)); + ofs.push_back(Box::new(fs2)); + + assert!(ofs.exists(Path::new("/Cargo.toml"))); + assert!(ofs.exists(Path::new("/lib.rs"))); + assert!(!ofs.exists(Path::new("/foobaz.rs"))); + } + + #[test] + fn headless_test_physical_all() { + let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR")); + let fs = PhysicalFS::new(cargo_path, false); + let testdir = Path::new("/testdir"); + let f1 = Path::new("/testdir/file1.txt"); + + // Delete testdir if it is still lying around + if fs.exists(testdir) { + fs.rmrf(testdir).unwrap(); + } + assert!(!fs.exists(testdir)); + + // Create and delete test dir + fs.mkdir(testdir).unwrap(); + assert!(fs.exists(testdir)); + fs.rm(testdir).unwrap(); + assert!(!fs.exists(testdir)); + + let test_string = "Foo!"; + fs.mkdir(testdir).unwrap(); + { + let mut f = fs.append(f1).unwrap(); + let _ = f.write(test_string.as_bytes()).unwrap(); + } + { + let mut buf = Vec::new(); + let mut f = fs.open(f1).unwrap(); + let _ = f.read_to_end(&mut buf).unwrap(); + assert_eq!(&buf[..], test_string.as_bytes()); + } + + { + // Test metadata() + let m = fs.metadata(f1).unwrap(); + assert!(m.is_file()); + assert!(!m.is_dir()); + assert_eq!(m.len(), 4); + + let m = fs.metadata(testdir).unwrap(); + assert!(!m.is_file()); + assert!(m.is_dir()); + // Not exactly sure what the "length" of a directory is, buuuuuut... + // It appears to vary based on the platform in fact. + // On my desktop, it's 18. + // On Travis's VM, it's 4096. + // On Appveyor's VM, it's 0. + // So, it's meaningless. + //assert_eq!(m.len(), 18); + } + + { + // Test read_dir() + let r = fs.read_dir(testdir).unwrap(); + assert_eq!(r.count(), 1); + let r = fs.read_dir(testdir).unwrap(); + for f in r { + let fname = f.unwrap(); + assert!(fs.exists(&fname)); + } + } + + { + assert!(fs.exists(f1)); + fs.rm(f1).unwrap(); + assert!(!fs.exists(f1)); + } + + fs.rmrf(testdir).unwrap(); + assert!(!fs.exists(testdir)); + } + + // BUGGO: TODO: Make sure all functions are tested for OverlayFS and ZipFS!! +} diff --git a/src/input/combined_menu_controller.rs b/src/input/combined_menu_controller.rs index e48ada0..e7d0355 100644 --- a/src/input/combined_menu_controller.rs +++ b/src/input/combined_menu_controller.rs @@ -1,6 +1,7 @@ +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::input::player_controller::PlayerController; use crate::shared_game_state::SharedGameState; -use ggez::{GameResult, Context}; pub struct CombinedMenuController { controllers: Vec>, diff --git a/src/input/dummy_player_controller.rs b/src/input/dummy_player_controller.rs index f93dac7..31a6887 100644 --- a/src/input/dummy_player_controller.rs +++ b/src/input/dummy_player_controller.rs @@ -1,5 +1,5 @@ -use ggez::{Context, GameResult}; - +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::input::player_controller::PlayerController; use crate::shared_game_state::SharedGameState; diff --git a/src/input/keyboard_player_controller.rs b/src/input/keyboard_player_controller.rs index ee24b92..32be7ca 100644 --- a/src/input/keyboard_player_controller.rs +++ b/src/input/keyboard_player_controller.rs @@ -1,11 +1,11 @@ -use ggez::{Context, GameResult}; -use ggez::input::keyboard; -use winit::event::VirtualKeyCode; - use crate::bitfield; use crate::input::player_controller::PlayerController; use crate::player::TargetPlayer; use crate::shared_game_state::SharedGameState; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::keyboard; +use crate::framework::keyboard::ScanCode; bitfield! { #[derive(Clone, Copy)] @@ -64,8 +64,8 @@ impl PlayerController for KeyboardController { self.state.set_skip(keyboard::is_key_pressed(ctx, keymap.skip)); self.state.set_prev_weapon(keyboard::is_key_pressed(ctx, keymap.prev_weapon)); self.state.set_next_weapon(keyboard::is_key_pressed(ctx, keymap.next_weapon)); - self.state.set_enter(keyboard::is_key_pressed(ctx, VirtualKeyCode::Return)); - self.state.set_escape(keyboard::is_key_pressed(ctx, VirtualKeyCode::Escape)); + self.state.set_enter(keyboard::is_key_pressed(ctx, ScanCode::Return)); + self.state.set_escape(keyboard::is_key_pressed(ctx, ScanCode::Escape)); Ok(()) } diff --git a/src/input/player_controller.rs b/src/input/player_controller.rs index 79ef3d7..2e455d5 100644 --- a/src/input/player_controller.rs +++ b/src/input/player_controller.rs @@ -1,4 +1,5 @@ -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::shared_game_state::SharedGameState; diff --git a/src/input/touch_controls.rs b/src/input/touch_controls.rs index e13893e..b5dc2bc 100644 --- a/src/input/touch_controls.rs +++ b/src/input/touch_controls.rs @@ -1,5 +1,5 @@ -use ggez::{Context, GameResult}; -use winit::event::TouchPhase; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::common::Rect; use crate::engine_constants::EngineConstants; @@ -39,6 +39,7 @@ impl TouchControls { } } + /* pub fn process_winit_event(&mut self, scale: f32, touch: winit::event::Touch) { match touch.phase { TouchPhase::Started | TouchPhase::Moved => { @@ -66,7 +67,7 @@ impl TouchControls { self.clicks.retain(|p| p.id != touch.id); } } - } + }*/ pub fn point_in(&self, bounds: Rect) -> Option { for point in self.points.iter() { diff --git a/src/input/touch_player_controller.rs b/src/input/touch_player_controller.rs index 581b209..04b026a 100644 --- a/src/input/touch_player_controller.rs +++ b/src/input/touch_player_controller.rs @@ -1,4 +1,6 @@ -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; + use crate::bitfield; use crate::common::Rect; diff --git a/src/lib.rs b/src/lib.rs index 181c53e..a289b4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,27 +7,17 @@ extern crate strum; #[macro_use] extern crate strum_macros; -use std::{env, mem}; +use std::env; use std::cell::UnsafeCell; -use std::path; use std::time::Instant; -use ggez::{Context, ContextBuilder, GameError, GameResult}; -use ggez::conf::{Backend, WindowMode, WindowSetup}; -use ggez::event::{KeyCode, KeyMods}; -use ggez::filesystem::mount_vfs; -use ggez::graphics; -use ggez::graphics::{Canvas, DrawParam, window}; -use ggez::graphics::glutin_ext::WindowUpdateExt; -use ggez::input::keyboard; -use ggez::mint::ColumnMatrix4; -use ggez::nalgebra::Vector2; use log::*; use pretty_env_logger::env_logger::Env; -use winit::event::{ElementState, Event, KeyboardInput, WindowEvent}; -use winit::event_loop::ControlFlow; -use crate::builtin_fs::BuiltinFS; +use crate::framework::context::Context; +use crate::framework::error::{GameError, GameResult}; +use crate::framework::graphics; +use crate::framework::keyboard::ScanCode; use crate::scene::loading_scene::LoadingScene; use crate::scene::Scene; use crate::shared_game_state::{SharedGameState, TimingMode}; @@ -46,6 +36,7 @@ mod encoding; mod engine_constants; mod entity; mod frame; +mod framework; mod inventory; mod input; mod live_debugger; @@ -61,6 +52,7 @@ mod scene; #[cfg(feature = "scripting")] mod scripting; mod settings; +#[cfg(feature = "backend-gfx")] mod shaders; mod shared_game_state; mod stage; @@ -74,7 +66,6 @@ struct Game { scene: Option>, state: UnsafeCell, ui: UI, - def_matrix: ColumnMatrix4, start_time: Instant, last_tick: u128, next_tick: u128, @@ -86,7 +77,6 @@ impl Game { let s = Game { scene: None, ui: UI::new(ctx)?, - def_matrix: DrawParam::new().to_matrix(), state: UnsafeCell::new(SharedGameState::new(ctx)?), start_time: Instant::now(), last_tick: 0, @@ -161,10 +151,9 @@ impl Game { self.loops = 0; graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into()); - graphics::set_transform(ctx, DrawParam::new() - .scale(Vector2::new(state_ref.scale, state_ref.scale)) - .to_matrix()); - graphics::apply_transformations(ctx)?; + /*graphics::set_projection(ctx, DrawParam::new() + .scale(Vec2::new(state_ref.scale, state_ref.scale)) + .to_matrix());*/ if let Some(scene) = self.scene.as_mut() { scene.draw(state_ref, ctx)?; @@ -172,8 +161,7 @@ impl Game { state_ref.touch_controls.draw(state_ref.canvas_size, &state_ref.constants, &mut state_ref.texture_set, ctx)?; } - graphics::set_transform(ctx, self.def_matrix); - graphics::apply_transformations(ctx)?; + //graphics::set_projection(ctx, self.def_matrix); self.ui.draw(state_ref, ctx, scene)?; } @@ -181,34 +169,30 @@ impl Game { Ok(()) } - fn key_down_event(&mut self, key_code: KeyCode, _key_mod: KeyMods, repeat: bool) { + fn key_down_event(&mut self, key_code: ScanCode, repeat: bool) { if repeat { return; } let state = unsafe { &mut *self.state.get() }; match key_code { - KeyCode::F5 => { state.settings.subpixel_coords = !state.settings.subpixel_coords } - KeyCode::F6 => { state.settings.motion_interpolation = !state.settings.motion_interpolation } - KeyCode::F7 => { state.set_speed(1.0) } - KeyCode::F8 => { + ScanCode::F5 => { state.settings.subpixel_coords = !state.settings.subpixel_coords } + ScanCode::F6 => { state.settings.motion_interpolation = !state.settings.motion_interpolation } + ScanCode::F7 => { state.set_speed(1.0) } + ScanCode::F8 => { if state.settings.speed > 0.2 { state.set_speed(state.settings.speed - 0.1); } } - KeyCode::F9 => { + ScanCode::F9 => { if state.settings.speed < 3.0 { state.set_speed(state.settings.speed + 0.1); } } - KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines } - KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode } - KeyCode::F12 => { state.settings.infinite_booster = !state.settings.infinite_booster } + ScanCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines } + ScanCode::F11 => { state.settings.god_mode = !state.settings.god_mode } + ScanCode::F12 => { state.settings.infinite_booster = !state.settings.infinite_booster } _ => {} } } - - fn key_up_event(&mut self, _key_code: KeyCode, _key_mod: KeyMods) { - // - } } #[cfg(target_os = "android")] @@ -271,68 +255,22 @@ pub fn android_main() { init().unwrap(); } -#[cfg(target_os = "android")] -static BACKENDS: [Backend; 2] = [ - Backend::OpenGLES { major: 3, minor: 0 }, - Backend::OpenGLES { major: 2, minor: 0 } -]; - -#[cfg(not(target_os = "android"))] -static BACKENDS: [Backend; 4] = [ - Backend::OpenGL { major: 3, minor: 2 }, - Backend::OpenGLES { major: 3, minor: 2 }, - Backend::OpenGLES { major: 3, minor: 0 }, - Backend::OpenGLES { major: 2, minor: 0 } -]; - -fn init_ctx + Clone>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult { - for backend in BACKENDS.iter() { - let ctx = ContextBuilder::new("doukutsu-rs") - .window_setup(WindowSetup::default().title("Cave Story ~ Doukutsu Monogatari (doukutsu-rs)")) - .window_mode(WindowMode::default() - .resizable(true) - .min_dimensions(320.0, 240.0) - .dimensions(640.0, 480.0)) - .add_resource_path(resource_dir.clone()) - .backend(*backend) - .build(event_loop); - - match ctx { - Ok(mut ctx) => { - mount_vfs(&mut ctx, Box::new(BuiltinFS::new())); - - return Ok(ctx); - } - Err(err) => { - log::warn!("Failed to create backend using config {:?}: {}", backend, err); - } - } - } - - Err(GameError::EventLoopError("Failed to initialize OpenGL backend. Perhaps the driver is outdated?".to_string())) -} - pub fn init() -> GameResult { pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info")) - .filter(Some("gfx_device_gl::factory"), LevelFilter::Warn) .init(); - let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") { - path::PathBuf::from(data_dir) - } else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { - let mut path = path::PathBuf::from(manifest_dir); - path.push("data"); - path - } else { - path::PathBuf::from("data") - }; + let mut resource_dir = env::current_exe()?; + + // Ditch the filename (if any) + if resource_dir.file_name().is_some() { + let _ = resource_dir.pop(); + } + resource_dir.push("data"); info!("Resource directory: {:?}", resource_dir); info!("Initializing engine..."); - let event_loop = winit::event_loop::EventLoop::new(); - let mut context: Option; - let mut game: Option = None; + let mut context: Context = Context::new(); #[cfg(target_os = "android")] { @@ -346,152 +284,4 @@ pub fn init() -> GameResult { } } } - - context = Some(init_ctx(&event_loop, resource_dir.clone())?); - - event_loop.run(move |event, target, flow| { - #[cfg(target_os = "windows")] - { - // Windows' system clock implementation isn't monotonic when the process gets switched to another core. - // Rust has mitigations for this, but apparently aren't very effective unless Instant is called very often. - let _ = Instant::now(); - } - if let Some(ctx) = &mut context { - ctx.process_event(&event); - - if let Some(game) = &mut game { - game.ui.handle_events(ctx, &event); - } else { - let mut new_game = Game::new(ctx).unwrap(); - let state_ref = unsafe { &mut *new_game.state.get() }; - state_ref.next_scene = Some(Box::new(LoadingScene::new())); - game = Some(new_game); - - #[cfg(feature = "scripting")] - { - unsafe { - let game_ref = game.as_mut().unwrap(); - let state_ref = game_ref.state.get(); - - (&mut *state_ref).lua.update_refs(game_ref.state.get(), ctx as *mut Context); - } - } - } - } - - match event { - Event::Resumed => { - #[cfg(target_os = "android")] - if context.is_none() { - context = Some(init_ctx(target, resource_dir.clone()).unwrap()); - } - let _ = target; - - if let Some(game) = &mut game { - game.loops = 0; - } - } - Event::Suspended => { - #[cfg(target_os = "android")] - { - context = None; - } - if let Some(game) = &mut game { - game.loops = 0; - } - } - Event::WindowEvent { event, .. } => { - match event { - WindowEvent::CloseRequested => { - if let Some(game) = &mut game { - let state_ref = unsafe { &mut *game.state.get() }; - state_ref.shutdown(); - } - *flow = ControlFlow::Exit; - } - WindowEvent::Resized(size) => { - // Minimizing a window on Windows causes this event to get called with a 0x0 size - if size.width != 0 && size.height != 0 { - if let (Some(ctx), Some(game)) = (&mut context, &mut game) { - let state_ref = unsafe { &mut *game.state.get() }; - - state_ref.tmp_canvas = Canvas::with_window_size(ctx).unwrap(); - state_ref.game_canvas = Canvas::with_window_size(ctx).unwrap(); - state_ref.lightmap_canvas = Canvas::with_window_size(ctx).unwrap(); - state_ref.handle_resize(ctx).unwrap(); - graphics::window(ctx).update_gfx(&mut game.ui.main_color, &mut game.ui.main_depth); - } - } - } - WindowEvent::Touch(touch) => { - if let Some(game) = &mut game { - let state_ref = unsafe { &mut *game.state.get() }; - state_ref.touch_controls.process_winit_event(state_ref.scale, touch); - } - } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: el_state, - virtual_keycode: Some(keycode), - modifiers, - .. - }, - .. - } => { - if let (Some(ctx), Some(game)) = (&mut context, &mut game) { - match el_state { - ElementState::Pressed => { - let repeat = keyboard::is_key_repeated(ctx); - game.key_down_event(keycode, modifiers.into(), repeat); - } - ElementState::Released => { - game.key_up_event(keycode, modifiers.into()); - } - } - } - } - _ => {} - } - } - Event::RedrawRequested(win) => { - if let (Some(ctx), Some(game)) = (&mut context, &mut game) { - if win == window(ctx).window().id() { - ctx.timer_context.tick(); - game.draw(ctx).unwrap(); - } - } - } - Event::MainEventsCleared => { - if let (Some(ctx), Some(game)) = (&mut context, &mut game) { - game.update(ctx).unwrap(); - - #[cfg(target_os = "android")] - { - ctx.timer_context.tick(); - game.draw(ctx).unwrap(); // redraw request is unimplemented on shitdroid - } - window(ctx).window().request_redraw(); - - let state_ref = unsafe { &mut *game.state.get() }; - - if state_ref.shutdown { - log::info!("Shutting down..."); - *flow = ControlFlow::Exit; - return; - } - - if state_ref.next_scene.is_some() { - mem::swap(&mut game.scene, &mut state_ref.next_scene); - state_ref.next_scene = None; - - game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap(); - game.loops = 0; - state_ref.frame_time = 0.0; - } - } - } - _ => {} - } - }) } diff --git a/src/live_debugger.rs b/src/live_debugger.rs index 0f53fa0..f9ee7b0 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -1,4 +1,6 @@ -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; + use imgui::{CollapsingHeader, Condition, im_str, ImStr, ImString, Slider, Window}; use itertools::Itertools; diff --git a/src/main.rs b/src/main.rs index e903cbd..b548c45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,34 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use std::process::exit; + fn main() { - doukutsu_rs::init().unwrap(); + let result = doukutsu_rs::init(); + + #[cfg(target_os = "windows")] + unsafe { + use winapi::_core::ptr::null_mut; + use winapi::um::winuser::MessageBoxW; + use winapi::um::winuser::MB_OK; + use winapi::shared::ntdef::LPCWSTR; + use std::ffi::OsStr; + use std::os::windows::prelude::*; + + if let Err(e) = result { + let title: LPCWSTR = OsStr::new("Error!") + .encode_wide().chain(Some(0)).collect::>().as_ptr(); + let message: LPCWSTR = OsStr::new(format!("Whoops, doukutsu-rs crashed: {}", e).as_str()) + .encode_wide().chain(Some(0)).collect::>().as_ptr(); + MessageBoxW(null_mut(), + message, + title, + MB_OK); + exit(1); + } + } + + if let Err(e) = result { + println!("Initialization error: {}", e); + exit(1); + } } diff --git a/src/map.rs b/src/map.rs index cc18462..53950d1 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,10 +1,12 @@ use std::io; use byteorder::{LE, ReadBytesExt}; -use ggez::GameError::ResourceLoadError; -use ggez::GameResult; + +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::str; +use crate::framework::error::GameError::ResourceLoadError; static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10]; static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10]; @@ -28,7 +30,6 @@ impl Map { let version = map_data.read_u8()?; - // It's something Booster's Lab supports but I haven't seen anywhere being used in practice if !SUPPORTED_PXM_VERSIONS.contains(&version) { return Err(ResourceLoadError(format!("Unsupported PXM version: {:#x}", version))); } diff --git a/src/menu/mod.rs b/src/menu/mod.rs index aa3e83c..04702a3 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -1,12 +1,11 @@ -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::common::Rect; use crate::input::combined_menu_controller::CombinedMenuController; use crate::shared_game_state::SharedGameState; -pub struct MenuSaveInfo { - -} +pub struct MenuSaveInfo {} pub enum MenuEntry { Hidden, diff --git a/src/npc/ai/balrog.rs b/src/npc/ai/balrog.rs index 992956b..d76f668 100644 --- a/src/npc/ai/balrog.rs +++ b/src/npc/ai/balrog.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::clamp; use crate::caret::CaretType; diff --git a/src/npc/ai/booster.rs b/src/npc/ai/booster.rs index cd05542..c2e9dc0 100644 --- a/src/npc/ai/booster.rs +++ b/src/npc/ai/booster.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::common::Direction; use crate::npc::NPC; diff --git a/src/npc/ai/chaco.rs b/src/npc/ai/chaco.rs index 668fe3e..2f7177e 100644 --- a/src/npc/ai/chaco.rs +++ b/src/npc/ai/chaco.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::caret::CaretType; use crate::common::Direction; diff --git a/src/npc/ai/characters.rs b/src/npc/ai/characters.rs index 4435857..419368d 100644 --- a/src/npc/ai/characters.rs +++ b/src/npc/ai/characters.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::{abs, clamp}; use crate::common::Direction; diff --git a/src/npc/ai/egg_corridor.rs b/src/npc/ai/egg_corridor.rs index c5b7d82..b2697bb 100644 --- a/src/npc/ai/egg_corridor.rs +++ b/src/npc/ai/egg_corridor.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::{abs, clamp}; use crate::caret::CaretType; diff --git a/src/npc/ai/first_cave.rs b/src/npc/ai/first_cave.rs index 6cd48c4..8225295 100644 --- a/src/npc/ai/first_cave.rs +++ b/src/npc/ai/first_cave.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::clamp; use crate::common::Direction; diff --git a/src/npc/ai/grasstown.rs b/src/npc/ai/grasstown.rs index a31e365..b2d27ab 100644 --- a/src/npc/ai/grasstown.rs +++ b/src/npc/ai/grasstown.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::abs; use num_traits::clamp; diff --git a/src/npc/ai/igor.rs b/src/npc/ai/igor.rs index 2c0e72d..1aca8aa 100644 --- a/src/npc/ai/igor.rs +++ b/src/npc/ai/igor.rs @@ -1,6 +1,6 @@ -use ggez::GameResult; - use crate::common::{CDEG_RAD, Direction}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::npc::list::NPCList; use crate::npc::NPC; use crate::player::Player; diff --git a/src/npc/ai/intro.rs b/src/npc/ai/intro.rs index c53fafa..51b36f5 100644 --- a/src/npc/ai/intro.rs +++ b/src/npc/ai/intro.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::caret::CaretType; use crate::common::Direction; diff --git a/src/npc/ai/last_cave.rs b/src/npc/ai/last_cave.rs index 7881aa5..fe6fc29 100644 --- a/src/npc/ai/last_cave.rs +++ b/src/npc/ai/last_cave.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::common::Direction; use crate::npc::NPC; diff --git a/src/npc/ai/maze.rs b/src/npc/ai/maze.rs index 75367d7..148d85c 100644 --- a/src/npc/ai/maze.rs +++ b/src/npc/ai/maze.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::clamp; use crate::caret::CaretType; diff --git a/src/npc/ai/mimiga_village.rs b/src/npc/ai/mimiga_village.rs index 42497c2..37e6fca 100644 --- a/src/npc/ai/mimiga_village.rs +++ b/src/npc/ai/mimiga_village.rs @@ -1,9 +1,10 @@ use std::cmp::Ordering; -use ggez::GameResult; use num_traits::{abs, clamp}; use crate::common::Direction; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::npc::NPC; use crate::player::Player; use crate::rng::RNG; diff --git a/src/npc/ai/misc.rs b/src/npc/ai/misc.rs index a8bc750..97bc61f 100644 --- a/src/npc/ai/misc.rs +++ b/src/npc/ai/misc.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::{abs, clamp}; use crate::caret::CaretType; diff --git a/src/npc/ai/misery.rs b/src/npc/ai/misery.rs index a699e9f..26b27c3 100644 --- a/src/npc/ai/misery.rs +++ b/src/npc/ai/misery.rs @@ -1,7 +1,8 @@ -use ggez::GameResult; use num_traits::clamp; use crate::common::Direction; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::npc::list::NPCList; use crate::npc::NPC; use crate::rng::RNG; diff --git a/src/npc/ai/outer_wall.rs b/src/npc/ai/outer_wall.rs index 6ad18e0..3a53237 100644 --- a/src/npc/ai/outer_wall.rs +++ b/src/npc/ai/outer_wall.rs @@ -1,9 +1,11 @@ -use crate::npc::NPC; -use crate::shared_game_state::SharedGameState; -use crate::player::Player; -use ggez::GameResult; use num_traits::abs; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::npc::NPC; +use crate::player::Player; +use crate::shared_game_state::SharedGameState; + impl NPC { pub(crate) fn tick_n215_sandcroc_outer_wall(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult { match self.action_num { diff --git a/src/npc/ai/pickups.rs b/src/npc/ai/pickups.rs index 2bd0028..a2804c6 100644 --- a/src/npc/ai/pickups.rs +++ b/src/npc/ai/pickups.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::clamp; use crate::common::Direction; diff --git a/src/npc/ai/quote.rs b/src/npc/ai/quote.rs index d31499e..fc3f390 100644 --- a/src/npc/ai/quote.rs +++ b/src/npc/ai/quote.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::common::Direction; use crate::npc::list::NPCList; diff --git a/src/npc/ai/sand_zone.rs b/src/npc/ai/sand_zone.rs index f08a002..35bdd52 100644 --- a/src/npc/ai/sand_zone.rs +++ b/src/npc/ai/sand_zone.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::{abs, clamp}; use crate::caret::CaretType; diff --git a/src/npc/ai/santa.rs b/src/npc/ai/santa.rs index 3eb5f72..1743ca0 100644 --- a/src/npc/ai/santa.rs +++ b/src/npc/ai/santa.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::abs; use crate::common::Direction; diff --git a/src/npc/ai/sue.rs b/src/npc/ai/sue.rs index e33a89a..928b962 100644 --- a/src/npc/ai/sue.rs +++ b/src/npc/ai/sue.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::clamp; use crate::common::Direction; diff --git a/src/npc/ai/toroko.rs b/src/npc/ai/toroko.rs index 2990ee0..18802b9 100644 --- a/src/npc/ai/toroko.rs +++ b/src/npc/ai/toroko.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::clamp; use crate::common::Direction; diff --git a/src/npc/ai/weapon_trail.rs b/src/npc/ai/weapon_trail.rs index 967c3e1..126e62c 100644 --- a/src/npc/ai/weapon_trail.rs +++ b/src/npc/ai/weapon_trail.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::npc::NPC; use crate::shared_game_state::SharedGameState; diff --git a/src/npc/boss/balfrog.rs b/src/npc/boss/balfrog.rs index 004e3c0..59a0ffe 100644 --- a/src/npc/boss/balfrog.rs +++ b/src/npc/boss/balfrog.rs @@ -1,4 +1,5 @@ -use ggez::GameResult; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::caret::CaretType; use crate::common::{CDEG_RAD, Direction, Rect}; diff --git a/src/npc/boss/mod.rs b/src/npc/boss/mod.rs index 355d89f..55ff275 100644 --- a/src/npc/boss/mod.rs +++ b/src/npc/boss/mod.rs @@ -1,11 +1,11 @@ use std::mem::MaybeUninit; -use ggez::{Context, GameResult}; - use crate::bullet::BulletManager; use crate::common::{Direction, interpolate_fix9_scale}; use crate::entity::GameEntity; use crate::frame::Frame; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::npc::list::NPCList; use crate::npc::NPC; use crate::player::Player; diff --git a/src/npc/boss/monster_x.rs b/src/npc/boss/monster_x.rs index d947c2d..c187b0e 100644 --- a/src/npc/boss/monster_x.rs +++ b/src/npc/boss/monster_x.rs @@ -1,8 +1,9 @@ -use ggez::GameResult; use num_traits::{abs, clamp}; use crate::caret::CaretType; use crate::common::{CDEG_RAD, Direction, Rect}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::npc::boss::BossNPC; use crate::npc::list::NPCList; use crate::npc::NPC; diff --git a/src/npc/boss/omega.rs b/src/npc/boss/omega.rs index 8360c31..2deae7f 100644 --- a/src/npc/boss/omega.rs +++ b/src/npc/boss/omega.rs @@ -1,4 +1,4 @@ -use ggez::GameResult; + use crate::bullet::BulletManager; use crate::caret::CaretType; @@ -9,6 +9,8 @@ use crate::npc::NPC; use crate::player::Player; use crate::rng::RNG; use crate::shared_game_state::SharedGameState; +use crate::framework::context::Context; +use crate::framework::error::GameResult; impl NPC { pub(crate) fn tick_n048_omega_projectiles(&mut self, state: &mut SharedGameState) -> GameResult { diff --git a/src/npc/list.rs b/src/npc/list.rs index 436dea8..84377fe 100644 --- a/src/npc/list.rs +++ b/src/npc/list.rs @@ -1,7 +1,8 @@ use std::cell::{Cell, UnsafeCell}; use std::mem::MaybeUninit; -use ggez::{GameError, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::{GameResult, GameError}; use crate::npc::NPC; diff --git a/src/npc/mod.rs b/src/npc/mod.rs index 4565654..dc77d79 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -2,7 +2,8 @@ use std::io; use std::io::Cursor; use byteorder::{LE, ReadBytesExt}; -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_traits::abs; use crate::bitfield; diff --git a/src/player/mod.rs b/src/player/mod.rs index 51eab33..6722190 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -1,6 +1,7 @@ use std::clone::Clone; -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use num_derive::FromPrimitive; use num_traits::clamp; diff --git a/src/profile.rs b/src/profile.rs index c147d85..1681da8 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -4,13 +4,15 @@ use byteorder::{BE, LE, ReadBytesExt, WriteBytesExt}; use num_traits::{clamp, FromPrimitive}; use crate::common::{Direction, FadeState}; -use ggez::{Context, GameResult}; -use ggez::GameError::ResourceLoadError; + +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::player::ControlMode; use crate::scene::game_scene::GameScene; use crate::shared_game_state::SharedGameState; use crate::str; use crate::weapon::{WeaponLevel, WeaponType}; +use crate::framework::error::GameError::ResourceLoadError; pub struct WeaponData { pub weapon_id: u32, diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index c8cdbb0..3f8cfab 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -1,13 +1,9 @@ -use ggez::{Context, GameResult, graphics, timer}; -use ggez::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, mint}; -use ggez::graphics::spritebatch::SpriteBatch; -use ggez::nalgebra::{clamp, Vector2}; use log::info; -use num_traits::abs; +use num_traits::{abs, clamp}; use crate::bullet::BulletManager; use crate::caret::CaretType; -use crate::common::{Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect}; +use crate::common::{Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect, Color}; use crate::components::boss_life_bar::BossLifeBar; use crate::components::draw_common::{Alignment, draw_number}; use crate::components::hud::HUD; @@ -30,6 +26,10 @@ use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, use crate::texture_set::SizedBatch; use crate::ui::Components; use crate::weapon::WeaponType; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::graphics; +use crate::framework::graphics::FilterMode; pub struct GameScene { pub tick: u32, @@ -502,10 +502,10 @@ impl GameScene { } fn draw_light_map(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - graphics::set_canvas(ctx, Some(&state.lightmap_canvas)); + /*graphics::set_canvas(ctx, Some(&state.lightmap_canvas)); graphics::set_blend_mode(ctx, BlendMode::Add)?; - graphics::clear(ctx, Color::from_rgb(100, 100, 110)); + graphics::clear(ctx, Color::from_rgb(100, 100, 110));*/ { let scale = state.scale; let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/lightmap/spot")?; @@ -634,94 +634,13 @@ impl GameScene { batch.draw_filtered(FilterMode::Linear, ctx)?; } - graphics::set_blend_mode(ctx, BlendMode::Multiply)?; + /*graphics::set_blend_mode(ctx, BlendMode::Multiply)?; graphics::set_canvas(ctx, Some(&state.game_canvas)); state.lightmap_canvas.set_filter(FilterMode::Linear); state.lightmap_canvas.draw(ctx, DrawParam::new() .scale(Vector2::new(1.0 / state.scale, 1.0 / state.scale)))?; - graphics::set_blend_mode(ctx, BlendMode::Alpha)?; - - Ok(()) - } - - fn is_water(&self, tile: u8) -> bool { - [0x02, 0x04, 0x60, 0x61, 0x62, 0x64, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0xa0, 0xa1, 0xa2, 0xa3].contains(&tile) - } - - fn draw_water(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - let (frame_x, frame_y) = self.frame.xy_interpolated(state.frame_time, state.scale); - - { - state.shaders.water_shader_params.resolution = [state.canvas_size.0, state.canvas_size.1]; - state.shaders.water_shader_params.frame_pos = [frame_x, frame_y]; - state.shaders.water_shader_params.t = self.tick as f32; - let _lock = graphics::use_shader(ctx, &state.shaders.water_shader); - state.shaders.water_shader.send(ctx, state.shaders.water_shader_params)?; - - graphics::set_canvas(ctx, Some(&state.tmp_canvas)); - graphics::clear(ctx, Color::new(0.0, 0.0, 0.0, 1.0)); - state.game_canvas.draw(ctx, DrawParam::new() - .scale(mint::Vector2 { x: 1.0 / state.scale, y: -1.0 / state.scale }) - .offset(mint::Point2 { x: 0.0, y: -1.0 }))?; - } - graphics::set_canvas(ctx, Some(&state.game_canvas)); - - // cheap, clones a reference underneath - let mut tmp_batch = SpriteBatch::new(state.tmp_canvas.image().clone()); - - let tile_start_x = clamp(self.frame.x / 0x200 / 16, 0, self.stage.map.width as i32) as usize; - let tile_start_y = clamp(self.frame.y / 0x200 / 16, 0, self.stage.map.height as i32) as usize; - let tile_end_x = clamp((self.frame.x / 0x200 + 8 + state.canvas_size.0 as i32) / 16 + 1, 0, self.stage.map.width as i32) as usize; - let tile_end_y = clamp((self.frame.y / 0x200 + 8 + state.canvas_size.1 as i32) / 16 + 1, 0, self.stage.map.height as i32) as usize; - let mut rect = Rect { - left: 0.0, - top: 0.0, - right: 16.0, - bottom: 16.0, - }; - - for y in tile_start_y..tile_end_y { - for x in tile_start_x..tile_end_x { - let tile = unsafe { - self.stage.map.attrib[*self.stage.map.tiles - .get_unchecked((y * self.stage.map.width as usize) + x) as usize] - }; - let tile_above = unsafe { - self.stage.map.attrib[*self.stage.map.tiles - .get_unchecked((y.saturating_sub(1) * self.stage.map.width as usize) + x) as usize] - }; - - if !self.is_water(tile) { - continue; - } - - rect.left = (x as f32 * 16.0 - 8.0) - frame_x; - rect.top = (y as f32 * 16.0 - 8.0) - frame_y; - rect.right = rect.left + 16.0; - rect.bottom = rect.top + 16.0; - - if tile_above == 0 { - rect.top += 3.0; - } - - tmp_batch.add(DrawParam::new() - .src(ggez::graphics::Rect::new(rect.left / state.canvas_size.0, - rect.top / state.canvas_size.1, - (rect.right - rect.left) / state.canvas_size.0, - (rect.bottom - rect.top) / state.canvas_size.1)) - .scale(mint::Vector2 { - x: 1.0 / state.scale, - y: 1.0 / state.scale, - }) - .dest(mint::Point2 { - x: rect.left, - y: rect.top, - })); - } - } - - tmp_batch.draw(ctx, DrawParam::new())?; + graphics::set_blend_mode(ctx, BlendMode::Alpha)?;*/ Ok(()) } @@ -1291,7 +1210,7 @@ impl Scene for GameScene { } fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - graphics::set_canvas(ctx, Some(&state.game_canvas)); + //graphics::set_canvas(ctx, Some(&state.game_canvas)); self.draw_background(state, ctx)?; self.draw_tiles(state, ctx, TileLayer::Background)?; if state.settings.shader_effects @@ -1316,9 +1235,9 @@ impl Scene for GameScene { self.draw_bullets(state, ctx)?; self.player2.draw(state, ctx, &self.frame)?; self.player1.draw(state, ctx, &self.frame)?; - if state.settings.shader_effects && self.water_visible { + /*if state.settings.shader_effects && self.water_visible { self.draw_water(state, ctx)?; - } + }*/ self.draw_tiles(state, ctx, TileLayer::Foreground)?; self.draw_tiles(state, ctx, TileLayer::Snack)?; @@ -1329,9 +1248,9 @@ impl Scene for GameScene { self.draw_light_map(state, ctx)?; } - graphics::set_canvas(ctx, None); + /*graphics::set_canvas(ctx, None); state.game_canvas.draw(ctx, DrawParam::new() - .scale(Vector2::new(1.0 / state.scale, 1.0 / state.scale)))?; + .scale(Vector2::new(1.0 / state.scale, 1.0 / state.scale)))?;*/ self.draw_black_bars(state, ctx)?; if state.control_flags.control_enabled() { @@ -1389,7 +1308,7 @@ impl Scene for GameScene { self.draw_debug_outlines(state, ctx)?; } - draw_number(state.canvas_size.0 - 8.0, 8.0, timer::fps(ctx) as usize, Alignment::Right, state, ctx)?; + //draw_number(state.canvas_size.0 - 8.0, 8.0, timer::fps(ctx) as usize, Alignment::Right, state, ctx)?; Ok(()) } diff --git a/src/scene/loading_scene.rs b/src/scene/loading_scene.rs index c760954..575e19c 100644 --- a/src/scene/loading_scene.rs +++ b/src/scene/loading_scene.rs @@ -1,5 +1,6 @@ -use ggez::{Context, filesystem, GameResult}; - +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::filesystem; use crate::npc::NPCTable; use crate::scene::no_data_scene::NoDataScene; use crate::scene::Scene; diff --git a/src/scene/mod.rs b/src/scene/mod.rs index f0357b0..d692e04 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1,4 +1,5 @@ -use ggez::{Context, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::shared_game_state::SharedGameState; use crate::ui::Components; diff --git a/src/scene/no_data_scene.rs b/src/scene/no_data_scene.rs index 5706e04..a7b20a4 100644 --- a/src/scene/no_data_scene.rs +++ b/src/scene/no_data_scene.rs @@ -1,4 +1,5 @@ -use ggez::{Context, GameError, GameResult}; +use crate::framework::context::Context; +use crate::framework::error::{GameResult, GameError}; use crate::common::Rect; use crate::scene::Scene; diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index 1d52481..41709fb 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -1,12 +1,12 @@ -use ggez::{Context, GameResult, graphics}; -use ggez::graphics::Color; - -use crate::common::{Rect, VERSION_BANNER}; +use crate::common::{Rect, VERSION_BANNER, Color}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::graphics; +use crate::input::combined_menu_controller::CombinedMenuController; +use crate::input::touch_controls::TouchControlType; use crate::menu::{Menu, MenuEntry, MenuSelectionResult}; use crate::scene::Scene; use crate::shared_game_state::{SharedGameState, TimingMode}; -use crate::input::combined_menu_controller::CombinedMenuController; -use crate::input::touch_controls::TouchControlType; #[derive(PartialEq, Eq, Copy, Clone)] #[repr(u8)] diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs index 63a62e4..5e24cc2 100644 --- a/src/scripting/mod.rs +++ b/src/scripting/mod.rs @@ -1,14 +1,19 @@ use std::io::{Read, Seek}; use std::ptr::null_mut; -use ggez::{Context, filesystem, GameError, GameResult}; -use ggez::filesystem::File; + +use crate::framework::context::Context; +use crate::framework::error::{GameResult, GameError}; + + use lua_ffi::{c_int, LuaFunction, LuaObject, State, ThreadStatus}; use lua_ffi::ffi::lua_pushcfunction; use crate::scene::game_scene::GameScene; use crate::scripting::doukutsu::Doukutsu; use crate::shared_game_state::SharedGameState; +use crate::framework::filesystem::File; +use crate::framework::filesystem; mod doukutsu; mod player; diff --git a/src/settings.rs b/src/settings.rs index 8543ee9..629456e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,11 +1,12 @@ -use ggez::{Context, GameResult}; use serde::{Deserialize, Serialize}; -use winit::event::VirtualKeyCode; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::keyboard::ScanCode; use crate::input::keyboard_player_controller::KeyboardController; use crate::input::player_controller::PlayerController; -use crate::player::TargetPlayer; use crate::input::touch_player_controller::TouchPlayerController; +use crate::player::TargetPlayer; #[derive(Serialize, Deserialize)] pub struct Settings { @@ -66,47 +67,47 @@ impl Default for Settings { #[derive(Serialize, Deserialize)] pub struct PlayerKeyMap { - pub left: VirtualKeyCode, - pub up: VirtualKeyCode, - pub right: VirtualKeyCode, - pub down: VirtualKeyCode, - pub prev_weapon: VirtualKeyCode, - pub next_weapon: VirtualKeyCode, - pub jump: VirtualKeyCode, - pub shoot: VirtualKeyCode, - pub skip: VirtualKeyCode, - pub inventory: VirtualKeyCode, - pub map: VirtualKeyCode, + pub left: ScanCode, + pub up: ScanCode, + pub right: ScanCode, + pub down: ScanCode, + pub prev_weapon: ScanCode, + pub next_weapon: ScanCode, + pub jump: ScanCode, + pub shoot: ScanCode, + pub skip: ScanCode, + pub inventory: ScanCode, + pub map: ScanCode, } fn p1_default_keymap() -> PlayerKeyMap { PlayerKeyMap { - left: VirtualKeyCode::Left, - up: VirtualKeyCode::Up, - right: VirtualKeyCode::Right, - down: VirtualKeyCode::Down, - prev_weapon: VirtualKeyCode::A, - next_weapon: VirtualKeyCode::S, - jump: VirtualKeyCode::Z, - shoot: VirtualKeyCode::X, - skip: VirtualKeyCode::LControl, - inventory: VirtualKeyCode::Q, - map: VirtualKeyCode::W, + left: ScanCode::Left, + up: ScanCode::Up, + right: ScanCode::Right, + down: ScanCode::Down, + prev_weapon: ScanCode::A, + next_weapon: ScanCode::S, + jump: ScanCode::Z, + shoot: ScanCode::X, + skip: ScanCode::LControl, + inventory: ScanCode::Q, + map: ScanCode::W, } } fn p2_default_keymap() -> PlayerKeyMap { PlayerKeyMap { - left: VirtualKeyCode::Comma, - up: VirtualKeyCode::L, - right: VirtualKeyCode::Slash, - down: VirtualKeyCode::Period, - prev_weapon: VirtualKeyCode::G, - next_weapon: VirtualKeyCode::H, - jump: VirtualKeyCode::B, - shoot: VirtualKeyCode::N, - skip: VirtualKeyCode::RControl, - inventory: VirtualKeyCode::T, - map: VirtualKeyCode::Y, + left: ScanCode::Comma, + up: ScanCode::L, + right: ScanCode::Slash, + down: ScanCode::Period, + prev_weapon: ScanCode::G, + next_weapon: ScanCode::H, + jump: ScanCode::B, + shoot: ScanCode::N, + skip: ScanCode::U, + inventory: ScanCode::T, + map: ScanCode::Y, } } diff --git a/src/shaders.rs b/src/shaders.rs index 099a767..11f0130 100644 --- a/src/shaders.rs +++ b/src/shaders.rs @@ -1,6 +1,7 @@ use gfx::{self, *}; -use ggez::graphics::Shader; -use ggez::{Context, GameResult}; + +use crate::framework::context::Context; +use crate::framework::error::GameResult; gfx_defines! { constant WaterShaderParams { @@ -11,7 +12,7 @@ gfx_defines! { } pub struct Shaders { - pub water_shader: Shader, + //pub water_shader: Shader, pub water_shader_params: WaterShaderParams, } @@ -24,14 +25,14 @@ impl Shaders { }; Ok(Shaders { - water_shader: Shader::new( + /*water_shader: Shader::new( ctx, "/builtin/shaders/basic_es300.vert.glsl", "/builtin/shaders/water_es300.frag.glsl", water_shader_params, "WaterShaderParams", None, - )?, + )?,*/ water_shader_params, }) } diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index a242a4c..c7f5111 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -1,17 +1,17 @@ use std::ops::Div; -use std::time::Instant; use bitvec::vec::BitVec; use chrono::{Datelike, Local}; -use ggez::{Context, filesystem, GameResult, graphics}; -use ggez::filesystem::OpenOptions; -use ggez::graphics::Canvas; use num_traits::clamp; +use num_traits::real::Real; use crate::bmfont_renderer::BMFontRenderer; use crate::caret::{Caret, CaretType}; use crate::common::{ControlFlags, Direction, FadeState}; use crate::engine_constants::EngineConstants; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::vfs::OpenOptions; use crate::input::touch_controls::TouchControls; use crate::npc::NPCTable; use crate::profile::GameProfile; @@ -21,12 +21,12 @@ use crate::scene::Scene; #[cfg(feature = "scripting")] use crate::scripting::LuaScriptingState; use crate::settings::Settings; -use crate::shaders::Shaders; use crate::sound::SoundManager; use crate::stage::StageData; use crate::str; use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM}; -use crate::texture_set::{TextureSet}; +use crate::texture_set::TextureSet; +use crate::framework::{filesystem, graphics}; #[derive(PartialEq, Eq, Copy, Clone)] pub enum TimingMode { @@ -102,10 +102,6 @@ pub struct SharedGameState { pub stages: Vec, pub frame_time: f64, pub scale: f32, - pub shaders: Shaders, - pub tmp_canvas: Canvas, - pub game_canvas: Canvas, - pub lightmap_canvas: Canvas, pub canvas_size: (f32, f32), pub screen_size: (f32, f32), pub next_scene: Option>, @@ -123,8 +119,8 @@ pub struct SharedGameState { impl SharedGameState { pub fn new(ctx: &mut Context) -> GameResult { - let screen_size = graphics::drawable_size(ctx); - let scale = screen_size.1.div(235.0).floor().max(1.0); + let screen_size = (320.0, 240.0); + let scale = *screen_size.1.div(230.0).floor().max(&1.0); let canvas_size = (screen_size.0 / scale, screen_size.1 / scale); @@ -164,7 +160,7 @@ impl SharedGameState { game_flags: bitvec::bitvec![0; 8000], fade_state: FadeState::Hidden, game_rng: XorShift::new(0), - effect_rng: XorShift::new(Instant::now().elapsed().as_nanos() as i32), + effect_rng: XorShift::new(123), quake_counter: 0, teleporter_slots: Vec::with_capacity(8), carets: Vec::with_capacity(32), @@ -175,10 +171,6 @@ impl SharedGameState { stages: Vec::with_capacity(96), frame_time: 0.0, scale, - shaders: Shaders::new(ctx)?, - tmp_canvas: Canvas::with_window_size(ctx)?, - game_canvas: Canvas::with_window_size(ctx)?, - lightmap_canvas: Canvas::with_window_size(ctx)?, screen_size, canvas_size, next_scene: None, @@ -289,10 +281,10 @@ impl SharedGameState { pub fn handle_resize(&mut self, ctx: &mut Context) -> GameResult { self.screen_size = graphics::drawable_size(ctx); - self.scale = self.screen_size.1.div(240.0).floor().max(1.0); + self.scale = self.screen_size.1.div(230.0).floor().max(1.0); self.canvas_size = (self.screen_size.0 / self.scale, self.screen_size.1 / self.scale); - graphics::set_screen_coordinates(ctx, graphics::Rect::new(0.0, 0.0, self.screen_size.0, self.screen_size.1))?; + //graphics::set_screen_coordinates(ctx, graphics::Rect::new(0.0, 0.0, self.screen_size.0, self.screen_size.1))?; Ok(()) } diff --git a/src/sound/mod.rs b/src/sound/mod.rs index 2ed0a5e..e87d091 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -4,16 +4,18 @@ use std::time::Duration; use cpal::Sample; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use ggez::{Context, filesystem, GameResult}; -use ggez::GameError::{AudioError, InvalidValue, ResourceLoadError}; use num_traits::clamp; use crate::engine_constants::EngineConstants; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::filesystem; use crate::sound::organya::Song; use crate::sound::pixtone::PixTonePlayback; use crate::sound::playback::{PlaybackEngine, SavedPlaybackState}; use crate::sound::wave_bank::SoundBank; use crate::str; +use crate::framework::error::GameError::{AudioError, ResourceLoadError, InvalidValue}; mod wave_bank; mod organya; diff --git a/src/stage.rs b/src/stage.rs index ae243ff..256d03a 100644 --- a/src/stage.rs +++ b/src/stage.rs @@ -7,10 +7,12 @@ use log::info; use crate::encoding::read_cur_shift_jis; use crate::engine_constants::EngineConstants; -use ggez::{Context, filesystem, GameResult}; -use ggez::GameError::ResourceLoadError; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::filesystem; use crate::map::{Map, NPCData}; use crate::text_script::TextScript; +use crate::framework::error::GameError::ResourceLoadError; #[derive(Debug, PartialEq, Eq, Hash)] pub struct NpcType { diff --git a/src/text_script.rs b/src/text_script.rs index 5379f3c..c709d3d 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -9,8 +9,8 @@ use std::ops::Not; use std::str::FromStr; use byteorder::ReadBytesExt; -use ggez::{Context, GameResult}; -use ggez::GameError::{InvalidValue, ParseError}; + + use itertools::Itertools; use num_derive::FromPrimitive; use num_traits::{clamp, FromPrimitive}; @@ -28,6 +28,7 @@ use crate::scene::title_scene::TitleScene; use crate::shared_game_state::SharedGameState; use crate::str; use crate::weapon::WeaponType; +use crate::framework::error::GameError::ParseError; /// Engine's text script VM operation codes. #[derive(EnumString, Debug, FromPrimitive, PartialEq)] diff --git a/src/texture_set.rs b/src/texture_set.rs index 969f3bf..9368941 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -1,12 +1,6 @@ use std::collections::HashMap; use std::io::{BufReader, Read, Seek, SeekFrom}; -use ggez; -use ggez::{Context, GameError, GameResult, graphics}; -use ggez::filesystem; -use ggez::graphics::{Drawable, DrawMode, DrawParam, FilterMode, Image, Mesh, mint, Rect}; -use ggez::graphics::spritebatch::SpriteBatch; -use ggez::nalgebra::{Point2, Vector2}; use image::RgbaImage; use itertools::Itertools; use log::info; @@ -14,6 +8,10 @@ use log::info; use crate::common; use crate::common::FILE_TYPES; use crate::engine_constants::EngineConstants; +use crate::framework::context::Context; +use crate::framework::error::{GameError, GameResult}; +use crate::framework::filesystem; +use crate::framework::image::Image; use crate::settings::Settings; use crate::shared_game_state::Season; use crate::str; @@ -21,7 +19,6 @@ use crate::str; pub static mut G_MAG: f32 = 1.0; pub struct SizedBatch { - pub batch: SpriteBatch, width: usize, height: usize, real_width: usize, @@ -63,15 +60,15 @@ impl SizedBatch { #[inline(always)] pub fn clear(&mut self) { - self.batch.clear(); + /*self.batch.clear();*/ } pub fn add(&mut self, x: f32, y: f32) { - let param = DrawParam::new() + /*let param = DrawParam::new() .dest(Point2::new(x, y)) .scale(Vector2::new(self.scale_x, self.scale_y)); - self.batch.add(param); + self.batch.add(param);*/ } #[inline(always)] @@ -94,7 +91,7 @@ impl SizedBatch { y = (y * G_MAG).floor() / G_MAG; } - let param = DrawParam::new() + /*let param = DrawParam::new() .src(Rect::new(rect.left as f32 / self.width as f32, rect.top as f32 / self.height as f32, (rect.right - rect.left) as f32 / self.width as f32, @@ -102,7 +99,7 @@ impl SizedBatch { .dest(mint::Point2 { x, y }) .scale(Vector2::new(scale_x, scale_y)); - self.batch.add(param); + self.batch.add(param);*/ } pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect) { @@ -110,7 +107,7 @@ impl SizedBatch { return; } - let param = DrawParam::new() + /*let param = DrawParam::new() .color(color.into()) .src(Rect::new(rect.left as f32 / self.width as f32, rect.top as f32 / self.height as f32, @@ -119,18 +116,19 @@ impl SizedBatch { .dest(mint::Point2 { x, y }) .scale(Vector2::new(scale_x, scale_y)); - self.batch.add(param); + self.batch.add(param);*/ } #[inline(always)] pub fn draw(&mut self, ctx: &mut Context) -> GameResult { - self.draw_filtered(FilterMode::Nearest, ctx) + //self.draw_filtered(FilterMode::Nearest, ctx) + Ok(()) } pub fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult { - self.batch.set_filter(filter); + /*self.batch.set_filter(filter); self.batch.draw(ctx, DrawParam::new())?; - self.batch.clear(); + self.batch.clear();*/ Ok(()) } } @@ -213,7 +211,6 @@ impl TextureSet { let height = (size.h * scale_y) as usize; Ok(SizedBatch { - batch: SpriteBatch::new(image), width, height, scale_x, @@ -233,14 +230,10 @@ impl TextureSet { } pub fn draw_rect(&self, rect: common::Rect, color: [f32; 4], ctx: &mut Context) -> GameResult { - let rect = Mesh::new_rectangle(ctx, DrawMode::fill(), rect.into(), color.into())?; - graphics::draw(ctx, &rect, DrawParam::new())?; Ok(()) } pub fn draw_outline_rect(&self, rect: common::Rect, width: f32, color: [f32; 4], ctx: &mut Context) -> GameResult { - let rect = Mesh::new_rectangle(ctx, DrawMode::stroke(width), rect.into(), color.into())?; - graphics::draw(ctx, &rect, DrawParam::new())?; Ok(()) } } diff --git a/src/ui.rs b/src/ui.rs index 0261282..0322907 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -2,31 +2,16 @@ use std::time::Instant; use imgui::{FontConfig, FontSource}; use imgui::sys::*; -use imgui_gfx_renderer::{Renderer, Shaders}; -use imgui_gfx_renderer::gfx::format::DepthStencil; -use imgui_gfx_renderer::gfx::format::Rgba8; -use imgui_gfx_renderer::gfx::handle::DepthStencilView; -use imgui_gfx_renderer::gfx::handle::RenderTargetView; -use imgui_gfx_renderer::gfx::memory::Typed; -use imgui_winit_support::{HiDpiMode, WinitPlatform}; -use ggez::{Context, GameResult, graphics}; -use ggez::GameError::RenderError; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::live_debugger::LiveDebugger; use crate::scene::Scene; use crate::shared_game_state::SharedGameState; -mod types { - pub type Resources = gfx_device_gl::Resources; -} - pub struct UI { pub imgui: imgui::Context, - pub platform: WinitPlatform, - pub renderer: Renderer, pub components: Components, - pub main_color: RenderTargetView, - pub main_depth: DepthStencilView, last_frame: Instant, } @@ -119,56 +104,18 @@ impl UI { colors[ImGuiCol_NavWindowingDimBg as usize] = [0.20, 0.20, 0.20, 0.20]; colors[ImGuiCol_ModalWindowDimBg as usize] = [0.20, 0.20, 0.20, 0.35]; - let mut platform = WinitPlatform::init(&mut imgui); - platform.attach_window(imgui.io_mut(), graphics::window(ctx).window(), HiDpiMode::Rounded); - - let (factory, dev, _, depth, color) = graphics::gfx_objects(ctx); - let shaders = { - let version = dev.get_info().shading_language; - if version.is_embedded { - if version.major >= 3 { - Shaders::GlSlEs300 - } else { - Shaders::GlSlEs100 - } - } else if version.major >= 4 { - Shaders::GlSl400 - } else if version.major >= 3 { - if version.minor >= 2 { - Shaders::GlSl150 - } else { - Shaders::GlSl130 - } - } else { - Shaders::GlSl110 - } - }; - let renderer = Renderer::init(&mut imgui, factory, shaders) - .map_err(|e| RenderError(e.to_string()))?; - Ok(Self { imgui, - platform, - renderer, components: Components { live_debugger: LiveDebugger::new(), }, - main_color: RenderTargetView::new(color), - main_depth: DepthStencilView::new(depth), last_frame: Instant::now(), }) } - pub fn handle_events(&mut self, ctx: &mut Context, event: &winit::event::Event<()>) { - self.platform.handle_event(self.imgui.io_mut(), graphics::window(ctx).window(), &event); - } - pub fn draw(&mut self, state: &mut SharedGameState, ctx: &mut Context, scene: &mut Box) -> GameResult { - { + /*{ let io = self.imgui.io_mut(); - self.platform.prepare_frame(io, graphics::window(ctx).window()) - .map_err(|e| RenderError(e.to_string()))?; - let now = Instant::now(); io.update_delta_time(now - self.last_frame); self.last_frame = now; @@ -177,15 +124,7 @@ impl UI { scene.debug_overlay_draw(&mut self.components, state, ctx, &mut ui)?; - self.platform.prepare_render(&ui, graphics::window(ctx).window()); - let draw_data = ui.render(); - let (factory, dev, encoder, _, _) = graphics::gfx_objects(ctx); - self.renderer - .render(factory, encoder, &mut self.main_color, draw_data) - .map_err(|e| RenderError(e.to_string()))?; - - encoder.flush(dev); - + ui.render();*/ Ok(()) } }