From 3c20e089f94aeff016c127bee16a5d04b7b66c90 Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Wed, 27 Jan 2021 19:20:47 +0100 Subject: [PATCH 1/8] (wip) start rewriting rendering stuff --- Cargo.toml | 45 +- src/bmfont.rs | 5 +- src/bmfont_renderer.rs | 7 +- src/builtin_fs.rs | 14 +- src/common.rs | 183 ++++- src/components/boss_life_bar.rs | 6 +- src/components/draw_common.rs | 4 +- src/components/hud.rs | 3 +- src/components/stage_select.rs | 3 +- src/entity.rs | 3 +- src/framework/backend.rs | 9 + .../mod.rs => framework/backend_null.rs} | 0 src/framework/backend_sdl2.rs | 0 src/framework/context.rs | 18 + src/framework/error.rs | 145 ++++ src/framework/filesystem.rs | 373 ++++++++++ src/framework/graphics.rs | 24 + src/framework/image.rs | 15 + src/framework/keyboard.rs | 205 ++++++ src/framework/mod.rs | 10 + src/framework/vfs.rs | 678 ++++++++++++++++++ src/input/combined_menu_controller.rs | 3 +- src/input/dummy_player_controller.rs | 4 +- src/input/keyboard_player_controller.rs | 12 +- src/input/player_controller.rs | 3 +- src/input/touch_controls.rs | 7 +- src/input/touch_player_controller.rs | 4 +- src/lib.rs | 266 +------ src/live_debugger.rs | 4 +- src/main.rs | 31 +- src/map.rs | 7 +- src/menu/mod.rs | 7 +- src/npc/ai/balrog.rs | 3 +- src/npc/ai/booster.rs | 3 +- src/npc/ai/chaco.rs | 3 +- src/npc/ai/characters.rs | 3 +- src/npc/ai/egg_corridor.rs | 3 +- src/npc/ai/first_cave.rs | 3 +- src/npc/ai/grasstown.rs | 3 +- src/npc/ai/igor.rs | 4 +- src/npc/ai/intro.rs | 3 +- src/npc/ai/last_cave.rs | 3 +- src/npc/ai/maze.rs | 3 +- src/npc/ai/mimiga_village.rs | 3 +- src/npc/ai/misc.rs | 3 +- src/npc/ai/misery.rs | 3 +- src/npc/ai/outer_wall.rs | 10 +- src/npc/ai/pickups.rs | 3 +- src/npc/ai/quote.rs | 3 +- src/npc/ai/sand_zone.rs | 3 +- src/npc/ai/santa.rs | 3 +- src/npc/ai/sue.rs | 3 +- src/npc/ai/toroko.rs | 3 +- src/npc/ai/weapon_trail.rs | 3 +- src/npc/boss/balfrog.rs | 3 +- src/npc/boss/mod.rs | 4 +- src/npc/boss/monster_x.rs | 3 +- src/npc/boss/omega.rs | 4 +- src/npc/list.rs | 3 +- src/npc/mod.rs | 3 +- src/player/mod.rs | 3 +- src/profile.rs | 6 +- src/scene/game_scene.rs | 113 +-- src/scene/loading_scene.rs | 5 +- src/scene/mod.rs | 3 +- src/scene/no_data_scene.rs | 3 +- src/scene/title_scene.rs | 12 +- src/scripting/mod.rs | 9 +- src/settings.rs | 73 +- src/shaders.rs | 11 +- src/shared_game_state.rs | 30 +- src/sound/mod.rs | 6 +- src/stage.rs | 6 +- src/text_script.rs | 5 +- src/texture_set.rs | 37 +- src/ui.rs | 69 +- 76 files changed, 1961 insertions(+), 622 deletions(-) create mode 100644 src/framework/backend.rs rename src/{render/mod.rs => framework/backend_null.rs} (100%) create mode 100644 src/framework/backend_sdl2.rs create mode 100644 src/framework/context.rs create mode 100644 src/framework/error.rs create mode 100644 src/framework/filesystem.rs create mode 100644 src/framework/graphics.rs create mode 100644 src/framework/image.rs create mode 100644 src/framework/keyboard.rs create mode 100644 src/framework/mod.rs create mode 100644 src/framework/vfs.rs 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(()) } } From 02f0f7f3145c9f0a9efbe4b7c7ffb6c26a21be9c Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Thu, 28 Jan 2021 23:33:43 +0100 Subject: [PATCH 2/8] initial sdl2 port --- Cargo.toml | 2 +- src/bmfont_renderer.rs | 2 - src/common.rs | 37 ++- src/engine_constants/mod.rs | 2 +- src/framework/backend.rs | 39 +++- src/framework/backend_sdl2.rs | 420 ++++++++++++++++++++++++++++++++++ src/framework/context.rs | 21 +- src/framework/error.rs | 2 - src/framework/filesystem.rs | 28 +-- src/framework/graphics.rs | 33 ++- src/framework/image.rs | 15 -- src/framework/keyboard.rs | 146 +++++++++++- src/framework/mod.rs | 3 +- src/lib.rs | 86 ++++++- src/scene/game_scene.rs | 32 +-- src/shared_game_state.rs | 15 +- src/text_script.rs | 6 +- src/texture_set.rs | 132 +++++++---- 18 files changed, 864 insertions(+), 157 deletions(-) delete mode 100644 src/framework/image.rs diff --git a/Cargo.toml b/Cargo.toml index ff66501..cf1fb71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" pretty_env_logger = "0.4.0" -sdl2 = { version = "0.34.3", optional = true } +sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures"] } serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_yaml = "0.8" diff --git a/src/bmfont_renderer.rs b/src/bmfont_renderer.rs index a4c68f6..396b886 100644 --- a/src/bmfont_renderer.rs +++ b/src/bmfont_renderer.rs @@ -28,7 +28,6 @@ impl BMFontRenderer { let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?; let mut pages = Vec::new(); - println!("stem: {:?}", stem); let (zeros, _, _) = FILE_TYPES .iter() .map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext))) @@ -41,7 +40,6 @@ impl BMFontRenderer { for i in 0..font.pages { let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros); - println!("x: {}", &page_path); pages.push(page_path); } diff --git a/src/common.rs b/src/common.rs index 41f2d05..b2acea1 100644 --- a/src/common.rs +++ b/src/common.rs @@ -234,6 +234,22 @@ impl Direction { } } +#[derive(Debug, Clone, Copy)] +pub struct Point { + pub x: T, + pub y: T, +} + +impl Point { + #[inline(always)] + pub fn new(x: T, y: T) -> Point { + Point { + x, + y, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct Rect { pub left: T, @@ -243,6 +259,7 @@ pub struct Rect { } impl Rect { + #[inline(always)] pub fn new(left: T, top: T, right: T, bottom: T) -> Rect { Rect { left, @@ -252,6 +269,7 @@ impl Rect { } } + #[inline(always)] pub fn new_size(x: T, y: T, width: T, height: T) -> Rect { Rect { left: x, @@ -260,6 +278,7 @@ impl Rect { bottom: y.add(height), } } + pub fn width(&self) -> T { if let Some(Ordering::Greater) = self.left.partial_cmp(&self.right) { self.left.sub(self.right) @@ -392,30 +411,30 @@ impl Color { } /// 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 { + pub 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 { + pub 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) { + pub 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) { + pub 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 { + pub fn from_rgba_u32(c: u32) -> Color { let c = c.to_be_bytes(); Color::from((c[0], c[1], c[2], c[3])) @@ -423,21 +442,21 @@ impl Color { /// 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 { + pub 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 { + pub 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 { + pub 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]) @@ -515,4 +534,4 @@ impl From for [f32; 4] { fn from(color: Color) -> Self { [color.r, color.g, color.b, color.a] } -} \ No newline at end of file +} diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index b9d3a79..ad5f6fc 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -216,7 +216,7 @@ pub struct EngineConstants { pub world: WorldConsts, pub npc: NPCConsts, pub weapon: WeaponConsts, - pub tex_sizes: CaseInsensitiveHashMap<(usize, usize)>, + pub tex_sizes: CaseInsensitiveHashMap<(u16, u16)>, pub textscript: TextScriptConsts, pub title: TitleConsts, pub font_path: String, diff --git a/src/framework/backend.rs b/src/framework/backend.rs index c20c7da..1fcfda4 100644 --- a/src/framework/backend.rs +++ b/src/framework/backend.rs @@ -1,9 +1,38 @@ +use crate::common::{Color, Rect, Point}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::Game; -pub(crate) trait Backend { - fn create_event_loop(&self) -> Box; +pub trait Backend { + fn create_event_loop(&self) -> GameResult>; } -pub(crate) trait BackendEventLoop { - fn run(&self, game: &mut Game); -} \ No newline at end of file +pub trait BackendEventLoop { + fn run(&mut self, game: &mut Game, ctx: &mut Context); + + fn new_renderer(&self) -> GameResult>; +} + +pub trait BackendRenderer { + fn clear(&mut self, color: Color); + + fn present(&mut self) -> GameResult; + + fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult>; +} + +pub trait BackendTexture { + fn dimensions(&self) -> (u16, u16); + fn add(&mut self, command: SpriteBatchCommand); + fn clear(&mut self); + fn draw(&mut self) -> GameResult; +} + +pub fn init_backend() -> GameResult> { + crate::framework::backend_sdl2::SDL2Backend::new() +} + +pub enum SpriteBatchCommand { + DrawRect(Rect, Rect), + DrawRectTinted(Rect, Rect, Color), +} diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index e69de29..a3c0023 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -0,0 +1,420 @@ +use core::mem; +use std::cell::RefCell; +use std::rc::Rc; + +use sdl2::{EventPump, keyboard, pixels, Sdl}; +use sdl2::event::{Event, WindowEvent}; +use sdl2::keyboard::Scancode; +use sdl2::pixels::PixelFormatEnum; +use sdl2::render::{BlendMode, Texture, TextureCreator, WindowCanvas}; +use sdl2::video::WindowContext; + +use crate::common::Color; +use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; +use crate::framework::context::Context; +use crate::framework::error::{GameError, GameResult}; +use crate::framework::keyboard::ScanCode; +use crate::Game; + +pub struct SDL2Backend { + context: Sdl, +} + +impl SDL2Backend { + pub fn new() -> GameResult> { + let context = sdl2::init().map_err(|e| GameError::WindowError(e))?; + + let backend = SDL2Backend { + context, + }; + + Ok(Box::new(backend)) + } +} + +impl Backend for SDL2Backend { + fn create_event_loop(&self) -> GameResult> { + SDL2EventLoop::new(&self.context) + } +} + +struct SDL2EventLoop { + event_pump: EventPump, + refs: Rc>, +} + +struct SDL2Context { + canvas: WindowCanvas, + texture_creator: TextureCreator, +} + +impl SDL2EventLoop { + pub fn new(sdl: &Sdl) -> GameResult> { + let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?; + let video = sdl.video().map_err(|e| GameError::WindowError(e))?; + let window = video.window("Cave Story (doukutsu-rs)", 640, 480) + .position_centered() + .resizable() + .build() + .map_err(|e| GameError::WindowError(e.to_string()))?; + + let canvas = window.into_canvas() + .accelerated() + .present_vsync() + .build() + .map_err(|e| GameError::RenderError(e.to_string()))?; + + let texture_creator = canvas.texture_creator(); + + let event_loop = SDL2EventLoop { + event_pump, + refs: Rc::new(RefCell::new(SDL2Context { + canvas, + texture_creator, + })), + }; + + Ok(Box::new(event_loop)) + } +} + +impl BackendEventLoop for SDL2EventLoop { + fn run(&mut self, game: &mut Game, ctx: &mut Context) { + let state = unsafe { &mut *game.state.get() }; + + loop { + for event in self.event_pump.poll_iter() { + match event { + Event::Quit { .. } => { + state.shutdown(); + } + Event::Window { win_event, .. } => { + match win_event { + WindowEvent::Shown => {} + WindowEvent::Hidden => {} + WindowEvent::SizeChanged(width, height) => { + ctx.screen_size = (width.max(1) as f32, height.max(1) as f32); + state.handle_resize(ctx); + } + _ => {} + } + } + Event::KeyDown { scancode, repeat, .. } => { + if let Some(scancode) = scancode { + if let Some(drs_scan) = conv_scancode(scancode) { + game.key_down_event(drs_scan, repeat); + ctx.keyboard_context.set_key(drs_scan, true); + } + } + } + Event::KeyUp { scancode, .. } => { + if let Some(scancode) = scancode { + if let Some(drs_scan) = conv_scancode(scancode) { + ctx.keyboard_context.set_key(drs_scan, false); + } + } + } + _ => {} + } + } + + game.update(ctx).unwrap(); + + if state.shutdown { + log::info!("Shutting down..."); + break; + } + + if state.next_scene.is_some() { + mem::swap(&mut game.scene, &mut state.next_scene); + state.next_scene = None; + + game.scene.as_mut().unwrap().init(state, ctx).unwrap(); + game.loops = 0; + state.frame_time = 0.0; + } + + game.draw(ctx).unwrap(); + } + } + + fn new_renderer(&self) -> GameResult> { + SDL2Renderer::new(self.refs.clone()) + } +} + +struct SDL2Renderer { + refs: Rc>, +} + +impl SDL2Renderer { + pub fn new(refs: Rc>) -> GameResult> { + Ok(Box::new(SDL2Renderer { + refs, + })) + } +} + +fn to_sdl(color: Color) -> pixels::Color { + let (r, g, b, a) = color.to_rgba(); + pixels::Color::RGBA(r, g, b, a) +} + +impl BackendRenderer for SDL2Renderer { + fn clear(&mut self, color: Color) { + let mut refs = self.refs.borrow_mut(); + + refs.canvas.set_draw_color(to_sdl(color)); + refs.canvas.clear(); + } + + fn present(&mut self) -> GameResult { + let mut refs = self.refs.borrow_mut(); + + refs.canvas.present(); + + Ok(()) + } + + fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult> { + let mut refs = self.refs.borrow_mut(); + + let mut texture = refs.texture_creator + .create_texture_streaming(PixelFormatEnum::RGBA32, width as u32, height as u32) + .map_err(|e| GameError::RenderError(e.to_string()))?; + + texture.set_blend_mode(BlendMode::Blend); + texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { + for y in 0..(height as usize) { + for x in 0..(width as usize) { + let offset = y * pitch + x * 4; + let data_offset = (y * width as usize + x) * 4; + + buffer[offset] = data[data_offset]; + buffer[offset + 1] = data[data_offset + 1]; + buffer[offset + 2] = data[data_offset + 2]; + buffer[offset + 3] = data[data_offset + 3]; + } + } + }).map_err(|e| GameError::RenderError(e.to_string()))?; + + return Ok(Box::new(SDL2Texture { + refs: self.refs.clone(), + texture: Some(texture), + width, + height, + commands: vec![], + })); + } +} + +struct SDL2Texture { + refs: Rc>, + texture: Option, + width: u16, + height: u16, + commands: Vec, +} + +impl BackendTexture for SDL2Texture { + fn dimensions(&self) -> (u16, u16) { + (self.width, self.height) + } + + fn add(&mut self, command: SpriteBatchCommand) { + self.commands.push(command); + } + + fn clear(&mut self) { + self.commands.clear(); + } + + fn draw(&mut self) -> GameResult { + match self.texture.as_mut() { + None => Ok(()), + Some(texture) => { + let mut refs = self.refs.borrow_mut(); + for command in self.commands.iter() { + match command { + SpriteBatchCommand::DrawRect(src, dest) => { + texture.set_color_mod(255, 255, 255); + texture.set_alpha_mod(255); + + refs.canvas.copy(texture, + Some(sdl2::rect::Rect::new(src.left as i32, src.top as i32, src.width() as u32, src.height() as u32)), + Some(sdl2::rect::Rect::new(dest.left as i32, dest.top as i32, dest.width() as u32, dest.height() as u32))) + .map_err(|e| GameError::RenderError(e.to_string()))?; + } + SpriteBatchCommand::DrawRectTinted(src, dest, color) => { + let (r, g, b, a) = color.to_rgba(); + texture.set_color_mod(r, g, b); + texture.set_alpha_mod(a); + + refs.canvas.copy(texture, + Some(sdl2::rect::Rect::new(src.left as i32, src.top as i32, src.width() as u32, src.height() as u32)), + Some(sdl2::rect::Rect::new(dest.left as i32, dest.top as i32, dest.width() as u32, dest.height() as u32))) + .map_err(|e| GameError::RenderError(e.to_string()))?; + } + } + } + + Ok(()) + } + } + } +} + +impl Drop for SDL2Texture { + fn drop(&mut self) { + let mut texture_opt = None; + mem::swap(&mut self.texture, &mut texture_opt); + + if let Some(texture) = texture_opt { + unsafe { texture.destroy(); } + } + } +} + +fn conv_scancode(code: keyboard::Scancode) -> Option { + match code { + Scancode::A => Some(ScanCode::A), + Scancode::B => Some(ScanCode::B), + Scancode::C => Some(ScanCode::C), + Scancode::D => Some(ScanCode::D), + Scancode::E => Some(ScanCode::E), + Scancode::F => Some(ScanCode::F), + Scancode::G => Some(ScanCode::G), + Scancode::H => Some(ScanCode::H), + Scancode::I => Some(ScanCode::I), + Scancode::J => Some(ScanCode::J), + Scancode::K => Some(ScanCode::K), + Scancode::L => Some(ScanCode::L), + Scancode::M => Some(ScanCode::M), + Scancode::N => Some(ScanCode::N), + Scancode::O => Some(ScanCode::O), + Scancode::P => Some(ScanCode::P), + Scancode::Q => Some(ScanCode::Q), + Scancode::R => Some(ScanCode::R), + Scancode::S => Some(ScanCode::S), + Scancode::T => Some(ScanCode::T), + Scancode::U => Some(ScanCode::U), + Scancode::V => Some(ScanCode::V), + Scancode::W => Some(ScanCode::W), + Scancode::X => Some(ScanCode::X), + Scancode::Y => Some(ScanCode::Y), + Scancode::Z => Some(ScanCode::Z), + Scancode::Num1 => Some(ScanCode::Key1), + Scancode::Num2 => Some(ScanCode::Key2), + Scancode::Num3 => Some(ScanCode::Key3), + Scancode::Num4 => Some(ScanCode::Key4), + Scancode::Num5 => Some(ScanCode::Key5), + Scancode::Num6 => Some(ScanCode::Key6), + Scancode::Num7 => Some(ScanCode::Key7), + Scancode::Num8 => Some(ScanCode::Key8), + Scancode::Num9 => Some(ScanCode::Key9), + Scancode::Num0 => Some(ScanCode::Key0), + Scancode::Return => Some(ScanCode::Return), + Scancode::Escape => Some(ScanCode::Escape), + Scancode::Backspace => Some(ScanCode::Backspace), + Scancode::Tab => Some(ScanCode::Tab), + Scancode::Space => Some(ScanCode::Space), + Scancode::Minus => Some(ScanCode::Minus), + Scancode::Equals => Some(ScanCode::Equals), + Scancode::LeftBracket => Some(ScanCode::LBracket), + Scancode::RightBracket => Some(ScanCode::RBracket), + Scancode::Backslash => Some(ScanCode::Backslash), + Scancode::NonUsHash => Some(ScanCode::NonUsHash), + Scancode::Semicolon => Some(ScanCode::Semicolon), + Scancode::Apostrophe => Some(ScanCode::Apostrophe), + Scancode::Grave => Some(ScanCode::Grave), + Scancode::Comma => Some(ScanCode::Comma), + Scancode::Period => Some(ScanCode::Period), + Scancode::Slash => Some(ScanCode::Slash), + Scancode::CapsLock => Some(ScanCode::Capslock), + Scancode::F1 => Some(ScanCode::F1), + Scancode::F2 => Some(ScanCode::F2), + Scancode::F3 => Some(ScanCode::F3), + Scancode::F4 => Some(ScanCode::F4), + Scancode::F5 => Some(ScanCode::F5), + Scancode::F6 => Some(ScanCode::F6), + Scancode::F7 => Some(ScanCode::F7), + Scancode::F8 => Some(ScanCode::F8), + Scancode::F9 => Some(ScanCode::F9), + Scancode::F10 => Some(ScanCode::F10), + Scancode::F11 => Some(ScanCode::F11), + Scancode::F12 => Some(ScanCode::F12), + Scancode::PrintScreen => Some(ScanCode::Sysrq), + Scancode::ScrollLock => Some(ScanCode::Scrolllock), + Scancode::Pause => Some(ScanCode::Pause), + Scancode::Insert => Some(ScanCode::Insert), + Scancode::Home => Some(ScanCode::Home), + Scancode::PageUp => Some(ScanCode::PageUp), + Scancode::Delete => Some(ScanCode::Delete), + Scancode::End => Some(ScanCode::End), + Scancode::PageDown => Some(ScanCode::PageDown), + Scancode::Right => Some(ScanCode::Right), + Scancode::Left => Some(ScanCode::Left), + Scancode::Down => Some(ScanCode::Down), + Scancode::Up => Some(ScanCode::Up), + Scancode::NumLockClear => Some(ScanCode::Numlock), + Scancode::KpDivide => Some(ScanCode::NumpadDivide), + Scancode::KpMultiply => Some(ScanCode::NumpadMultiply), + Scancode::KpMinus => Some(ScanCode::NumpadSubtract), + Scancode::KpPlus => Some(ScanCode::NumpadAdd), + Scancode::KpEnter => Some(ScanCode::NumpadEnter), + Scancode::Kp1 => Some(ScanCode::Numpad1), + Scancode::Kp2 => Some(ScanCode::Numpad2), + Scancode::Kp3 => Some(ScanCode::Numpad3), + Scancode::Kp4 => Some(ScanCode::Numpad4), + Scancode::Kp5 => Some(ScanCode::Numpad5), + Scancode::Kp6 => Some(ScanCode::Numpad6), + Scancode::Kp7 => Some(ScanCode::Numpad7), + Scancode::Kp8 => Some(ScanCode::Numpad8), + Scancode::Kp9 => Some(ScanCode::Numpad9), + Scancode::Kp0 => Some(ScanCode::Numpad0), + Scancode::NonUsBackslash => Some(ScanCode::NonUsBackslash), + Scancode::Application => Some(ScanCode::Apps), + Scancode::Power => Some(ScanCode::Power), + Scancode::KpEquals => Some(ScanCode::NumpadEquals), + Scancode::F13 => Some(ScanCode::F13), + Scancode::F14 => Some(ScanCode::F14), + Scancode::F15 => Some(ScanCode::F15), + Scancode::F16 => Some(ScanCode::F16), + Scancode::F17 => Some(ScanCode::F17), + Scancode::F18 => Some(ScanCode::F18), + Scancode::F19 => Some(ScanCode::F19), + Scancode::F20 => Some(ScanCode::F20), + Scancode::F21 => Some(ScanCode::F21), + Scancode::F22 => Some(ScanCode::F22), + Scancode::F23 => Some(ScanCode::F23), + Scancode::F24 => Some(ScanCode::F24), + Scancode::Stop => Some(ScanCode::Stop), + Scancode::Cut => Some(ScanCode::Cut), + Scancode::Copy => Some(ScanCode::Copy), + Scancode::Paste => Some(ScanCode::Paste), + Scancode::Mute => Some(ScanCode::Mute), + Scancode::VolumeUp => Some(ScanCode::VolumeUp), + Scancode::VolumeDown => Some(ScanCode::VolumeDown), + Scancode::KpComma => Some(ScanCode::NumpadComma), + Scancode::SysReq => Some(ScanCode::Sysrq), + Scancode::Return2 => Some(ScanCode::NumpadEnter), + Scancode::LCtrl => Some(ScanCode::LControl), + Scancode::LShift => Some(ScanCode::LShift), + Scancode::LAlt => Some(ScanCode::LAlt), + Scancode::LGui => Some(ScanCode::LWin), + Scancode::RCtrl => Some(ScanCode::RControl), + Scancode::RShift => Some(ScanCode::RShift), + Scancode::RAlt => Some(ScanCode::RAlt), + Scancode::RGui => Some(ScanCode::RWin), + Scancode::AudioNext => Some(ScanCode::NextTrack), + Scancode::AudioPrev => Some(ScanCode::PrevTrack), + Scancode::AudioStop => Some(ScanCode::MediaStop), + Scancode::AudioPlay => Some(ScanCode::PlayPause), + Scancode::AudioMute => Some(ScanCode::Mute), + Scancode::MediaSelect => Some(ScanCode::MediaSelect), + Scancode::Mail => Some(ScanCode::Mail), + Scancode::Calculator => Some(ScanCode::Calculator), + Scancode::Sleep => Some(ScanCode::Sleep), + _ => None, + } +} diff --git a/src/framework/context.rs b/src/framework/context.rs index 1ee64f9..0868543 100644 --- a/src/framework/context.rs +++ b/src/framework/context.rs @@ -1,18 +1,33 @@ +use crate::framework::backend::{Backend, init_backend, BackendRenderer}; +use crate::framework::error::GameResult; use crate::framework::filesystem::Filesystem; use crate::Game; +use crate::framework::keyboard::KeyboardContext; pub struct Context { pub(crate) filesystem: Filesystem, + pub(crate) renderer: Option>, + pub(crate) keyboard_context: KeyboardContext, + pub(crate) screen_size: (f32, f32), } impl Context { pub fn new() -> Context { Context { filesystem: Filesystem::new(), + renderer: None, + keyboard_context: KeyboardContext::new(), + screen_size: (320.0, 240.0), } } - pub fn run(&mut self, game: &mut Game) { - loop {} + pub fn run(&mut self, game: &mut Game) -> GameResult { + let backend = init_backend()?; + let mut event_loop = backend.create_event_loop()?; + self.renderer = Some(event_loop.new_renderer()?); + + event_loop.run(game, self); + + Ok(()) } -} \ No newline at end of file +} diff --git a/src/framework/error.rs b/src/framework/error.rs index 88f53e7..52b19d5 100644 --- a/src/framework/error.rs +++ b/src/framework/error.rs @@ -64,9 +64,7 @@ impl fmt::Display for GameError { 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, } } diff --git a/src/framework/filesystem.rs b/src/framework/filesystem.rs index 306f10e..aff2397 100644 --- a/src/framework/filesystem.rs +++ b/src/framework/filesystem.rs @@ -241,6 +241,11 @@ impl Filesystem { pub fn mount_vfs(&mut self, vfs: Box) { self.vfs.push_back(vfs); } + + + pub fn mount_user_vfs(&mut self, vfs: Box) { + self.user_vfs.push_back(vfs); + } } /// Opens the given path and returns the resulting `File` @@ -340,22 +345,6 @@ pub fn read_dir>( 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. /// @@ -370,4 +359,9 @@ pub fn mount(ctx: &mut Context, path: &path::Path, readonly: bool) { /// 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 +} + +/// Adds a VFS to the list of user data search locations. +pub fn mount_user_vfs(ctx: &mut Context, vfs: Box) { + ctx.filesystem.mount_user_vfs(vfs) +} diff --git a/src/framework/graphics.rs b/src/framework/graphics.rs index 0c08fd8..a596e91 100644 --- a/src/framework/graphics.rs +++ b/src/framework/graphics.rs @@ -1,6 +1,7 @@ use crate::common::Color; use crate::framework::context::Context; -use crate::framework::error::GameResult; +use crate::framework::error::{GameResult, GameError}; +use crate::framework::backend::BackendTexture; pub enum FilterMode { Nearest, @@ -13,12 +14,32 @@ impl Canvas { } -pub fn clear(ctx: &mut Context, color: Color) {} +pub fn clear(ctx: &mut Context, color: Color) { + if let Some(renderer) = ctx.renderer.as_mut() { + renderer.clear(color) + } +} + +pub fn present(ctx: &mut Context) -> GameResult { + if let Some(renderer) = ctx.renderer.as_mut() { + renderer.present()?; + } -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 +pub fn renderer_initialized(ctx: &mut Context) -> bool { + ctx.renderer.is_some() +} + +pub fn create_texture(ctx: &mut Context, width: u16, height: u16, data: &[u8]) -> GameResult> { + if let Some(renderer) = ctx.renderer.as_mut() { + return renderer.create_texture(width, height, data); + } + + Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) +} + +pub fn screen_size(ctx: &mut Context) -> (f32, f32) { + ctx.screen_size +} diff --git a/src/framework/image.rs b/src/framework/image.rs deleted file mode 100644 index 84d14f1..0000000 --- a/src/framework/image.rs +++ /dev/null @@ -1,15 +0,0 @@ -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 index 427808a..432f4ef 100644 --- a/src/framework/keyboard.rs +++ b/src/framework/keyboard.rs @@ -1,6 +1,12 @@ +use std::collections::HashSet; + +use serde::{Deserialize, Serialize}; + +use crate::bitfield; use crate::framework::context::Context; #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] +#[derive(Serialize, Deserialize)] #[repr(u32)] pub enum ScanCode { /// The '1' key over the letters. @@ -109,6 +115,8 @@ pub enum ScanCode { /// The "Compose" key on Linux. Compose, + NonUsHash, + NonUsBackslash, Caret, Numlock, @@ -141,6 +149,7 @@ pub enum ScanCode { Backslash, Calculator, Capital, + Capslock, Colon, Comma, Convert, @@ -176,6 +185,7 @@ pub enum ScanCode { RControl, RShift, RWin, + Scrolllock, Semicolon, Slash, Sleep, @@ -200,6 +210,136 @@ pub enum ScanCode { Cut, } -pub fn is_key_pressed(ctx: &mut Context, code: ScanCode) -> bool { - false -} \ No newline at end of file +bitfield! { + /// Bitflags describing the state of keyboard modifiers, such as `Control` or `Shift`. + #[derive(Debug, Copy, Clone)] + pub struct KeyMods(u8); + + /// No modifiers; equivalent to `KeyMods::default()` and + /// [`KeyMods::empty()`](struct.KeyMods.html#method.empty). + /// Left or right Shift key. + pub shift, set_shift: 0; + /// Left or right Control key. + pub ctrl, set_ctrl: 1; + /// Left or right Alt key. + pub alt, set_alt: 2; + /// Left or right Win/Cmd/equivalent key. + pub win, set_win: 3; +} + +#[derive(Clone, Debug)] +pub struct KeyboardContext { + active_modifiers: KeyMods, + pressed_keys_set: HashSet, + last_pressed: Option, + current_pressed: Option, +} + +impl KeyboardContext { + pub(crate) fn new() -> Self { + Self { + active_modifiers: KeyMods(0), + pressed_keys_set: HashSet::with_capacity(256), + last_pressed: None, + current_pressed: None, + } + } + + pub(crate) fn set_key(&mut self, key: ScanCode, pressed: bool) { + if pressed { + let _ = self.pressed_keys_set.insert(key); + self.last_pressed = self.current_pressed; + self.current_pressed = Some(key); + } else { + let _ = self.pressed_keys_set.remove(&key); + self.current_pressed = None; + } + + self.set_key_modifier(key, pressed); + } + + /// Take a modifier key code and alter our state. + /// + /// Double check that this edge handling is necessary; + /// winit sounds like it should do this for us, + /// see https://docs.rs/winit/0.18.0/winit/struct.KeyboardInput.html#structfield.modifiers + /// + /// ...more specifically, we should refactor all this to consistant-ify events a bit and + /// make winit do more of the work. + /// But to quote Scott Pilgrim, "This is... this is... Booooooring." + fn set_key_modifier(&mut self, key: ScanCode, pressed: bool) { + if pressed { + match key { + ScanCode::LShift | ScanCode::RShift => self.active_modifiers.set_shift(true), + ScanCode::LControl | ScanCode::RControl => self.active_modifiers.set_ctrl(true), + ScanCode::LAlt | ScanCode::RAlt => self.active_modifiers.set_alt(true), + ScanCode::LWin | ScanCode::RWin => self.active_modifiers.set_win(true), + _ => (), + } + } else { + match key { + ScanCode::LShift | ScanCode::RShift => self.active_modifiers.set_shift(false), + ScanCode::LControl | ScanCode::RControl => self.active_modifiers.set_ctrl(false), + ScanCode::LAlt | ScanCode::RAlt => self.active_modifiers.set_alt(false), + ScanCode::LWin | ScanCode::RWin => self.active_modifiers.set_win(false), + _ => (), + } + } + } + + pub(crate) fn set_modifiers(&mut self, keymods: KeyMods) { + self.active_modifiers = keymods; + } + + pub(crate) fn is_key_pressed(&self, key: ScanCode) -> bool { + self.pressed_keys_set.contains(&key) + } + + pub(crate) fn is_key_repeated(&self) -> bool { + if self.last_pressed.is_some() { + self.last_pressed == self.current_pressed + } else { + false + } + } + + pub(crate) fn pressed_keys(&self) -> &HashSet { + &self.pressed_keys_set + } + + pub(crate) fn active_mods(&self) -> KeyMods { + self.active_modifiers + } +} + +impl Default for KeyboardContext { + fn default() -> Self { + Self::new() + } +} + +/// Checks if a key is currently pressed down. +pub fn is_key_pressed(ctx: &Context, key: ScanCode) -> bool { + ctx.keyboard_context.is_key_pressed(key) +} + +/// Checks if the last keystroke sent by the system is repeated, +/// like when a key is held down for a period of time. +pub fn is_key_repeated(ctx: &Context) -> bool { + ctx.keyboard_context.is_key_repeated() +} + +/// Returns a reference to the set of currently pressed keys. +pub fn pressed_keys(ctx: &Context) -> &HashSet { + ctx.keyboard_context.pressed_keys() +} + +/// Checks if keyboard modifier (or several) is active. +pub fn is_mod_active(ctx: &Context, keymods: KeyMods) -> bool { + (ctx.keyboard_context.active_mods().0 & keymods.0) != 0 +} + +/// Returns currently active keyboard modifiers. +pub fn active_mods(ctx: &Context) -> KeyMods { + ctx.keyboard_context.active_mods() +} diff --git a/src/framework/mod.rs b/src/framework/mod.rs index bad713e..af03421 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -4,7 +4,6 @@ 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 +pub mod backend_null; diff --git a/src/lib.rs b/src/lib.rs index a289b4a..8e6246c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,21 +7,28 @@ extern crate strum; #[macro_use] extern crate strum_macros; -use std::env; +use core::mem; use std::cell::UnsafeCell; +use std::env; +use std::path::PathBuf; use std::time::Instant; +use directories::ProjectDirs; use log::*; use pretty_env_logger::env_logger::Env; +use crate::builtin_fs::BuiltinFS; +use crate::framework::backend::init_backend; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; +use crate::framework::filesystem::{mount_user_vfs, mount_vfs}; use crate::framework::graphics; use crate::framework::keyboard::ScanCode; +use crate::framework::vfs::PhysicalFS; use crate::scene::loading_scene::LoadingScene; use crate::scene::Scene; use crate::shared_game_state::{SharedGameState, TimingMode}; -use crate::texture_set::G_MAG; +use crate::texture_set::{G_MAG, I_MAG}; use crate::ui::UI; mod bmfont; @@ -62,7 +69,7 @@ mod texture_set; mod ui; mod weapon; -struct Game { +pub struct Game { scene: Option>, state: UnsafeCell, ui: UI, @@ -147,7 +154,10 @@ impl Game { n1 / n2 } else { 1.0 }; } - unsafe { G_MAG = if state_ref.settings.subpixel_coords { state_ref.scale } else { 1.0 } }; + unsafe { + G_MAG = if state_ref.settings.subpixel_coords { state_ref.scale } else { 1.0 }; + I_MAG = state_ref.scale; + } self.loops = 0; graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into()); @@ -259,18 +269,35 @@ pub fn init() -> GameResult { pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info")) .init(); - 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"); + let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") { + PathBuf::from(data_dir) + } else { + let mut resource_dir = env::current_exe()?; + if resource_dir.file_name().is_some() { + let _ = resource_dir.pop(); + } + resource_dir.push("data"); + resource_dir + }; info!("Resource directory: {:?}", resource_dir); info!("Initializing engine..."); - let mut context: Context = Context::new(); + let mut context = Context::new(); + mount_vfs(&mut context, Box::new(BuiltinFS::new())); + mount_vfs(&mut context, Box::new(PhysicalFS::new(&resource_dir, true))); + + + #[cfg(not(target_os = "android"))] + let project_dirs = match ProjectDirs::from("", "", "doukutsu-rs") { + Some(dirs) => dirs, + None => { + return Err(GameError::FilesystemError(String::from( + "No valid home directory path could be retrieved.", + ))); + } + }; + mount_user_vfs(&mut context, Box::new(PhysicalFS::new(project_dirs.data_local_dir(), false))); #[cfg(target_os = "android")] { @@ -284,4 +311,39 @@ pub fn init() -> GameResult { } } } + + let mut game = Game::new(&mut context)?; + let state_ref = unsafe { &mut *game.state.get() }; + #[cfg(feature = "scripting")] + { + unsafe { + state_ref.lua.update_refs(game.state.get(), &mut context as *mut Context); + } + } + + state_ref.next_scene = Some(Box::new(LoadingScene::new())); + context.run(&mut game); + + /* loop { + game.update(&mut context)?; + + if state_ref.shutdown { + log::info!("Shutting down..."); + break; + } + + 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, &mut context).unwrap(); + game.loops = 0; + state_ref.frame_time = 0.0; + } + + std::thread::sleep(std::time::Duration::from_millis(10)); + game.draw(&mut context)?; + }*/ + + Ok(()) } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 3f8cfab..8c744af 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -298,10 +298,10 @@ impl GameScene { let mut frame = tick; for x in (0..(state.canvas_size.0 as i32 + 16)).step_by(16) { - if frame > 15 { frame = 15; } else { frame += 1; } + if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { - rect.left = frame as u16 * 16; + rect.left = frame.abs() as u16 * 16; rect.right = rect.left + 16; for y in (0..(state.canvas_size.1 as i32 + 16)).step_by(16) { @@ -318,10 +318,10 @@ impl GameScene { let mut frame = tick; for y in (0..(state.canvas_size.1 as i32 + 16)).step_by(16) { - if frame > 15 { frame = 15; } else { frame += 1; } + if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { - rect.left = frame as u16 * 16; + rect.left = frame.abs() as u16 * 16; rect.right = rect.left + 16; for x in (0..(state.canvas_size.0 as i32 + 16)).step_by(16) { @@ -339,20 +339,20 @@ impl GameScene { let center_y = (state.canvas_size.1 / 2.0 - 8.0) as i32; let mut start_frame = tick; - for x in (0..(center_x + 16)).step_by(16) { + for x in 0..(center_x / 16 + 2) { let mut frame = start_frame; - for y in (0..(center_y + 16)).step_by(16) { - if frame > 15 { frame = 15; } else { frame += 1; } + for y in 0..(center_y / 16 + 1) { + if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { - rect.left = frame as u16 * 16; + rect.left = frame.abs() as u16 * 16; rect.right = rect.left + 16; - batch.add_rect((center_x - x) as f32, (center_y + y) as f32, &rect); - batch.add_rect((center_x - x) as f32, (center_y - y) as f32, &rect); - batch.add_rect((center_x + x) as f32, (center_y + y) as f32, &rect); - batch.add_rect((center_x + x) as f32, (center_y - y) as f32, &rect); + batch.add_rect((center_x - x * 16) as f32, (center_y + y * 16) as f32, &rect); + batch.add_rect((center_x - x * 16) as f32, (center_y - y * 16) as f32, &rect); + batch.add_rect((center_x + x * 16) as f32, (center_y + y * 16) as f32, &rect); + batch.add_rect((center_x + x * 16) as f32, (center_y - y * 16) as f32, &rect); } } @@ -1213,13 +1213,13 @@ impl Scene for GameScene { //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 + /*if state.settings.shader_effects && self.stage.data.background_type != BackgroundType::Black && self.stage.data.background_type != BackgroundType::Outside && self.stage.data.background_type != BackgroundType::OutsideWind && self.stage.data.background.name() != "bkBlack" { self.draw_light_map(state, ctx)?; - } + }*/ self.boss.draw(state, ctx, &self.frame)?; for npc in self.npc_list.iter_alive() { @@ -1242,11 +1242,11 @@ impl Scene for GameScene { self.draw_tiles(state, ctx, TileLayer::Foreground)?; self.draw_tiles(state, ctx, TileLayer::Snack)?; self.draw_carets(state, ctx)?; - if state.settings.shader_effects + /*if state.settings.shader_effects && (self.stage.data.background_type == BackgroundType::Black || self.stage.data.background.name() == "bkBlack") { self.draw_light_map(state, ctx)?; - } + }*/ /*graphics::set_canvas(ctx, None); state.game_canvas.draw(ctx, DrawParam::new() diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index c7f5111..284624f 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -119,11 +119,6 @@ pub struct SharedGameState { impl SharedGameState { pub fn new(ctx: &mut Context) -> GameResult { - 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); - let mut constants = EngineConstants::defaults(); let mut base_path = "/"; let settings = Settings::load(ctx)?; @@ -170,9 +165,9 @@ impl SharedGameState { npc_super_pos: (0, 0), stages: Vec::with_capacity(96), frame_time: 0.0, - scale, - screen_size, - canvas_size, + scale: 2.0, + screen_size: (640.0, 480.0), + canvas_size: (320.0, 240.0), next_scene: None, textscript_vm: TextScriptVM::new(), season, @@ -280,12 +275,10 @@ impl SharedGameState { } pub fn handle_resize(&mut self, ctx: &mut Context) -> GameResult { - self.screen_size = graphics::drawable_size(ctx); + self.screen_size = graphics::screen_size(ctx); 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))?; - Ok(()) } diff --git a/src/text_script.rs b/src/text_script.rs index c709d3d..58340a3 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -9,8 +9,6 @@ use std::ops::Not; use std::str::FromStr; use byteorder::ReadBytesExt; - - use itertools::Itertools; use num_derive::FromPrimitive; use num_traits::{clamp, FromPrimitive}; @@ -21,6 +19,9 @@ use crate::encoding::{read_cur_shift_jis, read_cur_wtf8}; use crate::engine_constants::EngineConstants; use crate::entity::GameEntity; use crate::frame::UpdateTarget; +use crate::framework::context::Context; +use crate::framework::error::GameError::{InvalidValue, ParseError}; +use crate::framework::error::GameResult; use crate::npc::NPC; use crate::player::{ControlMode, TargetPlayer}; use crate::scene::game_scene::GameScene; @@ -28,7 +29,6 @@ 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 9368941..5beb9e2 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -6,19 +6,22 @@ use itertools::Itertools; use log::info; use crate::common; -use crate::common::FILE_TYPES; +use crate::common::{FILE_TYPES, Point, Rect}; use crate::engine_constants::EngineConstants; +use crate::framework::backend::{BackendTexture, SpriteBatchCommand}; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; use crate::framework::filesystem; -use crate::framework::image::Image; +use crate::framework::graphics::{create_texture, FilterMode}; use crate::settings::Settings; use crate::shared_game_state::Season; use crate::str; +pub static mut I_MAG: f32 = 1.0; pub static mut G_MAG: f32 = 1.0; pub struct SizedBatch { + batch: Box, width: usize, height: usize, real_width: usize, @@ -60,15 +63,30 @@ 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() - .dest(Point2::new(x, y)) - .scale(Vector2::new(self.scale_x, self.scale_y)); + pub fn add(&mut self, mut x: f32, mut y: f32) { + unsafe { + x = (x * G_MAG).floor() / G_MAG; + y = (y * G_MAG).floor() / G_MAG; + } + let mag = unsafe { I_MAG }; - self.batch.add(param);*/ + self.batch.add(SpriteBatchCommand::DrawRect( + Rect { + left: 0 as f32, + top: 0 as f32, + right: self.real_width as f32, + bottom: self.real_height as f32, + }, + Rect { + left: x * mag, + top: y * mag, + right: (x + self.width() as f32) * mag, + bottom: (y + self.height() as f32) * mag, + }, + )); } #[inline(always)] @@ -81,7 +99,7 @@ impl SizedBatch { self.add_rect_scaled_tinted(x, y, color, self.scale_x, self.scale_y, rect) } - pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect) { + pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, mut scale_x: f32, mut scale_y: f32, rect: &common::Rect) { if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { return; } @@ -90,45 +108,61 @@ impl SizedBatch { x = (x * G_MAG).floor() / G_MAG; y = (y * G_MAG).floor() / G_MAG; } + let mag = unsafe { I_MAG }; - /*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, - (rect.bottom - rect.top) as f32 / self.height as f32)) - .dest(mint::Point2 { x, y }) - .scale(Vector2::new(scale_x, scale_y)); - - self.batch.add(param);*/ + self.batch.add(SpriteBatchCommand::DrawRect( + Rect { + left: rect.left as f32 / scale_x, + top: rect.top as f32 / scale_y, + right: rect.right as f32 / scale_x, + bottom: rect.bottom as f32 / scale_y, + }, + Rect { + left: x * mag, + top: y * mag, + right: (x + rect.width() as f32) * mag, + bottom: (y + rect.height() as f32) * mag, + }, + )); } - 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) { + pub fn add_rect_scaled_tinted(&mut self, mut x: f32, mut y: f32, color: (u8, u8, u8, u8), mut scale_x: f32, mut scale_y: f32, rect: &common::Rect) { if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { return; } - /*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, - (rect.right - rect.left) as f32 / self.width as f32, - (rect.bottom - rect.top) as f32 / self.height as f32)) - .dest(mint::Point2 { x, y }) - .scale(Vector2::new(scale_x, scale_y)); + unsafe { + x = (x * G_MAG).floor() / G_MAG; + y = (y * G_MAG).floor() / G_MAG; + } + let mag = unsafe { I_MAG }; - self.batch.add(param);*/ + self.batch.add(SpriteBatchCommand::DrawRectTinted( + Rect { + left: rect.left as f32 / scale_x, + top: rect.top as f32 / scale_y, + right: rect.right as f32 / scale_x, + bottom: rect.bottom as f32 / scale_y, + }, + Rect { + left: x * mag, + top: y * mag, + right: (x + rect.width() as f32) * mag, + bottom: (y + rect.height() as f32) * mag, + }, + color.into(), + )); } #[inline(always)] pub fn draw(&mut self, ctx: &mut Context) -> GameResult { - //self.draw_filtered(FilterMode::Nearest, ctx) - Ok(()) + self.draw_filtered(FilterMode::Nearest, ctx) } pub fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult { - /*self.batch.set_filter(filter); - self.batch.draw(ctx, DrawParam::new())?; - self.batch.clear();*/ + ///self.batch.set_filter(filter); + self.batch.draw()?; + self.batch.clear(); Ok(()) } } @@ -166,7 +200,7 @@ impl TextureSet { } } - fn load_image(&self, ctx: &mut Context, path: &str) -> GameResult { + fn load_image(&self, ctx: &mut Context, path: &str) -> GameResult> { let img = { let mut buf = [0u8; 8]; let mut reader = filesystem::open(ctx, path)?; @@ -182,7 +216,7 @@ impl TextureSet { }; let (width, height) = img.dimensions(); - Image::from_rgba8(ctx, width as u16, height as u16, img.as_ref()) + create_texture(ctx, width as u16, height as u16, &img) } pub fn load_texture(&self, ctx: &mut Context, constants: &EngineConstants, name: &str) -> GameResult { @@ -197,26 +231,26 @@ impl TextureSet { info!("Loading texture: {}", path); - let image = self.load_image(ctx, &path)?; - let size = image.dimensions(); + let batch = self.load_image(ctx, &path)?; + let size = batch.dimensions(); - assert_ne!(size.w as isize, 0, "size.w == 0"); - assert_ne!(size.h as isize, 0, "size.h == 0"); + assert_ne!(size.0 as isize, 0, "size.width == 0"); + assert_ne!(size.1 as isize, 0, "size.height == 0"); - let dim = (size.w as usize, size.h as usize); - let orig_dimensions = constants.tex_sizes.get(name).unwrap_or_else(|| &dim); - let scale_x = orig_dimensions.0 as f32 / size.w; - let scale_y = orig_dimensions.0 as f32 / size.w; - let width = (size.w * scale_x) as usize; - let height = (size.h * scale_y) as usize; + let orig_dimensions = constants.tex_sizes.get(name).unwrap_or_else(|| &size); + let scale = orig_dimensions.0 as f32 / size.0 as f32; + let width = (size.0 as f32 * scale) as usize; + let height = (size.1 as f32 * scale) as usize; + println!("{} {} {} {}", size.0, size.1, width, height); Ok(SizedBatch { + batch, width, height, - scale_x, - scale_y, - real_width: size.w as usize, - real_height: size.h as usize, + scale_x: scale, + scale_y: scale, + real_width: size.0 as usize, + real_height: size.1 as usize, }) } From cc816d03800e464fb1f174dd986ceeca70e21f96 Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Thu, 28 Jan 2021 23:41:42 +0100 Subject: [PATCH 3/8] use bundled sdl2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cf1fb71..696a5a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" pretty_env_logger = "0.4.0" -sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures"] } +sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures", "bundled"] } serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_yaml = "0.8" From c3887e31c7ab8d3b7fc44e36a284a2d955f76825 Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Fri, 5 Feb 2021 10:47:30 +0100 Subject: [PATCH 4/8] omg sdl2 port finally fully works? --- Cargo.toml | 5 +- src/framework/backend.rs | 7 +++ src/framework/backend_sdl2.rs | 87 +++++++++++++++++++++++++++++++---- src/framework/graphics.rs | 40 ++++++++++++++-- src/scene/game_scene.rs | 75 +++++++++++++++--------------- src/scripting/doukutsu.d.ts | 19 +++++++- src/shared_game_state.rs | 10 ++++ src/texture_set.rs | 36 +++++++-------- 8 files changed, 209 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 696a5a9..b158cdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ opt-level = 1 [features] default = ["scripting", "backend-sdl"] -backend-sdl = ["sdl2"] +backend-sdl = ["sdl2", "sdl2-sys"] backend-gfx = ["winit", "imgui-gfx-renderer", "imgui-winit-support"] scripting = ["lua-ffi"] editor = [] @@ -66,7 +66,8 @@ num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" pretty_env_logger = "0.4.0" -sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures", "bundled"] } +sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures", "bundled", "static-link"] } +sdl2-sys = { version = "0.34", optional = true } serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_yaml = "0.8" diff --git a/src/framework/backend.rs b/src/framework/backend.rs index 1fcfda4..c224faf 100644 --- a/src/framework/backend.rs +++ b/src/framework/backend.rs @@ -2,6 +2,7 @@ use crate::common::{Color, Rect, Point}; use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::Game; +use crate::framework::graphics::BlendMode; pub trait Backend { fn create_event_loop(&self) -> GameResult>; @@ -18,7 +19,13 @@ pub trait BackendRenderer { fn present(&mut self) -> GameResult; + fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult>; + fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult>; + + fn set_blend_mode(&mut self, blend: BlendMode) -> GameResult; + + fn set_render_target(&mut self, texture: Option<&Box>) -> GameResult; } pub trait BackendTexture { diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index a3c0023..ab954cd 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -6,13 +6,14 @@ use sdl2::{EventPump, keyboard, pixels, Sdl}; use sdl2::event::{Event, WindowEvent}; use sdl2::keyboard::Scancode; use sdl2::pixels::PixelFormatEnum; -use sdl2::render::{BlendMode, Texture, TextureCreator, WindowCanvas}; +use sdl2::render::{Texture, TextureCreator, WindowCanvas}; use sdl2::video::WindowContext; use crate::common::Color; use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; +use crate::framework::graphics::BlendMode; use crate::framework::keyboard::ScanCode; use crate::Game; @@ -46,10 +47,13 @@ struct SDL2EventLoop { struct SDL2Context { canvas: WindowCanvas, texture_creator: TextureCreator, + blend_mode: sdl2::render::BlendMode, } impl SDL2EventLoop { pub fn new(sdl: &Sdl) -> GameResult> { + sdl2::hint::set("SDL_HINT_RENDER_DRIVER", "opengles2"); + let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?; let video = sdl.video().map_err(|e| GameError::WindowError(e))?; let window = video.window("Cave Story (doukutsu-rs)", 640, 480) @@ -71,6 +75,7 @@ impl SDL2EventLoop { refs: Rc::new(RefCell::new(SDL2Context { canvas, texture_creator, + blend_mode: sdl2::render::BlendMode::Blend, })), }; @@ -82,6 +87,12 @@ impl BackendEventLoop for SDL2EventLoop { fn run(&mut self, game: &mut Game, ctx: &mut Context) { let state = unsafe { &mut *game.state.get() }; + { + let (width, height) = self.refs.borrow().canvas.window().size(); + ctx.screen_size = (width.max(1) as f32, height.max(1) as f32); + let _ = state.handle_resize(ctx); + } + loop { for event in self.event_pump.poll_iter() { match event { @@ -160,6 +171,14 @@ fn to_sdl(color: Color) -> pixels::Color { pixels::Color::RGBA(r, g, b, a) } +unsafe fn set_raw_target(renderer: *mut sdl2::sys::SDL_Renderer, raw_texture: *mut sdl2::sys::SDL_Texture) -> GameResult { + if sdl2::sys::SDL_SetRenderTarget(renderer, raw_texture) == 0 { + Ok(()) + } else { + Err(GameError::RenderError(sdl2::get_error())) + } +} + impl BackendRenderer for SDL2Renderer { fn clear(&mut self, color: Color) { let mut refs = self.refs.borrow_mut(); @@ -176,6 +195,22 @@ impl BackendRenderer for SDL2Renderer { Ok(()) } + fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult> { + let mut refs = self.refs.borrow_mut(); + + let mut texture = refs.texture_creator + .create_texture_target(PixelFormatEnum::RGBA32, width as u32, height as u32) + .map_err(|e| GameError::RenderError(e.to_string()))?; + + Ok(Box::new(SDL2Texture { + refs: self.refs.clone(), + texture: Some(texture), + width, + height, + commands: vec![], + })) + } + fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult> { let mut refs = self.refs.borrow_mut(); @@ -183,7 +218,7 @@ impl BackendRenderer for SDL2Renderer { .create_texture_streaming(PixelFormatEnum::RGBA32, width as u32, height as u32) .map_err(|e| GameError::RenderError(e.to_string()))?; - texture.set_blend_mode(BlendMode::Blend); + texture.set_blend_mode(sdl2::render::BlendMode::Blend); texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { for y in 0..(height as usize) { for x in 0..(width as usize) { @@ -198,13 +233,47 @@ impl BackendRenderer for SDL2Renderer { } }).map_err(|e| GameError::RenderError(e.to_string()))?; - return Ok(Box::new(SDL2Texture { + Ok(Box::new(SDL2Texture { refs: self.refs.clone(), texture: Some(texture), width, height, commands: vec![], - })); + })) + } + + fn set_blend_mode(&mut self, blend: BlendMode) -> GameResult { + let mut refs = self.refs.borrow_mut(); + + refs.blend_mode = match blend { + BlendMode::Add => sdl2::render::BlendMode::Add, + BlendMode::Alpha => sdl2::render::BlendMode::Blend, + BlendMode::Multiply => sdl2::render::BlendMode::Mod, + }; + + Ok(()) + } + + fn set_render_target(&mut self, texture: Option<&Box>) -> GameResult { + let renderer = self.refs.borrow().canvas.raw(); + + // todo: horribly unsafe + match texture { + Some(texture) => unsafe { + let sdl2_texture: &Box = std::mem::transmute(texture); + + if let Some(target) = sdl2_texture.texture.as_ref() { + set_raw_target(renderer, target.raw()); + } else { + set_raw_target(renderer, std::ptr::null_mut()); + } + } + None => unsafe { + set_raw_target(renderer, std::ptr::null_mut()); + } + } + + Ok(()) } } @@ -239,20 +308,22 @@ impl BackendTexture for SDL2Texture { SpriteBatchCommand::DrawRect(src, dest) => { texture.set_color_mod(255, 255, 255); texture.set_alpha_mod(255); + texture.set_blend_mode(refs.blend_mode); refs.canvas.copy(texture, - Some(sdl2::rect::Rect::new(src.left as i32, src.top as i32, src.width() as u32, src.height() as u32)), - Some(sdl2::rect::Rect::new(dest.left as i32, dest.top as i32, dest.width() as u32, dest.height() as u32))) + Some(sdl2::rect::Rect::new(src.left.round() as i32, src.top.round() as i32, src.width().round() as u32, src.height().round() as u32)), + Some(sdl2::rect::Rect::new(dest.left.round() as i32, dest.top.round() as i32, dest.width().round() as u32, dest.height().round() as u32))) .map_err(|e| GameError::RenderError(e.to_string()))?; } SpriteBatchCommand::DrawRectTinted(src, dest, color) => { let (r, g, b, a) = color.to_rgba(); texture.set_color_mod(r, g, b); texture.set_alpha_mod(a); + texture.set_blend_mode(refs.blend_mode); refs.canvas.copy(texture, - Some(sdl2::rect::Rect::new(src.left as i32, src.top as i32, src.width() as u32, src.height() as u32)), - Some(sdl2::rect::Rect::new(dest.left as i32, dest.top as i32, dest.width() as u32, dest.height() as u32))) + Some(sdl2::rect::Rect::new(src.left.round() as i32, src.top.round() as i32, src.width().round() as u32, src.height().round() as u32)), + Some(sdl2::rect::Rect::new(dest.left.round() as i32, dest.top.round() as i32, dest.width().round() as u32, dest.height().round() as u32))) .map_err(|e| GameError::RenderError(e.to_string()))?; } } diff --git a/src/framework/graphics.rs b/src/framework/graphics.rs index a596e91..583c9bd 100644 --- a/src/framework/graphics.rs +++ b/src/framework/graphics.rs @@ -8,10 +8,18 @@ pub enum FilterMode { Linear, } -pub struct Canvas {} - -impl Canvas { - +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum BlendMode { + /// When combining two fragments, add their values together, saturating + /// at 1.0 + Add, + /// When combining two fragments, add the value of the source times its + /// alpha channel with the value of the destination multiplied by the inverse + /// of the source alpha channel. Has the usual transparency effect: mixes the + /// two colors using a fraction of each one specified by the alpha of the source. + Alpha, + /// When combining two fragments, multiply their values together. + Multiply, } pub fn clear(ctx: &mut Context, color: Color) { @@ -32,6 +40,14 @@ pub fn renderer_initialized(ctx: &mut Context) -> bool { ctx.renderer.is_some() } +pub fn create_texture_mutable(ctx: &mut Context, width: u16, height: u16) -> GameResult> { + if let Some(renderer) = ctx.renderer.as_mut() { + return renderer.create_texture_mutable(width, height); + } + + Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) +} + pub fn create_texture(ctx: &mut Context, width: u16, height: u16, data: &[u8]) -> GameResult> { if let Some(renderer) = ctx.renderer.as_mut() { return renderer.create_texture(width, height, data); @@ -43,3 +59,19 @@ pub fn create_texture(ctx: &mut Context, width: u16, height: u16, data: &[u8]) - pub fn screen_size(ctx: &mut Context) -> (f32, f32) { ctx.screen_size } + +pub fn set_render_target(ctx: &mut Context, texture: Option<&Box>) -> GameResult { + if let Some(renderer) = ctx.renderer.as_mut() { + return renderer.set_render_target(texture); + } + + Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) +} + +pub fn set_blend_mode(ctx: &mut Context, blend: BlendMode) -> GameResult { + if let Some(renderer) = ctx.renderer.as_mut() { + return renderer.set_blend_mode(blend); + } + + Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) +} diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 8c744af..254038a 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -3,13 +3,18 @@ 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, Color}; +use crate::common::{Color, Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect}; use crate::components::boss_life_bar::BossLifeBar; use crate::components::draw_common::{Alignment, draw_number}; use crate::components::hud::HUD; use crate::components::stage_select::StageSelect; use crate::entity::GameEntity; use crate::frame::{Frame, UpdateTarget}; +use crate::framework::backend::SpriteBatchCommand; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::graphics; +use crate::framework::graphics::{BlendMode, FilterMode}; use crate::input::touch_controls::TouchControlType; use crate::inventory::{Inventory, TakeExperienceResult}; use crate::npc::boss::BossNPC; @@ -26,10 +31,6 @@ 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, @@ -297,18 +298,18 @@ impl GameScene { FadeDirection::Left | FadeDirection::Right => { let mut frame = tick; - for x in (0..(state.canvas_size.0 as i32 + 16)).step_by(16) { + for x in 0..(state.canvas_size.0 as i32 / 16 + 1) { if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { rect.left = frame.abs() as u16 * 16; rect.right = rect.left + 16; - for y in (0..(state.canvas_size.1 as i32 + 16)).step_by(16) { + for y in 0..(state.canvas_size.1 as i32 / 16 + 1) { if direction == FadeDirection::Left { - batch.add_rect(state.canvas_size.0 - x as f32, y as f32, &rect); + batch.add_rect(state.canvas_size.0 - x as f32 * 16.0, y as f32 * 16.0, &rect); } else { - batch.add_rect(x as f32, y as f32, &rect); + batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect); } } } @@ -317,18 +318,18 @@ impl GameScene { FadeDirection::Up | FadeDirection::Down => { let mut frame = tick; - for y in (0..(state.canvas_size.1 as i32 + 16)).step_by(16) { + for y in 0..(state.canvas_size.1 as i32 / 16 + 1) { if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { rect.left = frame.abs() as u16 * 16; rect.right = rect.left + 16; - for x in (0..(state.canvas_size.0 as i32 + 16)).step_by(16) { + for x in 0..(state.canvas_size.0 as i32 / 16 + 1) { if direction == FadeDirection::Down { - batch.add_rect(x as f32, y as f32, &rect); + batch.add_rect(x as f32 * 16.0, y as f32 * 16.0, &rect); } else { - batch.add_rect(x as f32, state.canvas_size.1 - y as f32, &rect); + batch.add_rect(x as f32 * 16.0, state.canvas_size.1 - y as f32 * 16.0, &rect); } } } @@ -342,7 +343,7 @@ impl GameScene { for x in 0..(center_x / 16 + 2) { let mut frame = start_frame; - for y in 0..(center_y / 16 + 1) { + for y in 0..(center_y / 16 + 2) { if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { @@ -502,10 +503,17 @@ impl GameScene { } fn draw_light_map(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - /*graphics::set_canvas(ctx, Some(&state.lightmap_canvas)); + let canvas = state.lightmap_canvas.as_mut(); + if let None = canvas { + return Ok(()); + } + + let canvas = canvas.unwrap(); + + graphics::set_render_target(ctx, Some(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")?; @@ -547,9 +555,9 @@ impl GameScene { fix9_scale(npc.y - self.frame.y, scale), 0.4, (255, 255, 0), batch); } - 4 | 7 => self.draw_light(fix9_scale(npc.x - self.frame.x, scale), - fix9_scale(npc.y - self.frame.y, scale), - 1.0, (100, 100, 100), batch), + 7 => self.draw_light(fix9_scale(npc.x - self.frame.x, scale), + fix9_scale(npc.y - self.frame.y, scale), + 1.0, (100, 100, 100), batch), 17 if npc.anim_num == 0 => { self.draw_light(fix9_scale(npc.x - self.frame.x, scale), fix9_scale(npc.y - self.frame.y, scale), @@ -584,13 +592,6 @@ impl GameScene { fix9_scale(npc.y - self.frame.y, scale), 3.5, (130 + flicker, 40 + flicker, 0), batch); } - 66 if npc.action_num == 1 && npc.anim_counter % 2 == 0 => - self.draw_light(fix9_scale(npc.x - self.frame.x, scale), - fix9_scale(npc.y - self.frame.y, scale), - 3.0, (0, 100, 255), batch), - 67 => self.draw_light(fix9_scale(npc.x - self.frame.x, scale), - fix9_scale(npc.y - self.frame.y, scale), - 2.0, (0, 100, 200), batch), 70 => { let flicker = 50 + npc.anim_num as u8 * 15; self.draw_light(fix9_scale(npc.x - self.frame.x, scale), @@ -634,13 +635,15 @@ impl GameScene { batch.draw_filtered(FilterMode::Linear, ctx)?; } - /*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::Multiply)?; + graphics::set_render_target(ctx, None); - graphics::set_blend_mode(ctx, BlendMode::Alpha)?;*/ + let rect = Rect { left: 0.0, top: 0.0, right: state.screen_size.0, bottom: state.screen_size.1 }; + canvas.clear(); + canvas.add(SpriteBatchCommand::DrawRect(rect, rect)); + canvas.draw()?; + + graphics::set_blend_mode(ctx, BlendMode::Alpha)?; Ok(()) } @@ -1213,13 +1216,13 @@ impl Scene for GameScene { //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 + if state.settings.shader_effects && self.stage.data.background_type != BackgroundType::Black && self.stage.data.background_type != BackgroundType::Outside && self.stage.data.background_type != BackgroundType::OutsideWind && self.stage.data.background.name() != "bkBlack" { self.draw_light_map(state, ctx)?; - }*/ + } self.boss.draw(state, ctx, &self.frame)?; for npc in self.npc_list.iter_alive() { @@ -1242,11 +1245,11 @@ impl Scene for GameScene { self.draw_tiles(state, ctx, TileLayer::Foreground)?; self.draw_tiles(state, ctx, TileLayer::Snack)?; self.draw_carets(state, ctx)?; - /*if state.settings.shader_effects + if state.settings.shader_effects && (self.stage.data.background_type == BackgroundType::Black || self.stage.data.background.name() == "bkBlack") { self.draw_light_map(state, ctx)?; - }*/ + } /*graphics::set_canvas(ctx, None); state.game_canvas.draw(ctx, DrawParam::new() diff --git a/src/scripting/doukutsu.d.ts b/src/scripting/doukutsu.d.ts index 0c024d8..6ea4d4b 100644 --- a/src/scripting/doukutsu.d.ts +++ b/src/scripting/doukutsu.d.ts @@ -14,9 +14,24 @@ declare interface DoukutsuScene { tick(): number; /** - * Returns player at specified index. + * Returns a list of players connected to current game. */ - player(index: number): DoukutsuPlayer | null; + onlinePlayers(): DoukutsuPlayer[]; + + /** + * Returns a list of players on current map. + */ + mapPlayers(): DoukutsuPlayer[]; + + /** + * Returns the id of local player. + */ + localPlayerId(): number; + + /** + * Returns player with specified id. + */ + player(id: number): DoukutsuPlayer | null; }; declare namespace doukutsu { diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 284624f..340bb2b 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -27,6 +27,8 @@ use crate::str; use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM}; use crate::texture_set::TextureSet; use crate::framework::{filesystem, graphics}; +use crate::framework::backend::BackendTexture; +use crate::framework::graphics::{create_texture, set_render_target, create_texture_mutable}; #[derive(PartialEq, Eq, Copy, Clone)] pub enum TimingMode { @@ -106,6 +108,7 @@ pub struct SharedGameState { pub screen_size: (f32, f32), pub next_scene: Option>, pub textscript_vm: TextScriptVM, + pub lightmap_canvas: Option>, pub season: Season, pub constants: EngineConstants, pub font: BMFontRenderer, @@ -170,6 +173,7 @@ impl SharedGameState { canvas_size: (320.0, 240.0), next_scene: None, textscript_vm: TextScriptVM::new(), + lightmap_canvas: None, season, constants, font, @@ -279,6 +283,12 @@ impl SharedGameState { 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); + let (width, height) = (self.screen_size.0 as u16, self.screen_size.1 as u16); + + // ensure no texture is bound before destroying them. + set_render_target(ctx, None)?; + self.lightmap_canvas = Some(create_texture_mutable(ctx, width, height)?); + Ok(()) } diff --git a/src/texture_set.rs b/src/texture_set.rs index 5beb9e2..1e91128 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -68,8 +68,8 @@ impl SizedBatch { pub fn add(&mut self, mut x: f32, mut y: f32) { unsafe { - x = (x * G_MAG).floor() / G_MAG; - y = (y * G_MAG).floor() / G_MAG; + x = (x * G_MAG).round() / G_MAG; + y = (y * G_MAG).round() / G_MAG; } let mag = unsafe { I_MAG }; @@ -105,23 +105,23 @@ impl SizedBatch { } unsafe { - x = (x * G_MAG).floor() / G_MAG; - y = (y * G_MAG).floor() / G_MAG; + x = (x * G_MAG).round() / G_MAG; + y = (y * G_MAG).round() / G_MAG; } let mag = unsafe { I_MAG }; self.batch.add(SpriteBatchCommand::DrawRect( Rect { - left: rect.left as f32 / scale_x, - top: rect.top as f32 / scale_y, - right: rect.right as f32 / scale_x, - bottom: rect.bottom as f32 / scale_y, + left: rect.left as f32, + top: rect.top as f32, + right: rect.right as f32, + bottom: rect.bottom as f32, }, Rect { - left: x * mag, - top: y * mag, - right: (x + rect.width() as f32) * mag, - bottom: (y + rect.height() as f32) * mag, + left: x * mag * scale_x, + top: y * mag * scale_y, + right: (x + rect.width() as f32) * mag * scale_x, + bottom: (y + rect.height() as f32) * mag * scale_y, }, )); } @@ -139,16 +139,16 @@ impl SizedBatch { self.batch.add(SpriteBatchCommand::DrawRectTinted( Rect { - left: rect.left as f32 / scale_x, - top: rect.top as f32 / scale_y, - right: rect.right as f32 / scale_x, - bottom: rect.bottom as f32 / scale_y, + left: rect.left as f32, + top: rect.top as f32, + right: rect.right as f32, + bottom: rect.bottom as f32, }, Rect { left: x * mag, top: y * mag, - right: (x + rect.width() as f32) * mag, - bottom: (y + rect.height() as f32) * mag, + right: (x + rect.width() as f32 * scale_x) * mag, + bottom: (y + rect.height() as f32 * scale_y) * mag, }, color.into(), )); From 318d94d84345071baeef560b24e0912b1e663828 Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Fri, 5 Feb 2021 23:47:13 +0100 Subject: [PATCH 5/8] ggez is gone --- .appveyor.yml | 8 +- Cargo.toml | 13 ++- README.md | 6 +- src/framework/backend.rs | 15 ++- src/framework/backend_sdl2.rs | 212 +++++++++++++++++++++++++++++++++- src/framework/graphics.rs | 24 ++++ src/framework/mod.rs | 5 +- src/framework/ui.rs | 136 ++++++++++++++++++++++ src/lib.rs | 3 +- src/live_debugger.rs | 2 +- src/scene/game_scene.rs | 4 +- src/scene/mod.rs | 2 +- src/sound/mod.rs | 21 ++-- src/sound/organya.rs | 135 +++++++++++----------- src/sound/playback.rs | 10 +- src/texture_set.rs | 23 ++-- src/ui.rs | 130 --------------------- src/weapon.rs | 4 +- 18 files changed, 509 insertions(+), 244 deletions(-) create mode 100644 src/framework/ui.rs delete mode 100644 src/ui.rs diff --git a/.appveyor.yml b/.appveyor.yml index 0bb3b58..c30a923 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -25,10 +25,10 @@ install: - cargo -vV cache: - - '%USERPROFILE%\.cache\sccache -> .appveyor.yml' - - '%USERPROFILE%\.cargo -> .appveyor.yml' - - '%USERPROFILE%\.rustup -> .appveyor.yml' - - 'target -> .appveyor.yml' + - '%USERPROFILE%\.cache\sccache -> Cargo.toml' + - '%USERPROFILE%\.cargo -> Cargo.toml' + - '%USERPROFILE%\.rustup -> Cargo.toml' + - 'target -> Cargo.toml' #test_script: # - cargo build --verbose --all diff --git a/Cargo.toml b/Cargo.toml index b158cdc..67291ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ opt-level = 1 [features] default = ["scripting", "backend-sdl"] -backend-sdl = ["sdl2", "sdl2-sys"] +backend-sdl = ["sdl2", "sdl2-sys", "imgui-sdl2"] backend-gfx = ["winit", "imgui-gfx-renderer", "imgui-winit-support"] scripting = ["lua-ffi"] editor = [] @@ -53,9 +53,10 @@ case_insensitive_hashmap = "1.0.0" chrono = "0.4" 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 } +imgui = "0.6.0" +imgui-gfx-renderer = { version = "0.6.0", optional = true } +imgui-winit-support = { version = "0.6.0", default-features = false, features = ["winit-23"], optional = true } +imgui-sdl2 = { version = "0.13.0", optional = true } image = { version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"] } itertools = "0.9.0" lazy_static = "1.4.0" @@ -66,8 +67,8 @@ num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" pretty_env_logger = "0.4.0" -sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures", "bundled", "static-link"] } -sdl2-sys = { version = "0.34", optional = true } +sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures", "bundled", "gfx"] } +sdl2-sys = { version = "0.34", optional = true, features = ["bundled", "gfx"] } serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_yaml = "0.8" diff --git a/README.md b/README.md index 1318604..59e46f4 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,10 @@ Vanilla Cave Story does not work yet because some important data files have been - [ ] Machine Gun - [ ] Missile Launcher - [ ] Bubbler - - [ ] Blade + - [x] Blade - [ ] Super Missile Launcher - - [ ] Nemesis - - [ ] Spur + - [x] Nemesis + - [x] Spur - [x] Saving and loading game state - [ ] Support for different game editions - [ ] Vanilla diff --git a/src/framework/backend.rs b/src/framework/backend.rs index c224faf..be95261 100644 --- a/src/framework/backend.rs +++ b/src/framework/backend.rs @@ -1,8 +1,10 @@ -use crate::common::{Color, Rect, Point}; +use imgui::DrawData; + +use crate::common::{Color, Point, Rect}; use crate::framework::context::Context; use crate::framework::error::GameResult; -use crate::Game; use crate::framework::graphics::BlendMode; +use crate::Game; pub trait Backend { fn create_event_loop(&self) -> GameResult>; @@ -26,12 +28,21 @@ pub trait BackendRenderer { fn set_blend_mode(&mut self, blend: BlendMode) -> GameResult; fn set_render_target(&mut self, texture: Option<&Box>) -> GameResult; + + fn imgui(&self) -> GameResult<&mut imgui::Context>; + + fn render_imgui(&mut self, draw_data: &DrawData) -> GameResult; + + fn prepare_frame(&self, ui: &imgui::Ui) -> GameResult; } pub trait BackendTexture { fn dimensions(&self) -> (u16, u16); + fn add(&mut self, command: SpriteBatchCommand); + fn clear(&mut self); + fn draw(&mut self) -> GameResult; } diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index ab954cd..168db77 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -1,20 +1,26 @@ use core::mem; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; +use imgui::{DrawCmd, DrawData, ImString, TextureId, Ui}; +use imgui::internal::RawWrapper; use sdl2::{EventPump, keyboard, pixels, Sdl}; use sdl2::event::{Event, WindowEvent}; +use sdl2::gfx::primitives::DrawRenderer; use sdl2::keyboard::Scancode; use sdl2::pixels::PixelFormatEnum; use sdl2::render::{Texture, TextureCreator, WindowCanvas}; +use sdl2::surface::Surface; use sdl2::video::WindowContext; use crate::common::Color; use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; -use crate::framework::graphics::BlendMode; +use crate::framework::graphics::{BlendMode, imgui_context}; use crate::framework::keyboard::ScanCode; +use crate::framework::ui::init_imgui; use crate::Game; pub struct SDL2Backend { @@ -87,14 +93,24 @@ impl BackendEventLoop for SDL2EventLoop { fn run(&mut self, game: &mut Game, ctx: &mut Context) { let state = unsafe { &mut *game.state.get() }; + let (imgui, imgui_sdl2) = unsafe { + let renderer: &Box = std::mem::transmute(ctx.renderer.as_ref().unwrap()); + + (&mut *renderer.imgui.as_ptr(), &mut *renderer.imgui_event.as_ptr()) + }; + { let (width, height) = self.refs.borrow().canvas.window().size(); ctx.screen_size = (width.max(1) as f32, height.max(1) as f32); + + imgui.io_mut().display_size = [ctx.screen_size.0, ctx.screen_size.1]; let _ = state.handle_resize(ctx); } loop { for event in self.event_pump.poll_iter() { + imgui_sdl2.handle_event(imgui, &event); + match event { Event::Quit { .. } => { state.shutdown(); @@ -105,6 +121,12 @@ impl BackendEventLoop for SDL2EventLoop { WindowEvent::Hidden => {} WindowEvent::SizeChanged(width, height) => { ctx.screen_size = (width.max(1) as f32, height.max(1) as f32); + + if let Some(renderer) = ctx.renderer.as_ref() { + if let Ok(imgui) = renderer.imgui() { + imgui.io_mut().display_size = [ctx.screen_size.0, ctx.screen_size.1]; + } + } state.handle_resize(ctx); } _ => {} @@ -145,6 +167,7 @@ impl BackendEventLoop for SDL2EventLoop { state.frame_time = 0.0; } + imgui_sdl2.prepare_frame(imgui.io_mut(), self.refs.borrow().canvas.window(), &self.event_pump.mouse_state()); game.draw(ctx).unwrap(); } } @@ -156,12 +179,62 @@ impl BackendEventLoop for SDL2EventLoop { struct SDL2Renderer { refs: Rc>, + imgui: Rc>, + imgui_event: Rc>, + imgui_textures: HashMap, } impl SDL2Renderer { + #[allow(clippy::new_ret_no_self)] pub fn new(refs: Rc>) -> GameResult> { + let mut imgui = init_imgui()?; + let mut imgui_textures = HashMap::new(); + + imgui.set_renderer_name(ImString::new("SDL2Renderer")); + { + let mut refs = refs.clone(); + let mut fonts = imgui.fonts(); + let id = fonts.tex_id; + let font_tex = fonts.build_rgba32_texture(); + + let mut texture = refs.borrow_mut().texture_creator + .create_texture_streaming(PixelFormatEnum::RGBA32, font_tex.width, font_tex.height) + .map_err(|e| GameError::RenderError(e.to_string()))?; + + texture.set_blend_mode(sdl2::render::BlendMode::Blend); + texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { + for y in 0..(font_tex.height as usize) { + for x in 0..(font_tex.width as usize) { + let offset = y * pitch + x * 4; + let data_offset = (y * font_tex.width as usize + x) * 4; + + buffer[offset] = font_tex.data[data_offset]; + buffer[offset + 1] = font_tex.data[data_offset + 1]; + buffer[offset + 2] = font_tex.data[data_offset + 2]; + buffer[offset + 3] = font_tex.data[data_offset + 3]; + } + } + }).map_err(|e| GameError::RenderError(e.to_string()))?; + + imgui_textures.insert(id, SDL2Texture { + refs: refs.clone(), + texture: Some(texture), + width: font_tex.width as u16, + height: font_tex.height as u16, + commands: vec![], + }); + } + + let imgui_sdl2 = unsafe { + let refs = &mut *refs.as_ptr(); + imgui_sdl2::ImguiSdl2::new(&mut imgui, refs.canvas.window()) + }; + Ok(Box::new(SDL2Renderer { refs, + imgui: Rc::new(RefCell::new(imgui)), + imgui_event: Rc::new(RefCell::new(imgui_sdl2)), + imgui_textures, })) } } @@ -179,6 +252,14 @@ unsafe fn set_raw_target(renderer: *mut sdl2::sys::SDL_Renderer, raw_texture: *m } } +fn min3(x: f32, y: f32, z: f32) -> f32 { + if x < y && x < z { x } else if y < z { y } else { z } +} + +fn max3(x: f32, y: f32, z: f32) -> f32 { + if x > y && x > z { x } else if y > z { y } else { z } +} + impl BackendRenderer for SDL2Renderer { fn clear(&mut self, color: Color) { let mut refs = self.refs.borrow_mut(); @@ -275,8 +356,137 @@ impl BackendRenderer for SDL2Renderer { Ok(()) } + + fn imgui(&self) -> GameResult<&mut imgui::Context> { + unsafe { + Ok(&mut *self.imgui.as_ptr()) + } + } + + fn render_imgui(&mut self, draw_data: &DrawData) -> GameResult { + let mut refs = self.refs.borrow_mut(); + + for draw_list in draw_data.draw_lists() { + for cmd in draw_list.commands() { + match cmd { + DrawCmd::Elements { count, cmd_params } => { + refs.canvas.set_clip_rect(Some(sdl2::rect::Rect::new( + cmd_params.clip_rect[0] as i32, + cmd_params.clip_rect[1] as i32, + (cmd_params.clip_rect[2] - cmd_params.clip_rect[0]) as u32, + (cmd_params.clip_rect[3] - cmd_params.clip_rect[1]) as u32, + ))); + + let idx_buffer = draw_list.idx_buffer(); + let mut vert_x = [0i16; 6]; + let mut vert_y = [0i16; 6]; + let mut min = [0f32; 2]; + let mut max = [0f32; 2]; + let mut tex_pos = [0f32; 4]; + let mut is_rect = false; + + for i in (0..count).step_by(3) { + if is_rect { + is_rect = false; + continue; + } + + let v1 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i] as usize]; + let v2 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 1] as usize]; + let v3 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 2] as usize]; + + vert_x[0] = (v1.pos[0] - 0.5) as i16; + vert_y[0] = (v1.pos[1] - 0.5) as i16; + vert_x[1] = (v2.pos[0] - 0.5) as i16; + vert_y[1] = (v2.pos[1] - 0.5) as i16; + vert_x[2] = (v3.pos[0] - 0.5) as i16; + vert_y[2] = (v3.pos[1] - 0.5) as i16; + + #[allow(clippy::float_cmp)] + if i < count - 3 { + let v4 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 3] as usize]; + let v5 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 4] as usize]; + let v6 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 5] as usize]; + + min[0] = min3(v1.pos[0], v2.pos[0], v3.pos[0]); + min[1] = min3(v1.pos[1], v2.pos[1], v3.pos[1]); + max[0] = max3(v1.pos[0], v2.pos[0], v3.pos[0]); + max[1] = max3(v1.pos[1], v2.pos[1], v3.pos[1]); + + is_rect = (v1.pos[0] == min[0] || v1.pos[0] == max[0]) && + (v1.pos[1] == min[1] || v1.pos[1] == max[1]) && + (v2.pos[0] == min[0] || v2.pos[0] == max[0]) && + (v2.pos[1] == min[1] || v2.pos[1] == max[1]) && + (v3.pos[0] == min[0] || v3.pos[0] == max[0]) && + (v3.pos[1] == min[1] || v3.pos[1] == max[1]) && + (v4.pos[0] == min[0] || v4.pos[0] == max[0]) && + (v4.pos[1] == min[1] || v4.pos[1] == max[1]) && + (v5.pos[0] == min[0] || v5.pos[0] == max[0]) && + (v5.pos[1] == min[1] || v5.pos[1] == max[1]) && + (v6.pos[0] == min[0] || v6.pos[0] == max[0]) && + (v6.pos[1] == min[1] || v6.pos[1] == max[1]); + + if is_rect { + tex_pos[0] = min3(v1.uv[0], v2.uv[0], v3.uv[0]); + tex_pos[1] = min3(v1.uv[1], v2.uv[1], v3.uv[1]); + tex_pos[2] = max3(v1.uv[0], v2.uv[0], v3.uv[0]); + tex_pos[3] = max3(v1.uv[1], v2.uv[1], v3.uv[1]); + } + } + + if let Some(surf) = self.imgui_textures.get_mut(&cmd_params.texture_id) { + unsafe { + if is_rect { + let src = sdl2::rect::Rect::new((tex_pos[0] * surf.width as f32) as i32, + (tex_pos[1] * surf.height as f32) as i32, + ((tex_pos[2] - tex_pos[0]) * surf.width as f32) as u32, + ((tex_pos[3] - tex_pos[1]) * surf.height as f32) as u32); + let dest = sdl2::rect::Rect::new(min[0] as i32, + min[1] as i32, + (max[0] - min[0]) as u32, + (max[1] - min[1]) as u32); + + let tex = surf.texture.as_mut().unwrap(); + tex.set_color_mod(v1.col[0], v1.col[1], v1.col[2]); + tex.set_alpha_mod(v1.col[3]); + + refs.canvas.copy(tex, src, dest); + } else { + sdl2::sys::gfx::primitives::filledPolygonRGBA( + refs.canvas.raw(), + vert_x.as_ptr(), + vert_y.as_ptr(), + 3, + v1.col[0], + v1.col[1], + v1.col[2], + v1.col[3], + ); + } + } + } + } + + refs.canvas.set_clip_rect(None); + } + DrawCmd::ResetRenderState => {} + DrawCmd::RawCallback { callback, raw_cmd } => unsafe { + callback(draw_list.raw(), raw_cmd) + } + } + } + } + + Ok(()) + } + + fn prepare_frame<'ui>(&self, ui: &Ui<'ui>) -> GameResult { + Ok(()) + } } +impl SDL2Renderer {} + struct SDL2Texture { refs: Rc>, texture: Option, diff --git a/src/framework/graphics.rs b/src/framework/graphics.rs index 583c9bd..57f3070 100644 --- a/src/framework/graphics.rs +++ b/src/framework/graphics.rs @@ -75,3 +75,27 @@ pub fn set_blend_mode(ctx: &mut Context, blend: BlendMode) -> GameResult { Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) } + +pub fn imgui_context(ctx: &Context) -> GameResult<&mut imgui::Context> { + if let Some(renderer) = ctx.renderer.as_ref() { + return renderer.imgui(); + } + + Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) +} + +pub fn render_imgui(ctx: &mut Context, draw_data: &imgui::DrawData) -> GameResult { + if let Some(renderer) = ctx.renderer.as_mut() { + return renderer.render_imgui(draw_data); + } + + Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) +} + +pub fn imgui_prepare_frame(ctx: &Context, ui: &imgui::Ui) -> GameResult { + if let Some(renderer) = ctx.renderer.as_ref() { + return renderer.prepare_frame(ui); + } + + Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) +} diff --git a/src/framework/mod.rs b/src/framework/mod.rs index af03421..3c0ee4f 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -1,9 +1,10 @@ pub mod backend; pub mod backend_sdl2; +pub mod backend_null; pub mod context; pub mod error; pub mod filesystem; -pub mod vfs; pub mod graphics; pub mod keyboard; -pub mod backend_null; +pub mod ui; +pub mod vfs; diff --git a/src/framework/ui.rs b/src/framework/ui.rs new file mode 100644 index 0000000..09b501e --- /dev/null +++ b/src/framework/ui.rs @@ -0,0 +1,136 @@ +use std::time::Instant; + +use imgui::{FontConfig, FontSource}; +use imgui::sys::*; + +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::graphics::{imgui_context, render_imgui}; +use crate::live_debugger::LiveDebugger; +use crate::scene::Scene; +use crate::shared_game_state::SharedGameState; + +pub struct UI { + pub components: Components, + last_frame: Instant, +} + +pub struct Components { + pub live_debugger: LiveDebugger, +} + +pub fn init_imgui() -> GameResult { + let mut imgui = imgui::Context::create(); + imgui.set_ini_filename(None); + imgui.fonts().add_font(&[ + FontSource::DefaultFontData { + config: Some(FontConfig::default()), + }, + ]); + + imgui.style_mut().window_padding = [4.0, 6.0]; + imgui.style_mut().frame_padding = [8.0, 6.0]; + imgui.style_mut().item_spacing = [8.0, 6.0]; + imgui.style_mut().item_inner_spacing = [8.0, 6.0]; + imgui.style_mut().indent_spacing = 20.0; + + imgui.style_mut().scrollbar_size = 20.0; + imgui.style_mut().grab_min_size = 8.0; + imgui.style_mut().window_border_size = 0.0; + imgui.style_mut().child_border_size = 0.0; + imgui.style_mut().popup_border_size = 0.0; + imgui.style_mut().frame_border_size = 0.0; + imgui.style_mut().tab_border_size = 0.0; + + imgui.style_mut().window_rounding = 0.0; + imgui.style_mut().child_rounding = 0.0; + imgui.style_mut().frame_rounding = 0.0; + imgui.style_mut().popup_rounding = 0.0; + imgui.style_mut().scrollbar_rounding = 0.0; + imgui.style_mut().grab_rounding = 0.0; + imgui.style_mut().tab_rounding = 0.0; + + imgui.style_mut().window_title_align = [0.50, 0.50]; + imgui.style_mut().window_rounding = 0.0; + + let colors = &mut imgui.style_mut().colors; + colors[ImGuiCol_Text as usize] = [0.90, 0.90, 0.90, 1.00]; + colors[ImGuiCol_TextDisabled as usize] = [0.50, 0.50, 0.50, 1.00]; + colors[ImGuiCol_WindowBg as usize] = [0.05, 0.05, 0.05, 0.60]; + colors[ImGuiCol_ChildBg as usize] = [0.05, 0.05, 0.05, 0.60]; + colors[ImGuiCol_PopupBg as usize] = [0.00, 0.00, 0.00, 0.60]; + colors[ImGuiCol_Border as usize] = [0.40, 0.40, 0.40, 1.00]; + colors[ImGuiCol_BorderShadow as usize] = [1.00, 1.00, 1.00, 0.00]; + colors[ImGuiCol_FrameBg as usize] = [0.00, 0.00, 0.00, 0.60]; + colors[ImGuiCol_FrameBgHovered as usize] = [0.84, 0.37, 0.00, 0.20]; + colors[ImGuiCol_FrameBgActive as usize] = [0.84, 0.37, 0.00, 1.00]; + colors[ImGuiCol_TitleBg as usize] = [0.06, 0.06, 0.06, 1.00]; + colors[ImGuiCol_TitleBgActive as usize] = [0.00, 0.00, 0.00, 1.00]; + colors[ImGuiCol_TitleBgCollapsed as usize] = [0.06, 0.06, 0.06, 0.40]; + colors[ImGuiCol_MenuBarBg as usize] = [0.14, 0.14, 0.14, 1.00]; + colors[ImGuiCol_ScrollbarBg as usize] = [0.14, 0.14, 0.14, 0.40]; + colors[ImGuiCol_ScrollbarGrab as usize] = [0.31, 0.31, 0.31, 0.30]; + colors[ImGuiCol_ScrollbarGrabHovered as usize] = [1.00, 1.00, 1.00, 0.30]; + colors[ImGuiCol_ScrollbarGrabActive as usize] = [1.00, 1.00, 1.00, 0.50]; + colors[ImGuiCol_CheckMark as usize] = [0.90, 0.90, 0.90, 1.00]; + colors[ImGuiCol_SliderGrab as usize] = [0.31, 0.31, 0.31, 1.00]; + colors[ImGuiCol_SliderGrabActive as usize] = [1.00, 1.00, 1.00, 0.50]; + colors[ImGuiCol_Button as usize] = [0.14, 0.14, 0.14, 1.00]; + colors[ImGuiCol_ButtonHovered as usize] = [0.84, 0.37, 0.00, 0.20]; + colors[ImGuiCol_ButtonActive as usize] = [0.84, 0.37, 0.00, 1.00]; + colors[ImGuiCol_Header as usize] = [0.14, 0.14, 0.14, 1.00]; + colors[ImGuiCol_HeaderHovered as usize] = [0.84, 0.37, 0.00, 0.20]; + colors[ImGuiCol_HeaderActive as usize] = [0.84, 0.37, 0.00, 1.00]; + colors[ImGuiCol_Separator as usize] = [0.50, 0.50, 0.43, 0.50]; + colors[ImGuiCol_SeparatorHovered as usize] = [0.75, 0.45, 0.10, 0.78]; + colors[ImGuiCol_SeparatorActive as usize] = [0.75, 0.45, 0.10, 1.00]; + colors[ImGuiCol_ResizeGrip as usize] = [0.98, 0.65, 0.26, 0.25]; + colors[ImGuiCol_ResizeGripHovered as usize] = [0.98, 0.65, 0.26, 0.67]; + colors[ImGuiCol_ResizeGripActive as usize] = [0.98, 0.65, 0.26, 0.95]; + colors[ImGuiCol_Tab as usize] = [0.17, 0.10, 0.04, 0.94]; + colors[ImGuiCol_TabHovered as usize] = [0.84, 0.37, 0.00, 0.60]; + colors[ImGuiCol_TabActive as usize] = [0.67, 0.30, 0.00, 0.68]; + colors[ImGuiCol_TabUnfocused as usize] = [0.06, 0.05, 0.05, 0.69]; + colors[ImGuiCol_TabUnfocusedActive as usize] = [0.36, 0.17, 0.03, 0.64]; + colors[ImGuiCol_PlotLines as usize] = [0.39, 0.39, 0.39, 1.00]; + colors[ImGuiCol_PlotLinesHovered as usize] = [0.35, 0.92, 1.00, 1.00]; + colors[ImGuiCol_PlotHistogram as usize] = [0.00, 0.20, 0.90, 1.00]; + colors[ImGuiCol_PlotHistogramHovered as usize] = [0.00, 0.40, 1.00, 1.00]; + colors[ImGuiCol_TextSelectedBg as usize] = [0.98, 0.65, 0.26, 0.35]; + colors[ImGuiCol_DragDropTarget as usize] = [0.00, 0.00, 1.00, 0.90]; + colors[ImGuiCol_NavHighlight as usize] = [0.98, 0.65, 0.26, 1.00]; + colors[ImGuiCol_NavWindowingHighlight as usize] = [0.00, 0.00, 0.00, 0.70]; + 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]; + + Ok(imgui) +} + +impl UI { + pub fn new(ctx: &mut Context) -> GameResult { + Ok(Self { + components: Components { + live_debugger: LiveDebugger::new(), + }, + last_frame: Instant::now(), + }) + } + + pub fn draw(&mut self, state: &mut SharedGameState, ctx: &mut Context, scene: &mut Box) -> GameResult { + let ctx2 = unsafe { &mut *(ctx as *const Context as *mut Context)}; + let imgui = imgui_context(ctx)?; + let io = imgui.io_mut(); + let now = Instant::now(); + io.update_delta_time(now - self.last_frame); + self.last_frame = now; + + let mut ui = imgui.frame(); + + scene.debug_overlay_draw(&mut self.components, state, ctx2, &mut ui)?; + + let draw_data = ui.render(); + render_imgui(ctx2, draw_data)?; + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8e6246c..b46ddbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,12 +24,12 @@ use crate::framework::error::{GameError, GameResult}; use crate::framework::filesystem::{mount_user_vfs, mount_vfs}; use crate::framework::graphics; use crate::framework::keyboard::ScanCode; +use crate::framework::ui::UI; use crate::framework::vfs::PhysicalFS; use crate::scene::loading_scene::LoadingScene; use crate::scene::Scene; use crate::shared_game_state::{SharedGameState, TimingMode}; use crate::texture_set::{G_MAG, I_MAG}; -use crate::ui::UI; mod bmfont; mod bmfont_renderer; @@ -66,7 +66,6 @@ mod stage; mod sound; mod text_script; mod texture_set; -mod ui; mod weapon; pub struct Game { diff --git a/src/live_debugger.rs b/src/live_debugger.rs index f9ee7b0..ac20093 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -59,7 +59,7 @@ impl LiveDebugger { Window::new(im_str!("Debugger")) .resizable(false) - .collapsed(true, Condition::FirstUseEver) + .collapsed(false, Condition::FirstUseEver) .position([5.0, 5.0], Condition::FirstUseEver) .size([400.0, 170.0], Condition::FirstUseEver) .build(ui, || { diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 254038a..77aa954 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -15,6 +15,7 @@ use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::framework::graphics; use crate::framework::graphics::{BlendMode, FilterMode}; +use crate::framework::ui::Components; use crate::input::touch_controls::TouchControlType; use crate::inventory::{Inventory, TakeExperienceResult}; use crate::npc::boss::BossNPC; @@ -29,7 +30,6 @@ use crate::shared_game_state::{Season, SharedGameState}; use crate::stage::{BackgroundType, Stage}; use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptVM}; use crate::texture_set::SizedBatch; -use crate::ui::Components; use crate::weapon::WeaponType; pub struct GameScene { @@ -636,7 +636,7 @@ impl GameScene { } graphics::set_blend_mode(ctx, BlendMode::Multiply)?; - graphics::set_render_target(ctx, None); + graphics::set_render_target(ctx, None)?; let rect = Rect { left: 0.0, top: 0.0, right: state.screen_size.0, bottom: state.screen_size.1 }; canvas.clear(); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index d692e04..5534c1d 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -2,7 +2,7 @@ use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::shared_game_state::SharedGameState; -use crate::ui::Components; +use crate::framework::ui::Components; pub mod game_scene; pub mod loading_scene; diff --git a/src/sound/mod.rs b/src/sound/mod.rs index e87d091..4ce7c7d 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -8,7 +8,7 @@ use num_traits::clamp; use crate::engine_constants::EngineConstants; use crate::framework::context::Context; -use crate::framework::error::GameResult; +use crate::framework::error::{GameResult, GameError}; use crate::framework::filesystem; use crate::sound::organya::Song; use crate::sound::pixtone::PixTonePlayback; @@ -16,6 +16,7 @@ use crate::sound::playback::{PlaybackEngine, SavedPlaybackState}; use crate::sound::wave_bank::SoundBank; use crate::str; use crate::framework::error::GameError::{AudioError, ResourceLoadError, InvalidValue}; +use std::io::Error; mod wave_bank; mod organya; @@ -128,13 +129,19 @@ impl SoundManager { .find(|path| filesystem::exists(ctx, path)) .ok_or_else(|| ResourceLoadError(format!("BGM {:?} does not exist.", song_name)))?; - let org = organya::Song::load_from(filesystem::open(ctx, path)?)?; - log::info!("Playing BGM: {}", song_name); + match filesystem::open(ctx, path).map(|f| organya::Song::load_from(f)) { + Ok(Ok(org)) => { + log::info!("Playing BGM: {} {}", song_id, song_name); - self.prev_song_id = self.current_song_id; - self.current_song_id = song_id; - self.tx.send(PlaybackMessage::SaveState)?; - self.tx.send(PlaybackMessage::PlaySong(Box::new(org)))?; + self.prev_song_id = self.current_song_id; + self.current_song_id = song_id; + self.tx.send(PlaybackMessage::SaveState)?; + self.tx.send(PlaybackMessage::PlaySong(Box::new(org)))?; + } + Ok(Err(err)) | Err(err) => { + log::warn!("Failed to load BGM {}: {}", song_id, err); + } + } } Ok(()) } diff --git a/src/sound/organya.rs b/src/sound/organya.rs index bb90c6b..3c1733a 100644 --- a/src/sound/organya.rs +++ b/src/sound/organya.rs @@ -1,12 +1,18 @@ +use std::io; + +use byteorder::{LE, ReadBytesExt}; + +use crate::framework::error::{GameError, GameResult}; + #[repr(u8)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Version { // Can't find any files with this signature, // But apparently these files had no Pi flag. Beta = b'1', Main = b'2', // OrgMaker 2.05 Extended Drums - Extended = b'3' + Extended = b'3', } #[derive(Debug, Copy, Clone)] @@ -14,19 +20,19 @@ pub struct LoopRange { // inclusive pub start: i32, // exclusive - pub end: i32 + pub end: i32, } #[derive(Debug, Copy, Clone)] pub struct Display { pub beats: u8, - pub steps: u8 + pub steps: u8, } #[derive(Debug, Copy, Clone)] pub struct Timing { pub wait: u16, - pub loop_range: LoopRange + pub loop_range: LoopRange, } #[derive(Copy, Clone)] @@ -35,12 +41,12 @@ pub struct Instrument { pub freq: u16, pub inst: u8, pub pipi: u8, - pub notes: u16 + pub notes: u16, } pub struct Track { pub inst: Instrument, - pub notes: Vec + pub notes: Vec, } impl Clone for Track { @@ -65,14 +71,14 @@ pub struct Note { pub key: u8, pub len: u8, pub vol: u8, - pub pan: u8 + pub pan: u8, } #[derive(Debug)] pub struct Song { pub version: Version, pub time: Timing, - pub tracks: [Track; 16] + pub tracks: [Track; 16], } impl Clone for Song { @@ -85,85 +91,82 @@ impl Clone for Song { } } -use byteorder::{LE, ReadBytesExt}; -use std::io; - impl Song { pub fn empty() -> Song { Song { version: Version::Main, time: Timing { wait: 8, loop_range: LoopRange { start: 0, end: 1 } }, tracks: [ - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] }, - ] + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + Track { inst: Instrument { freq: 1000, inst: 0, pipi: 0, notes: 0 }, notes: vec![] }, + ], } } - pub fn load_from(mut f: R) -> io::Result { + pub fn load_from(mut f: R) -> GameResult { let mut magic = [0; 6]; - + f.read_exact(&mut magic)?; - - let version = + + let version = match &magic { b"Org-01" => Version::Beta, b"Org-02" => Version::Main, b"Org-03" => Version::Extended, - _ => return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid magic number")) + _ => return Err(GameError::ResourceLoadError("Invalid magic number".to_string())) }; - - let wait = f.read_u16::()?; - let _bpm = f.read_u8()?; - let _spb = f.read_u8()?; + + let wait = f.read_u16::()?; + let _bpm = f.read_u8()?; + let _spb = f.read_u8()?; let start = f.read_i32::()?; - let end = f.read_i32::()?; - + let end = f.read_i32::()?; + use std::mem::MaybeUninit as Mu; - + let mut insts: [Mu; 16] = unsafe { Mu::uninit().assume_init() }; - + for i in insts.iter_mut() { - let freq = f.read_u16::()?; - let inst = f.read_u8()?; - let pipi = f.read_u8()?; + let freq = f.read_u16::()?; + let inst = f.read_u8()?; + let pipi = f.read_u8()?; let notes = f.read_u16::()?; - + *i = Mu::new(Instrument { freq, inst, pipi, - notes + notes, }); } - + let insts: [Instrument; 16] = unsafe { std::mem::transmute(insts) }; - + let mut tracks: [Mu; 16] = unsafe { Mu::uninit().assume_init() }; - + for (i, t) in tracks.iter_mut().enumerate() { let count = insts[i].notes as usize; - + #[repr(C)] #[derive(Copy, Clone)] struct UninitNote { @@ -171,55 +174,55 @@ impl Song { key: Mu, len: Mu, vol: Mu, - pan: Mu + pan: Mu, } - + let mut notes: Vec = unsafe { vec![Mu::uninit().assume_init(); count] }; - + for note in notes.iter_mut() { note.pos = Mu::new(f.read_i32::()?); } - + for note in notes.iter_mut() { note.key = Mu::new(f.read_u8()?); } - + for note in notes.iter_mut() { note.len = Mu::new(f.read_u8()?); } - + for note in notes.iter_mut() { note.vol = Mu::new(f.read_u8()?); } - + for note in notes.iter_mut() { note.pan = Mu::new(f.read_u8()?); } - + *t = Mu::new(Track { inst: insts[i], - notes: unsafe { std::mem::transmute(notes) } + notes: unsafe { std::mem::transmute(notes) }, }); } - + let tracks = unsafe { std::mem::transmute(tracks) }; - + let song = Song { version, time: Timing { wait, loop_range: LoopRange { start, - end - } + end, + }, }, - tracks + tracks, }; - + Ok(song) } } diff --git a/src/sound/playback.rs b/src/sound/playback.rs index 82b5997..e5cde85 100644 --- a/src/sound/playback.rs +++ b/src/sound/playback.rs @@ -1,6 +1,6 @@ use std::mem::MaybeUninit; -use crate::sound::organya::Song as Organya; +use crate::sound::organya::{Song as Organya, Version}; use crate::sound::stuff::*; use crate::sound::wav::*; use crate::sound::wave_bank::SoundBank; @@ -144,8 +144,12 @@ impl PlaybackEngine { } } - for (idx, (_track, buf)) in song.tracks[8..].iter().zip(self.track_buffers[128..].iter_mut()).enumerate() { - *buf = RenderBuffer::new(samples.samples[idx].clone()); + for (idx, (track, buf)) in song.tracks[8..].iter().zip(self.track_buffers[128..].iter_mut()).enumerate() { + if self.song.version == Version::Extended { + *buf = RenderBuffer::new(samples.samples[track.inst.inst as usize].clone()); + } else { + *buf = RenderBuffer::new(samples.samples[idx].clone()); + } } self.song = song; diff --git a/src/texture_set.rs b/src/texture_set.rs index 1e91128..61a70d6 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -91,12 +91,12 @@ impl SizedBatch { #[inline(always)] pub fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect) { - self.add_rect_scaled(x, y, self.scale_x, self.scale_y, rect) + self.add_rect_scaled(x, y, 1.0, 1.0, rect) } #[inline(always)] pub fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect) { - self.add_rect_scaled_tinted(x, y, color, self.scale_x, self.scale_y, rect) + self.add_rect_scaled_tinted(x, y, color, 1.0, 1.0, rect) } pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, mut scale_x: f32, mut scale_y: f32, rect: &common::Rect) { @@ -112,16 +112,16 @@ impl SizedBatch { self.batch.add(SpriteBatchCommand::DrawRect( Rect { - left: rect.left as f32, - top: rect.top as f32, - right: rect.right as f32, - bottom: rect.bottom as f32, + left: rect.left as f32 / self.scale_x, + top: rect.top as f32 / self.scale_y, + right: rect.right as f32 / self.scale_x, + bottom: rect.bottom as f32 / self.scale_y, }, Rect { - left: x * mag * scale_x, - top: y * mag * scale_y, - right: (x + rect.width() as f32) * mag * scale_x, - bottom: (y + rect.height() as f32) * mag * scale_y, + left: x * mag, + top: y * mag, + right: (x + rect.width() as f32 * scale_x) * mag, + bottom: (y + rect.height() as f32 * scale_y) * mag, }, )); } @@ -160,7 +160,7 @@ impl SizedBatch { } 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()?; self.batch.clear(); Ok(()) @@ -241,7 +241,6 @@ impl TextureSet { let scale = orig_dimensions.0 as f32 / size.0 as f32; let width = (size.0 as f32 * scale) as usize; let height = (size.1 as f32 * scale) as usize; - println!("{} {} {} {}", size.0, size.1, width, height); Ok(SizedBatch { batch, diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index 0322907..0000000 --- a/src/ui.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::time::Instant; - -use imgui::{FontConfig, FontSource}; -use imgui::sys::*; - -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; - -pub struct UI { - pub imgui: imgui::Context, - pub components: Components, - last_frame: Instant, -} - -pub struct Components { - pub live_debugger: LiveDebugger, -} - -impl UI { - pub fn new(ctx: &mut Context) -> GameResult { - let mut imgui = imgui::Context::create(); - imgui.set_ini_filename(None); - imgui.fonts().add_font(&[ - FontSource::DefaultFontData { - config: Some(FontConfig::default()), - }, - ]); - - imgui.style_mut().window_padding = [4.0, 6.0]; - imgui.style_mut().frame_padding = [8.0, 6.0]; - imgui.style_mut().item_spacing = [8.0, 6.0]; - imgui.style_mut().item_inner_spacing = [8.0, 6.0]; - imgui.style_mut().indent_spacing = 20.0; - - imgui.style_mut().scrollbar_size = 20.0; - imgui.style_mut().grab_min_size = 5.0; - imgui.style_mut().window_border_size = 0.0; - imgui.style_mut().child_border_size = 1.0; - imgui.style_mut().popup_border_size = 1.0; - imgui.style_mut().frame_border_size = 1.0; - imgui.style_mut().tab_border_size = 0.0; - - imgui.style_mut().window_rounding = 0.0; - imgui.style_mut().child_rounding = 0.0; - imgui.style_mut().frame_rounding = 0.0; - imgui.style_mut().popup_rounding = 0.0; - imgui.style_mut().scrollbar_rounding = 0.0; - imgui.style_mut().grab_rounding = 0.0; - imgui.style_mut().tab_rounding = 0.0; - - imgui.style_mut().window_title_align = [0.50, 0.50]; - imgui.style_mut().window_rounding = 0.0; - - let colors = &mut imgui.style_mut().colors; - colors[ImGuiCol_Text as usize] = [0.90, 0.90, 0.90, 1.00]; - colors[ImGuiCol_TextDisabled as usize] = [0.50, 0.50, 0.50, 1.00]; - colors[ImGuiCol_WindowBg as usize] = [0.05, 0.05, 0.05, 0.60]; - colors[ImGuiCol_ChildBg as usize] = [0.05, 0.05, 0.05, 0.60]; - colors[ImGuiCol_PopupBg as usize] = [0.00, 0.00, 0.00, 0.60]; - colors[ImGuiCol_Border as usize] = [0.40, 0.40, 0.40, 1.00]; - colors[ImGuiCol_BorderShadow as usize] = [1.00, 1.00, 1.00, 0.00]; - colors[ImGuiCol_FrameBg as usize] = [0.00, 0.00, 0.00, 0.60]; - colors[ImGuiCol_FrameBgHovered as usize] = [0.84, 0.37, 0.00, 0.20]; - colors[ImGuiCol_FrameBgActive as usize] = [0.84, 0.37, 0.00, 1.00]; - colors[ImGuiCol_TitleBg as usize] = [0.06, 0.06, 0.06, 1.00]; - colors[ImGuiCol_TitleBgActive as usize] = [0.00, 0.00, 0.00, 1.00]; - colors[ImGuiCol_TitleBgCollapsed as usize] = [0.06, 0.06, 0.06, 0.40]; - colors[ImGuiCol_MenuBarBg as usize] = [0.14, 0.14, 0.14, 1.00]; - colors[ImGuiCol_ScrollbarBg as usize] = [0.14, 0.14, 0.14, 0.40]; - colors[ImGuiCol_ScrollbarGrab as usize] = [0.31, 0.31, 0.31, 0.30]; - colors[ImGuiCol_ScrollbarGrabHovered as usize] = [1.00, 1.00, 1.00, 0.30]; - colors[ImGuiCol_ScrollbarGrabActive as usize] = [1.00, 1.00, 1.00, 0.50]; - colors[ImGuiCol_CheckMark as usize] = [0.90, 0.90, 0.90, 1.00]; - colors[ImGuiCol_SliderGrab as usize] = [0.31, 0.31, 0.31, 1.00]; - colors[ImGuiCol_SliderGrabActive as usize] = [1.00, 1.00, 1.00, 0.50]; - colors[ImGuiCol_Button as usize] = [0.14, 0.14, 0.14, 1.00]; - colors[ImGuiCol_ButtonHovered as usize] = [0.84, 0.37, 0.00, 0.20]; - colors[ImGuiCol_ButtonActive as usize] = [0.84, 0.37, 0.00, 1.00]; - colors[ImGuiCol_Header as usize] = [0.14, 0.14, 0.14, 1.00]; - colors[ImGuiCol_HeaderHovered as usize] = [0.84, 0.37, 0.00, 0.20]; - colors[ImGuiCol_HeaderActive as usize] = [0.84, 0.37, 0.00, 1.00]; - colors[ImGuiCol_Separator as usize] = [0.50, 0.50, 0.43, 0.50]; - colors[ImGuiCol_SeparatorHovered as usize] = [0.75, 0.45, 0.10, 0.78]; - colors[ImGuiCol_SeparatorActive as usize] = [0.75, 0.45, 0.10, 1.00]; - colors[ImGuiCol_ResizeGrip as usize] = [0.98, 0.65, 0.26, 0.25]; - colors[ImGuiCol_ResizeGripHovered as usize] = [0.98, 0.65, 0.26, 0.67]; - colors[ImGuiCol_ResizeGripActive as usize] = [0.98, 0.65, 0.26, 0.95]; - colors[ImGuiCol_Tab as usize] = [0.17, 0.10, 0.04, 0.94]; - colors[ImGuiCol_TabHovered as usize] = [0.84, 0.37, 0.00, 0.60]; - colors[ImGuiCol_TabActive as usize] = [0.67, 0.30, 0.00, 0.68]; - colors[ImGuiCol_TabUnfocused as usize] = [0.06, 0.05, 0.05, 0.69]; - colors[ImGuiCol_TabUnfocusedActive as usize] = [0.36, 0.17, 0.03, 0.64]; - colors[ImGuiCol_PlotLines as usize] = [0.39, 0.39, 0.39, 1.00]; - colors[ImGuiCol_PlotLinesHovered as usize] = [0.35, 0.92, 1.00, 1.00]; - colors[ImGuiCol_PlotHistogram as usize] = [0.00, 0.20, 0.90, 1.00]; - colors[ImGuiCol_PlotHistogramHovered as usize] = [0.00, 0.40, 1.00, 1.00]; - colors[ImGuiCol_TextSelectedBg as usize] = [0.98, 0.65, 0.26, 0.35]; - colors[ImGuiCol_DragDropTarget as usize] = [0.00, 0.00, 1.00, 0.90]; - colors[ImGuiCol_NavHighlight as usize] = [0.98, 0.65, 0.26, 1.00]; - colors[ImGuiCol_NavWindowingHighlight as usize] = [0.00, 0.00, 0.00, 0.70]; - 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]; - - Ok(Self { - imgui, - components: Components { - live_debugger: LiveDebugger::new(), - }, - last_frame: Instant::now(), - }) - } - - pub fn draw(&mut self, state: &mut SharedGameState, ctx: &mut Context, scene: &mut Box) -> GameResult { - /*{ - let io = self.imgui.io_mut(); - let now = Instant::now(); - io.update_delta_time(now - self.last_frame); - self.last_frame = now; - } - let mut ui = self.imgui.frame(); - - scene.debug_overlay_draw(&mut self.components, state, ctx, &mut ui)?; - - ui.render();*/ - Ok(()) - } -} diff --git a/src/weapon.rs b/src/weapon.rs index 1920770..d2b2687 100644 --- a/src/weapon.rs +++ b/src/weapon.rs @@ -443,8 +443,8 @@ impl Weapon { WeaponLevel::None => unreachable!(), } - const bullets: [u16; 6] = [44, 45, 46, 47, 48, 49]; - if bullet_manager.count_bullets_multi(&bullets, player_id) == 0 + const BULLETS: [u16; 6] = [44, 45, 46, 47, 48, 49]; + if bullet_manager.count_bullets_multi(&BULLETS, player_id) == 0 && (player.controller.trigger_shoot() || shoot) { if !self.consume_ammo(1) { state.sound_manager.play_sfx(37); From a8a9c6316dc3484fff5157027d24d5ee2dc2073f Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Wed, 10 Feb 2021 12:53:49 +0100 Subject: [PATCH 6/8] fix a bunch of warnings, bugs and compilation --- Cargo.toml | 12 +- src/common.rs | 16 - src/framework/backend.rs | 1 + src/framework/backend_sdl2.rs | 590 ++++++++++++++++++++++++++-------- src/framework/context.rs | 2 +- src/framework/filesystem.rs | 2 +- src/lib.rs | 50 +-- src/live_debugger.rs | 2 +- src/npc/ai/misc.rs | 4 +- src/npc/ai/sand_zone.rs | 2 +- src/npc/mod.rs | 2 + src/rng.rs | 2 +- src/scene/game_scene.rs | 9 +- src/scene/no_data_scene.rs | 1 + src/scripting/doukutsu.rs | 3 +- src/scripting/mod.rs | 7 +- src/scripting/player.rs | 1 + src/shared_game_state.rs | 33 +- src/sound/mod.rs | 3 +- src/sound/playback.rs | 1 + src/text_script.rs | 1 + src/texture_set.rs | 34 +- src/weapon.rs | 2 +- 23 files changed, 548 insertions(+), 232 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 67291ae..2bd2505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ opt-level = 1 [features] default = ["scripting", "backend-sdl"] -backend-sdl = ["sdl2", "sdl2-sys", "imgui-sdl2"] +backend-sdl = ["sdl2"] backend-gfx = ["winit", "imgui-gfx-renderer", "imgui-winit-support"] scripting = ["lua-ffi"] editor = [] @@ -53,10 +53,9 @@ case_insensitive_hashmap = "1.0.0" chrono = "0.4" cpal = { git = "https://github.com/doukutsu-rs/cpal.git", branch = "android-support" } directories = "3" -imgui = "0.6.0" -imgui-gfx-renderer = { version = "0.6.0", optional = true } -imgui-winit-support = { version = "0.6.0", default-features = false, features = ["winit-23"], optional = true } -imgui-sdl2 = { version = "0.13.0", optional = true } +imgui = "0.7.0" +imgui-gfx-renderer = { version = "0.7.0", optional = true } +imgui-winit-support = { version = "0.7.0", default-features = false, features = ["winit-23"], optional = true } image = { version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"] } itertools = "0.9.0" lazy_static = "1.4.0" @@ -67,8 +66,7 @@ num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" pretty_env_logger = "0.4.0" -sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures", "bundled", "gfx"] } -sdl2-sys = { version = "0.34", optional = true, features = ["bundled", "gfx"] } +sdl2 = { version = "0.34", optional = true, features = ["unsafe_textures", "bundled", "gfx", "static-link"] } serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_yaml = "0.8" diff --git a/src/common.rs b/src/common.rs index b2acea1..44f425c 100644 --- a/src/common.rs +++ b/src/common.rs @@ -388,22 +388,6 @@ pub struct Color { 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 { diff --git a/src/framework/backend.rs b/src/framework/backend.rs index be95261..13975fd 100644 --- a/src/framework/backend.rs +++ b/src/framework/backend.rs @@ -52,5 +52,6 @@ pub fn init_backend() -> GameResult> { pub enum SpriteBatchCommand { DrawRect(Rect, Rect), + DrawRectFlip(Rect, Rect, bool, bool), DrawRectTinted(Rect, Rect, Color), } diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index 168db77..e96aa48 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -3,22 +3,23 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -use imgui::{DrawCmd, DrawData, ImString, TextureId, Ui}; use imgui::internal::RawWrapper; -use sdl2::{EventPump, keyboard, pixels, Sdl}; +use imgui::{ConfigFlags, DrawCmd, DrawData, ImString, Key, MouseCursor, TextureId, Ui}; use sdl2::event::{Event, WindowEvent}; -use sdl2::gfx::primitives::DrawRenderer; use sdl2::keyboard::Scancode; +use sdl2::mouse::{Cursor, SystemCursor}; use sdl2::pixels::PixelFormatEnum; use sdl2::render::{Texture, TextureCreator, WindowCanvas}; -use sdl2::surface::Surface; use sdl2::video::WindowContext; +use sdl2::{keyboard, pixels, EventPump, Sdl}; use crate::common::Color; -use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; +use crate::framework::backend::{ + Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand, +}; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; -use crate::framework::graphics::{BlendMode, imgui_context}; +use crate::framework::graphics::{imgui_context, BlendMode}; use crate::framework::keyboard::ScanCode; use crate::framework::ui::init_imgui; use crate::Game; @@ -31,9 +32,7 @@ impl SDL2Backend { pub fn new() -> GameResult> { let context = sdl2::init().map_err(|e| GameError::WindowError(e))?; - let backend = SDL2Backend { - context, - }; + let backend = SDL2Backend { context }; Ok(Box::new(backend)) } @@ -62,13 +61,15 @@ impl SDL2EventLoop { let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?; let video = sdl.video().map_err(|e| GameError::WindowError(e))?; - let window = video.window("Cave Story (doukutsu-rs)", 640, 480) + let window = video + .window("Cave Story (doukutsu-rs)", 640, 480) .position_centered() .resizable() .build() .map_err(|e| GameError::WindowError(e.to_string()))?; - let canvas = window.into_canvas() + let canvas = window + .into_canvas() .accelerated() .present_vsync() .build() @@ -96,7 +97,10 @@ impl BackendEventLoop for SDL2EventLoop { let (imgui, imgui_sdl2) = unsafe { let renderer: &Box = std::mem::transmute(ctx.renderer.as_ref().unwrap()); - (&mut *renderer.imgui.as_ptr(), &mut *renderer.imgui_event.as_ptr()) + ( + &mut *renderer.imgui.as_ptr(), + &mut *renderer.imgui_event.as_ptr(), + ) }; { @@ -115,27 +119,30 @@ impl BackendEventLoop for SDL2EventLoop { Event::Quit { .. } => { state.shutdown(); } - Event::Window { win_event, .. } => { - match win_event { - WindowEvent::Shown => {} - WindowEvent::Hidden => {} - WindowEvent::SizeChanged(width, height) => { - ctx.screen_size = (width.max(1) as f32, height.max(1) as f32); + Event::Window { win_event, .. } => match win_event { + WindowEvent::Shown => {} + WindowEvent::Hidden => {} + WindowEvent::SizeChanged(width, height) => { + ctx.screen_size = (width.max(1) as f32, height.max(1) as f32); - if let Some(renderer) = ctx.renderer.as_ref() { - if let Ok(imgui) = renderer.imgui() { - imgui.io_mut().display_size = [ctx.screen_size.0, ctx.screen_size.1]; - } + if let Some(renderer) = ctx.renderer.as_ref() { + if let Ok(imgui) = renderer.imgui() { + imgui.io_mut().display_size = + [ctx.screen_size.0, ctx.screen_size.1]; } - state.handle_resize(ctx); } - _ => {} + state.handle_resize(ctx); } - } - Event::KeyDown { scancode, repeat, .. } => { + _ => {} + }, + Event::KeyDown { + scancode, repeat, .. + } => { if let Some(scancode) = scancode { if let Some(drs_scan) = conv_scancode(scancode) { - game.key_down_event(drs_scan, repeat); + if !repeat { + state.process_debug_keys(drs_scan); + } ctx.keyboard_context.set_key(drs_scan, true); } } @@ -167,7 +174,11 @@ impl BackendEventLoop for SDL2EventLoop { state.frame_time = 0.0; } - imgui_sdl2.prepare_frame(imgui.io_mut(), self.refs.borrow().canvas.window(), &self.event_pump.mouse_state()); + imgui_sdl2.prepare_frame( + imgui.io_mut(), + self.refs.borrow().canvas.window(), + &self.event_pump.mouse_state(), + ); game.draw(ctx).unwrap(); } } @@ -180,7 +191,7 @@ impl BackendEventLoop for SDL2EventLoop { struct SDL2Renderer { refs: Rc>, imgui: Rc>, - imgui_event: Rc>, + imgui_event: Rc>, imgui_textures: HashMap, } @@ -192,42 +203,49 @@ impl SDL2Renderer { imgui.set_renderer_name(ImString::new("SDL2Renderer")); { - let mut refs = refs.clone(); + let refs = refs.clone(); let mut fonts = imgui.fonts(); let id = fonts.tex_id; let font_tex = fonts.build_rgba32_texture(); - let mut texture = refs.borrow_mut().texture_creator + let mut texture = refs + .borrow_mut() + .texture_creator .create_texture_streaming(PixelFormatEnum::RGBA32, font_tex.width, font_tex.height) .map_err(|e| GameError::RenderError(e.to_string()))?; texture.set_blend_mode(sdl2::render::BlendMode::Blend); - texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { - for y in 0..(font_tex.height as usize) { - for x in 0..(font_tex.width as usize) { - let offset = y * pitch + x * 4; - let data_offset = (y * font_tex.width as usize + x) * 4; + texture + .with_lock(None, |buffer: &mut [u8], pitch: usize| { + for y in 0..(font_tex.height as usize) { + for x in 0..(font_tex.width as usize) { + let offset = y * pitch + x * 4; + let data_offset = (y * font_tex.width as usize + x) * 4; - buffer[offset] = font_tex.data[data_offset]; - buffer[offset + 1] = font_tex.data[data_offset + 1]; - buffer[offset + 2] = font_tex.data[data_offset + 2]; - buffer[offset + 3] = font_tex.data[data_offset + 3]; + buffer[offset] = font_tex.data[data_offset]; + buffer[offset + 1] = font_tex.data[data_offset + 1]; + buffer[offset + 2] = font_tex.data[data_offset + 2]; + buffer[offset + 3] = font_tex.data[data_offset + 3]; + } } - } - }).map_err(|e| GameError::RenderError(e.to_string()))?; + }) + .map_err(|e| GameError::RenderError(e.to_string()))?; - imgui_textures.insert(id, SDL2Texture { - refs: refs.clone(), - texture: Some(texture), - width: font_tex.width as u16, - height: font_tex.height as u16, - commands: vec![], - }); + imgui_textures.insert( + id, + SDL2Texture { + refs: refs.clone(), + texture: Some(texture), + width: font_tex.width as u16, + height: font_tex.height as u16, + commands: vec![], + }, + ); } let imgui_sdl2 = unsafe { let refs = &mut *refs.as_ptr(); - imgui_sdl2::ImguiSdl2::new(&mut imgui, refs.canvas.window()) + ImguiSdl2::new(&mut imgui, refs.canvas.window()) }; Ok(Box::new(SDL2Renderer { @@ -244,7 +262,10 @@ fn to_sdl(color: Color) -> pixels::Color { pixels::Color::RGBA(r, g, b, a) } -unsafe fn set_raw_target(renderer: *mut sdl2::sys::SDL_Renderer, raw_texture: *mut sdl2::sys::SDL_Texture) -> GameResult { +unsafe fn set_raw_target( + renderer: *mut sdl2::sys::SDL_Renderer, + raw_texture: *mut sdl2::sys::SDL_Texture, +) -> GameResult { if sdl2::sys::SDL_SetRenderTarget(renderer, raw_texture) == 0 { Ok(()) } else { @@ -253,11 +274,23 @@ unsafe fn set_raw_target(renderer: *mut sdl2::sys::SDL_Renderer, raw_texture: *m } fn min3(x: f32, y: f32, z: f32) -> f32 { - if x < y && x < z { x } else if y < z { y } else { z } + if x < y && x < z { + x + } else if y < z { + y + } else { + z + } } fn max3(x: f32, y: f32, z: f32) -> f32 { - if x > y && x > z { x } else if y > z { y } else { z } + if x > y && x > z { + x + } else if y > z { + y + } else { + z + } } impl BackendRenderer for SDL2Renderer { @@ -276,10 +309,15 @@ impl BackendRenderer for SDL2Renderer { Ok(()) } - fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult> { - let mut refs = self.refs.borrow_mut(); + fn create_texture_mutable( + &mut self, + width: u16, + height: u16, + ) -> GameResult> { + let refs = self.refs.borrow_mut(); - let mut texture = refs.texture_creator + let texture = refs + .texture_creator .create_texture_target(PixelFormatEnum::RGBA32, width as u32, height as u32) .map_err(|e| GameError::RenderError(e.to_string()))?; @@ -292,27 +330,35 @@ impl BackendRenderer for SDL2Renderer { })) } - fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult> { - let mut refs = self.refs.borrow_mut(); + fn create_texture( + &mut self, + width: u16, + height: u16, + data: &[u8], + ) -> GameResult> { + let refs = self.refs.borrow_mut(); - let mut texture = refs.texture_creator + let mut texture = refs + .texture_creator .create_texture_streaming(PixelFormatEnum::RGBA32, width as u32, height as u32) .map_err(|e| GameError::RenderError(e.to_string()))?; texture.set_blend_mode(sdl2::render::BlendMode::Blend); - texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { - for y in 0..(height as usize) { - for x in 0..(width as usize) { - let offset = y * pitch + x * 4; - let data_offset = (y * width as usize + x) * 4; + texture + .with_lock(None, |buffer: &mut [u8], pitch: usize| { + for y in 0..(height as usize) { + for x in 0..(width as usize) { + let offset = y * pitch + x * 4; + let data_offset = (y * width as usize + x) * 4; - buffer[offset] = data[data_offset]; - buffer[offset + 1] = data[data_offset + 1]; - buffer[offset + 2] = data[data_offset + 2]; - buffer[offset + 3] = data[data_offset + 3]; + buffer[offset] = data[data_offset]; + buffer[offset + 1] = data[data_offset + 1]; + buffer[offset + 2] = data[data_offset + 2]; + buffer[offset + 3] = data[data_offset + 3]; + } } - } - }).map_err(|e| GameError::RenderError(e.to_string()))?; + }) + .map_err(|e| GameError::RenderError(e.to_string()))?; Ok(Box::new(SDL2Texture { refs: self.refs.clone(), @@ -344,23 +390,21 @@ impl BackendRenderer for SDL2Renderer { let sdl2_texture: &Box = std::mem::transmute(texture); if let Some(target) = sdl2_texture.texture.as_ref() { - set_raw_target(renderer, target.raw()); + set_raw_target(renderer, target.raw())?; } else { - set_raw_target(renderer, std::ptr::null_mut()); + set_raw_target(renderer, std::ptr::null_mut())?; } - } + }, None => unsafe { - set_raw_target(renderer, std::ptr::null_mut()); - } + set_raw_target(renderer, std::ptr::null_mut())?; + }, } Ok(()) } fn imgui(&self) -> GameResult<&mut imgui::Context> { - unsafe { - Ok(&mut *self.imgui.as_ptr()) - } + unsafe { Ok(&mut *self.imgui.as_ptr()) } } fn render_imgui(&mut self, draw_data: &DrawData) -> GameResult { @@ -391,9 +435,12 @@ impl BackendRenderer for SDL2Renderer { continue; } - let v1 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i] as usize]; - let v2 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 1] as usize]; - let v3 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 2] as usize]; + let v1 = draw_list.vtx_buffer()[cmd_params.vtx_offset + + idx_buffer[cmd_params.idx_offset + i] as usize]; + let v2 = draw_list.vtx_buffer()[cmd_params.vtx_offset + + idx_buffer[cmd_params.idx_offset + i + 1] as usize]; + let v3 = draw_list.vtx_buffer()[cmd_params.vtx_offset + + idx_buffer[cmd_params.idx_offset + i + 2] as usize]; vert_x[0] = (v1.pos[0] - 0.5) as i16; vert_y[0] = (v1.pos[1] - 0.5) as i16; @@ -404,27 +451,30 @@ impl BackendRenderer for SDL2Renderer { #[allow(clippy::float_cmp)] if i < count - 3 { - let v4 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 3] as usize]; - let v5 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 4] as usize]; - let v6 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 5] as usize]; + let v4 = draw_list.vtx_buffer()[cmd_params.vtx_offset + + idx_buffer[cmd_params.idx_offset + i + 3] as usize]; + let v5 = draw_list.vtx_buffer()[cmd_params.vtx_offset + + idx_buffer[cmd_params.idx_offset + i + 4] as usize]; + let v6 = draw_list.vtx_buffer()[cmd_params.vtx_offset + + idx_buffer[cmd_params.idx_offset + i + 5] as usize]; min[0] = min3(v1.pos[0], v2.pos[0], v3.pos[0]); min[1] = min3(v1.pos[1], v2.pos[1], v3.pos[1]); max[0] = max3(v1.pos[0], v2.pos[0], v3.pos[0]); max[1] = max3(v1.pos[1], v2.pos[1], v3.pos[1]); - is_rect = (v1.pos[0] == min[0] || v1.pos[0] == max[0]) && - (v1.pos[1] == min[1] || v1.pos[1] == max[1]) && - (v2.pos[0] == min[0] || v2.pos[0] == max[0]) && - (v2.pos[1] == min[1] || v2.pos[1] == max[1]) && - (v3.pos[0] == min[0] || v3.pos[0] == max[0]) && - (v3.pos[1] == min[1] || v3.pos[1] == max[1]) && - (v4.pos[0] == min[0] || v4.pos[0] == max[0]) && - (v4.pos[1] == min[1] || v4.pos[1] == max[1]) && - (v5.pos[0] == min[0] || v5.pos[0] == max[0]) && - (v5.pos[1] == min[1] || v5.pos[1] == max[1]) && - (v6.pos[0] == min[0] || v6.pos[0] == max[0]) && - (v6.pos[1] == min[1] || v6.pos[1] == max[1]); + is_rect = (v1.pos[0] == min[0] || v1.pos[0] == max[0]) + && (v1.pos[1] == min[1] || v1.pos[1] == max[1]) + && (v2.pos[0] == min[0] || v2.pos[0] == max[0]) + && (v2.pos[1] == min[1] || v2.pos[1] == max[1]) + && (v3.pos[0] == min[0] || v3.pos[0] == max[0]) + && (v3.pos[1] == min[1] || v3.pos[1] == max[1]) + && (v4.pos[0] == min[0] || v4.pos[0] == max[0]) + && (v4.pos[1] == min[1] || v4.pos[1] == max[1]) + && (v5.pos[0] == min[0] || v5.pos[0] == max[0]) + && (v5.pos[1] == min[1] || v5.pos[1] == max[1]) + && (v6.pos[0] == min[0] || v6.pos[0] == max[0]) + && (v6.pos[1] == min[1] || v6.pos[1] == max[1]); if is_rect { tex_pos[0] = min3(v1.uv[0], v2.uv[0], v3.uv[0]); @@ -434,35 +484,40 @@ impl BackendRenderer for SDL2Renderer { } } - if let Some(surf) = self.imgui_textures.get_mut(&cmd_params.texture_id) { - unsafe { - if is_rect { - let src = sdl2::rect::Rect::new((tex_pos[0] * surf.width as f32) as i32, - (tex_pos[1] * surf.height as f32) as i32, - ((tex_pos[2] - tex_pos[0]) * surf.width as f32) as u32, - ((tex_pos[3] - tex_pos[1]) * surf.height as f32) as u32); - let dest = sdl2::rect::Rect::new(min[0] as i32, - min[1] as i32, - (max[0] - min[0]) as u32, - (max[1] - min[1]) as u32); + if let Some(surf) = self.imgui_textures.get_mut(&cmd_params.texture_id) + { + if is_rect { + let src = sdl2::rect::Rect::new( + (tex_pos[0] * surf.width as f32) as i32, + (tex_pos[1] * surf.height as f32) as i32, + ((tex_pos[2] - tex_pos[0]) * surf.width as f32) as u32, + ((tex_pos[3] - tex_pos[1]) * surf.height as f32) as u32, + ); + let dest = sdl2::rect::Rect::new( + min[0] as i32, + min[1] as i32, + (max[0] - min[0]) as u32, + (max[1] - min[1]) as u32, + ); - let tex = surf.texture.as_mut().unwrap(); - tex.set_color_mod(v1.col[0], v1.col[1], v1.col[2]); - tex.set_alpha_mod(v1.col[3]); + let tex = surf.texture.as_mut().unwrap(); + tex.set_color_mod(v1.col[0], v1.col[1], v1.col[2]); + tex.set_alpha_mod(v1.col[3]); - refs.canvas.copy(tex, src, dest); - } else { - sdl2::sys::gfx::primitives::filledPolygonRGBA( - refs.canvas.raw(), - vert_x.as_ptr(), - vert_y.as_ptr(), - 3, - v1.col[0], - v1.col[1], - v1.col[2], - v1.col[3], - ); - } + refs.canvas + .copy(tex, src, dest) + .map_err(|e| GameError::RenderError(e.to_string()))?; + } else { + /*sdl2::sys::gfx::primitives::filledPolygonRGBA( + refs.canvas.raw(), + vert_x.as_ptr(), + vert_y.as_ptr(), + 3, + v1.col[0], + v1.col[1], + v1.col[2], + v1.col[3], + );*/ } } } @@ -472,7 +527,7 @@ impl BackendRenderer for SDL2Renderer { DrawCmd::ResetRenderState => {} DrawCmd::RawCallback { callback, raw_cmd } => unsafe { callback(draw_list.raw(), raw_cmd) - } + }, } } } @@ -480,7 +535,7 @@ impl BackendRenderer for SDL2Renderer { Ok(()) } - fn prepare_frame<'ui>(&self, ui: &Ui<'ui>) -> GameResult { + fn prepare_frame<'ui>(&self, _ui: &Ui<'ui>) -> GameResult { Ok(()) } } @@ -520,9 +575,22 @@ impl BackendTexture for SDL2Texture { texture.set_alpha_mod(255); texture.set_blend_mode(refs.blend_mode); - refs.canvas.copy(texture, - Some(sdl2::rect::Rect::new(src.left.round() as i32, src.top.round() as i32, src.width().round() as u32, src.height().round() as u32)), - Some(sdl2::rect::Rect::new(dest.left.round() as i32, dest.top.round() as i32, dest.width().round() as u32, dest.height().round() as u32))) + refs.canvas + .copy( + texture, + Some(sdl2::rect::Rect::new( + src.left.round() as i32, + src.top.round() as i32, + src.width().round() as u32, + src.height().round() as u32, + )), + Some(sdl2::rect::Rect::new( + dest.left.round() as i32, + dest.top.round() as i32, + dest.width().round() as u32, + dest.height().round() as u32, + )), + ) .map_err(|e| GameError::RenderError(e.to_string()))?; } SpriteBatchCommand::DrawRectTinted(src, dest, color) => { @@ -531,9 +599,49 @@ impl BackendTexture for SDL2Texture { texture.set_alpha_mod(a); texture.set_blend_mode(refs.blend_mode); - refs.canvas.copy(texture, - Some(sdl2::rect::Rect::new(src.left.round() as i32, src.top.round() as i32, src.width().round() as u32, src.height().round() as u32)), - Some(sdl2::rect::Rect::new(dest.left.round() as i32, dest.top.round() as i32, dest.width().round() as u32, dest.height().round() as u32))) + refs.canvas + .copy( + texture, + Some(sdl2::rect::Rect::new( + src.left.round() as i32, + src.top.round() as i32, + src.width().round() as u32, + src.height().round() as u32, + )), + Some(sdl2::rect::Rect::new( + dest.left.round() as i32, + dest.top.round() as i32, + dest.width().round() as u32, + dest.height().round() as u32, + )), + ) + .map_err(|e| GameError::RenderError(e.to_string()))?; + } + SpriteBatchCommand::DrawRectFlip(src, dest, flip_x, flip_y) => { + texture.set_color_mod(255, 255, 255); + texture.set_alpha_mod(255); + texture.set_blend_mode(refs.blend_mode); + + refs.canvas + .copy_ex( + texture, + Some(sdl2::rect::Rect::new( + src.left.round() as i32, + src.top.round() as i32, + src.width().round() as u32, + src.height().round() as u32, + )), + Some(sdl2::rect::Rect::new( + dest.left.round() as i32, + dest.top.round() as i32, + dest.width().round() as u32, + dest.height().round() as u32, + )), + 0.0, + None, + *flip_x, + *flip_y, + ) .map_err(|e| GameError::RenderError(e.to_string()))?; } } @@ -551,7 +659,9 @@ impl Drop for SDL2Texture { mem::swap(&mut self.texture, &mut texture_opt); if let Some(texture) = texture_opt { - unsafe { texture.destroy(); } + unsafe { + texture.destroy(); + } } } } @@ -699,3 +809,219 @@ fn conv_scancode(code: keyboard::Scancode) -> Option { _ => None, } } + +// based on imgui-sdl2 crate +pub struct ImguiSdl2 { + mouse_press: [bool; 5], + ignore_mouse: bool, + ignore_keyboard: bool, + cursor: Option, + sdl_cursor: Option, +} + +struct Sdl2ClipboardBackend(sdl2::clipboard::ClipboardUtil); + +impl imgui::ClipboardBackend for Sdl2ClipboardBackend { + fn get(&mut self) -> Option { + if !self.0.has_clipboard_text() { + return None; + } + + self.0.clipboard_text().ok().map(imgui::ImString::new) + } + + fn set(&mut self, value: &imgui::ImStr) { + let _ = self.0.set_clipboard_text(value.to_str()); + } +} + +impl ImguiSdl2 { + pub fn new(imgui: &mut imgui::Context, window: &sdl2::video::Window) -> Self { + let clipboard_util = window.subsystem().clipboard(); + imgui.set_clipboard_backend(Box::new(Sdl2ClipboardBackend(clipboard_util))); + + imgui.io_mut().key_map[Key::Tab as usize] = Scancode::Tab as u32; + imgui.io_mut().key_map[Key::LeftArrow as usize] = Scancode::Left as u32; + imgui.io_mut().key_map[Key::RightArrow as usize] = Scancode::Right as u32; + imgui.io_mut().key_map[Key::UpArrow as usize] = Scancode::Up as u32; + imgui.io_mut().key_map[Key::DownArrow as usize] = Scancode::Down as u32; + imgui.io_mut().key_map[Key::PageUp as usize] = Scancode::PageUp as u32; + imgui.io_mut().key_map[Key::PageDown as usize] = Scancode::PageDown as u32; + imgui.io_mut().key_map[Key::Home as usize] = Scancode::Home as u32; + imgui.io_mut().key_map[Key::End as usize] = Scancode::End as u32; + imgui.io_mut().key_map[Key::Delete as usize] = Scancode::Delete as u32; + imgui.io_mut().key_map[Key::Backspace as usize] = Scancode::Backspace as u32; + imgui.io_mut().key_map[Key::Enter as usize] = Scancode::Return as u32; + imgui.io_mut().key_map[Key::Escape as usize] = Scancode::Escape as u32; + imgui.io_mut().key_map[Key::Space as usize] = Scancode::Space as u32; + imgui.io_mut().key_map[Key::A as usize] = Scancode::A as u32; + imgui.io_mut().key_map[Key::C as usize] = Scancode::C as u32; + imgui.io_mut().key_map[Key::V as usize] = Scancode::V as u32; + imgui.io_mut().key_map[Key::X as usize] = Scancode::X as u32; + imgui.io_mut().key_map[Key::Y as usize] = Scancode::Y as u32; + imgui.io_mut().key_map[Key::Z as usize] = Scancode::Z as u32; + + Self { + mouse_press: [false; 5], + ignore_keyboard: false, + ignore_mouse: false, + cursor: None, + sdl_cursor: None, + } + } + + pub fn ignore_event(&self, event: &Event) -> bool { + match *event { + Event::KeyDown { .. } + | Event::KeyUp { .. } + | Event::TextEditing { .. } + | Event::TextInput { .. } => self.ignore_keyboard, + Event::MouseMotion { .. } + | Event::MouseButtonDown { .. } + | Event::MouseButtonUp { .. } + | Event::MouseWheel { .. } + | Event::FingerDown { .. } + | Event::FingerUp { .. } + | Event::FingerMotion { .. } + | Event::DollarGesture { .. } + | Event::DollarRecord { .. } + | Event::MultiGesture { .. } => self.ignore_mouse, + _ => false, + } + } + + pub fn handle_event(&mut self, imgui: &mut imgui::Context, event: &Event) { + use sdl2::keyboard; + use sdl2::mouse::MouseButton; + + fn set_mod(imgui: &mut imgui::Context, keymod: keyboard::Mod) { + let ctrl = keymod.intersects(keyboard::Mod::RCTRLMOD | keyboard::Mod::LCTRLMOD); + let alt = keymod.intersects(keyboard::Mod::RALTMOD | keyboard::Mod::LALTMOD); + let shift = keymod.intersects(keyboard::Mod::RSHIFTMOD | keyboard::Mod::LSHIFTMOD); + let super_ = keymod.intersects(keyboard::Mod::RGUIMOD | keyboard::Mod::LGUIMOD); + + imgui.io_mut().key_ctrl = ctrl; + imgui.io_mut().key_alt = alt; + imgui.io_mut().key_shift = shift; + imgui.io_mut().key_super = super_; + } + + match *event { + Event::MouseWheel { y, .. } => { + imgui.io_mut().mouse_wheel = y as f32; + } + Event::MouseButtonDown { mouse_btn, .. } => { + if mouse_btn != MouseButton::Unknown { + let index = match mouse_btn { + MouseButton::Left => 0, + MouseButton::Right => 1, + MouseButton::Middle => 2, + MouseButton::X1 => 3, + MouseButton::X2 => 4, + MouseButton::Unknown => unreachable!(), + }; + self.mouse_press[index] = true; + } + } + Event::TextInput { ref text, .. } => { + for chr in text.chars() { + imgui.io_mut().add_input_character(chr); + } + } + Event::KeyDown { + scancode, keymod, .. + } => { + set_mod(imgui, keymod); + if let Some(scancode) = scancode { + imgui.io_mut().keys_down[scancode as usize] = true; + } + } + Event::KeyUp { + scancode, keymod, .. + } => { + set_mod(imgui, keymod); + if let Some(scancode) = scancode { + imgui.io_mut().keys_down[scancode as usize] = false; + } + } + _ => {} + } + } + + pub fn prepare_frame( + &mut self, + io: &mut imgui::Io, + window: &sdl2::video::Window, + mouse_state: &sdl2::mouse::MouseState, + ) { + let mouse_util = window.subsystem().sdl().mouse(); + + let (win_w, win_h) = window.size(); + let (draw_w, draw_h) = window.drawable_size(); + + io.display_size = [win_w as f32, win_h as f32]; + io.display_framebuffer_scale = [ + (draw_w as f32) / (win_w as f32), + (draw_h as f32) / (win_h as f32), + ]; + + // Merging the mousedown events we received into the current state prevents us from missing + // clicks that happen faster than a frame + io.mouse_down = [ + self.mouse_press[0] || mouse_state.left(), + self.mouse_press[1] || mouse_state.right(), + self.mouse_press[2] || mouse_state.middle(), + self.mouse_press[3] || mouse_state.x1(), + self.mouse_press[4] || mouse_state.x2(), + ]; + self.mouse_press = [false; 5]; + + let any_mouse_down = io.mouse_down.iter().any(|&b| b); + mouse_util.capture(any_mouse_down); + + io.mouse_pos = [mouse_state.x() as f32, mouse_state.y() as f32]; + + self.ignore_keyboard = io.want_capture_keyboard; + self.ignore_mouse = io.want_capture_mouse; + } + + pub fn prepare_render(&mut self, ui: &imgui::Ui, window: &sdl2::video::Window) { + let io = ui.io(); + if !io + .config_flags + .contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE) + { + let mouse_util = window.subsystem().sdl().mouse(); + + match ui.mouse_cursor() { + Some(mouse_cursor) if !io.mouse_draw_cursor => { + mouse_util.show_cursor(true); + + let sdl_cursor = match mouse_cursor { + MouseCursor::Arrow => SystemCursor::Arrow, + MouseCursor::TextInput => SystemCursor::IBeam, + MouseCursor::ResizeAll => SystemCursor::SizeAll, + MouseCursor::ResizeNS => SystemCursor::SizeNS, + MouseCursor::ResizeEW => SystemCursor::SizeWE, + MouseCursor::ResizeNESW => SystemCursor::SizeNESW, + MouseCursor::ResizeNWSE => SystemCursor::SizeNWSE, + MouseCursor::Hand => SystemCursor::Hand, + MouseCursor::NotAllowed => SystemCursor::No, + }; + + if self.cursor != Some(mouse_cursor) { + let sdl_cursor = Cursor::from_system(sdl_cursor).unwrap(); + sdl_cursor.set(); + self.cursor = Some(mouse_cursor); + self.sdl_cursor = Some(sdl_cursor); + } + } + _ => { + self.cursor = None; + self.sdl_cursor = None; + mouse_util.show_cursor(false); + } + } + } + } +} diff --git a/src/framework/context.rs b/src/framework/context.rs index 0868543..3a2688d 100644 --- a/src/framework/context.rs +++ b/src/framework/context.rs @@ -1,4 +1,4 @@ -use crate::framework::backend::{Backend, init_backend, BackendRenderer}; +use crate::framework::backend::{init_backend, BackendRenderer}; use crate::framework::error::GameResult; use crate::framework::filesystem::Filesystem; use crate::Game; diff --git a/src/framework/filesystem.rs b/src/framework/filesystem.rs index aff2397..09d80b7 100644 --- a/src/framework/filesystem.rs +++ b/src/framework/filesystem.rs @@ -71,7 +71,7 @@ impl 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(); + let user_overlay = vfs::OverlayFS::new(); Filesystem { vfs: overlay, diff --git a/src/lib.rs b/src/lib.rs index b46ddbb..bae596c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,7 +138,7 @@ impl Game { let state_ref = unsafe { &mut *self.state.get() }; if state_ref.timing_mode != TimingMode::FrameSynchronized { - let mut elapsed = self.start_time.elapsed().as_nanos(); + let elapsed = self.start_time.elapsed().as_nanos(); #[cfg(target_os = "windows")] { // Even with the non-monotonic Instant mitigation at the start of the event loop, there's still a chance of it not working. @@ -177,31 +177,6 @@ impl Game { graphics::present(ctx)?; Ok(()) } - - fn key_down_event(&mut self, key_code: ScanCode, repeat: bool) { - if repeat { return; } - - let state = unsafe { &mut *self.state.get() }; - match key_code { - 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); - } - } - ScanCode::F9 => { - if state.settings.speed < 3.0 { - state.set_speed(state.settings.speed + 0.1); - } - } - 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 } - _ => {} - } - } } #[cfg(target_os = "android")] @@ -321,28 +296,7 @@ pub fn init() -> GameResult { } state_ref.next_scene = Some(Box::new(LoadingScene::new())); - context.run(&mut game); - - /* loop { - game.update(&mut context)?; - - if state_ref.shutdown { - log::info!("Shutting down..."); - break; - } - - 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, &mut context).unwrap(); - game.loops = 0; - state_ref.frame_time = 0.0; - } - - std::thread::sleep(std::time::Duration::from_millis(10)); - game.draw(&mut context)?; - }*/ + context.run(&mut game)?; Ok(()) } diff --git a/src/live_debugger.rs b/src/live_debugger.rs index ac20093..f9ee7b0 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -59,7 +59,7 @@ impl LiveDebugger { Window::new(im_str!("Debugger")) .resizable(false) - .collapsed(false, Condition::FirstUseEver) + .collapsed(true, Condition::FirstUseEver) .position([5.0, 5.0], Condition::FirstUseEver) .size([400.0, 170.0], Condition::FirstUseEver) .build(ui, || { diff --git a/src/npc/ai/misc.rs b/src/npc/ai/misc.rs index 97bc61f..21243d2 100644 --- a/src/npc/ai/misc.rs +++ b/src/npc/ai/misc.rs @@ -1587,7 +1587,7 @@ impl NPC { npc.y = self.y - 0x1800; npc.tsc_direction = 10 * self.anim_num + 40; - npc_list.spawn(0, npc); + let _ = npc_list.spawn(0, npc); self.cond.set_explode_die(true); } } @@ -1630,7 +1630,7 @@ impl NPC { npc.direction = Direction::Right; npc.parent_id = self.id; - npc_list.spawn(0x100, npc); + let _ = npc_list.spawn(0x100, npc); } } diff --git a/src/npc/ai/sand_zone.rs b/src/npc/ai/sand_zone.rs index 35bdd52..8eae288 100644 --- a/src/npc/ai/sand_zone.rs +++ b/src/npc/ai/sand_zone.rs @@ -289,7 +289,7 @@ impl NPC { } pub(crate) fn tick_n049_skullhead(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult { - let mut parent = self.get_parent_ref_mut(npc_list); + let parent = self.get_parent_ref_mut(npc_list); if self.action_num > 9 && parent.as_ref().map(|n| n.npc_type == 3).unwrap_or(false) { self.action_num = 3; diff --git a/src/npc/mod.rs b/src/npc/mod.rs index dc77d79..a5c064b 100644 --- a/src/npc/mod.rs +++ b/src/npc/mod.rs @@ -284,6 +284,8 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)> for NP 216 => self.tick_n216_debug_cat(state), 222 => self.tick_n222_prison_bars(state), 227 => self.tick_n227_bucket(state), + 229 => self.tick_n229_red_flowers_sprouts(state), + 230 => self.tick_n230_red_flowers_blooming(state), 234 => self.tick_n234_red_flowers_picked(state), 239 => self.tick_n239_cage_bars(state), 241 => self.tick_n241_critter_red(state, players), diff --git a/src/rng.rs b/src/rng.rs index 39a7fc1..5f65c9b 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -73,7 +73,7 @@ impl Xoroshiro32PlusPlus { pub fn next_u16(&self) -> u16 { let mut state = self.0.get(); - let mut result = (state.0.wrapping_add(state.1)).rotate_left(9).wrapping_add(state.0); + let result = (state.0.wrapping_add(state.1)).rotate_left(9).wrapping_add(state.0); state.1 ^= state.0; state.0 = state.0.rotate_left(13) ^ state.1 ^ (state.1 << 5); diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 77aa954..f1a6b49 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -434,13 +434,14 @@ impl GameScene { }; let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex_name)?; - // switch version uses +1000 face offset to display a flipped version + // switch version uses 1xxx flag to show a flipped version of face let flip = state.textscript_vm.face > 1000; + // x1xx flag shows a talking animation + let talking = (state.textscript_vm.face % 1000) > 100; let face_num = state.textscript_vm.face % 100; - let (scale_x, scale_y) = batch.scale(); - batch.add_rect_scaled(left_pos + 14.0 + if flip { 48.0 } else { 0.0 }, top_pos + 8.0, - scale_x * if flip { -1.0 } else { 1.0 }, scale_y, + batch.add_rect_flip(left_pos + 14.0, top_pos + 8.0, + flip, false, &Rect::new_size( (face_num as u16 % 6) * 48, (face_num as u16 / 6) * 48, diff --git a/src/scene/no_data_scene.rs b/src/scene/no_data_scene.rs index a7b20a4..46037b5 100644 --- a/src/scene/no_data_scene.rs +++ b/src/scene/no_data_scene.rs @@ -19,6 +19,7 @@ impl NoDataScene { } } +#[cfg(target_os = "android")] static REL_URL: &str = "https://github.com/doukutsu-rs/game-data/releases"; impl Scene for NoDataScene { diff --git a/src/scripting/doukutsu.rs b/src/scripting/doukutsu.rs index d2c8326..6bbfebf 100644 --- a/src/scripting/doukutsu.rs +++ b/src/scripting/doukutsu.rs @@ -1,14 +1,13 @@ use lua_ffi::ffi::luaL_Reg; use lua_ffi::{LuaObject, State, c_int}; -use crate::scene::game_scene::GameScene; use crate::scripting::LuaScriptingState; -use crate::shared_game_state::SharedGameState; pub struct Doukutsu { pub ptr: *mut LuaScriptingState, } +#[allow(unused)] impl Doukutsu { pub fn new(ptr: *mut LuaScriptingState) -> Doukutsu { Doukutsu { diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs index 5e24cc2..5c97342 100644 --- a/src/scripting/mod.rs +++ b/src/scripting/mod.rs @@ -1,4 +1,4 @@ -use std::io::{Read, Seek}; +use std::io::Read; use std::ptr::null_mut; @@ -6,8 +6,7 @@ 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 lua_ffi::{c_int, State, ThreadStatus}; use crate::scene::game_scene::GameScene; use crate::scripting::doukutsu::Doukutsu; @@ -117,7 +116,7 @@ impl LuaScriptingState { if filesystem::exists(ctx, "/scripts/") { let mut script_count = 0; - let mut files = filesystem::read_dir(ctx, "/scripts/")? + let files = filesystem::read_dir(ctx, "/scripts/")? .filter(|f| f.to_string_lossy().to_lowercase().ends_with(".lua")); for file in files { diff --git a/src/scripting/player.rs b/src/scripting/player.rs index 4f01ead..4edc822 100644 --- a/src/scripting/player.rs +++ b/src/scripting/player.rs @@ -12,6 +12,7 @@ pub struct LuaPlayer { inv_ptr: *mut Inventory, } +#[allow(unused)] impl LuaPlayer { fn check_ref(&self, state: &mut State) -> bool { if !self.valid_reference { diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 340bb2b..e14c9e2 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -28,7 +28,8 @@ use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM}; use crate::texture_set::TextureSet; use crate::framework::{filesystem, graphics}; use crate::framework::backend::BackendTexture; -use crate::framework::graphics::{create_texture, set_render_target, create_texture_mutable}; +use crate::framework::graphics::{set_render_target, create_texture_mutable}; +use crate::framework::keyboard::ScanCode; #[derive(PartialEq, Eq, Copy, Clone)] pub enum TimingMode { @@ -103,6 +104,7 @@ pub struct SharedGameState { pub npc_super_pos: (i32, i32), pub stages: Vec, pub frame_time: f64, + pub debugger: bool, pub scale: f32, pub canvas_size: (f32, f32), pub screen_size: (f32, f32), @@ -117,7 +119,7 @@ pub struct SharedGameState { pub lua: LuaScriptingState, pub sound_manager: SoundManager, pub settings: Settings, - pub shutdown: bool, + pub shutdown: bool } impl SharedGameState { @@ -168,6 +170,7 @@ impl SharedGameState { npc_super_pos: (0, 0), stages: Vec::with_capacity(96), frame_time: 0.0, + debugger: false, scale: 2.0, screen_size: (640.0, 480.0), canvas_size: (320.0, 240.0), @@ -186,6 +189,28 @@ impl SharedGameState { }) } + pub fn process_debug_keys(&mut self, key_code: ScanCode) { + match key_code { + ScanCode::F3 => { self.settings.god_mode = !self.settings.god_mode } + ScanCode::F4 => { self.settings.infinite_booster = !self.settings.infinite_booster } + ScanCode::F5 => { self.settings.subpixel_coords = !self.settings.subpixel_coords } + ScanCode::F6 => { self.settings.motion_interpolation = !self.settings.motion_interpolation } + ScanCode::F7 => { self.set_speed(1.0) } + ScanCode::F8 => { + if self.settings.speed > 0.2 { + self.set_speed(self.settings.speed - 0.1); + } + } + ScanCode::F9 => { + if self.settings.speed < 3.0 { + self.set_speed(self.settings.speed + 0.1); + } + } + ScanCode::F10 => { self.settings.debug_outlines = !self.settings.debug_outlines } + _ => {} + } + } + pub fn reload_textures(&mut self) { let mut texture_set = TextureSet::new(self.base_path.as_str()); @@ -307,10 +332,6 @@ impl SharedGameState { pub fn set_speed(&mut self, value: f64) { self.settings.speed = clamp(value, 0.1, 3.0); self.frame_time = 0.0; - - if let Err(err) = self.sound_manager.set_speed(value as f32) { - log::error!("Error while sending a message to sound manager: {}", err); - } } pub fn current_tps(&self) -> f64 { diff --git a/src/sound/mod.rs b/src/sound/mod.rs index 4ce7c7d..6b1f170 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -8,7 +8,7 @@ use num_traits::clamp; use crate::engine_constants::EngineConstants; use crate::framework::context::Context; -use crate::framework::error::{GameResult, GameError}; +use crate::framework::error::{GameResult}; use crate::framework::filesystem; use crate::sound::organya::Song; use crate::sound::pixtone::PixTonePlayback; @@ -16,7 +16,6 @@ use crate::sound::playback::{PlaybackEngine, SavedPlaybackState}; use crate::sound::wave_bank::SoundBank; use crate::str; use crate::framework::error::GameError::{AudioError, ResourceLoadError, InvalidValue}; -use std::io::Error; mod wave_bank; mod organya; diff --git a/src/sound/playback.rs b/src/sound/playback.rs index e5cde85..3f51974 100644 --- a/src/sound/playback.rs +++ b/src/sound/playback.rs @@ -166,6 +166,7 @@ impl PlaybackEngine { self.play_pos = position; } + #[allow(unused)] pub fn get_total_samples(&self) -> u32 { let ticks_intro = self.song.time.loop_range.start; let ticks_loop = self.song.time.loop_range.end - self.song.time.loop_range.start; diff --git a/src/text_script.rs b/src/text_script.rs index 58340a3..91bf259 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -1751,6 +1751,7 @@ impl TextScript { } } + #[allow(unused)] fn read_varint>(iter: &mut I) -> GameResult { let mut result = 0u32; diff --git a/src/texture_set.rs b/src/texture_set.rs index 61a70d6..b206af7 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use log::info; use crate::common; -use crate::common::{FILE_TYPES, Point, Rect}; +use crate::common::{FILE_TYPES, Rect}; use crate::engine_constants::EngineConstants; use crate::framework::backend::{BackendTexture, SpriteBatchCommand}; use crate::framework::context::Context; @@ -94,12 +94,40 @@ impl SizedBatch { self.add_rect_scaled(x, y, 1.0, 1.0, rect) } + pub fn add_rect_flip(&mut self, mut x: f32, mut y: f32, flip_x: bool, flip_y: bool, rect: &common::Rect) { + if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { + return; + } + + unsafe { + x = (x * G_MAG).round() / G_MAG; + y = (y * G_MAG).round() / G_MAG; + } + let mag = unsafe { I_MAG }; + + self.batch.add(SpriteBatchCommand::DrawRectFlip( + Rect { + left: rect.left as f32 / self.scale_x, + top: rect.top as f32 / self.scale_y, + right: rect.right as f32 / self.scale_x, + bottom: rect.bottom as f32 / self.scale_y, + }, + Rect { + left: x * mag, + top: y * mag, + right: (x + rect.width() as f32) * mag, + bottom: (y + rect.height() as f32) * mag, + }, + flip_x, flip_y + )); + } + #[inline(always)] pub fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect) { self.add_rect_scaled_tinted(x, y, color, 1.0, 1.0, rect) } - pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, mut scale_x: f32, mut scale_y: f32, rect: &common::Rect) { + pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect) { if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { return; } @@ -126,7 +154,7 @@ impl SizedBatch { )); } - pub fn add_rect_scaled_tinted(&mut self, mut x: f32, mut y: f32, color: (u8, u8, u8, u8), mut scale_x: f32, mut scale_y: f32, rect: &common::Rect) { + pub fn add_rect_scaled_tinted(&mut self, mut x: f32, mut y: f32, color: (u8, u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect) { if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { return; } diff --git a/src/weapon.rs b/src/weapon.rs index d2b2687..ce959cb 100644 --- a/src/weapon.rs +++ b/src/weapon.rs @@ -384,7 +384,7 @@ impl Weapon { fn tick_spur(&mut self, player: &mut Player, player_id: TargetPlayer, inventory: &mut Inventory, bullet_manager: &mut BulletManager, state: &mut SharedGameState) { let mut shoot = false; - let mut btype = 0; + let mut btype; if player.controller.shoot() { inventory.add_xp(if player.equip.has_turbocharge() { 3 } else { 2 }, player, state); From 2ccfb0694358ace59f48f56ee644b5587632ee17 Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Wed, 10 Feb 2021 13:12:38 +0100 Subject: [PATCH 7/8] fix building on windoze --- src/lib.rs | 2 +- src/scene/no_data_scene.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bae596c..55bbd26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,7 +138,7 @@ impl Game { let state_ref = unsafe { &mut *self.state.get() }; if state_ref.timing_mode != TimingMode::FrameSynchronized { - let elapsed = self.start_time.elapsed().as_nanos(); + let mut elapsed = self.start_time.elapsed().as_nanos(); #[cfg(target_os = "windows")] { // Even with the non-monotonic Instant mitigation at the start of the event loop, there's still a chance of it not working. diff --git a/src/scene/no_data_scene.rs b/src/scene/no_data_scene.rs index 46037b5..853aaab 100644 --- a/src/scene/no_data_scene.rs +++ b/src/scene/no_data_scene.rs @@ -24,7 +24,6 @@ static REL_URL: &str = "https://github.com/doukutsu-rs/game-data/releases"; impl Scene for NoDataScene { fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - #[cfg(target_os = "android")] { if !self.flag { From a17d3afe2dc406f874356dbfbd6cc75830a48442 Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Wed, 10 Feb 2021 13:30:18 +0100 Subject: [PATCH 8/8] remove gfx feature and prevent pdb conflict --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2bd2505..608cdad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ edition = "2018" name = "doukutsu-rs" version = "0.1.0" -[lib] -crate-type = ["lib", "cdylib"] +#[lib] +#crate-type = ["lib", "cdylib"] [package.metadata.android] android_version = 29 @@ -66,7 +66,7 @@ num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" pretty_env_logger = "0.4.0" -sdl2 = { version = "0.34", optional = true, features = ["unsafe_textures", "bundled", "gfx", "static-link"] } +sdl2 = { version = "0.34", optional = true, features = ["unsafe_textures", "bundled", "static-link"] } serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_yaml = "0.8"