mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-09-20 11:13:41 +00:00
Compare commits
11 commits
c9c746a6db
...
174910b1a3
Author | SHA1 | Date | |
---|---|---|---|
|
174910b1a3 | ||
|
82b039f7b6 | ||
|
abd7d269f2 | ||
|
a17d3afe2d | ||
|
2ccfb06943 | ||
|
a8a9c6316d | ||
|
318d94d843 | ||
|
c3887e31c7 | ||
|
cc816d0380 | ||
|
02f0f7f314 | ||
|
3c20e089f9 |
|
@ -25,10 +25,10 @@ install:
|
||||||
- cargo -vV
|
- cargo -vV
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- '%USERPROFILE%\.cache\sccache -> .appveyor.yml'
|
- '%USERPROFILE%\.cache\sccache -> Cargo.toml'
|
||||||
- '%USERPROFILE%\.cargo -> .appveyor.yml'
|
- '%USERPROFILE%\.cargo -> Cargo.toml'
|
||||||
- '%USERPROFILE%\.rustup -> .appveyor.yml'
|
- '%USERPROFILE%\.rustup -> Cargo.toml'
|
||||||
- 'target -> .appveyor.yml'
|
- 'target -> Cargo.toml'
|
||||||
|
|
||||||
#test_script:
|
#test_script:
|
||||||
# - cargo build --verbose --all
|
# - cargo build --verbose --all
|
||||||
|
|
49
Cargo.toml
49
Cargo.toml
|
@ -4,8 +4,8 @@ edition = "2018"
|
||||||
name = "doukutsu-rs"
|
name = "doukutsu-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[lib]
|
#[lib]
|
||||||
crate-type = ["lib", "cdylib"]
|
#crate-type = ["lib", "cdylib"]
|
||||||
|
|
||||||
[package.metadata.android]
|
[package.metadata.android]
|
||||||
android_version = 29
|
android_version = 29
|
||||||
|
@ -18,12 +18,12 @@ opengles_version = [3, 1]
|
||||||
fullscreen = true
|
fullscreen = true
|
||||||
orientation = "sensorLandscape"
|
orientation = "sensorLandscape"
|
||||||
permission = [
|
permission = [
|
||||||
{name = "android.permission.READ_EXTERNAL_STORAGE"},
|
{ name = "android.permission.READ_EXTERNAL_STORAGE" },
|
||||||
{name = "android.permission.WRITE_EXTERNAL_STORAGE"}
|
{ name = "android.permission.WRITE_EXTERNAL_STORAGE" }
|
||||||
]
|
]
|
||||||
application_metadatas = [
|
application_metadatas = [
|
||||||
{name = "android:hardwareAccelerated", value = "true"},
|
{ name = "android:hardwareAccelerated", value = "true" },
|
||||||
{name = "android:requestLegacyExternalStorage", value = "true"}
|
{ name = "android:requestLegacyExternalStorage", value = "true" }
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
@ -40,42 +40,34 @@ opt-level = 1
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["scripting"]
|
default = ["scripting", "backend-sdl"]
|
||||||
|
backend-sdl = ["sdl2"]
|
||||||
|
backend-gfx = ["winit", "imgui-gfx-renderer", "imgui-winit-support"]
|
||||||
scripting = ["lua-ffi"]
|
scripting = ["lua-ffi"]
|
||||||
editor = []
|
editor = []
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
bitvec = "0.17.4"
|
||||||
byteorder = "1.3"
|
byteorder = "1.3"
|
||||||
case_insensitive_hashmap = "1.0.0"
|
case_insensitive_hashmap = "1.0.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
cpal = {git = "https://github.com/doukutsu-rs/cpal.git", branch = "android-support"}
|
cpal = { git = "https://github.com/doukutsu-rs/cpal.git", branch = "android-support" }
|
||||||
directories = "2"
|
directories = "3"
|
||||||
gfx = "0.18"
|
imgui = "0.7.0"
|
||||||
gfx_core = "0.9"
|
imgui-gfx-renderer = { version = "0.7.0", optional = true }
|
||||||
gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"}
|
imgui-winit-support = { version = "0.7.0", default-features = false, features = ["winit-23"], optional = true }
|
||||||
ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "43631b0401271d4bc8fe4a5afba8aad63976dba1"}
|
image = { version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"] }
|
||||||
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"]}
|
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4"
|
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"
|
lru = "0.6.0"
|
||||||
num-derive = "0.3.2"
|
num-derive = "0.3.2"
|
||||||
num-traits = "0.2.12"
|
num-traits = "0.2.12"
|
||||||
paste = "1.0.0"
|
paste = "1.0.0"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
serde = {version = "1", features = ["derive"]}
|
sdl2 = { version = "0.34", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
strum = "0.18.0"
|
strum = "0.18.0"
|
||||||
|
@ -83,7 +75,10 @@ strum_macros = "0.18.0"
|
||||||
# remove and replace when drain_filter is in stable
|
# remove and replace when drain_filter is in stable
|
||||||
vec_mut_scan = "0.3.0"
|
vec_mut_scan = "0.3.0"
|
||||||
webbrowser = "0.5.5"
|
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]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
ndk = "0.2"
|
ndk = "0.2"
|
||||||
|
|
65
README.md
65
README.md
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
A re-implementation of Cave Story (Doukutsu Monogatari) engine written in [Rust](https://www.rust-lang.org/).
|
A re-implementation of Cave Story (Doukutsu Monogatari) engine written in [Rust](https://www.rust-lang.org/).
|
||||||
|
|
||||||
**The project is still incomplete and not fully playable yet.**
|
**The project is still incomplete and not fully playable yet.** [Click here to see the current status of the project](https://github.com/doukutsu-rs/doukutsu-rs/issues/10).
|
||||||
|
|
||||||
[Join the Discord server](https://discord.gg/fbRsNNB)
|
[Join the Discord server](https://discord.gg/fbRsNNB)
|
||||||
|
|
||||||
|
@ -34,69 +34,6 @@ Vanilla Cave Story does not work yet because some important data files have been
|
||||||
- PC release (EGS) - (Untested, but the game is essentially the same as Steam release) Same thing as with Steam version.
|
- PC release (EGS) - (Untested, but the game is essentially the same as Steam release) Same thing as with Steam version.
|
||||||
- Switch release - (Tested once, no guarantee to work) You need a hacked Switch and physical release. Google should help you.
|
- Switch release - (Tested once, no guarantee to work) You need a hacked Switch and physical release. Google should help you.
|
||||||
|
|
||||||
#### Gameplay support roadmap
|
|
||||||
|
|
||||||
- [x] Checkmarked things = fully implemented
|
|
||||||
- [ ] Unmarked things = partially or not implemented yet.
|
|
||||||
|
|
||||||
- [ ] Text scripts (TSC)
|
|
||||||
- [x] Initial implementation
|
|
||||||
- [x] Full implementation of gameplay opcodes
|
|
||||||
- [x] Shift-JIS encoding support
|
|
||||||
- [ ] Credits opcodes
|
|
||||||
- [ ] Audio
|
|
||||||
- [x] Organya BGM playback
|
|
||||||
- [x] Text script bindings
|
|
||||||
- [ ] CS+ style .ogg BGM playback
|
|
||||||
- [x] PixTone SFX
|
|
||||||
- [ ] NPCs/entities
|
|
||||||
- [x] Initial implementation
|
|
||||||
- [ ] Miscellaneous entities (~40% done)
|
|
||||||
- [ ] Bosses (~30% done)
|
|
||||||
- [x] Omega
|
|
||||||
- [x] Balfrog
|
|
||||||
- [x] Monster X
|
|
||||||
- [x] First Cave
|
|
||||||
- [x] Mimiga Village
|
|
||||||
- [x] Egg Corridor
|
|
||||||
- [x] Grasstown
|
|
||||||
- [ ] Sand Zone (~50% done)
|
|
||||||
- [ ] Labirynth (~10% done)
|
|
||||||
- [x] Waterway
|
|
||||||
- [ ] Egg Corridor? (~20% done)
|
|
||||||
- [ ] Outer Wall
|
|
||||||
- [ ] Plantation
|
|
||||||
- [ ] Last Cave
|
|
||||||
- [ ] Balcony
|
|
||||||
- [ ] Hell
|
|
||||||
- [ ] Weapons
|
|
||||||
- [x] Leveling / XP system
|
|
||||||
- [x] Initial implementation
|
|
||||||
- [x] Snake
|
|
||||||
- [x] Polar Star
|
|
||||||
- [x] Fireball
|
|
||||||
- [ ] Machine Gun
|
|
||||||
- [ ] Missile Launcher
|
|
||||||
- [ ] Bubbler
|
|
||||||
- [ ] Blade
|
|
||||||
- [ ] Super Missile Launcher
|
|
||||||
- [ ] Nemesis
|
|
||||||
- [ ] Spur
|
|
||||||
- [x] Saving and loading game state
|
|
||||||
- [ ] Support for different game editions
|
|
||||||
- [ ] Vanilla
|
|
||||||
- [x] Modified vanilla
|
|
||||||
- [ ] Cave Story+ (PC/Switch)
|
|
||||||
- [x] Base mod
|
|
||||||
- [ ] Mod loading
|
|
||||||
- [x] Curly Story
|
|
||||||
- [ ] Wind Fortress
|
|
||||||
- [ ] Boss Run
|
|
||||||
- [x] Seasonal graphics
|
|
||||||
- [x] Co-op gameplay
|
|
||||||
|
|
||||||
*(tbd)*
|
|
||||||
|
|
||||||
#### Mandatory screenshots
|
#### Mandatory screenshots
|
||||||
|
|
||||||
**Freeware data files:**
|
**Freeware data files:**
|
||||||
|
|
|
@ -3,8 +3,9 @@ use std::io;
|
||||||
|
|
||||||
use byteorder::{LE, ReadBytesExt};
|
use byteorder::{LE, ReadBytesExt};
|
||||||
|
|
||||||
use ggez::GameError::ResourceLoadError;
|
use crate::framework::context::Context;
|
||||||
use ggez::GameResult;
|
use crate::framework::error::GameError::ResourceLoadError;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use crate::str;
|
use crate::str;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -4,10 +4,13 @@ use std::path::PathBuf;
|
||||||
use crate::bmfont::BMFont;
|
use crate::bmfont::BMFont;
|
||||||
use crate::common::{FILE_TYPES, Rect};
|
use crate::common::{FILE_TYPES, Rect};
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
use ggez::{Context, filesystem, GameResult};
|
|
||||||
use ggez::GameError::ResourceLoadError;
|
|
||||||
use crate::str;
|
use crate::str;
|
||||||
use crate::texture_set::TextureSet;
|
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 {
|
pub struct BMFontRenderer {
|
||||||
font: BMFont,
|
font: BMFont,
|
||||||
|
@ -25,7 +28,6 @@ impl BMFontRenderer {
|
||||||
let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?;
|
let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?;
|
||||||
let mut pages = Vec::new();
|
let mut pages = Vec::new();
|
||||||
|
|
||||||
println!("stem: {:?}", stem);
|
|
||||||
let (zeros, _, _) = FILE_TYPES
|
let (zeros, _, _) = FILE_TYPES
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext)))
|
.map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext)))
|
||||||
|
@ -38,7 +40,6 @@ impl BMFontRenderer {
|
||||||
|
|
||||||
for i in 0..font.pages {
|
for i in 0..font.pages {
|
||||||
let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros);
|
let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros);
|
||||||
println!("x: {}", &page_path);
|
|
||||||
|
|
||||||
pages.push(page_path);
|
pages.push(page_path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ use std::io::ErrorKind;
|
||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
use ggez::GameResult;
|
use crate::framework::error::GameError::FilesystemError;
|
||||||
use ggez::GameError::FilesystemError;
|
use crate::framework::error::GameResult;
|
||||||
use ggez::vfs::{OpenOptions, VFile, VFS, VMetadata};
|
use crate::framework::vfs::{OpenOptions, VFile, VMetadata, VFS};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
||||||
|
@ -59,7 +59,7 @@ impl VMetadata for BuiltinMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
enum FSNode {
|
enum FSNode {
|
||||||
File(&'static str, &'static [u8]),
|
File(&'static str, &'static [u8]),
|
||||||
Directory(&'static str, Vec<FSNode>),
|
Directory(&'static str, Vec<FSNode>),
|
||||||
|
@ -232,7 +232,7 @@ impl VFS for BuiltinFS {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[test]
|
||||||
fn test_builtin_fs() {
|
fn test_builtin_fs() {
|
||||||
let fs = BuiltinFS {
|
let fs = BuiltinFS {
|
||||||
root: vec![
|
root: vec![
|
||||||
|
@ -240,7 +240,7 @@ fn test_builtin_fs() {
|
||||||
FSNode::Directory("memes", vec![
|
FSNode::Directory("memes", vec![
|
||||||
FSNode::File("nothing.txt", &[]),
|
FSNode::File("nothing.txt", &[]),
|
||||||
FSNode::Directory("secret stuff", vec![
|
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", &[]),
|
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")).unwrap());
|
||||||
println!("{:?}", fs.get_node(Path::new("/memes/nothing.txt")).unwrap());
|
println!("{:?}", fs.get_node(Path::new("/memes/nothing.txt")).unwrap());
|
||||||
println!("{:?}", fs.get_node(Path::new("/memes/secret stuff/passwords.txt")).unwrap());
|
println!("{:?}", fs.get_node(Path::new("/memes/secret stuff/passwords.txt")).unwrap());
|
||||||
}*/
|
}
|
||||||
|
|
184
src/common.rs
184
src/common.rs
|
@ -234,6 +234,22 @@ impl Direction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Point<T: Num + PartialOrd + Copy = isize> {
|
||||||
|
pub x: T,
|
||||||
|
pub y: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Num + PartialOrd + Copy> Point<T> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(x: T, y: T) -> Point<T> {
|
||||||
|
Point {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Rect<T: Num + PartialOrd + Copy = isize> {
|
pub struct Rect<T: Num + PartialOrd + Copy = isize> {
|
||||||
pub left: T,
|
pub left: T,
|
||||||
|
@ -243,6 +259,7 @@ pub struct Rect<T: Num + PartialOrd + Copy = isize> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Num + PartialOrd + Copy> Rect<T> {
|
impl<T: Num + PartialOrd + Copy> Rect<T> {
|
||||||
|
#[inline(always)]
|
||||||
pub fn new(left: T, top: T, right: T, bottom: T) -> Rect<T> {
|
pub fn new(left: T, top: T, right: T, bottom: T) -> Rect<T> {
|
||||||
Rect {
|
Rect {
|
||||||
left,
|
left,
|
||||||
|
@ -252,6 +269,7 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn new_size(x: T, y: T, width: T, height: T) -> Rect<T> {
|
pub fn new_size(x: T, y: T, width: T, height: T) -> Rect<T> {
|
||||||
Rect {
|
Rect {
|
||||||
left: x,
|
left: x,
|
||||||
|
@ -261,15 +279,6 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from(rect: ggez::graphics::Rect) -> Rect<f32> {
|
|
||||||
Rect {
|
|
||||||
left: rect.x,
|
|
||||||
top: rect.y,
|
|
||||||
right: (rect.x + rect.w),
|
|
||||||
bottom: (rect.y + rect.h),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width(&self) -> T {
|
pub fn width(&self) -> T {
|
||||||
if let Some(Ordering::Greater) = self.left.partial_cmp(&self.right) {
|
if let Some(Ordering::Greater) = self.left.partial_cmp(&self.right) {
|
||||||
self.left.sub(self.right)
|
self.left.sub(self.right)
|
||||||
|
@ -287,15 +296,6 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Num + PartialOrd + Copy + AsPrimitive<f32>> Into<ggez::graphics::Rect> for Rect<T> {
|
|
||||||
fn into(self) -> ggez::graphics::Rect {
|
|
||||||
ggez::graphics::Rect::new(self.left.as_(),
|
|
||||||
self.top.as_(),
|
|
||||||
self.width().as_(),
|
|
||||||
self.height().as_())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Num + PartialOrd + Copy + Serialize> Serialize for Rect<T> {
|
impl<T: Num + PartialOrd + Copy + Serialize> Serialize for Rect<T> {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
@ -371,3 +371,151 @@ 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
|
(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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 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 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 fn to_rgb(self) -> (u8, u8, u8) {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a packed `u32` containing `0xRRGGBBAA` into a `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]))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 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 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 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<Color> 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<Color> 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<Color> 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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use ggez::{Context, GameResult};
|
|
||||||
|
|
||||||
use crate::common::Rect;
|
use crate::common::Rect;
|
||||||
use crate::entity::GameEntity;
|
use crate::entity::GameEntity;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use crate::npc::boss::BossNPC;
|
use crate::npc::boss::BossNPC;
|
||||||
use crate::npc::list::NPCList;
|
use crate::npc::list::NPCList;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
@ -52,7 +52,7 @@ impl BossLifeBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameEntity<(&NPCList, &BossNPC)> for 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 {
|
match self.target {
|
||||||
BossLifeTarget::NPC(npc_id) => {
|
BossLifeTarget::NPC(npc_id) => {
|
||||||
if let Some(npc) = npc_list.get_npc(npc_id as usize) {
|
if let Some(npc) = npc_list.get_npc(npc_id as usize) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use ggez::{Context, GameResult};
|
|
||||||
|
|
||||||
use crate::common::Rect;
|
use crate::common::Rect;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::common::Rect;
|
use crate::common::Rect;
|
||||||
use crate::components::draw_common::{Alignment, draw_number};
|
use crate::components::draw_common::{Alignment, draw_number};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::common::Rect;
|
use crate::common::Rect;
|
||||||
use crate::entity::GameEntity;
|
use crate::entity::GameEntity;
|
||||||
|
|
|
@ -216,7 +216,7 @@ pub struct EngineConstants {
|
||||||
pub world: WorldConsts,
|
pub world: WorldConsts,
|
||||||
pub npc: NPCConsts,
|
pub npc: NPCConsts,
|
||||||
pub weapon: WeaponConsts,
|
pub weapon: WeaponConsts,
|
||||||
pub tex_sizes: CaseInsensitiveHashMap<(usize, usize)>,
|
pub tex_sizes: CaseInsensitiveHashMap<(u16, u16)>,
|
||||||
pub textscript: TextScriptConsts,
|
pub textscript: TextScriptConsts,
|
||||||
pub title: TitleConsts,
|
pub title: TitleConsts,
|
||||||
pub font_path: String,
|
pub font_path: String,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
|
57
src/framework/backend.rs
Normal file
57
src/framework/backend.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use imgui::DrawData;
|
||||||
|
|
||||||
|
use crate::common::{Color, Point, Rect};
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
use crate::framework::graphics::BlendMode;
|
||||||
|
use crate::Game;
|
||||||
|
|
||||||
|
pub trait Backend {
|
||||||
|
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BackendEventLoop {
|
||||||
|
fn run(&mut self, game: &mut Game, ctx: &mut Context);
|
||||||
|
|
||||||
|
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BackendRenderer {
|
||||||
|
fn clear(&mut self, color: Color);
|
||||||
|
|
||||||
|
fn present(&mut self) -> GameResult;
|
||||||
|
|
||||||
|
fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult<Box<dyn BackendTexture>>;
|
||||||
|
|
||||||
|
fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult<Box<dyn BackendTexture>>;
|
||||||
|
|
||||||
|
fn set_blend_mode(&mut self, blend: BlendMode) -> GameResult;
|
||||||
|
|
||||||
|
fn set_render_target(&mut self, texture: Option<&Box<dyn BackendTexture>>) -> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_backend() -> GameResult<Box<dyn Backend>> {
|
||||||
|
crate::framework::backend_sdl2::SDL2Backend::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SpriteBatchCommand {
|
||||||
|
DrawRect(Rect<f32>, Rect<f32>),
|
||||||
|
DrawRectFlip(Rect<f32>, Rect<f32>, bool, bool),
|
||||||
|
DrawRectTinted(Rect<f32>, Rect<f32>, Color),
|
||||||
|
}
|
1027
src/framework/backend_sdl2.rs
Normal file
1027
src/framework/backend_sdl2.rs
Normal file
File diff suppressed because it is too large
Load diff
33
src/framework/context.rs
Normal file
33
src/framework/context.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::framework::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<Box<dyn BackendRenderer>>,
|
||||||
|
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) -> 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(())
|
||||||
|
}
|
||||||
|
}
|
143
src/framework/error.rs
Normal file
143
src/framework/error.rs
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
//! 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<std::io::Error>),
|
||||||
|
/// 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::IOError(ref e) => Some(&**e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A convenient result type consisting of a return type and a `GameError`
|
||||||
|
pub type GameResult<T = ()> = Result<T, GameError>;
|
||||||
|
|
||||||
|
impl From<std::io::Error> for GameError {
|
||||||
|
fn from(e: std::io::Error) -> GameError {
|
||||||
|
GameError::IOError(Arc::new(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<image::ImageError> for GameError {
|
||||||
|
fn from(e: image::ImageError) -> GameError {
|
||||||
|
let errstr = format!("Image load error: {}", e);
|
||||||
|
GameError::ResourceLoadError(errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::string::FromUtf8Error> for GameError {
|
||||||
|
fn from(e: FromUtf8Error) -> Self {
|
||||||
|
let errstr = format!("UTF-8 decoding error: {:?}", e);
|
||||||
|
GameError::ConfigError(errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
impl From<jni::errors::Error> for GameError {
|
||||||
|
fn from(e: jni::errors::Error) -> GameError {
|
||||||
|
GameError::WindowError(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<strum::ParseError> for GameError {
|
||||||
|
fn from(s: strum::ParseError) -> GameError {
|
||||||
|
let errstr = format!("Strum parse error: {}", s);
|
||||||
|
GameError::ParseError(errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<cpal::DefaultStreamConfigError> for GameError {
|
||||||
|
fn from(s: cpal::DefaultStreamConfigError) -> GameError {
|
||||||
|
let errstr = format!("Default stream config error: {}", s);
|
||||||
|
GameError::AudioError(errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<cpal::PlayStreamError> for GameError {
|
||||||
|
fn from(s: cpal::PlayStreamError) -> GameError {
|
||||||
|
let errstr = format!("Play stream error: {}", s);
|
||||||
|
GameError::AudioError(errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<cpal::BuildStreamError> for GameError {
|
||||||
|
fn from(s: cpal::BuildStreamError) -> GameError {
|
||||||
|
let errstr = format!("Build stream error: {}", s);
|
||||||
|
GameError::AudioError(errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<PoisonError<T>> for GameError {
|
||||||
|
fn from(s: PoisonError<T>) -> GameError {
|
||||||
|
let errstr = format!("Poison error: {}", s);
|
||||||
|
GameError::EventLoopError(errstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<SendError<T>> for GameError {
|
||||||
|
fn from(s: SendError<T>) -> GameError {
|
||||||
|
let errstr = format!("Send error: {}", s);
|
||||||
|
GameError::EventLoopError(errstr)
|
||||||
|
}
|
||||||
|
}
|
367
src/framework/filesystem.rs
Normal file
367
src/framework/filesystem.rs
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
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<dyn vfs::VFile>),
|
||||||
|
}
|
||||||
|
|
||||||
|
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<usize> {
|
||||||
|
match *self {
|
||||||
|
File::VfsFile(ref mut f) => f.read(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for File {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
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<u64> {
|
||||||
|
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 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<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
|
||||||
|
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<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
|
||||||
|
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<P: AsRef<path::Path>>(
|
||||||
|
&mut self,
|
||||||
|
path: P,
|
||||||
|
options: OpenOptions,
|
||||||
|
) -> GameResult<File> {
|
||||||
|
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<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
|
||||||
|
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<P: AsRef<path::Path>>(&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<P: AsRef<path::Path>>(&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<P: AsRef<path::Path>>(&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<P: AsRef<path::Path>>(&self, path: P) -> bool {
|
||||||
|
self.user_vfs.exists(path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a file or directory exists.
|
||||||
|
pub(crate) fn exists<P: AsRef<path::Path>>(&self, path: P) -> bool {
|
||||||
|
self.vfs.exists(path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a path points at a file.
|
||||||
|
pub(crate) fn user_is_file<P: AsRef<path::Path>>(&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<P: AsRef<path::Path>>(&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<P: AsRef<path::Path>>(&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<P: AsRef<path::Path>>(&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<P: AsRef<path::Path>>(
|
||||||
|
&mut self,
|
||||||
|
path: P,
|
||||||
|
) -> GameResult<Box<dyn Iterator<Item=path::PathBuf>>> {
|
||||||
|
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<P: AsRef<path::Path>>(
|
||||||
|
&mut self,
|
||||||
|
path: P,
|
||||||
|
) -> GameResult<Box<dyn Iterator<Item=path::PathBuf>>> {
|
||||||
|
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<dyn vfs::VFS>) {
|
||||||
|
self.vfs.push_back(vfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn mount_user_vfs(&mut self, vfs: Box<dyn vfs::VFS>) {
|
||||||
|
self.user_vfs.push_back(vfs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the given path and returns the resulting `File`
|
||||||
|
/// in read-only mode.
|
||||||
|
pub fn open<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult<File> {
|
||||||
|
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<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult<File> {
|
||||||
|
ctx.filesystem.user_open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens a file in the user directory with the given `filesystem::OpenOptions`.
|
||||||
|
pub fn open_options<P: AsRef<path::Path>>(
|
||||||
|
ctx: &mut Context,
|
||||||
|
path: P,
|
||||||
|
options: OpenOptions,
|
||||||
|
) -> GameResult<File> {
|
||||||
|
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<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult<File> {
|
||||||
|
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<P: AsRef<path::Path>>(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<P: AsRef<path::Path>>(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<P: AsRef<path::Path>>(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<P: AsRef<path::Path>>(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<P: AsRef<path::Path>>(ctx: &Context, path: P) -> bool {
|
||||||
|
ctx.filesystem.user_is_file(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a path points at a directory.
|
||||||
|
pub fn user_is_dir<P: AsRef<path::Path>>(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<P: AsRef<path::Path>>(
|
||||||
|
ctx: &mut Context,
|
||||||
|
path: P,
|
||||||
|
) -> GameResult<Box<dyn Iterator<Item=path::PathBuf>>> {
|
||||||
|
ctx.filesystem.user_read_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a file or directory exists.
|
||||||
|
pub fn exists<P: AsRef<path::Path>>(ctx: &Context, path: P) -> bool {
|
||||||
|
ctx.filesystem.exists(path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a path points at a file.
|
||||||
|
pub fn is_file<P: AsRef<path::Path>>(ctx: &Context, path: P) -> bool {
|
||||||
|
ctx.filesystem.is_file(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a path points at a directory.
|
||||||
|
pub fn is_dir<P: AsRef<path::Path>>(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<P: AsRef<path::Path>>(
|
||||||
|
ctx: &mut Context,
|
||||||
|
path: P,
|
||||||
|
) -> GameResult<Box<dyn Iterator<Item=path::PathBuf>>> {
|
||||||
|
ctx.filesystem.read_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<dyn vfs::VFS>) {
|
||||||
|
ctx.filesystem.mount_vfs(vfs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a VFS to the list of user data search locations.
|
||||||
|
pub fn mount_user_vfs(ctx: &mut Context, vfs: Box<dyn vfs::VFS>) {
|
||||||
|
ctx.filesystem.mount_user_vfs(vfs)
|
||||||
|
}
|
101
src/framework/graphics.rs
Normal file
101
src/framework/graphics.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
use crate::common::Color;
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::{GameResult, GameError};
|
||||||
|
use crate::framework::backend::BackendTexture;
|
||||||
|
|
||||||
|
pub enum FilterMode {
|
||||||
|
Nearest,
|
||||||
|
Linear,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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) {
|
||||||
|
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()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Box<dyn BackendTexture>> {
|
||||||
|
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<Box<dyn BackendTexture>> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_render_target(ctx: &mut Context, texture: Option<&Box<dyn BackendTexture>>) -> 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()))
|
||||||
|
}
|
||||||
|
|
||||||
|
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()))
|
||||||
|
}
|
345
src/framework/keyboard.rs
Normal file
345
src/framework/keyboard.rs
Normal file
|
@ -0,0 +1,345 @@
|
||||||
|
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.
|
||||||
|
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,
|
||||||
|
|
||||||
|
NonUsHash,
|
||||||
|
NonUsBackslash,
|
||||||
|
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,
|
||||||
|
Capslock,
|
||||||
|
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,
|
||||||
|
Scrolllock,
|
||||||
|
Semicolon,
|
||||||
|
Slash,
|
||||||
|
Sleep,
|
||||||
|
Stop,
|
||||||
|
Sysrq,
|
||||||
|
Tab,
|
||||||
|
Underline,
|
||||||
|
Unlabeled,
|
||||||
|
VolumeDown,
|
||||||
|
VolumeUp,
|
||||||
|
Wake,
|
||||||
|
WebBack,
|
||||||
|
WebFavorites,
|
||||||
|
WebForward,
|
||||||
|
WebHome,
|
||||||
|
WebRefresh,
|
||||||
|
WebSearch,
|
||||||
|
WebStop,
|
||||||
|
Yen,
|
||||||
|
Copy,
|
||||||
|
Paste,
|
||||||
|
Cut,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ScanCode>,
|
||||||
|
last_pressed: Option<ScanCode>,
|
||||||
|
current_pressed: Option<ScanCode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ScanCode> {
|
||||||
|
&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<ScanCode> {
|
||||||
|
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()
|
||||||
|
}
|
10
src/framework/mod.rs
Normal file
10
src/framework/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
pub mod backend;
|
||||||
|
pub mod backend_sdl2;
|
||||||
|
pub mod backend_null;
|
||||||
|
pub mod context;
|
||||||
|
pub mod error;
|
||||||
|
pub mod filesystem;
|
||||||
|
pub mod graphics;
|
||||||
|
pub mod keyboard;
|
||||||
|
pub mod ui;
|
||||||
|
pub mod vfs;
|
136
src/framework/ui.rs
Normal file
136
src/framework/ui.rs
Normal file
|
@ -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<imgui::Context> {
|
||||||
|
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<Self> {
|
||||||
|
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<dyn Scene>) -> 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(())
|
||||||
|
}
|
||||||
|
}
|
678
src/framework/vfs.rs
Normal file
678
src/framework/vfs.rs
Normal file
|
@ -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<T> 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<Box<dyn VFile>>;
|
||||||
|
/// Open the file at this path for reading
|
||||||
|
fn open(&self, path: &Path) -> GameResult<Box<dyn VFile>> {
|
||||||
|
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<Box<dyn VFile>> {
|
||||||
|
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<Box<dyn VFile>> {
|
||||||
|
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<Box<dyn VMetadata>>;
|
||||||
|
|
||||||
|
/// Retrieve all file and directory entries in the given directory.
|
||||||
|
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>>;
|
||||||
|
|
||||||
|
/// Retrieve the actual location of the VFS root, if available.
|
||||||
|
fn to_path_buf(&self) -> Option<PathBuf>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<PathBuf> {
|
||||||
|
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<PathBuf> {
|
||||||
|
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, "<PhysicalFS root: {}>", 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<Box<dyn VFile>> {
|
||||||
|
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<dyn VFile>)
|
||||||
|
.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<Box<dyn VMetadata>> {
|
||||||
|
self.create_root()?;
|
||||||
|
let p = self.to_absolute(path)?;
|
||||||
|
p.metadata()
|
||||||
|
.map(|m| Box::new(PhysicalMetadata(m)) as Box<dyn VMetadata>)
|
||||||
|
.map_err(GameError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the path entries in this path
|
||||||
|
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> {
|
||||||
|
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<PathBuf> {
|
||||||
|
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::<Vec<_>>()
|
||||||
|
.into_iter();
|
||||||
|
Ok(Box::new(itr))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the actual location of the VFS root, if available.
|
||||||
|
fn to_path_buf(&self) -> Option<PathBuf> {
|
||||||
|
Some(self.root.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A structure that joins several VFS's together in order.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OverlayFS {
|
||||||
|
roots: VecDeque<Box<dyn VFS>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<dyn VFS>) {
|
||||||
|
self.roots.push_front(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new VFS to the end of the list.
|
||||||
|
pub fn push_back(&mut self, fs: Box<dyn VFS>) {
|
||||||
|
self.roots.push_back(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a list of registered VFS roots.
|
||||||
|
pub fn roots(&self) -> &VecDeque<Box<dyn VFS>> {
|
||||||
|
&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<Box<dyn VFile>> {
|
||||||
|
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("<invalid path>"), 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<Box<dyn VMetadata>> {
|
||||||
|
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<Box<dyn Iterator<Item=GameResult<PathBuf>>>> {
|
||||||
|
// 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<PathBuf> {
|
||||||
|
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!!
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use crate::input::player_controller::PlayerController;
|
use crate::input::player_controller::PlayerController;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use ggez::{GameResult, Context};
|
|
||||||
|
|
||||||
pub struct CombinedMenuController {
|
pub struct CombinedMenuController {
|
||||||
controllers: Vec<Box<dyn PlayerController>>,
|
controllers: Vec<Box<dyn PlayerController>>,
|
||||||
|
|
|
@ -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::input::player_controller::PlayerController;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use ggez::{Context, GameResult};
|
|
||||||
use ggez::input::keyboard;
|
|
||||||
use winit::event::VirtualKeyCode;
|
|
||||||
|
|
||||||
use crate::bitfield;
|
use crate::bitfield;
|
||||||
use crate::input::player_controller::PlayerController;
|
use crate::input::player_controller::PlayerController;
|
||||||
use crate::player::TargetPlayer;
|
use crate::player::TargetPlayer;
|
||||||
use crate::shared_game_state::SharedGameState;
|
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! {
|
bitfield! {
|
||||||
#[derive(Clone, Copy)]
|
#[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_skip(keyboard::is_key_pressed(ctx, keymap.skip));
|
||||||
self.state.set_prev_weapon(keyboard::is_key_pressed(ctx, keymap.prev_weapon));
|
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_next_weapon(keyboard::is_key_pressed(ctx, keymap.next_weapon));
|
||||||
self.state.set_enter(keyboard::is_key_pressed(ctx, VirtualKeyCode::Return));
|
self.state.set_enter(keyboard::is_key_pressed(ctx, ScanCode::Return));
|
||||||
self.state.set_escape(keyboard::is_key_pressed(ctx, VirtualKeyCode::Escape));
|
self.state.set_escape(keyboard::is_key_pressed(ctx, ScanCode::Escape));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::shared_game_state::SharedGameState;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
use winit::event::TouchPhase;
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::common::Rect;
|
use crate::common::Rect;
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
|
@ -39,6 +39,7 @@ impl TouchControls {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
pub fn process_winit_event(&mut self, scale: f32, touch: winit::event::Touch) {
|
pub fn process_winit_event(&mut self, scale: f32, touch: winit::event::Touch) {
|
||||||
match touch.phase {
|
match touch.phase {
|
||||||
TouchPhase::Started | TouchPhase::Moved => {
|
TouchPhase::Started | TouchPhase::Moved => {
|
||||||
|
@ -66,7 +67,7 @@ impl TouchControls {
|
||||||
self.clicks.retain(|p| p.id != touch.id);
|
self.clicks.retain(|p| p.id != touch.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
pub fn point_in(&self, bounds: Rect) -> Option<u64> {
|
pub fn point_in(&self, bounds: Rect) -> Option<u64> {
|
||||||
for point in self.points.iter() {
|
for point in self.points.iter() {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
|
|
||||||
use crate::bitfield;
|
use crate::bitfield;
|
||||||
use crate::common::Rect;
|
use crate::common::Rect;
|
||||||
|
|
305
src/lib.rs
305
src/lib.rs
|
@ -7,32 +7,29 @@ extern crate strum;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate strum_macros;
|
extern crate strum_macros;
|
||||||
|
|
||||||
use std::{env, mem};
|
use core::mem;
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::path;
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use ggez::{Context, ContextBuilder, GameError, GameResult};
|
use directories::ProjectDirs;
|
||||||
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 log::*;
|
||||||
use pretty_env_logger::env_logger::Env;
|
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::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::ui::UI;
|
||||||
|
use crate::framework::vfs::PhysicalFS;
|
||||||
use crate::scene::loading_scene::LoadingScene;
|
use crate::scene::loading_scene::LoadingScene;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use crate::shared_game_state::{SharedGameState, TimingMode};
|
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;
|
mod bmfont;
|
||||||
mod bmfont_renderer;
|
mod bmfont_renderer;
|
||||||
|
@ -46,6 +43,7 @@ mod encoding;
|
||||||
mod engine_constants;
|
mod engine_constants;
|
||||||
mod entity;
|
mod entity;
|
||||||
mod frame;
|
mod frame;
|
||||||
|
mod framework;
|
||||||
mod inventory;
|
mod inventory;
|
||||||
mod input;
|
mod input;
|
||||||
mod live_debugger;
|
mod live_debugger;
|
||||||
|
@ -61,20 +59,19 @@ mod scene;
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting")]
|
||||||
mod scripting;
|
mod scripting;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
#[cfg(feature = "backend-gfx")]
|
||||||
mod shaders;
|
mod shaders;
|
||||||
mod shared_game_state;
|
mod shared_game_state;
|
||||||
mod stage;
|
mod stage;
|
||||||
mod sound;
|
mod sound;
|
||||||
mod text_script;
|
mod text_script;
|
||||||
mod texture_set;
|
mod texture_set;
|
||||||
mod ui;
|
|
||||||
mod weapon;
|
mod weapon;
|
||||||
|
|
||||||
struct Game {
|
pub struct Game {
|
||||||
scene: Option<Box<dyn Scene>>,
|
scene: Option<Box<dyn Scene>>,
|
||||||
state: UnsafeCell<SharedGameState>,
|
state: UnsafeCell<SharedGameState>,
|
||||||
ui: UI,
|
ui: UI,
|
||||||
def_matrix: ColumnMatrix4<f32>,
|
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
last_tick: u128,
|
last_tick: u128,
|
||||||
next_tick: u128,
|
next_tick: u128,
|
||||||
|
@ -86,7 +83,6 @@ impl Game {
|
||||||
let s = Game {
|
let s = Game {
|
||||||
scene: None,
|
scene: None,
|
||||||
ui: UI::new(ctx)?,
|
ui: UI::new(ctx)?,
|
||||||
def_matrix: DrawParam::new().to_matrix(),
|
|
||||||
state: UnsafeCell::new(SharedGameState::new(ctx)?),
|
state: UnsafeCell::new(SharedGameState::new(ctx)?),
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
last_tick: 0,
|
last_tick: 0,
|
||||||
|
@ -157,14 +153,16 @@ impl Game {
|
||||||
n1 / n2
|
n1 / n2
|
||||||
} else { 1.0 };
|
} 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;
|
self.loops = 0;
|
||||||
|
|
||||||
graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into());
|
graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into());
|
||||||
graphics::set_transform(ctx, DrawParam::new()
|
/*graphics::set_projection(ctx, DrawParam::new()
|
||||||
.scale(Vector2::new(state_ref.scale, state_ref.scale))
|
.scale(Vec2::new(state_ref.scale, state_ref.scale))
|
||||||
.to_matrix());
|
.to_matrix());*/
|
||||||
graphics::apply_transformations(ctx)?;
|
|
||||||
|
|
||||||
if let Some(scene) = self.scene.as_mut() {
|
if let Some(scene) = self.scene.as_mut() {
|
||||||
scene.draw(state_ref, ctx)?;
|
scene.draw(state_ref, ctx)?;
|
||||||
|
@ -172,43 +170,13 @@ impl Game {
|
||||||
state_ref.touch_controls.draw(state_ref.canvas_size, &state_ref.constants, &mut state_ref.texture_set, ctx)?;
|
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::set_projection(ctx, self.def_matrix);
|
||||||
graphics::apply_transformations(ctx)?;
|
|
||||||
self.ui.draw(state_ref, ctx, scene)?;
|
self.ui.draw(state_ref, ctx, scene)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics::present(ctx)?;
|
graphics::present(ctx)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_down_event(&mut self, key_code: KeyCode, _key_mod: KeyMods, 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 => {
|
|
||||||
if state.settings.speed > 0.2 {
|
|
||||||
state.set_speed(state.settings.speed - 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::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 }
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_up_event(&mut self, _key_code: KeyCode, _key_mod: KeyMods) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
|
@ -271,68 +239,39 @@ pub fn android_main() {
|
||||||
init().unwrap();
|
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<P: Into<path::PathBuf> + Clone>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult<Context> {
|
|
||||||
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 {
|
pub fn init() -> GameResult {
|
||||||
pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info"))
|
pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info"))
|
||||||
.filter(Some("gfx_device_gl::factory"), LevelFilter::Warn)
|
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") {
|
let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") {
|
||||||
path::PathBuf::from(data_dir)
|
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 {
|
} else {
|
||||||
path::PathBuf::from("data")
|
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!("Resource directory: {:?}", resource_dir);
|
||||||
info!("Initializing engine...");
|
info!("Initializing engine...");
|
||||||
|
|
||||||
let event_loop = winit::event_loop::EventLoop::new();
|
let mut context = Context::new();
|
||||||
let mut context: Option<Context>;
|
mount_vfs(&mut context, Box::new(BuiltinFS::new()));
|
||||||
let mut game: Option<Game> = None;
|
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")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
|
@ -347,151 +286,17 @@ pub fn init() -> GameResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context = Some(init_ctx(&event_loop, resource_dir.clone())?);
|
let mut game = Game::new(&mut context)?;
|
||||||
|
let state_ref = unsafe { &mut *game.state.get() };
|
||||||
event_loop.run(move |event, target, flow| {
|
#[cfg(feature = "scripting")]
|
||||||
#[cfg(target_os = "windows")]
|
{
|
||||||
{
|
unsafe {
|
||||||
// Windows' system clock implementation isn't monotonic when the process gets switched to another core.
|
state_ref.lua.update_refs(game.state.get(), &mut context as *mut Context);
|
||||||
// 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 {
|
state_ref.next_scene = Some(Box::new(LoadingScene::new()));
|
||||||
Event::Resumed => {
|
context.run(&mut game)?;
|
||||||
#[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 {
|
Ok(())
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 imgui::{CollapsingHeader, Condition, im_str, ImStr, ImString, Slider, Window};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
|
31
src/main.rs
31
src/main.rs
|
@ -1,5 +1,34 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
fn main() {
|
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::<Vec<u16>>().as_ptr();
|
||||||
|
let message: LPCWSTR = OsStr::new(format!("Whoops, doukutsu-rs crashed: {}", e).as_str())
|
||||||
|
.encode_wide().chain(Some(0)).collect::<Vec<u16>>().as_ptr();
|
||||||
|
MessageBoxW(null_mut(),
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
MB_OK);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
println!("Initialization error: {}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use byteorder::{LE, ReadBytesExt};
|
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::str;
|
||||||
|
use crate::framework::error::GameError::ResourceLoadError;
|
||||||
|
|
||||||
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
||||||
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
|
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
|
||||||
|
@ -28,7 +30,6 @@ impl Map {
|
||||||
|
|
||||||
let version = map_data.read_u8()?;
|
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) {
|
if !SUPPORTED_PXM_VERSIONS.contains(&version) {
|
||||||
return Err(ResourceLoadError(format!("Unsupported PXM version: {:#x}", version)));
|
return Err(ResourceLoadError(format!("Unsupported PXM version: {:#x}", version)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::common::Rect;
|
use crate::common::Rect;
|
||||||
use crate::input::combined_menu_controller::CombinedMenuController;
|
use crate::input::combined_menu_controller::CombinedMenuController;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
|
||||||
pub struct MenuSaveInfo {
|
pub struct MenuSaveInfo {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum MenuEntry {
|
pub enum MenuEntry {
|
||||||
Hidden,
|
Hidden,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::{abs, clamp};
|
use num_traits::{abs, clamp};
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::{abs, clamp};
|
use num_traits::{abs, clamp};
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::abs;
|
use num_traits::abs;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use ggez::GameResult;
|
|
||||||
|
|
||||||
use crate::common::{CDEG_RAD, Direction};
|
use crate::common::{CDEG_RAD, Direction};
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use crate::npc::list::NPCList;
|
use crate::npc::list::NPCList;
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use ggez::GameResult;
|
|
||||||
use num_traits::{abs, clamp};
|
use num_traits::{abs, clamp};
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
use crate::rng::RNG;
|
use crate::rng::RNG;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::{abs, clamp};
|
use num_traits::{abs, clamp};
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
|
@ -1586,7 +1587,7 @@ impl NPC {
|
||||||
npc.y = self.y - 0x1800;
|
npc.y = self.y - 0x1800;
|
||||||
npc.tsc_direction = 10 * self.anim_num + 40;
|
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);
|
self.cond.set_explode_die(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1629,7 +1630,7 @@ impl NPC {
|
||||||
npc.direction = Direction::Right;
|
npc.direction = Direction::Right;
|
||||||
npc.parent_id = self.id;
|
npc.parent_id = self.id;
|
||||||
|
|
||||||
npc_list.spawn(0x100, npc);
|
let _ = npc_list.spawn(0x100, npc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use ggez::GameResult;
|
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use crate::npc::list::NPCList;
|
use crate::npc::list::NPCList;
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
use crate::rng::RNG;
|
use crate::rng::RNG;
|
||||||
|
|
|
@ -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 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 {
|
impl NPC {
|
||||||
pub(crate) fn tick_n215_sandcroc_outer_wall(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
pub(crate) fn tick_n215_sandcroc_outer_wall(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
|
||||||
match self.action_num {
|
match self.action_num {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
use crate::npc::list::NPCList;
|
use crate::npc::list::NPCList;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::{abs, clamp};
|
use num_traits::{abs, clamp};
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
|
@ -288,7 +289,7 @@ impl NPC {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tick_n049_skullhead(&mut self, state: &mut SharedGameState, players: [&mut Player; 2], npc_list: &NPCList) -> GameResult {
|
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) {
|
if self.action_num > 9 && parent.as_ref().map(|n| n.npc_type == 3).unwrap_or(false) {
|
||||||
self.action_num = 3;
|
self.action_num = 3;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::abs;
|
use num_traits::abs;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::common::Direction;
|
use crate::common::Direction;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use ggez::GameResult;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
use crate::common::{CDEG_RAD, Direction, Rect};
|
use crate::common::{CDEG_RAD, Direction, Rect};
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
use ggez::{Context, GameResult};
|
|
||||||
|
|
||||||
use crate::bullet::BulletManager;
|
use crate::bullet::BulletManager;
|
||||||
use crate::common::{Direction, interpolate_fix9_scale};
|
use crate::common::{Direction, interpolate_fix9_scale};
|
||||||
use crate::entity::GameEntity;
|
use crate::entity::GameEntity;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use crate::npc::list::NPCList;
|
use crate::npc::list::NPCList;
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
|
@ -76,7 +76,8 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)> for Bo
|
||||||
5 => self.tick_b05_ironhead(),
|
5 => self.tick_b05_ironhead(),
|
||||||
6 => self.tick_b06_twins(),
|
6 => self.tick_b06_twins(),
|
||||||
7 => self.tick_b07_undead_core(),
|
7 => self.tick_b07_undead_core(),
|
||||||
8 => self.tick_b09_ballos(),
|
8 => self.tick_b08_press(),
|
||||||
|
9 => self.tick_b09_ballos(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use ggez::GameResult;
|
|
||||||
use num_traits::{abs, clamp};
|
use num_traits::{abs, clamp};
|
||||||
|
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
use crate::common::{CDEG_RAD, Direction, Rect};
|
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::boss::BossNPC;
|
||||||
use crate::npc::list::NPCList;
|
use crate::npc::list::NPCList;
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use ggez::GameResult;
|
|
||||||
|
|
||||||
use crate::bullet::BulletManager;
|
use crate::bullet::BulletManager;
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
|
@ -9,6 +9,8 @@ use crate::npc::NPC;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
use crate::rng::RNG;
|
use crate::rng::RNG;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
impl NPC {
|
impl NPC {
|
||||||
pub(crate) fn tick_n048_omega_projectiles(&mut self, state: &mut SharedGameState) -> GameResult {
|
pub(crate) fn tick_n048_omega_projectiles(&mut self, state: &mut SharedGameState) -> GameResult {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use std::cell::{Cell, UnsafeCell};
|
use std::cell::{Cell, UnsafeCell};
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
use ggez::{GameError, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::{GameResult, GameError};
|
||||||
|
|
||||||
use crate::npc::NPC;
|
use crate::npc::NPC;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ use std::io;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
use byteorder::{LE, ReadBytesExt};
|
use byteorder::{LE, ReadBytesExt};
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_traits::abs;
|
use num_traits::abs;
|
||||||
|
|
||||||
use crate::bitfield;
|
use crate::bitfield;
|
||||||
|
@ -283,6 +284,8 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager)> for NP
|
||||||
216 => self.tick_n216_debug_cat(state),
|
216 => self.tick_n216_debug_cat(state),
|
||||||
222 => self.tick_n222_prison_bars(state),
|
222 => self.tick_n222_prison_bars(state),
|
||||||
227 => self.tick_n227_bucket(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),
|
234 => self.tick_n234_red_flowers_picked(state),
|
||||||
239 => self.tick_n239_cage_bars(state),
|
239 => self.tick_n239_cage_bars(state),
|
||||||
241 => self.tick_n241_critter_red(state, players),
|
241 => self.tick_n241_critter_red(state, players),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
|
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,15 @@ use byteorder::{BE, LE, ReadBytesExt, WriteBytesExt};
|
||||||
use num_traits::{clamp, FromPrimitive};
|
use num_traits::{clamp, FromPrimitive};
|
||||||
|
|
||||||
use crate::common::{Direction, FadeState};
|
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::player::ControlMode;
|
||||||
use crate::scene::game_scene::GameScene;
|
use crate::scene::game_scene::GameScene;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::str;
|
use crate::str;
|
||||||
use crate::weapon::{WeaponLevel, WeaponType};
|
use crate::weapon::{WeaponLevel, WeaponType};
|
||||||
|
use crate::framework::error::GameError::ResourceLoadError;
|
||||||
|
|
||||||
pub struct WeaponData {
|
pub struct WeaponData {
|
||||||
pub weapon_id: u32,
|
pub weapon_id: u32,
|
||||||
|
|
|
@ -73,7 +73,7 @@ impl Xoroshiro32PlusPlus {
|
||||||
|
|
||||||
pub fn next_u16(&self) -> u16 {
|
pub fn next_u16(&self) -> u16 {
|
||||||
let mut state = self.0.get();
|
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.1 ^= state.0;
|
||||||
state.0 = state.0.rotate_left(13) ^ state.1 ^ (state.1 << 5);
|
state.0 = state.0.rotate_left(13) ^ state.1 ^ (state.1 << 5);
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
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 log::info;
|
||||||
use num_traits::abs;
|
use num_traits::{abs, clamp};
|
||||||
|
|
||||||
use crate::bullet::BulletManager;
|
use crate::bullet::BulletManager;
|
||||||
use crate::caret::CaretType;
|
use crate::caret::CaretType;
|
||||||
use crate::common::{Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect};
|
use crate::common::{Color, Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect};
|
||||||
use crate::components::boss_life_bar::BossLifeBar;
|
use crate::components::boss_life_bar::BossLifeBar;
|
||||||
use crate::components::draw_common::{Alignment, draw_number};
|
use crate::components::draw_common::{Alignment, draw_number};
|
||||||
use crate::components::hud::HUD;
|
use crate::components::hud::HUD;
|
||||||
use crate::components::stage_select::StageSelect;
|
use crate::components::stage_select::StageSelect;
|
||||||
use crate::entity::GameEntity;
|
use crate::entity::GameEntity;
|
||||||
use crate::frame::{Frame, UpdateTarget};
|
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::framework::ui::Components;
|
||||||
use crate::input::touch_controls::TouchControlType;
|
use crate::input::touch_controls::TouchControlType;
|
||||||
use crate::inventory::{Inventory, TakeExperienceResult};
|
use crate::inventory::{Inventory, TakeExperienceResult};
|
||||||
use crate::npc::boss::BossNPC;
|
use crate::npc::boss::BossNPC;
|
||||||
|
@ -28,7 +30,6 @@ use crate::shared_game_state::{Season, SharedGameState};
|
||||||
use crate::stage::{BackgroundType, Stage};
|
use crate::stage::{BackgroundType, Stage};
|
||||||
use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptVM};
|
use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||||
use crate::texture_set::SizedBatch;
|
use crate::texture_set::SizedBatch;
|
||||||
use crate::ui::Components;
|
|
||||||
use crate::weapon::WeaponType;
|
use crate::weapon::WeaponType;
|
||||||
|
|
||||||
pub struct GameScene {
|
pub struct GameScene {
|
||||||
|
@ -297,18 +298,18 @@ impl GameScene {
|
||||||
FadeDirection::Left | FadeDirection::Right => {
|
FadeDirection::Left | FadeDirection::Right => {
|
||||||
let mut frame = tick;
|
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 >= 15 { frame = 15; } else { frame += 1; }
|
||||||
|
|
||||||
if frame >= 0 {
|
if frame >= 0 {
|
||||||
rect.left = frame as u16 * 16;
|
rect.left = frame.abs() as u16 * 16;
|
||||||
rect.right = rect.left + 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 {
|
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 {
|
} 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 => {
|
FadeDirection::Up | FadeDirection::Down => {
|
||||||
let mut frame = tick;
|
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 >= 15 { frame = 15; } else { frame += 1; }
|
||||||
|
|
||||||
if frame >= 0 {
|
if frame >= 0 {
|
||||||
rect.left = frame as u16 * 16;
|
rect.left = frame.abs() as u16 * 16;
|
||||||
rect.right = rect.left + 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 {
|
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 {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,20 +340,20 @@ impl GameScene {
|
||||||
let center_y = (state.canvas_size.1 / 2.0 - 8.0) as i32;
|
let center_y = (state.canvas_size.1 / 2.0 - 8.0) as i32;
|
||||||
let mut start_frame = tick;
|
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;
|
let mut frame = start_frame;
|
||||||
|
|
||||||
for y in (0..(center_y + 16)).step_by(16) {
|
for y in 0..(center_y / 16 + 2) {
|
||||||
if frame > 15 { frame = 15; } else { frame += 1; }
|
if frame >= 15 { frame = 15; } else { frame += 1; }
|
||||||
|
|
||||||
if frame >= 0 {
|
if frame >= 0 {
|
||||||
rect.left = frame as u16 * 16;
|
rect.left = frame.abs() as u16 * 16;
|
||||||
rect.right = rect.left + 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 * 16) as f32, (center_y + y * 16) 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) 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) as f32, (center_y - y) as f32, &rect);
|
batch.add_rect((center_x + x * 16) as f32, (center_y - y * 16) as f32, &rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,13 +434,14 @@ impl GameScene {
|
||||||
};
|
};
|
||||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, tex_name)?;
|
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;
|
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 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,
|
batch.add_rect_flip(left_pos + 14.0, top_pos + 8.0,
|
||||||
scale_x * if flip { -1.0 } else { 1.0 }, scale_y,
|
flip, false,
|
||||||
&Rect::new_size(
|
&Rect::new_size(
|
||||||
(face_num as u16 % 6) * 48,
|
(face_num as u16 % 6) * 48,
|
||||||
(face_num as u16 / 6) * 48,
|
(face_num as u16 / 6) * 48,
|
||||||
|
@ -502,7 +504,14 @@ impl GameScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_light_map(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
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::set_blend_mode(ctx, BlendMode::Add)?;
|
||||||
|
|
||||||
graphics::clear(ctx, Color::from_rgb(100, 100, 110));
|
graphics::clear(ctx, Color::from_rgb(100, 100, 110));
|
||||||
|
@ -547,9 +556,9 @@ impl GameScene {
|
||||||
fix9_scale(npc.y - self.frame.y, scale),
|
fix9_scale(npc.y - self.frame.y, scale),
|
||||||
0.4, (255, 255, 0), batch);
|
0.4, (255, 255, 0), batch);
|
||||||
}
|
}
|
||||||
4 | 7 => self.draw_light(fix9_scale(npc.x - self.frame.x, scale),
|
7 => self.draw_light(fix9_scale(npc.x - self.frame.x, scale),
|
||||||
fix9_scale(npc.y - self.frame.y, scale),
|
fix9_scale(npc.y - self.frame.y, scale),
|
||||||
1.0, (100, 100, 100), batch),
|
1.0, (100, 100, 100), batch),
|
||||||
17 if npc.anim_num == 0 => {
|
17 if npc.anim_num == 0 => {
|
||||||
self.draw_light(fix9_scale(npc.x - self.frame.x, scale),
|
self.draw_light(fix9_scale(npc.x - self.frame.x, scale),
|
||||||
fix9_scale(npc.y - self.frame.y, scale),
|
fix9_scale(npc.y - self.frame.y, scale),
|
||||||
|
@ -584,13 +593,6 @@ impl GameScene {
|
||||||
fix9_scale(npc.y - self.frame.y, scale),
|
fix9_scale(npc.y - self.frame.y, scale),
|
||||||
3.5, (130 + flicker, 40 + flicker, 0), batch);
|
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 => {
|
70 => {
|
||||||
let flicker = 50 + npc.anim_num as u8 * 15;
|
let flicker = 50 + npc.anim_num as u8 * 15;
|
||||||
self.draw_light(fix9_scale(npc.x - self.frame.x, scale),
|
self.draw_light(fix9_scale(npc.x - self.frame.x, scale),
|
||||||
|
@ -635,97 +637,18 @@ impl GameScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics::set_blend_mode(ctx, BlendMode::Multiply)?;
|
graphics::set_blend_mode(ctx, BlendMode::Multiply)?;
|
||||||
graphics::set_canvas(ctx, Some(&state.game_canvas));
|
graphics::set_render_target(ctx, None)?;
|
||||||
state.lightmap_canvas.set_filter(FilterMode::Linear);
|
|
||||||
state.lightmap_canvas.draw(ctx, DrawParam::new()
|
let rect = Rect { left: 0.0, top: 0.0, right: state.screen_size.0, bottom: state.screen_size.1 };
|
||||||
.scale(Vector2::new(1.0 / state.scale, 1.0 / state.scale)))?;
|
canvas.clear();
|
||||||
|
canvas.add(SpriteBatchCommand::DrawRect(rect, rect));
|
||||||
|
canvas.draw()?;
|
||||||
|
|
||||||
graphics::set_blend_mode(ctx, BlendMode::Alpha)?;
|
graphics::set_blend_mode(ctx, BlendMode::Alpha)?;
|
||||||
|
|
||||||
Ok(())
|
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())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_tiles(&self, state: &mut SharedGameState, ctx: &mut Context, layer: TileLayer) -> GameResult {
|
fn draw_tiles(&self, state: &mut SharedGameState, ctx: &mut Context, layer: TileLayer) -> GameResult {
|
||||||
let tex = match layer {
|
let tex = match layer {
|
||||||
TileLayer::Snack => "Npc/NpcSym",
|
TileLayer::Snack => "Npc/NpcSym",
|
||||||
|
@ -1291,7 +1214,7 @@ impl Scene for GameScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
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_background(state, ctx)?;
|
||||||
self.draw_tiles(state, ctx, TileLayer::Background)?;
|
self.draw_tiles(state, ctx, TileLayer::Background)?;
|
||||||
if state.settings.shader_effects
|
if state.settings.shader_effects
|
||||||
|
@ -1316,9 +1239,9 @@ impl Scene for GameScene {
|
||||||
self.draw_bullets(state, ctx)?;
|
self.draw_bullets(state, ctx)?;
|
||||||
self.player2.draw(state, ctx, &self.frame)?;
|
self.player2.draw(state, ctx, &self.frame)?;
|
||||||
self.player1.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_water(state, ctx)?;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
self.draw_tiles(state, ctx, TileLayer::Foreground)?;
|
self.draw_tiles(state, ctx, TileLayer::Foreground)?;
|
||||||
self.draw_tiles(state, ctx, TileLayer::Snack)?;
|
self.draw_tiles(state, ctx, TileLayer::Snack)?;
|
||||||
|
@ -1329,9 +1252,9 @@ impl Scene for GameScene {
|
||||||
self.draw_light_map(state, ctx)?;
|
self.draw_light_map(state, ctx)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics::set_canvas(ctx, None);
|
/*graphics::set_canvas(ctx, None);
|
||||||
state.game_canvas.draw(ctx, DrawParam::new()
|
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)?;
|
self.draw_black_bars(state, ctx)?;
|
||||||
|
|
||||||
if state.control_flags.control_enabled() {
|
if state.control_flags.control_enabled() {
|
||||||
|
@ -1389,7 +1312,7 @@ impl Scene for GameScene {
|
||||||
self.draw_debug_outlines(state, ctx)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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::npc::NPCTable;
|
||||||
use crate::scene::no_data_scene::NoDataScene;
|
use crate::scene::no_data_scene::NoDataScene;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::ui::Components;
|
use crate::framework::ui::Components;
|
||||||
|
|
||||||
pub mod game_scene;
|
pub mod game_scene;
|
||||||
pub mod loading_scene;
|
pub mod loading_scene;
|
||||||
|
|
|
@ -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::common::Rect;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
|
@ -18,11 +19,11 @@ impl NoDataScene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
static REL_URL: &str = "https://github.com/doukutsu-rs/game-data/releases";
|
static REL_URL: &str = "https://github.com/doukutsu-rs/game-data/releases";
|
||||||
|
|
||||||
impl Scene for NoDataScene {
|
impl Scene for NoDataScene {
|
||||||
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
if !self.flag {
|
if !self.flag {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use ggez::{Context, GameResult, graphics};
|
use crate::common::{Rect, VERSION_BANNER, Color};
|
||||||
use ggez::graphics::Color;
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
use crate::common::{Rect, VERSION_BANNER};
|
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::menu::{Menu, MenuEntry, MenuSelectionResult};
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use crate::shared_game_state::{SharedGameState, TimingMode};
|
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)]
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
|
19
src/scripting/doukutsu.d.ts
vendored
19
src/scripting/doukutsu.d.ts
vendored
|
@ -14,9 +14,24 @@ declare interface DoukutsuScene {
|
||||||
tick(): number;
|
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 {
|
declare namespace doukutsu {
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use lua_ffi::ffi::luaL_Reg;
|
use lua_ffi::ffi::luaL_Reg;
|
||||||
use lua_ffi::{LuaObject, State, c_int};
|
use lua_ffi::{LuaObject, State, c_int};
|
||||||
|
|
||||||
use crate::scene::game_scene::GameScene;
|
|
||||||
use crate::scripting::LuaScriptingState;
|
use crate::scripting::LuaScriptingState;
|
||||||
use crate::shared_game_state::SharedGameState;
|
|
||||||
|
|
||||||
pub struct Doukutsu {
|
pub struct Doukutsu {
|
||||||
pub ptr: *mut LuaScriptingState,
|
pub ptr: *mut LuaScriptingState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
impl Doukutsu {
|
impl Doukutsu {
|
||||||
pub fn new(ptr: *mut LuaScriptingState) -> Doukutsu {
|
pub fn new(ptr: *mut LuaScriptingState) -> Doukutsu {
|
||||||
Doukutsu {
|
Doukutsu {
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
use std::io::{Read, Seek};
|
use std::io::Read;
|
||||||
use std::ptr::null_mut;
|
use std::ptr::null_mut;
|
||||||
|
|
||||||
use ggez::{Context, filesystem, GameError, GameResult};
|
|
||||||
use ggez::filesystem::File;
|
use crate::framework::context::Context;
|
||||||
use lua_ffi::{c_int, LuaFunction, LuaObject, State, ThreadStatus};
|
use crate::framework::error::{GameResult, GameError};
|
||||||
use lua_ffi::ffi::lua_pushcfunction;
|
|
||||||
|
|
||||||
|
use lua_ffi::{c_int, State, ThreadStatus};
|
||||||
|
|
||||||
use crate::scene::game_scene::GameScene;
|
use crate::scene::game_scene::GameScene;
|
||||||
use crate::scripting::doukutsu::Doukutsu;
|
use crate::scripting::doukutsu::Doukutsu;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
use crate::framework::filesystem::File;
|
||||||
|
use crate::framework::filesystem;
|
||||||
|
|
||||||
mod doukutsu;
|
mod doukutsu;
|
||||||
mod player;
|
mod player;
|
||||||
|
@ -112,7 +116,7 @@ impl LuaScriptingState {
|
||||||
|
|
||||||
if filesystem::exists(ctx, "/scripts/") {
|
if filesystem::exists(ctx, "/scripts/") {
|
||||||
let mut script_count = 0;
|
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"));
|
.filter(|f| f.to_string_lossy().to_lowercase().ends_with(".lua"));
|
||||||
|
|
||||||
for file in files {
|
for file in files {
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub struct LuaPlayer {
|
||||||
inv_ptr: *mut Inventory,
|
inv_ptr: *mut Inventory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
impl LuaPlayer {
|
impl LuaPlayer {
|
||||||
fn check_ref(&self, state: &mut State) -> bool {
|
fn check_ref(&self, state: &mut State) -> bool {
|
||||||
if !self.valid_reference {
|
if !self.valid_reference {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use ggez::{Context, GameResult};
|
|
||||||
use serde::{Deserialize, Serialize};
|
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::keyboard_player_controller::KeyboardController;
|
||||||
use crate::input::player_controller::PlayerController;
|
use crate::input::player_controller::PlayerController;
|
||||||
use crate::player::TargetPlayer;
|
|
||||||
use crate::input::touch_player_controller::TouchPlayerController;
|
use crate::input::touch_player_controller::TouchPlayerController;
|
||||||
|
use crate::player::TargetPlayer;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
|
@ -66,47 +67,47 @@ impl Default for Settings {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct PlayerKeyMap {
|
pub struct PlayerKeyMap {
|
||||||
pub left: VirtualKeyCode,
|
pub left: ScanCode,
|
||||||
pub up: VirtualKeyCode,
|
pub up: ScanCode,
|
||||||
pub right: VirtualKeyCode,
|
pub right: ScanCode,
|
||||||
pub down: VirtualKeyCode,
|
pub down: ScanCode,
|
||||||
pub prev_weapon: VirtualKeyCode,
|
pub prev_weapon: ScanCode,
|
||||||
pub next_weapon: VirtualKeyCode,
|
pub next_weapon: ScanCode,
|
||||||
pub jump: VirtualKeyCode,
|
pub jump: ScanCode,
|
||||||
pub shoot: VirtualKeyCode,
|
pub shoot: ScanCode,
|
||||||
pub skip: VirtualKeyCode,
|
pub skip: ScanCode,
|
||||||
pub inventory: VirtualKeyCode,
|
pub inventory: ScanCode,
|
||||||
pub map: VirtualKeyCode,
|
pub map: ScanCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn p1_default_keymap() -> PlayerKeyMap {
|
fn p1_default_keymap() -> PlayerKeyMap {
|
||||||
PlayerKeyMap {
|
PlayerKeyMap {
|
||||||
left: VirtualKeyCode::Left,
|
left: ScanCode::Left,
|
||||||
up: VirtualKeyCode::Up,
|
up: ScanCode::Up,
|
||||||
right: VirtualKeyCode::Right,
|
right: ScanCode::Right,
|
||||||
down: VirtualKeyCode::Down,
|
down: ScanCode::Down,
|
||||||
prev_weapon: VirtualKeyCode::A,
|
prev_weapon: ScanCode::A,
|
||||||
next_weapon: VirtualKeyCode::S,
|
next_weapon: ScanCode::S,
|
||||||
jump: VirtualKeyCode::Z,
|
jump: ScanCode::Z,
|
||||||
shoot: VirtualKeyCode::X,
|
shoot: ScanCode::X,
|
||||||
skip: VirtualKeyCode::LControl,
|
skip: ScanCode::LControl,
|
||||||
inventory: VirtualKeyCode::Q,
|
inventory: ScanCode::Q,
|
||||||
map: VirtualKeyCode::W,
|
map: ScanCode::W,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn p2_default_keymap() -> PlayerKeyMap {
|
fn p2_default_keymap() -> PlayerKeyMap {
|
||||||
PlayerKeyMap {
|
PlayerKeyMap {
|
||||||
left: VirtualKeyCode::Comma,
|
left: ScanCode::Comma,
|
||||||
up: VirtualKeyCode::L,
|
up: ScanCode::L,
|
||||||
right: VirtualKeyCode::Slash,
|
right: ScanCode::Slash,
|
||||||
down: VirtualKeyCode::Period,
|
down: ScanCode::Period,
|
||||||
prev_weapon: VirtualKeyCode::G,
|
prev_weapon: ScanCode::G,
|
||||||
next_weapon: VirtualKeyCode::H,
|
next_weapon: ScanCode::H,
|
||||||
jump: VirtualKeyCode::B,
|
jump: ScanCode::B,
|
||||||
shoot: VirtualKeyCode::N,
|
shoot: ScanCode::N,
|
||||||
skip: VirtualKeyCode::RControl,
|
skip: ScanCode::U,
|
||||||
inventory: VirtualKeyCode::T,
|
inventory: ScanCode::T,
|
||||||
map: VirtualKeyCode::Y,
|
map: ScanCode::Y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use gfx::{self, *};
|
use gfx::{self, *};
|
||||||
use ggez::graphics::Shader;
|
|
||||||
use ggez::{Context, GameResult};
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
|
||||||
gfx_defines! {
|
gfx_defines! {
|
||||||
constant WaterShaderParams {
|
constant WaterShaderParams {
|
||||||
|
@ -11,7 +12,7 @@ gfx_defines! {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Shaders {
|
pub struct Shaders {
|
||||||
pub water_shader: Shader<WaterShaderParams>,
|
//pub water_shader: Shader<WaterShaderParams>,
|
||||||
pub water_shader_params: WaterShaderParams,
|
pub water_shader_params: WaterShaderParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,14 +25,14 @@ impl Shaders {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Shaders {
|
Ok(Shaders {
|
||||||
water_shader: Shader::new(
|
/*water_shader: Shader::new(
|
||||||
ctx,
|
ctx,
|
||||||
"/builtin/shaders/basic_es300.vert.glsl",
|
"/builtin/shaders/basic_es300.vert.glsl",
|
||||||
"/builtin/shaders/water_es300.frag.glsl",
|
"/builtin/shaders/water_es300.frag.glsl",
|
||||||
water_shader_params,
|
water_shader_params,
|
||||||
"WaterShaderParams",
|
"WaterShaderParams",
|
||||||
None,
|
None,
|
||||||
)?,
|
)?,*/
|
||||||
water_shader_params,
|
water_shader_params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use std::ops::Div;
|
use std::ops::Div;
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use bitvec::vec::BitVec;
|
use bitvec::vec::BitVec;
|
||||||
use chrono::{Datelike, Local};
|
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::clamp;
|
||||||
|
use num_traits::real::Real;
|
||||||
|
|
||||||
use crate::bmfont_renderer::BMFontRenderer;
|
use crate::bmfont_renderer::BMFontRenderer;
|
||||||
use crate::caret::{Caret, CaretType};
|
use crate::caret::{Caret, CaretType};
|
||||||
use crate::common::{ControlFlags, Direction, FadeState};
|
use crate::common::{ControlFlags, Direction, FadeState};
|
||||||
use crate::engine_constants::EngineConstants;
|
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::input::touch_controls::TouchControls;
|
||||||
use crate::npc::NPCTable;
|
use crate::npc::NPCTable;
|
||||||
use crate::profile::GameProfile;
|
use crate::profile::GameProfile;
|
||||||
|
@ -21,12 +21,15 @@ use crate::scene::Scene;
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting")]
|
||||||
use crate::scripting::LuaScriptingState;
|
use crate::scripting::LuaScriptingState;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::shaders::Shaders;
|
|
||||||
use crate::sound::SoundManager;
|
use crate::sound::SoundManager;
|
||||||
use crate::stage::StageData;
|
use crate::stage::StageData;
|
||||||
use crate::str;
|
use crate::str;
|
||||||
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||||
use crate::texture_set::{TextureSet};
|
use crate::texture_set::TextureSet;
|
||||||
|
use crate::framework::{filesystem, graphics};
|
||||||
|
use crate::framework::backend::BackendTexture;
|
||||||
|
use crate::framework::graphics::{set_render_target, create_texture_mutable};
|
||||||
|
use crate::framework::keyboard::ScanCode;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum TimingMode {
|
pub enum TimingMode {
|
||||||
|
@ -101,15 +104,13 @@ pub struct SharedGameState {
|
||||||
pub npc_super_pos: (i32, i32),
|
pub npc_super_pos: (i32, i32),
|
||||||
pub stages: Vec<StageData>,
|
pub stages: Vec<StageData>,
|
||||||
pub frame_time: f64,
|
pub frame_time: f64,
|
||||||
|
pub debugger: bool,
|
||||||
pub scale: f32,
|
pub scale: f32,
|
||||||
pub shaders: Shaders,
|
|
||||||
pub tmp_canvas: Canvas,
|
|
||||||
pub game_canvas: Canvas,
|
|
||||||
pub lightmap_canvas: Canvas,
|
|
||||||
pub canvas_size: (f32, f32),
|
pub canvas_size: (f32, f32),
|
||||||
pub screen_size: (f32, f32),
|
pub screen_size: (f32, f32),
|
||||||
pub next_scene: Option<Box<dyn Scene>>,
|
pub next_scene: Option<Box<dyn Scene>>,
|
||||||
pub textscript_vm: TextScriptVM,
|
pub textscript_vm: TextScriptVM,
|
||||||
|
pub lightmap_canvas: Option<Box<dyn BackendTexture>>,
|
||||||
pub season: Season,
|
pub season: Season,
|
||||||
pub constants: EngineConstants,
|
pub constants: EngineConstants,
|
||||||
pub font: BMFontRenderer,
|
pub font: BMFontRenderer,
|
||||||
|
@ -118,16 +119,11 @@ pub struct SharedGameState {
|
||||||
pub lua: LuaScriptingState,
|
pub lua: LuaScriptingState,
|
||||||
pub sound_manager: SoundManager,
|
pub sound_manager: SoundManager,
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
pub shutdown: bool,
|
pub shutdown: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedGameState {
|
impl SharedGameState {
|
||||||
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
|
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
|
||||||
let screen_size = graphics::drawable_size(ctx);
|
|
||||||
let scale = screen_size.1.div(235.0).floor().max(1.0);
|
|
||||||
|
|
||||||
let canvas_size = (screen_size.0 / scale, screen_size.1 / scale);
|
|
||||||
|
|
||||||
let mut constants = EngineConstants::defaults();
|
let mut constants = EngineConstants::defaults();
|
||||||
let mut base_path = "/";
|
let mut base_path = "/";
|
||||||
let settings = Settings::load(ctx)?;
|
let settings = Settings::load(ctx)?;
|
||||||
|
@ -164,7 +160,7 @@ impl SharedGameState {
|
||||||
game_flags: bitvec::bitvec![0; 8000],
|
game_flags: bitvec::bitvec![0; 8000],
|
||||||
fade_state: FadeState::Hidden,
|
fade_state: FadeState::Hidden,
|
||||||
game_rng: XorShift::new(0),
|
game_rng: XorShift::new(0),
|
||||||
effect_rng: XorShift::new(Instant::now().elapsed().as_nanos() as i32),
|
effect_rng: XorShift::new(123),
|
||||||
quake_counter: 0,
|
quake_counter: 0,
|
||||||
teleporter_slots: Vec::with_capacity(8),
|
teleporter_slots: Vec::with_capacity(8),
|
||||||
carets: Vec::with_capacity(32),
|
carets: Vec::with_capacity(32),
|
||||||
|
@ -174,15 +170,13 @@ impl SharedGameState {
|
||||||
npc_super_pos: (0, 0),
|
npc_super_pos: (0, 0),
|
||||||
stages: Vec::with_capacity(96),
|
stages: Vec::with_capacity(96),
|
||||||
frame_time: 0.0,
|
frame_time: 0.0,
|
||||||
scale,
|
debugger: false,
|
||||||
shaders: Shaders::new(ctx)?,
|
scale: 2.0,
|
||||||
tmp_canvas: Canvas::with_window_size(ctx)?,
|
screen_size: (640.0, 480.0),
|
||||||
game_canvas: Canvas::with_window_size(ctx)?,
|
canvas_size: (320.0, 240.0),
|
||||||
lightmap_canvas: Canvas::with_window_size(ctx)?,
|
|
||||||
screen_size,
|
|
||||||
canvas_size,
|
|
||||||
next_scene: None,
|
next_scene: None,
|
||||||
textscript_vm: TextScriptVM::new(),
|
textscript_vm: TextScriptVM::new(),
|
||||||
|
lightmap_canvas: None,
|
||||||
season,
|
season,
|
||||||
constants,
|
constants,
|
||||||
font,
|
font,
|
||||||
|
@ -195,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) {
|
pub fn reload_textures(&mut self) {
|
||||||
let mut texture_set = TextureSet::new(self.base_path.as_str());
|
let mut texture_set = TextureSet::new(self.base_path.as_str());
|
||||||
|
|
||||||
|
@ -288,11 +304,15 @@ impl SharedGameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_resize(&mut self, ctx: &mut Context) -> GameResult {
|
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(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);
|
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))?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -312,10 +332,6 @@ impl SharedGameState {
|
||||||
pub fn set_speed(&mut self, value: f64) {
|
pub fn set_speed(&mut self, value: f64) {
|
||||||
self.settings.speed = clamp(value, 0.1, 3.0);
|
self.settings.speed = clamp(value, 0.1, 3.0);
|
||||||
self.frame_time = 0.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 {
|
pub fn current_tps(&self) -> f64 {
|
||||||
|
|
|
@ -4,16 +4,18 @@ use std::time::Duration;
|
||||||
|
|
||||||
use cpal::Sample;
|
use cpal::Sample;
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use ggez::{Context, filesystem, GameResult};
|
|
||||||
use ggez::GameError::{AudioError, InvalidValue, ResourceLoadError};
|
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::engine_constants::EngineConstants;
|
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::organya::Song;
|
||||||
use crate::sound::pixtone::PixTonePlayback;
|
use crate::sound::pixtone::PixTonePlayback;
|
||||||
use crate::sound::playback::{PlaybackEngine, SavedPlaybackState};
|
use crate::sound::playback::{PlaybackEngine, SavedPlaybackState};
|
||||||
use crate::sound::wave_bank::SoundBank;
|
use crate::sound::wave_bank::SoundBank;
|
||||||
use crate::str;
|
use crate::str;
|
||||||
|
use crate::framework::error::GameError::{AudioError, ResourceLoadError, InvalidValue};
|
||||||
|
|
||||||
mod wave_bank;
|
mod wave_bank;
|
||||||
mod organya;
|
mod organya;
|
||||||
|
@ -126,13 +128,19 @@ impl SoundManager {
|
||||||
.find(|path| filesystem::exists(ctx, path))
|
.find(|path| filesystem::exists(ctx, path))
|
||||||
.ok_or_else(|| ResourceLoadError(format!("BGM {:?} does not exist.", song_name)))?;
|
.ok_or_else(|| ResourceLoadError(format!("BGM {:?} does not exist.", song_name)))?;
|
||||||
|
|
||||||
let org = organya::Song::load_from(filesystem::open(ctx, path)?)?;
|
match filesystem::open(ctx, path).map(|f| organya::Song::load_from(f)) {
|
||||||
log::info!("Playing BGM: {}", song_name);
|
Ok(Ok(org)) => {
|
||||||
|
log::info!("Playing BGM: {} {}", song_id, song_name);
|
||||||
|
|
||||||
self.prev_song_id = self.current_song_id;
|
self.prev_song_id = self.current_song_id;
|
||||||
self.current_song_id = song_id;
|
self.current_song_id = song_id;
|
||||||
self.tx.send(PlaybackMessage::SaveState)?;
|
self.tx.send(PlaybackMessage::SaveState)?;
|
||||||
self.tx.send(PlaybackMessage::PlaySong(Box::new(org)))?;
|
self.tx.send(PlaybackMessage::PlaySong(Box::new(org)))?;
|
||||||
|
}
|
||||||
|
Ok(Err(err)) | Err(err) => {
|
||||||
|
log::warn!("Failed to load BGM {}: {}", song_id, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use byteorder::{LE, ReadBytesExt};
|
||||||
|
|
||||||
|
use crate::framework::error::{GameError, GameResult};
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum Version {
|
pub enum Version {
|
||||||
// Can't find any files with this signature,
|
// Can't find any files with this signature,
|
||||||
// But apparently these files had no Pi flag.
|
// But apparently these files had no Pi flag.
|
||||||
Beta = b'1',
|
Beta = b'1',
|
||||||
Main = b'2',
|
Main = b'2',
|
||||||
// OrgMaker 2.05 Extended Drums
|
// OrgMaker 2.05 Extended Drums
|
||||||
Extended = b'3'
|
Extended = b'3',
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
@ -14,19 +20,19 @@ pub struct LoopRange {
|
||||||
// inclusive
|
// inclusive
|
||||||
pub start: i32,
|
pub start: i32,
|
||||||
// exclusive
|
// exclusive
|
||||||
pub end: i32
|
pub end: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
pub beats: u8,
|
pub beats: u8,
|
||||||
pub steps: u8
|
pub steps: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Timing {
|
pub struct Timing {
|
||||||
pub wait: u16,
|
pub wait: u16,
|
||||||
pub loop_range: LoopRange
|
pub loop_range: LoopRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
@ -35,12 +41,12 @@ pub struct Instrument {
|
||||||
pub freq: u16,
|
pub freq: u16,
|
||||||
pub inst: u8,
|
pub inst: u8,
|
||||||
pub pipi: u8,
|
pub pipi: u8,
|
||||||
pub notes: u16
|
pub notes: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
pub inst: Instrument,
|
pub inst: Instrument,
|
||||||
pub notes: Vec<Note>
|
pub notes: Vec<Note>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Track {
|
impl Clone for Track {
|
||||||
|
@ -65,14 +71,14 @@ pub struct Note {
|
||||||
pub key: u8,
|
pub key: u8,
|
||||||
pub len: u8,
|
pub len: u8,
|
||||||
pub vol: u8,
|
pub vol: u8,
|
||||||
pub pan: u8
|
pub pan: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub time: Timing,
|
pub time: Timing,
|
||||||
pub tracks: [Track; 16]
|
pub tracks: [Track; 16],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Song {
|
impl Clone for Song {
|
||||||
|
@ -85,85 +91,82 @@ impl Clone for Song {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use byteorder::{LE, ReadBytesExt};
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
impl Song {
|
impl Song {
|
||||||
pub fn empty() -> Song {
|
pub fn empty() -> Song {
|
||||||
Song {
|
Song {
|
||||||
version: Version::Main,
|
version: Version::Main,
|
||||||
time: Timing { wait: 8, loop_range: LoopRange { start: 0, end: 1 } },
|
time: Timing { wait: 8, loop_range: LoopRange { start: 0, end: 1 } },
|
||||||
tracks: [
|
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<R: io::Read>(mut f: R) -> io::Result<Song> {
|
pub fn load_from<R: io::Read>(mut f: R) -> GameResult<Song> {
|
||||||
let mut magic = [0; 6];
|
let mut magic = [0; 6];
|
||||||
|
|
||||||
f.read_exact(&mut magic)?;
|
f.read_exact(&mut magic)?;
|
||||||
|
|
||||||
let version =
|
let version =
|
||||||
match &magic {
|
match &magic {
|
||||||
b"Org-01" => Version::Beta,
|
b"Org-01" => Version::Beta,
|
||||||
b"Org-02" => Version::Main,
|
b"Org-02" => Version::Main,
|
||||||
b"Org-03" => Version::Extended,
|
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::<LE>()?;
|
let wait = f.read_u16::<LE>()?;
|
||||||
let _bpm = f.read_u8()?;
|
let _bpm = f.read_u8()?;
|
||||||
let _spb = f.read_u8()?;
|
let _spb = f.read_u8()?;
|
||||||
let start = f.read_i32::<LE>()?;
|
let start = f.read_i32::<LE>()?;
|
||||||
let end = f.read_i32::<LE>()?;
|
let end = f.read_i32::<LE>()?;
|
||||||
|
|
||||||
use std::mem::MaybeUninit as Mu;
|
use std::mem::MaybeUninit as Mu;
|
||||||
|
|
||||||
let mut insts: [Mu<Instrument>; 16] = unsafe {
|
let mut insts: [Mu<Instrument>; 16] = unsafe {
|
||||||
Mu::uninit().assume_init()
|
Mu::uninit().assume_init()
|
||||||
};
|
};
|
||||||
|
|
||||||
for i in insts.iter_mut() {
|
for i in insts.iter_mut() {
|
||||||
let freq = f.read_u16::<LE>()?;
|
let freq = f.read_u16::<LE>()?;
|
||||||
let inst = f.read_u8()?;
|
let inst = f.read_u8()?;
|
||||||
let pipi = f.read_u8()?;
|
let pipi = f.read_u8()?;
|
||||||
let notes = f.read_u16::<LE>()?;
|
let notes = f.read_u16::<LE>()?;
|
||||||
|
|
||||||
*i = Mu::new(Instrument {
|
*i = Mu::new(Instrument {
|
||||||
freq,
|
freq,
|
||||||
inst,
|
inst,
|
||||||
pipi,
|
pipi,
|
||||||
notes
|
notes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let insts: [Instrument; 16] = unsafe {
|
let insts: [Instrument; 16] = unsafe {
|
||||||
std::mem::transmute(insts)
|
std::mem::transmute(insts)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut tracks: [Mu<Track>; 16] = unsafe {
|
let mut tracks: [Mu<Track>; 16] = unsafe {
|
||||||
Mu::uninit().assume_init()
|
Mu::uninit().assume_init()
|
||||||
};
|
};
|
||||||
|
|
||||||
for (i, t) in tracks.iter_mut().enumerate() {
|
for (i, t) in tracks.iter_mut().enumerate() {
|
||||||
let count = insts[i].notes as usize;
|
let count = insts[i].notes as usize;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
struct UninitNote {
|
struct UninitNote {
|
||||||
|
@ -171,55 +174,55 @@ impl Song {
|
||||||
key: Mu<u8>,
|
key: Mu<u8>,
|
||||||
len: Mu<u8>,
|
len: Mu<u8>,
|
||||||
vol: Mu<u8>,
|
vol: Mu<u8>,
|
||||||
pan: Mu<u8>
|
pan: Mu<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut notes: Vec<UninitNote> = unsafe {
|
let mut notes: Vec<UninitNote> = unsafe {
|
||||||
vec![Mu::uninit().assume_init(); count]
|
vec![Mu::uninit().assume_init(); count]
|
||||||
};
|
};
|
||||||
|
|
||||||
for note in notes.iter_mut() {
|
for note in notes.iter_mut() {
|
||||||
note.pos = Mu::new(f.read_i32::<LE>()?);
|
note.pos = Mu::new(f.read_i32::<LE>()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
for note in notes.iter_mut() {
|
for note in notes.iter_mut() {
|
||||||
note.key = Mu::new(f.read_u8()?);
|
note.key = Mu::new(f.read_u8()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
for note in notes.iter_mut() {
|
for note in notes.iter_mut() {
|
||||||
note.len = Mu::new(f.read_u8()?);
|
note.len = Mu::new(f.read_u8()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
for note in notes.iter_mut() {
|
for note in notes.iter_mut() {
|
||||||
note.vol = Mu::new(f.read_u8()?);
|
note.vol = Mu::new(f.read_u8()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
for note in notes.iter_mut() {
|
for note in notes.iter_mut() {
|
||||||
note.pan = Mu::new(f.read_u8()?);
|
note.pan = Mu::new(f.read_u8()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
*t = Mu::new(Track {
|
*t = Mu::new(Track {
|
||||||
inst: insts[i],
|
inst: insts[i],
|
||||||
notes: unsafe { std::mem::transmute(notes) }
|
notes: unsafe { std::mem::transmute(notes) },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let tracks = unsafe {
|
let tracks = unsafe {
|
||||||
std::mem::transmute(tracks)
|
std::mem::transmute(tracks)
|
||||||
};
|
};
|
||||||
|
|
||||||
let song = Song {
|
let song = Song {
|
||||||
version,
|
version,
|
||||||
time: Timing {
|
time: Timing {
|
||||||
wait,
|
wait,
|
||||||
loop_range: LoopRange {
|
loop_range: LoopRange {
|
||||||
start,
|
start,
|
||||||
end
|
end,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
tracks
|
tracks,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(song)
|
Ok(song)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::mem::MaybeUninit;
|
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::stuff::*;
|
||||||
use crate::sound::wav::*;
|
use crate::sound::wav::*;
|
||||||
use crate::sound::wave_bank::SoundBank;
|
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() {
|
for (idx, (track, buf)) in song.tracks[8..].iter().zip(self.track_buffers[128..].iter_mut()).enumerate() {
|
||||||
*buf = RenderBuffer::new(samples.samples[idx].clone());
|
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;
|
self.song = song;
|
||||||
|
@ -162,6 +166,7 @@ impl PlaybackEngine {
|
||||||
self.play_pos = position;
|
self.play_pos = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
pub fn get_total_samples(&self) -> u32 {
|
pub fn get_total_samples(&self) -> u32 {
|
||||||
let ticks_intro = self.song.time.loop_range.start;
|
let ticks_intro = self.song.time.loop_range.start;
|
||||||
let ticks_loop = self.song.time.loop_range.end - self.song.time.loop_range.start;
|
let ticks_loop = self.song.time.loop_range.end - self.song.time.loop_range.start;
|
||||||
|
|
|
@ -7,10 +7,12 @@ use log::info;
|
||||||
|
|
||||||
use crate::encoding::read_cur_shift_jis;
|
use crate::encoding::read_cur_shift_jis;
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
use ggez::{Context, filesystem, GameResult};
|
use crate::framework::context::Context;
|
||||||
use ggez::GameError::ResourceLoadError;
|
use crate::framework::error::GameResult;
|
||||||
|
use crate::framework::filesystem;
|
||||||
use crate::map::{Map, NPCData};
|
use crate::map::{Map, NPCData};
|
||||||
use crate::text_script::TextScript;
|
use crate::text_script::TextScript;
|
||||||
|
use crate::framework::error::GameError::ResourceLoadError;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct NpcType {
|
pub struct NpcType {
|
||||||
|
|
|
@ -9,8 +9,6 @@ use std::ops::Not;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use byteorder::ReadBytesExt;
|
use byteorder::ReadBytesExt;
|
||||||
use ggez::{Context, GameResult};
|
|
||||||
use ggez::GameError::{InvalidValue, ParseError};
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use num_traits::{clamp, 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::engine_constants::EngineConstants;
|
||||||
use crate::entity::GameEntity;
|
use crate::entity::GameEntity;
|
||||||
use crate::frame::UpdateTarget;
|
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::npc::NPC;
|
||||||
use crate::player::{ControlMode, TargetPlayer};
|
use crate::player::{ControlMode, TargetPlayer};
|
||||||
use crate::scene::game_scene::GameScene;
|
use crate::scene::game_scene::GameScene;
|
||||||
|
@ -1750,6 +1751,7 @@ impl TextScript {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
fn read_varint<I: Iterator<Item=u8>>(iter: &mut I) -> GameResult<i32> {
|
fn read_varint<I: Iterator<Item=u8>>(iter: &mut I) -> GameResult<i32> {
|
||||||
let mut result = 0u32;
|
let mut result = 0u32;
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{BufReader, Read, Seek, SeekFrom};
|
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 image::RgbaImage;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::common;
|
use crate::common;
|
||||||
use crate::common::FILE_TYPES;
|
use crate::common::{FILE_TYPES, Rect};
|
||||||
use crate::engine_constants::EngineConstants;
|
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::graphics::{create_texture, FilterMode};
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::shared_game_state::Season;
|
use crate::shared_game_state::Season;
|
||||||
use crate::str;
|
use crate::str;
|
||||||
|
|
||||||
|
pub static mut I_MAG: f32 = 1.0;
|
||||||
pub static mut G_MAG: f32 = 1.0;
|
pub static mut G_MAG: f32 = 1.0;
|
||||||
|
|
||||||
pub struct SizedBatch {
|
pub struct SizedBatch {
|
||||||
pub batch: SpriteBatch,
|
batch: Box<dyn BackendTexture>,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
real_width: usize,
|
real_width: usize,
|
||||||
|
@ -66,22 +66,65 @@ impl SizedBatch {
|
||||||
self.batch.clear();
|
self.batch.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, x: f32, y: f32) {
|
pub fn add(&mut self, mut x: f32, mut y: f32) {
|
||||||
let param = DrawParam::new()
|
unsafe {
|
||||||
.dest(Point2::new(x, y))
|
x = (x * G_MAG).round() / G_MAG;
|
||||||
.scale(Vector2::new(self.scale_x, self.scale_y));
|
y = (y * G_MAG).round() / 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)]
|
#[inline(always)]
|
||||||
pub fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<u16>) {
|
pub fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<u16>) {
|
||||||
self.add_rect_scaled(x, y, self.scale_x, self.scale_y, rect)
|
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<u16>) {
|
||||||
|
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)]
|
#[inline(always)]
|
||||||
pub fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>) {
|
pub fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>) {
|
||||||
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, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
|
pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
|
||||||
|
@ -90,36 +133,53 @@ impl SizedBatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
x = (x * G_MAG).floor() / G_MAG;
|
x = (x * G_MAG).round() / G_MAG;
|
||||||
y = (y * G_MAG).floor() / G_MAG;
|
y = (y * G_MAG).round() / G_MAG;
|
||||||
}
|
}
|
||||||
|
let mag = unsafe { I_MAG };
|
||||||
|
|
||||||
let param = DrawParam::new()
|
self.batch.add(SpriteBatchCommand::DrawRect(
|
||||||
.src(Rect::new(rect.left as f32 / self.width as f32,
|
Rect {
|
||||||
rect.top as f32 / self.height as f32,
|
left: rect.left as f32 / self.scale_x,
|
||||||
(rect.right - rect.left) as f32 / self.width as f32,
|
top: rect.top as f32 / self.scale_y,
|
||||||
(rect.bottom - rect.top) as f32 / self.height as f32))
|
right: rect.right as f32 / self.scale_x,
|
||||||
.dest(mint::Point2 { x, y })
|
bottom: rect.bottom as f32 / self.scale_y,
|
||||||
.scale(Vector2::new(scale_x, scale_y));
|
},
|
||||||
|
Rect {
|
||||||
self.batch.add(param);
|
left: x * mag,
|
||||||
|
top: y * mag,
|
||||||
|
right: (x + rect.width() as f32 * scale_x) * mag,
|
||||||
|
bottom: (y + rect.height() as f32 * scale_y) * 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<u16>) {
|
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<u16>) {
|
||||||
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
|
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let param = DrawParam::new()
|
unsafe {
|
||||||
.color(color.into())
|
x = (x * G_MAG).floor() / G_MAG;
|
||||||
.src(Rect::new(rect.left as f32 / self.width as f32,
|
y = (y * G_MAG).floor() / G_MAG;
|
||||||
rect.top as f32 / self.height as f32,
|
}
|
||||||
(rect.right - rect.left) as f32 / self.width as f32,
|
let mag = unsafe { I_MAG };
|
||||||
(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::DrawRectTinted(
|
||||||
|
Rect {
|
||||||
|
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 * scale_x) * mag,
|
||||||
|
bottom: (y + rect.height() as f32 * scale_y) * mag,
|
||||||
|
},
|
||||||
|
color.into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -128,8 +188,8 @@ impl SizedBatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult {
|
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.draw()?;
|
||||||
self.batch.clear();
|
self.batch.clear();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -168,7 +228,7 @@ impl TextureSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_image(&self, ctx: &mut Context, path: &str) -> GameResult<Image> {
|
fn load_image(&self, ctx: &mut Context, path: &str) -> GameResult<Box<dyn BackendTexture>> {
|
||||||
let img = {
|
let img = {
|
||||||
let mut buf = [0u8; 8];
|
let mut buf = [0u8; 8];
|
||||||
let mut reader = filesystem::open(ctx, path)?;
|
let mut reader = filesystem::open(ctx, path)?;
|
||||||
|
@ -184,7 +244,7 @@ impl TextureSet {
|
||||||
};
|
};
|
||||||
let (width, height) = img.dimensions();
|
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<SizedBatch> {
|
pub fn load_texture(&self, ctx: &mut Context, constants: &EngineConstants, name: &str) -> GameResult<SizedBatch> {
|
||||||
|
@ -199,27 +259,25 @@ impl TextureSet {
|
||||||
|
|
||||||
info!("Loading texture: {}", path);
|
info!("Loading texture: {}", path);
|
||||||
|
|
||||||
let image = self.load_image(ctx, &path)?;
|
let batch = self.load_image(ctx, &path)?;
|
||||||
let size = image.dimensions();
|
let size = batch.dimensions();
|
||||||
|
|
||||||
assert_ne!(size.w as isize, 0, "size.w == 0");
|
assert_ne!(size.0 as isize, 0, "size.width == 0");
|
||||||
assert_ne!(size.h as isize, 0, "size.h == 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(|| &size);
|
||||||
let orig_dimensions = constants.tex_sizes.get(name).unwrap_or_else(|| &dim);
|
let scale = orig_dimensions.0 as f32 / size.0 as f32;
|
||||||
let scale_x = orig_dimensions.0 as f32 / size.w;
|
let width = (size.0 as f32 * scale) as usize;
|
||||||
let scale_y = orig_dimensions.0 as f32 / size.w;
|
let height = (size.1 as f32 * scale) as usize;
|
||||||
let width = (size.w * scale_x) as usize;
|
|
||||||
let height = (size.h * scale_y) as usize;
|
|
||||||
|
|
||||||
Ok(SizedBatch {
|
Ok(SizedBatch {
|
||||||
batch: SpriteBatch::new(image),
|
batch,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
scale_x,
|
scale_x: scale,
|
||||||
scale_y,
|
scale_y: scale,
|
||||||
real_width: size.w as usize,
|
real_width: size.0 as usize,
|
||||||
real_height: size.h as usize,
|
real_height: size.1 as usize,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,14 +291,10 @@ impl TextureSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_rect(&self, rect: common::Rect, color: [f32; 4], ctx: &mut Context) -> GameResult {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_outline_rect(&self, rect: common::Rect, width: f32, color: [f32; 4], ctx: &mut Context) -> GameResult {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
191
src/ui.rs
191
src/ui.rs
|
@ -1,191 +0,0 @@
|
||||||
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::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<Rgba8, types::Resources>,
|
|
||||||
pub components: Components,
|
|
||||||
pub main_color: RenderTargetView<types::Resources, Rgba8>,
|
|
||||||
pub main_depth: DepthStencilView<types::Resources, DepthStencil>,
|
|
||||||
last_frame: Instant,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Components {
|
|
||||||
pub live_debugger: LiveDebugger,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UI {
|
|
||||||
pub fn new(ctx: &mut Context) -> GameResult<Self> {
|
|
||||||
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];
|
|
||||||
|
|
||||||
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<dyn Scene>) -> 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;
|
|
||||||
}
|
|
||||||
let mut ui = self.imgui.frame();
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) {
|
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 shoot = false;
|
||||||
let mut btype = 0;
|
let mut btype;
|
||||||
|
|
||||||
if player.controller.shoot() {
|
if player.controller.shoot() {
|
||||||
inventory.add_xp(if player.equip.has_turbocharge() { 3 } else { 2 }, player, state);
|
inventory.add_xp(if player.equip.has_turbocharge() { 3 } else { 2 }, player, state);
|
||||||
|
@ -443,8 +443,8 @@ impl Weapon {
|
||||||
WeaponLevel::None => unreachable!(),
|
WeaponLevel::None => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
const bullets: [u16; 6] = [44, 45, 46, 47, 48, 49];
|
const BULLETS: [u16; 6] = [44, 45, 46, 47, 48, 49];
|
||||||
if bullet_manager.count_bullets_multi(&bullets, player_id) == 0
|
if bullet_manager.count_bullets_multi(&BULLETS, player_id) == 0
|
||||||
&& (player.controller.trigger_shoot() || shoot) {
|
&& (player.controller.trigger_shoot() || shoot) {
|
||||||
if !self.consume_ammo(1) {
|
if !self.consume_ammo(1) {
|
||||||
state.sound_manager.play_sfx(37);
|
state.sound_manager.play_sfx(37);
|
||||||
|
|
Loading…
Reference in a new issue