(wip) start rewriting rendering stuff

This commit is contained in:
Alula 2021-01-27 19:20:47 +01:00
parent c9c746a6db
commit 3c20e089f9
76 changed files with 1961 additions and 622 deletions

View File

@ -18,12 +18,12 @@ opengles_version = [3, 1]
fullscreen = true
orientation = "sensorLandscape"
permission = [
{name = "android.permission.READ_EXTERNAL_STORAGE"},
{name = "android.permission.WRITE_EXTERNAL_STORAGE"}
{ name = "android.permission.READ_EXTERNAL_STORAGE" },
{ name = "android.permission.WRITE_EXTERNAL_STORAGE" }
]
application_metadatas = [
{name = "android:hardwareAccelerated", value = "true"},
{name = "android:requestLegacyExternalStorage", value = "true"}
{ name = "android:hardwareAccelerated", value = "true" },
{ name = "android:requestLegacyExternalStorage", value = "true" }
]
[profile.release]
@ -40,42 +40,34 @@ opt-level = 1
opt-level = 1
[features]
default = ["scripting"]
default = ["scripting", "backend-sdl"]
backend-sdl = ["sdl2"]
backend-gfx = ["winit", "imgui-gfx-renderer", "imgui-winit-support"]
scripting = ["lua-ffi"]
editor = []
[dependencies]
#cpal = {path = "./3rdparty/cpal"}
#gfx_device_gl = {path = "./3rdparty/gfx/src/backend/gl"}
#ggez = {path = "./3rdparty/ggez"}
#glutin = {path = "./3rdparty/glutin/glutin"}
#lua-ffi = {path = "./3rdparty/luajit-rs", optional = true}
bitvec = "0.17.4"
byteorder = "1.3"
case_insensitive_hashmap = "1.0.0"
chrono = "0.4"
cpal = {git = "https://github.com/doukutsu-rs/cpal.git", branch = "android-support"}
directories = "2"
gfx = "0.18"
gfx_core = "0.9"
gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"}
ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "43631b0401271d4bc8fe4a5afba8aad63976dba1"}
glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "master"}
imgui = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"}
imgui-gfx-renderer = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"}
imgui-winit-support = {git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"}
image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"]}
cpal = { git = "https://github.com/doukutsu-rs/cpal.git", branch = "android-support" }
directories = "3"
imgui = { git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785" }
imgui-gfx-renderer = { git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785", optional = true }
imgui-winit-support = { git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785", optional = true }
image = { version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"] }
itertools = "0.9.0"
lazy_static = "1.4.0"
log = "0.4"
lua-ffi = {git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "1ef3caf772d72068297ddf75df06fd2ef8c1daab", optional = true}
lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "1ef3caf772d72068297ddf75df06fd2ef8c1daab", optional = true }
lru = "0.6.0"
num-derive = "0.3.2"
num-traits = "0.2.12"
paste = "1.0.0"
pretty_env_logger = "0.4.0"
serde = {version = "1", features = ["derive"]}
sdl2 = { version = "0.34.3", optional = true }
serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_yaml = "0.8"
strum = "0.18.0"
@ -83,7 +75,10 @@ strum_macros = "0.18.0"
# remove and replace when drain_filter is in stable
vec_mut_scan = "0.3.0"
webbrowser = "0.5.5"
winit = {version = "0.24.0", features = ["serde"]}
winit = { version = "0.24.0", features = ["serde"], optional = true }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["winuser"] }
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.2"

View File

@ -3,8 +3,9 @@ use std::io;
use byteorder::{LE, ReadBytesExt};
use ggez::GameError::ResourceLoadError;
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameError::ResourceLoadError;
use crate::framework::error::GameResult;
use crate::str;
#[derive(Debug)]

View File

@ -4,10 +4,13 @@ use std::path::PathBuf;
use crate::bmfont::BMFont;
use crate::common::{FILE_TYPES, Rect};
use crate::engine_constants::EngineConstants;
use ggez::{Context, filesystem, GameResult};
use ggez::GameError::ResourceLoadError;
use crate::str;
use crate::texture_set::TextureSet;
use crate::framework::error::GameError::ResourceLoadError;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
pub struct BMFontRenderer {
font: BMFont,

View File

@ -5,9 +5,9 @@ use std::io::ErrorKind;
use std::io::SeekFrom;
use std::path::{Component, Path, PathBuf};
use ggez::GameResult;
use ggez::GameError::FilesystemError;
use ggez::vfs::{OpenOptions, VFile, VFS, VMetadata};
use crate::framework::error::GameError::FilesystemError;
use crate::framework::error::GameResult;
use crate::framework::vfs::{OpenOptions, VFile, VMetadata, VFS};
#[derive(Debug)]
pub struct BuiltinFile(Cursor<&'static [u8]>);
@ -59,7 +59,7 @@ impl VMetadata for BuiltinMetadata {
}
}
#[derive(Clone)]
#[derive(Clone, Debug)]
enum FSNode {
File(&'static str, &'static [u8]),
Directory(&'static str, Vec<FSNode>),
@ -232,7 +232,7 @@ impl VFS for BuiltinFS {
}
}
/*#[test]
#[test]
fn test_builtin_fs() {
let fs = BuiltinFS {
root: vec![
@ -240,7 +240,7 @@ fn test_builtin_fs() {
FSNode::Directory("memes", vec![
FSNode::File("nothing.txt", &[]),
FSNode::Directory("secret stuff", vec![
FSNode::File("passwords.txt", &[b'1', b'2', b'3', b'4', b'5', b'6',]),
FSNode::File("passwords.txt", &b"12345678"),
]),
]),
FSNode::File("test2.txt", &[]),
@ -252,4 +252,4 @@ fn test_builtin_fs() {
println!("{:?}", fs.get_node(Path::new("/memes")).unwrap());
println!("{:?}", fs.get_node(Path::new("/memes/nothing.txt")).unwrap());
println!("{:?}", fs.get_node(Path::new("/memes/secret stuff/passwords.txt")).unwrap());
}*/
}

View File

@ -260,16 +260,6 @@ impl<T: Num + PartialOrd + Copy> Rect<T> {
bottom: y.add(height),
}
}
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 {
if let Some(Ordering::Greater) = self.left.partial_cmp(&self.right) {
self.left.sub(self.right)
@ -287,15 +277,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> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -371,3 +352,167 @@ pub fn interpolate_fix9_scale(old_val: i32, val: i32, frame_delta: f64) -> f32 {
(lerp_f64(old_val as f64, val as f64, frame_delta) / 512.0) as f32
}
/// A RGBA color in the `sRGB` color space represented as `f32`'s in the range `[0.0-1.0]`
///
/// For convenience, [`WHITE`](constant.WHITE.html) and [`BLACK`](constant.BLACK.html) are provided.
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct Color {
/// Red component
pub r: f32,
/// Green component
pub g: f32,
/// Blue component
pub b: f32,
/// Alpha component
pub a: f32,
}
/// White
pub const WHITE: Color = Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
/// Black
pub const BLACK: Color = Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
impl Color {
/// Create a new `Color` from four `f32`'s in the range `[0.0-1.0]`
pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Color { r, g, b, a }
}
/// Create a new `Color` from four `u8`'s in the range `[0-255]`
pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Color {
Color::from((r, g, b, a))
}
/// Create a new `Color` from three u8's in the range `[0-255]`,
/// with the alpha component fixed to 255 (opaque)
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Color {
Color::from((r, g, b))
}
/// Return a tuple of four `u8`'s in the range `[0-255]` with the `Color`'s
/// components.
pub const fn to_rgba(self) -> (u8, u8, u8, u8) {
self.into()
}
/// Return a tuple of three `u8`'s in the range `[0-255]` with the `Color`'s
/// components.
pub const fn to_rgb(self) -> (u8, u8, u8) {
self.into()
}
/// Convert a packed `u32` containing `0xRRGGBBAA` into a `Color`
pub const fn from_rgba_u32(c: u32) -> Color {
let c = c.to_be_bytes();
Color::from((c[0], c[1], c[2], c[3]))
}
/// Convert a packed `u32` containing `0x00RRGGBB` into a `Color`.
/// This lets you do things like `Color::from_rgb_u32(0xCD09AA)` easily if you want.
pub const fn from_rgb_u32(c: u32) -> Color {
let c = c.to_be_bytes();
Color::from((c[1], c[2], c[3]))
}
/// Convert a `Color` into a packed `u32`, containing `0xRRGGBBAA` as bytes.
pub const fn to_rgba_u32(self) -> u32 {
let (r, g, b, a): (u8, u8, u8, u8) = self.into();
u32::from_be_bytes([r, g, b, a])
}
/// Convert a `Color` into a packed `u32`, containing `0x00RRGGBB` as bytes.
pub const fn to_rgb_u32(self) -> u32 {
let (r, g, b, _a): (u8, u8, u8, u8) = self.into();
u32::from_be_bytes([0, r, g, b])
}
}
impl From<(u8, u8, u8, u8)> for Color {
/// Convert a `(R, G, B, A)` tuple of `u8`'s in the range `[0-255]` into a `Color`
fn from(val: (u8, u8, u8, u8)) -> Self {
let (r, g, b, a) = val;
let rf = (f32::from(r)) / 255.0;
let gf = (f32::from(g)) / 255.0;
let bf = (f32::from(b)) / 255.0;
let af = (f32::from(a)) / 255.0;
Color::new(rf, gf, bf, af)
}
}
impl From<(u8, u8, u8)> for Color {
/// Convert a `(R, G, B)` tuple of `u8`'s in the range `[0-255]` into a `Color`,
/// with a value of 255 for the alpha element (i.e., no transparency.)
fn from(val: (u8, u8, u8)) -> Self {
let (r, g, b) = val;
Color::from((r, g, b, 255))
}
}
impl From<[f32; 4]> for Color {
/// Turns an `[R, G, B, A] array of `f32`'s into a `Color` with no format changes.
/// All inputs should be in the range `[0.0-1.0]`.
fn from(val: [f32; 4]) -> Self {
Color::new(val[0], val[1], val[2], val[3])
}
}
impl From<(f32, f32, f32)> for Color {
/// Convert a `(R, G, B)` tuple of `f32`'s in the range `[0.0-1.0]` into a `Color`,
/// with a value of 1.0 to for the alpha element (ie, no transparency.)
fn from(val: (f32, f32, f32)) -> Self {
let (r, g, b) = val;
Color::new(r, g, b, 1.0)
}
}
impl From<(f32, f32, f32, f32)> for Color {
/// Convert a `(R, G, B, A)` tuple of `f32`'s in the range `[0.0-1.0]` into a `Color`
fn from(val: (f32, f32, f32, f32)) -> Self {
let (r, g, b, a) = val;
Color::new(r, g, b, a)
}
}
impl From<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]
}
}

View File

@ -1,8 +1,8 @@
use ggez::{Context, GameResult};
use crate::common::Rect;
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList;
use crate::shared_game_state::SharedGameState;
@ -52,7 +52,7 @@ impl BossLifeBar {
}
impl GameEntity<(&NPCList, &BossNPC)> for BossLifeBar {
fn tick(&mut self, _state: &mut SharedGameState, (npc_list, boss): (&NPCList, &BossNPC)) -> GameResult<> {
fn tick(&mut self, _state: &mut SharedGameState, (npc_list, boss): (&NPCList, &BossNPC)) -> GameResult<()> {
match self.target {
BossLifeTarget::NPC(npc_id) => {
if let Some(npc) = npc_list.get_npc(npc_id as usize) {

View File

@ -1,7 +1,7 @@
use ggez::{Context, GameResult};
use crate::common::Rect;
use crate::shared_game_state::SharedGameState;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
pub enum Alignment {

View File

@ -1,4 +1,5 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::common::Rect;
use crate::components::draw_common::{Alignment, draw_number};

View File

@ -1,4 +1,5 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::common::Rect;
use crate::entity::GameEntity;

View File

@ -1,4 +1,5 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::frame::Frame;
use crate::shared_game_state::SharedGameState;

9
src/framework/backend.rs Normal file
View File

@ -0,0 +1,9 @@
use crate::Game;
pub(crate) trait Backend {
fn create_event_loop(&self) -> Box<dyn BackendEventLoop>;
}
pub(crate) trait BackendEventLoop {
fn run(&self, game: &mut Game);
}

View File

18
src/framework/context.rs Normal file
View File

@ -0,0 +1,18 @@
use crate::framework::filesystem::Filesystem;
use crate::Game;
pub struct Context {
pub(crate) filesystem: Filesystem,
}
impl Context {
pub fn new() -> Context {
Context {
filesystem: Filesystem::new(),
}
}
pub fn run(&mut self, game: &mut Game) {
loop {}
}
}

145
src/framework/error.rs Normal file
View File

@ -0,0 +1,145 @@
//! Error types and conversion functions.
use std::error::Error;
use std::fmt;
use std::string::FromUtf8Error;
use std::sync::{Arc, PoisonError};
use std::sync::mpsc::SendError;
/// An enum containing all kinds of game framework errors.
#[derive(Debug, Clone)]
pub enum GameError {
/// An error in the filesystem layout
FilesystemError(String),
/// An error in the config file
ConfigError(String),
/// Happens when an `winit::EventsLoopProxy` attempts to
/// wake up an `winit::EventsLoop` that no longer exists.
EventLoopError(String),
/// An error trying to load a resource, such as getting an invalid image file.
ResourceLoadError(String),
/// Unable to find a resource; the `Vec` is the paths it searched for and associated errors
ResourceNotFound(String, Vec<(std::path::PathBuf, GameError)>),
/// Something went wrong in the renderer
RenderError(String),
/// Something went wrong in the audio playback
AudioError(String),
/// Something went wrong trying to set or get window properties.
WindowError(String),
/// Something went wrong trying to read from a file
IOError(Arc<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::WindowCreationError(ref e) => Some(&**e),
GameError::IOError(ref e) => Some(&**e),
GameError::ShaderProgramError(ref e) => Some(e),
_ => None,
}
}
}
/// A convenient result type consisting of a return type and a `GameError`
pub type GameResult<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)
}
}

373
src/framework/filesystem.rs Normal file
View File

@ -0,0 +1,373 @@
use std::env;
use std::fmt;
use std::io;
use std::io::SeekFrom;
use std::path;
use std::path::PathBuf;
use directories::ProjectDirs;
use crate::framework::vfs;
use crate::framework::vfs::{VFS, OpenOptions};
use crate::framework::error::{GameResult, GameError};
use crate::framework::context::Context;
/// A structure that contains the filesystem state and cache.
#[derive(Debug)]
pub struct Filesystem {
vfs: vfs::OverlayFS,
user_vfs: vfs::OverlayFS,
}
/// Represents a file, either in the filesystem, or in the resources zip file,
/// or whatever.
pub enum File {
/// A wrapper for a VFile trait object.
VfsFile(Box<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 mut user_overlay = vfs::OverlayFS::new();
Filesystem {
vfs: overlay,
user_vfs: user_overlay,
}
}
/// Opens the given `path` and returns the resulting `File`
/// in read-only mode.
pub(crate) fn open<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);
}
}
/// 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)
}
/// Prints the contents of all data directories.
/// Useful for debugging.
pub fn print_all(ctx: &mut Context) {
ctx.filesystem.print_all()
}
/// Outputs the contents of all data directories,
/// using the "info" log level of the `log` crate.
/// Useful for debugging.
///
/// See the [`logging` example](https://github.com/ggez/ggez/blob/master/examples/eventloop.rs)
/// for how to collect log information.
pub fn log_all(ctx: &mut Context) {
ctx.filesystem.log_all()
}
/// Adds the given (absolute) path to the list of directories
/// it will search to look for resources.
///
/// You probably shouldn't use this in the general case, since it is
/// harder than it looks to make it bulletproof across platforms.
/// But it can be very nice for debugging and dev purposes, such as
/// by pushing `$CARGO_MANIFEST_DIR/resources` to it
pub fn mount(ctx: &mut Context, path: &path::Path, readonly: bool) {
ctx.filesystem.mount(path, readonly)
}
/// Adds a VFS to the list of resource search locations.
pub fn mount_vfs(ctx: &mut Context, vfs: Box<dyn vfs::VFS>) {
ctx.filesystem.mount_vfs(vfs)
}

24
src/framework/graphics.rs Normal file
View File

@ -0,0 +1,24 @@
use crate::common::Color;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
pub enum FilterMode {
Nearest,
Linear,
}
pub struct Canvas {}
impl Canvas {
}
pub fn clear(ctx: &mut Context, color: Color) {}
pub fn present(ctx: &mut Context) -> GameResult<()> {
Ok(())
}
pub fn drawable_size(ctx: &mut Context) -> (f32, f32) {
(320.0, 240.0)
}

15
src/framework/image.rs Normal file
View File

@ -0,0 +1,15 @@
use crate::framework::context::Context;
use crate::framework::error::GameResult;
pub struct Image {}
impl Image {
pub fn from_rgba8(
context: &mut Context,
width: u16,
height: u16,
rgba: &[u8],
) -> GameResult<Self> {
Ok(Image {})
}
}

205
src/framework/keyboard.rs Normal file
View File

@ -0,0 +1,205 @@
use crate::framework::context::Context;
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum ScanCode {
/// The '1' key over the letters.
Key1,
/// The '2' key over the letters.
Key2,
/// The '3' key over the letters.
Key3,
/// The '4' key over the letters.
Key4,
/// The '5' key over the letters.
Key5,
/// The '6' key over the letters.
Key6,
/// The '7' key over the letters.
Key7,
/// The '8' key over the letters.
Key8,
/// The '9' key over the letters.
Key9,
/// The '0' key over the 'O' and 'P' keys.
Key0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
/// The Escape key, next to F1.
Escape,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
/// Print Screen/SysRq.
Snapshot,
/// Scroll Lock.
Scroll,
/// Pause/Break key, next to Scroll lock.
Pause,
/// `Insert`, next to Backspace.
Insert,
Home,
Delete,
End,
PageDown,
PageUp,
Left,
Up,
Right,
Down,
/// The Backspace key, right over Enter.
Backspace,
/// The Enter key.
Return,
/// The space bar.
Space,
/// The "Compose" key on Linux.
Compose,
Caret,
Numlock,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
NumpadAdd,
NumpadDivide,
NumpadDecimal,
NumpadComma,
NumpadEnter,
NumpadEquals,
NumpadMultiply,
NumpadSubtract,
AbntC1,
AbntC2,
Apostrophe,
Apps,
Asterisk,
At,
Ax,
Backslash,
Calculator,
Capital,
Colon,
Comma,
Convert,
Equals,
Grave,
Kana,
Kanji,
LAlt,
LBracket,
LControl,
LShift,
LWin,
Mail,
MediaSelect,
MediaStop,
Minus,
Mute,
MyComputer,
// also called "Next"
NavigateForward,
// also called "Prior"
NavigateBackward,
NextTrack,
NoConvert,
OEM102,
Period,
PlayPause,
Plus,
Power,
PrevTrack,
RAlt,
RBracket,
RControl,
RShift,
RWin,
Semicolon,
Slash,
Sleep,
Stop,
Sysrq,
Tab,
Underline,
Unlabeled,
VolumeDown,
VolumeUp,
Wake,
WebBack,
WebFavorites,
WebForward,
WebHome,
WebRefresh,
WebSearch,
WebStop,
Yen,
Copy,
Paste,
Cut,
}
pub fn is_key_pressed(ctx: &mut Context, code: ScanCode) -> bool {
false
}

10
src/framework/mod.rs Normal file
View File

@ -0,0 +1,10 @@
pub mod backend;
pub mod backend_sdl2;
pub mod context;
pub mod error;
pub mod filesystem;
pub mod vfs;
pub mod image;
pub mod graphics;
pub mod keyboard;
pub mod backend_null;

678
src/framework/vfs.rs Normal file
View 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!!
}

View File

@ -1,6 +1,7 @@
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::input::player_controller::PlayerController;
use crate::shared_game_state::SharedGameState;
use ggez::{GameResult, Context};
pub struct CombinedMenuController {
controllers: Vec<Box<dyn PlayerController>>,

View File

@ -1,5 +1,5 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::input::player_controller::PlayerController;
use crate::shared_game_state::SharedGameState;

View File

@ -1,11 +1,11 @@
use ggez::{Context, GameResult};
use ggez::input::keyboard;
use winit::event::VirtualKeyCode;
use crate::bitfield;
use crate::input::player_controller::PlayerController;
use crate::player::TargetPlayer;
use crate::shared_game_state::SharedGameState;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::keyboard;
use crate::framework::keyboard::ScanCode;
bitfield! {
#[derive(Clone, Copy)]
@ -64,8 +64,8 @@ impl PlayerController for KeyboardController {
self.state.set_skip(keyboard::is_key_pressed(ctx, keymap.skip));
self.state.set_prev_weapon(keyboard::is_key_pressed(ctx, keymap.prev_weapon));
self.state.set_next_weapon(keyboard::is_key_pressed(ctx, keymap.next_weapon));
self.state.set_enter(keyboard::is_key_pressed(ctx, VirtualKeyCode::Return));
self.state.set_escape(keyboard::is_key_pressed(ctx, VirtualKeyCode::Escape));
self.state.set_enter(keyboard::is_key_pressed(ctx, ScanCode::Return));
self.state.set_escape(keyboard::is_key_pressed(ctx, ScanCode::Escape));
Ok(())
}

View File

@ -1,4 +1,5 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::shared_game_state::SharedGameState;

View File

@ -1,5 +1,5 @@
use ggez::{Context, GameResult};
use winit::event::TouchPhase;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::common::Rect;
use crate::engine_constants::EngineConstants;
@ -39,6 +39,7 @@ impl TouchControls {
}
}
/*
pub fn process_winit_event(&mut self, scale: f32, touch: winit::event::Touch) {
match touch.phase {
TouchPhase::Started | TouchPhase::Moved => {
@ -66,7 +67,7 @@ impl TouchControls {
self.clicks.retain(|p| p.id != touch.id);
}
}
}
}*/
pub fn point_in(&self, bounds: Rect) -> Option<u64> {
for point in self.points.iter() {

View File

@ -1,4 +1,6 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::bitfield;
use crate::common::Rect;

View File

@ -7,27 +7,17 @@ extern crate strum;
#[macro_use]
extern crate strum_macros;
use std::{env, mem};
use std::env;
use std::cell::UnsafeCell;
use std::path;
use std::time::Instant;
use ggez::{Context, ContextBuilder, GameError, GameResult};
use ggez::conf::{Backend, WindowMode, WindowSetup};
use ggez::event::{KeyCode, KeyMods};
use ggez::filesystem::mount_vfs;
use ggez::graphics;
use ggez::graphics::{Canvas, DrawParam, window};
use ggez::graphics::glutin_ext::WindowUpdateExt;
use ggez::input::keyboard;
use ggez::mint::ColumnMatrix4;
use ggez::nalgebra::Vector2;
use log::*;
use pretty_env_logger::env_logger::Env;
use winit::event::{ElementState, Event, KeyboardInput, WindowEvent};
use winit::event_loop::ControlFlow;
use crate::builtin_fs::BuiltinFS;
use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
use crate::framework::graphics;
use crate::framework::keyboard::ScanCode;
use crate::scene::loading_scene::LoadingScene;
use crate::scene::Scene;
use crate::shared_game_state::{SharedGameState, TimingMode};
@ -46,6 +36,7 @@ mod encoding;
mod engine_constants;
mod entity;
mod frame;
mod framework;
mod inventory;
mod input;
mod live_debugger;
@ -61,6 +52,7 @@ mod scene;
#[cfg(feature = "scripting")]
mod scripting;
mod settings;
#[cfg(feature = "backend-gfx")]
mod shaders;
mod shared_game_state;
mod stage;
@ -74,7 +66,6 @@ struct Game {
scene: Option<Box<dyn Scene>>,
state: UnsafeCell<SharedGameState>,
ui: UI,
def_matrix: ColumnMatrix4<f32>,
start_time: Instant,
last_tick: u128,
next_tick: u128,
@ -86,7 +77,6 @@ impl Game {
let s = Game {
scene: None,
ui: UI::new(ctx)?,
def_matrix: DrawParam::new().to_matrix(),
state: UnsafeCell::new(SharedGameState::new(ctx)?),
start_time: Instant::now(),
last_tick: 0,
@ -161,10 +151,9 @@ impl Game {
self.loops = 0;
graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into());
graphics::set_transform(ctx, DrawParam::new()
.scale(Vector2::new(state_ref.scale, state_ref.scale))
.to_matrix());
graphics::apply_transformations(ctx)?;
/*graphics::set_projection(ctx, DrawParam::new()
.scale(Vec2::new(state_ref.scale, state_ref.scale))
.to_matrix());*/
if let Some(scene) = self.scene.as_mut() {
scene.draw(state_ref, ctx)?;
@ -172,8 +161,7 @@ impl Game {
state_ref.touch_controls.draw(state_ref.canvas_size, &state_ref.constants, &mut state_ref.texture_set, ctx)?;
}
graphics::set_transform(ctx, self.def_matrix);
graphics::apply_transformations(ctx)?;
//graphics::set_projection(ctx, self.def_matrix);
self.ui.draw(state_ref, ctx, scene)?;
}
@ -181,34 +169,30 @@ impl Game {
Ok(())
}
fn key_down_event(&mut self, key_code: KeyCode, _key_mod: KeyMods, repeat: bool) {
fn key_down_event(&mut self, key_code: ScanCode, repeat: bool) {
if repeat { return; }
let state = unsafe { &mut *self.state.get() };
match key_code {
KeyCode::F5 => { state.settings.subpixel_coords = !state.settings.subpixel_coords }
KeyCode::F6 => { state.settings.motion_interpolation = !state.settings.motion_interpolation }
KeyCode::F7 => { state.set_speed(1.0) }
KeyCode::F8 => {
ScanCode::F5 => { state.settings.subpixel_coords = !state.settings.subpixel_coords }
ScanCode::F6 => { state.settings.motion_interpolation = !state.settings.motion_interpolation }
ScanCode::F7 => { state.set_speed(1.0) }
ScanCode::F8 => {
if state.settings.speed > 0.2 {
state.set_speed(state.settings.speed - 0.1);
}
}
KeyCode::F9 => {
ScanCode::F9 => {
if state.settings.speed < 3.0 {
state.set_speed(state.settings.speed + 0.1);
}
}
KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines }
KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode }
KeyCode::F12 => { state.settings.infinite_booster = !state.settings.infinite_booster }
ScanCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines }
ScanCode::F11 => { state.settings.god_mode = !state.settings.god_mode }
ScanCode::F12 => { state.settings.infinite_booster = !state.settings.infinite_booster }
_ => {}
}
}
fn key_up_event(&mut self, _key_code: KeyCode, _key_mod: KeyMods) {
//
}
}
#[cfg(target_os = "android")]
@ -271,68 +255,22 @@ pub fn android_main() {
init().unwrap();
}
#[cfg(target_os = "android")]
static BACKENDS: [Backend; 2] = [
Backend::OpenGLES { major: 3, minor: 0 },
Backend::OpenGLES { major: 2, minor: 0 }
];
#[cfg(not(target_os = "android"))]
static BACKENDS: [Backend; 4] = [
Backend::OpenGL { major: 3, minor: 2 },
Backend::OpenGLES { major: 3, minor: 2 },
Backend::OpenGLES { major: 3, minor: 0 },
Backend::OpenGLES { major: 2, minor: 0 }
];
fn init_ctx<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 {
pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info"))
.filter(Some("gfx_device_gl::factory"), LevelFilter::Warn)
.init();
let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") {
path::PathBuf::from(data_dir)
} else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let mut path = path::PathBuf::from(manifest_dir);
path.push("data");
path
} else {
path::PathBuf::from("data")
};
let mut resource_dir = env::current_exe()?;
// Ditch the filename (if any)
if resource_dir.file_name().is_some() {
let _ = resource_dir.pop();
}
resource_dir.push("data");
info!("Resource directory: {:?}", resource_dir);
info!("Initializing engine...");
let event_loop = winit::event_loop::EventLoop::new();
let mut context: Option<Context>;
let mut game: Option<Game> = None;
let mut context: Context = Context::new();
#[cfg(target_os = "android")]
{
@ -346,152 +284,4 @@ pub fn init() -> GameResult {
}
}
}
context = Some(init_ctx(&event_loop, resource_dir.clone())?);
event_loop.run(move |event, target, flow| {
#[cfg(target_os = "windows")]
{
// Windows' system clock implementation isn't monotonic when the process gets switched to another core.
// Rust has mitigations for this, but apparently aren't very effective unless Instant is called very often.
let _ = Instant::now();
}
if let Some(ctx) = &mut context {
ctx.process_event(&event);
if let Some(game) = &mut game {
game.ui.handle_events(ctx, &event);
} else {
let mut new_game = Game::new(ctx).unwrap();
let state_ref = unsafe { &mut *new_game.state.get() };
state_ref.next_scene = Some(Box::new(LoadingScene::new()));
game = Some(new_game);
#[cfg(feature = "scripting")]
{
unsafe {
let game_ref = game.as_mut().unwrap();
let state_ref = game_ref.state.get();
(&mut *state_ref).lua.update_refs(game_ref.state.get(), ctx as *mut Context);
}
}
}
}
match event {
Event::Resumed => {
#[cfg(target_os = "android")]
if context.is_none() {
context = Some(init_ctx(target, resource_dir.clone()).unwrap());
}
let _ = target;
if let Some(game) = &mut game {
game.loops = 0;
}
}
Event::Suspended => {
#[cfg(target_os = "android")]
{
context = None;
}
if let Some(game) = &mut game {
game.loops = 0;
}
}
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CloseRequested => {
if let Some(game) = &mut game {
let state_ref = unsafe { &mut *game.state.get() };
state_ref.shutdown();
}
*flow = ControlFlow::Exit;
}
WindowEvent::Resized(size) => {
// Minimizing a window on Windows causes this event to get called with a 0x0 size
if size.width != 0 && size.height != 0 {
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
let state_ref = unsafe { &mut *game.state.get() };
state_ref.tmp_canvas = Canvas::with_window_size(ctx).unwrap();
state_ref.game_canvas = Canvas::with_window_size(ctx).unwrap();
state_ref.lightmap_canvas = Canvas::with_window_size(ctx).unwrap();
state_ref.handle_resize(ctx).unwrap();
graphics::window(ctx).update_gfx(&mut game.ui.main_color, &mut game.ui.main_depth);
}
}
}
WindowEvent::Touch(touch) => {
if let Some(game) = &mut game {
let state_ref = unsafe { &mut *game.state.get() };
state_ref.touch_controls.process_winit_event(state_ref.scale, touch);
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: el_state,
virtual_keycode: Some(keycode),
modifiers,
..
},
..
} => {
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
match el_state {
ElementState::Pressed => {
let repeat = keyboard::is_key_repeated(ctx);
game.key_down_event(keycode, modifiers.into(), repeat);
}
ElementState::Released => {
game.key_up_event(keycode, modifiers.into());
}
}
}
}
_ => {}
}
}
Event::RedrawRequested(win) => {
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
if win == window(ctx).window().id() {
ctx.timer_context.tick();
game.draw(ctx).unwrap();
}
}
}
Event::MainEventsCleared => {
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
game.update(ctx).unwrap();
#[cfg(target_os = "android")]
{
ctx.timer_context.tick();
game.draw(ctx).unwrap(); // redraw request is unimplemented on shitdroid
}
window(ctx).window().request_redraw();
let state_ref = unsafe { &mut *game.state.get() };
if state_ref.shutdown {
log::info!("Shutting down...");
*flow = ControlFlow::Exit;
return;
}
if state_ref.next_scene.is_some() {
mem::swap(&mut game.scene, &mut state_ref.next_scene);
state_ref.next_scene = None;
game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap();
game.loops = 0;
state_ref.frame_time = 0.0;
}
}
}
_ => {}
}
})
}

View File

@ -1,4 +1,6 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use imgui::{CollapsingHeader, Condition, im_str, ImStr, ImString, Slider, Window};
use itertools::Itertools;

View File

@ -1,5 +1,34 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::process::exit;
fn main() {
doukutsu_rs::init().unwrap();
let result = doukutsu_rs::init();
#[cfg(target_os = "windows")]
unsafe {
use winapi::_core::ptr::null_mut;
use winapi::um::winuser::MessageBoxW;
use winapi::um::winuser::MB_OK;
use winapi::shared::ntdef::LPCWSTR;
use std::ffi::OsStr;
use std::os::windows::prelude::*;
if let Err(e) = result {
let title: LPCWSTR = OsStr::new("Error!")
.encode_wide().chain(Some(0)).collect::<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);
}
}

View File

@ -1,10 +1,12 @@
use std::io;
use byteorder::{LE, ReadBytesExt};
use ggez::GameError::ResourceLoadError;
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::str;
use crate::framework::error::GameError::ResourceLoadError;
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
@ -28,7 +30,6 @@ impl Map {
let version = map_data.read_u8()?;
// It's something Booster's Lab supports but I haven't seen anywhere being used in practice
if !SUPPORTED_PXM_VERSIONS.contains(&version) {
return Err(ResourceLoadError(format!("Unsupported PXM version: {:#x}", version)));
}

View File

@ -1,12 +1,11 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::common::Rect;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::shared_game_state::SharedGameState;
pub struct MenuSaveInfo {
}
pub struct MenuSaveInfo {}
pub enum MenuEntry {
Hidden,

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::clamp;
use crate::caret::CaretType;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::common::Direction;
use crate::npc::NPC;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::caret::CaretType;
use crate::common::Direction;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::{abs, clamp};
use crate::common::Direction;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::{abs, clamp};
use crate::caret::CaretType;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::clamp;
use crate::common::Direction;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::abs;
use num_traits::clamp;

View File

@ -1,6 +1,6 @@
use ggez::GameResult;
use crate::common::{CDEG_RAD, Direction};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::player::Player;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::caret::CaretType;
use crate::common::Direction;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::common::Direction;
use crate::npc::NPC;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::clamp;
use crate::caret::CaretType;

View File

@ -1,9 +1,10 @@
use std::cmp::Ordering;
use ggez::GameResult;
use num_traits::{abs, clamp};
use crate::common::Direction;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::NPC;
use crate::player::Player;
use crate::rng::RNG;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::{abs, clamp};
use crate::caret::CaretType;

View File

@ -1,7 +1,8 @@
use ggez::GameResult;
use num_traits::clamp;
use crate::common::Direction;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::rng::RNG;

View File

@ -1,9 +1,11 @@
use crate::npc::NPC;
use crate::shared_game_state::SharedGameState;
use crate::player::Player;
use ggez::GameResult;
use num_traits::abs;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::NPC;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
impl NPC {
pub(crate) fn tick_n215_sandcroc_outer_wall(&mut self, state: &mut SharedGameState, players: [&mut Player; 2]) -> GameResult {
match self.action_num {

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::clamp;
use crate::common::Direction;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::common::Direction;
use crate::npc::list::NPCList;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::{abs, clamp};
use crate::caret::CaretType;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::abs;
use crate::common::Direction;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::clamp;
use crate::common::Direction;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::clamp;
use crate::common::Direction;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::NPC;
use crate::shared_game_state::SharedGameState;

View File

@ -1,4 +1,5 @@
use ggez::GameResult;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::caret::CaretType;
use crate::common::{CDEG_RAD, Direction, Rect};

View File

@ -1,11 +1,11 @@
use std::mem::MaybeUninit;
use ggez::{Context, GameResult};
use crate::bullet::BulletManager;
use crate::common::{Direction, interpolate_fix9_scale};
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::player::Player;

View File

@ -1,8 +1,9 @@
use ggez::GameResult;
use num_traits::{abs, clamp};
use crate::caret::CaretType;
use crate::common::{CDEG_RAD, Direction, Rect};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::npc::boss::BossNPC;
use crate::npc::list::NPCList;
use crate::npc::NPC;

View File

@ -1,4 +1,4 @@
use ggez::GameResult;
use crate::bullet::BulletManager;
use crate::caret::CaretType;
@ -9,6 +9,8 @@ use crate::npc::NPC;
use crate::player::Player;
use crate::rng::RNG;
use crate::shared_game_state::SharedGameState;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
impl NPC {
pub(crate) fn tick_n048_omega_projectiles(&mut self, state: &mut SharedGameState) -> GameResult {

View File

@ -1,7 +1,8 @@
use std::cell::{Cell, UnsafeCell};
use std::mem::MaybeUninit;
use ggez::{GameError, GameResult};
use crate::framework::context::Context;
use crate::framework::error::{GameResult, GameError};
use crate::npc::NPC;

View File

@ -2,7 +2,8 @@ use std::io;
use std::io::Cursor;
use byteorder::{LE, ReadBytesExt};
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_traits::abs;
use crate::bitfield;

View File

@ -1,6 +1,7 @@
use std::clone::Clone;
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use num_derive::FromPrimitive;
use num_traits::clamp;

View File

@ -4,13 +4,15 @@ use byteorder::{BE, LE, ReadBytesExt, WriteBytesExt};
use num_traits::{clamp, FromPrimitive};
use crate::common::{Direction, FadeState};
use ggez::{Context, GameResult};
use ggez::GameError::ResourceLoadError;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::player::ControlMode;
use crate::scene::game_scene::GameScene;
use crate::shared_game_state::SharedGameState;
use crate::str;
use crate::weapon::{WeaponLevel, WeaponType};
use crate::framework::error::GameError::ResourceLoadError;
pub struct WeaponData {
pub weapon_id: u32,

View File

@ -1,13 +1,9 @@
use ggez::{Context, GameResult, graphics, timer};
use ggez::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, mint};
use ggez::graphics::spritebatch::SpriteBatch;
use ggez::nalgebra::{clamp, Vector2};
use log::info;
use num_traits::abs;
use num_traits::{abs, clamp};
use crate::bullet::BulletManager;
use crate::caret::CaretType;
use crate::common::{Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect};
use crate::common::{Direction, FadeDirection, FadeState, fix9_scale, interpolate_fix9_scale, Rect, Color};
use crate::components::boss_life_bar::BossLifeBar;
use crate::components::draw_common::{Alignment, draw_number};
use crate::components::hud::HUD;
@ -30,6 +26,10 @@ use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState,
use crate::texture_set::SizedBatch;
use crate::ui::Components;
use crate::weapon::WeaponType;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::framework::graphics::FilterMode;
pub struct GameScene {
pub tick: u32,
@ -502,10 +502,10 @@ impl GameScene {
}
fn draw_light_map(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
graphics::set_canvas(ctx, Some(&state.lightmap_canvas));
/*graphics::set_canvas(ctx, Some(&state.lightmap_canvas));
graphics::set_blend_mode(ctx, BlendMode::Add)?;
graphics::clear(ctx, Color::from_rgb(100, 100, 110));
graphics::clear(ctx, Color::from_rgb(100, 100, 110));*/
{
let scale = state.scale;
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/lightmap/spot")?;
@ -634,94 +634,13 @@ impl GameScene {
batch.draw_filtered(FilterMode::Linear, ctx)?;
}
graphics::set_blend_mode(ctx, BlendMode::Multiply)?;
/*graphics::set_blend_mode(ctx, BlendMode::Multiply)?;
graphics::set_canvas(ctx, Some(&state.game_canvas));
state.lightmap_canvas.set_filter(FilterMode::Linear);
state.lightmap_canvas.draw(ctx, DrawParam::new()
.scale(Vector2::new(1.0 / state.scale, 1.0 / state.scale)))?;
graphics::set_blend_mode(ctx, BlendMode::Alpha)?;
Ok(())
}
fn is_water(&self, tile: u8) -> bool {
[0x02, 0x04, 0x60, 0x61, 0x62, 0x64, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0xa0, 0xa1, 0xa2, 0xa3].contains(&tile)
}
fn draw_water(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let (frame_x, frame_y) = self.frame.xy_interpolated(state.frame_time, state.scale);
{
state.shaders.water_shader_params.resolution = [state.canvas_size.0, state.canvas_size.1];
state.shaders.water_shader_params.frame_pos = [frame_x, frame_y];
state.shaders.water_shader_params.t = self.tick as f32;
let _lock = graphics::use_shader(ctx, &state.shaders.water_shader);
state.shaders.water_shader.send(ctx, state.shaders.water_shader_params)?;
graphics::set_canvas(ctx, Some(&state.tmp_canvas));
graphics::clear(ctx, Color::new(0.0, 0.0, 0.0, 1.0));
state.game_canvas.draw(ctx, DrawParam::new()
.scale(mint::Vector2 { x: 1.0 / state.scale, y: -1.0 / state.scale })
.offset(mint::Point2 { x: 0.0, y: -1.0 }))?;
}
graphics::set_canvas(ctx, Some(&state.game_canvas));
// cheap, clones a reference underneath
let mut tmp_batch = SpriteBatch::new(state.tmp_canvas.image().clone());
let tile_start_x = clamp(self.frame.x / 0x200 / 16, 0, self.stage.map.width as i32) as usize;
let tile_start_y = clamp(self.frame.y / 0x200 / 16, 0, self.stage.map.height as i32) as usize;
let tile_end_x = clamp((self.frame.x / 0x200 + 8 + state.canvas_size.0 as i32) / 16 + 1, 0, self.stage.map.width as i32) as usize;
let tile_end_y = clamp((self.frame.y / 0x200 + 8 + state.canvas_size.1 as i32) / 16 + 1, 0, self.stage.map.height as i32) as usize;
let mut rect = Rect {
left: 0.0,
top: 0.0,
right: 16.0,
bottom: 16.0,
};
for y in tile_start_y..tile_end_y {
for x in tile_start_x..tile_end_x {
let tile = unsafe {
self.stage.map.attrib[*self.stage.map.tiles
.get_unchecked((y * self.stage.map.width as usize) + x) as usize]
};
let tile_above = unsafe {
self.stage.map.attrib[*self.stage.map.tiles
.get_unchecked((y.saturating_sub(1) * self.stage.map.width as usize) + x) as usize]
};
if !self.is_water(tile) {
continue;
}
rect.left = (x as f32 * 16.0 - 8.0) - frame_x;
rect.top = (y as f32 * 16.0 - 8.0) - frame_y;
rect.right = rect.left + 16.0;
rect.bottom = rect.top + 16.0;
if tile_above == 0 {
rect.top += 3.0;
}
tmp_batch.add(DrawParam::new()
.src(ggez::graphics::Rect::new(rect.left / state.canvas_size.0,
rect.top / state.canvas_size.1,
(rect.right - rect.left) / state.canvas_size.0,
(rect.bottom - rect.top) / state.canvas_size.1))
.scale(mint::Vector2 {
x: 1.0 / state.scale,
y: 1.0 / state.scale,
})
.dest(mint::Point2 {
x: rect.left,
y: rect.top,
}));
}
}
tmp_batch.draw(ctx, DrawParam::new())?;
graphics::set_blend_mode(ctx, BlendMode::Alpha)?;*/
Ok(())
}
@ -1291,7 +1210,7 @@ impl Scene for GameScene {
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
graphics::set_canvas(ctx, Some(&state.game_canvas));
//graphics::set_canvas(ctx, Some(&state.game_canvas));
self.draw_background(state, ctx)?;
self.draw_tiles(state, ctx, TileLayer::Background)?;
if state.settings.shader_effects
@ -1316,9 +1235,9 @@ impl Scene for GameScene {
self.draw_bullets(state, ctx)?;
self.player2.draw(state, ctx, &self.frame)?;
self.player1.draw(state, ctx, &self.frame)?;
if state.settings.shader_effects && self.water_visible {
/*if state.settings.shader_effects && self.water_visible {
self.draw_water(state, ctx)?;
}
}*/
self.draw_tiles(state, ctx, TileLayer::Foreground)?;
self.draw_tiles(state, ctx, TileLayer::Snack)?;
@ -1329,9 +1248,9 @@ impl Scene for GameScene {
self.draw_light_map(state, ctx)?;
}
graphics::set_canvas(ctx, None);
/*graphics::set_canvas(ctx, None);
state.game_canvas.draw(ctx, DrawParam::new()
.scale(Vector2::new(1.0 / state.scale, 1.0 / state.scale)))?;
.scale(Vector2::new(1.0 / state.scale, 1.0 / state.scale)))?;*/
self.draw_black_bars(state, ctx)?;
if state.control_flags.control_enabled() {
@ -1389,7 +1308,7 @@ impl Scene for GameScene {
self.draw_debug_outlines(state, ctx)?;
}
draw_number(state.canvas_size.0 - 8.0, 8.0, timer::fps(ctx) as usize, Alignment::Right, state, ctx)?;
//draw_number(state.canvas_size.0 - 8.0, 8.0, timer::fps(ctx) as usize, Alignment::Right, state, ctx)?;
Ok(())
}

View File

@ -1,5 +1,6 @@
use ggez::{Context, filesystem, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::npc::NPCTable;
use crate::scene::no_data_scene::NoDataScene;
use crate::scene::Scene;

View File

@ -1,4 +1,5 @@
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::shared_game_state::SharedGameState;
use crate::ui::Components;

View File

@ -1,4 +1,5 @@
use ggez::{Context, GameError, GameResult};
use crate::framework::context::Context;
use crate::framework::error::{GameResult, GameError};
use crate::common::Rect;
use crate::scene::Scene;

View File

@ -1,12 +1,12 @@
use ggez::{Context, GameResult, graphics};
use ggez::graphics::Color;
use crate::common::{Rect, VERSION_BANNER};
use crate::common::{Rect, VERSION_BANNER, Color};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::input::touch_controls::TouchControlType;
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
use crate::scene::Scene;
use crate::shared_game_state::{SharedGameState, TimingMode};
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::input::touch_controls::TouchControlType;
#[derive(PartialEq, Eq, Copy, Clone)]
#[repr(u8)]

View File

@ -1,14 +1,19 @@
use std::io::{Read, Seek};
use std::ptr::null_mut;
use ggez::{Context, filesystem, GameError, GameResult};
use ggez::filesystem::File;
use crate::framework::context::Context;
use crate::framework::error::{GameResult, GameError};
use lua_ffi::{c_int, LuaFunction, LuaObject, State, ThreadStatus};
use lua_ffi::ffi::lua_pushcfunction;
use crate::scene::game_scene::GameScene;
use crate::scripting::doukutsu::Doukutsu;
use crate::shared_game_state::SharedGameState;
use crate::framework::filesystem::File;
use crate::framework::filesystem;
mod doukutsu;
mod player;

View File

@ -1,11 +1,12 @@
use ggez::{Context, GameResult};
use serde::{Deserialize, Serialize};
use winit::event::VirtualKeyCode;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::keyboard::ScanCode;
use crate::input::keyboard_player_controller::KeyboardController;
use crate::input::player_controller::PlayerController;
use crate::player::TargetPlayer;
use crate::input::touch_player_controller::TouchPlayerController;
use crate::player::TargetPlayer;
#[derive(Serialize, Deserialize)]
pub struct Settings {
@ -66,47 +67,47 @@ impl Default for Settings {
#[derive(Serialize, Deserialize)]
pub struct PlayerKeyMap {
pub left: VirtualKeyCode,
pub up: VirtualKeyCode,
pub right: VirtualKeyCode,
pub down: VirtualKeyCode,
pub prev_weapon: VirtualKeyCode,
pub next_weapon: VirtualKeyCode,
pub jump: VirtualKeyCode,
pub shoot: VirtualKeyCode,
pub skip: VirtualKeyCode,
pub inventory: VirtualKeyCode,
pub map: VirtualKeyCode,
pub left: ScanCode,
pub up: ScanCode,
pub right: ScanCode,
pub down: ScanCode,
pub prev_weapon: ScanCode,
pub next_weapon: ScanCode,
pub jump: ScanCode,
pub shoot: ScanCode,
pub skip: ScanCode,
pub inventory: ScanCode,
pub map: ScanCode,
}
fn p1_default_keymap() -> PlayerKeyMap {
PlayerKeyMap {
left: VirtualKeyCode::Left,
up: VirtualKeyCode::Up,
right: VirtualKeyCode::Right,
down: VirtualKeyCode::Down,
prev_weapon: VirtualKeyCode::A,
next_weapon: VirtualKeyCode::S,
jump: VirtualKeyCode::Z,
shoot: VirtualKeyCode::X,
skip: VirtualKeyCode::LControl,
inventory: VirtualKeyCode::Q,
map: VirtualKeyCode::W,
left: ScanCode::Left,
up: ScanCode::Up,
right: ScanCode::Right,
down: ScanCode::Down,
prev_weapon: ScanCode::A,
next_weapon: ScanCode::S,
jump: ScanCode::Z,
shoot: ScanCode::X,
skip: ScanCode::LControl,
inventory: ScanCode::Q,
map: ScanCode::W,
}
}
fn p2_default_keymap() -> PlayerKeyMap {
PlayerKeyMap {
left: VirtualKeyCode::Comma,
up: VirtualKeyCode::L,
right: VirtualKeyCode::Slash,
down: VirtualKeyCode::Period,
prev_weapon: VirtualKeyCode::G,
next_weapon: VirtualKeyCode::H,
jump: VirtualKeyCode::B,
shoot: VirtualKeyCode::N,
skip: VirtualKeyCode::RControl,
inventory: VirtualKeyCode::T,
map: VirtualKeyCode::Y,
left: ScanCode::Comma,
up: ScanCode::L,
right: ScanCode::Slash,
down: ScanCode::Period,
prev_weapon: ScanCode::G,
next_weapon: ScanCode::H,
jump: ScanCode::B,
shoot: ScanCode::N,
skip: ScanCode::U,
inventory: ScanCode::T,
map: ScanCode::Y,
}
}

View File

@ -1,6 +1,7 @@
use gfx::{self, *};
use ggez::graphics::Shader;
use ggez::{Context, GameResult};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
gfx_defines! {
constant WaterShaderParams {
@ -11,7 +12,7 @@ gfx_defines! {
}
pub struct Shaders {
pub water_shader: Shader<WaterShaderParams>,
//pub water_shader: Shader<WaterShaderParams>,
pub water_shader_params: WaterShaderParams,
}
@ -24,14 +25,14 @@ impl Shaders {
};
Ok(Shaders {
water_shader: Shader::new(
/*water_shader: Shader::new(
ctx,
"/builtin/shaders/basic_es300.vert.glsl",
"/builtin/shaders/water_es300.frag.glsl",
water_shader_params,
"WaterShaderParams",
None,
)?,
)?,*/
water_shader_params,
})
}

View File

@ -1,17 +1,17 @@
use std::ops::Div;
use std::time::Instant;
use bitvec::vec::BitVec;
use chrono::{Datelike, Local};
use ggez::{Context, filesystem, GameResult, graphics};
use ggez::filesystem::OpenOptions;
use ggez::graphics::Canvas;
use num_traits::clamp;
use num_traits::real::Real;
use crate::bmfont_renderer::BMFontRenderer;
use crate::caret::{Caret, CaretType};
use crate::common::{ControlFlags, Direction, FadeState};
use crate::engine_constants::EngineConstants;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::vfs::OpenOptions;
use crate::input::touch_controls::TouchControls;
use crate::npc::NPCTable;
use crate::profile::GameProfile;
@ -21,12 +21,12 @@ use crate::scene::Scene;
#[cfg(feature = "scripting")]
use crate::scripting::LuaScriptingState;
use crate::settings::Settings;
use crate::shaders::Shaders;
use crate::sound::SoundManager;
use crate::stage::StageData;
use crate::str;
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::texture_set::{TextureSet};
use crate::texture_set::TextureSet;
use crate::framework::{filesystem, graphics};
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum TimingMode {
@ -102,10 +102,6 @@ pub struct SharedGameState {
pub stages: Vec<StageData>,
pub frame_time: f64,
pub scale: f32,
pub shaders: Shaders,
pub tmp_canvas: Canvas,
pub game_canvas: Canvas,
pub lightmap_canvas: Canvas,
pub canvas_size: (f32, f32),
pub screen_size: (f32, f32),
pub next_scene: Option<Box<dyn Scene>>,
@ -123,8 +119,8 @@ pub struct SharedGameState {
impl 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 screen_size = (320.0, 240.0);
let scale = *screen_size.1.div(230.0).floor().max(&1.0);
let canvas_size = (screen_size.0 / scale, screen_size.1 / scale);
@ -164,7 +160,7 @@ impl SharedGameState {
game_flags: bitvec::bitvec![0; 8000],
fade_state: FadeState::Hidden,
game_rng: XorShift::new(0),
effect_rng: XorShift::new(Instant::now().elapsed().as_nanos() as i32),
effect_rng: XorShift::new(123),
quake_counter: 0,
teleporter_slots: Vec::with_capacity(8),
carets: Vec::with_capacity(32),
@ -175,10 +171,6 @@ impl SharedGameState {
stages: Vec::with_capacity(96),
frame_time: 0.0,
scale,
shaders: Shaders::new(ctx)?,
tmp_canvas: Canvas::with_window_size(ctx)?,
game_canvas: Canvas::with_window_size(ctx)?,
lightmap_canvas: Canvas::with_window_size(ctx)?,
screen_size,
canvas_size,
next_scene: None,
@ -289,10 +281,10 @@ impl SharedGameState {
pub fn handle_resize(&mut self, ctx: &mut Context) -> GameResult {
self.screen_size = graphics::drawable_size(ctx);
self.scale = self.screen_size.1.div(240.0).floor().max(1.0);
self.scale = self.screen_size.1.div(230.0).floor().max(1.0);
self.canvas_size = (self.screen_size.0 / self.scale, self.screen_size.1 / self.scale);
graphics::set_screen_coordinates(ctx, graphics::Rect::new(0.0, 0.0, self.screen_size.0, self.screen_size.1))?;
//graphics::set_screen_coordinates(ctx, graphics::Rect::new(0.0, 0.0, self.screen_size.0, self.screen_size.1))?;
Ok(())
}

View File

@ -4,16 +4,18 @@ use std::time::Duration;
use cpal::Sample;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use ggez::{Context, filesystem, GameResult};
use ggez::GameError::{AudioError, InvalidValue, ResourceLoadError};
use num_traits::clamp;
use crate::engine_constants::EngineConstants;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::sound::organya::Song;
use crate::sound::pixtone::PixTonePlayback;
use crate::sound::playback::{PlaybackEngine, SavedPlaybackState};
use crate::sound::wave_bank::SoundBank;
use crate::str;
use crate::framework::error::GameError::{AudioError, ResourceLoadError, InvalidValue};
mod wave_bank;
mod organya;

View File

@ -7,10 +7,12 @@ use log::info;
use crate::encoding::read_cur_shift_jis;
use crate::engine_constants::EngineConstants;
use ggez::{Context, filesystem, GameResult};
use ggez::GameError::ResourceLoadError;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::map::{Map, NPCData};
use crate::text_script::TextScript;
use crate::framework::error::GameError::ResourceLoadError;
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NpcType {

View File

@ -9,8 +9,8 @@ use std::ops::Not;
use std::str::FromStr;
use byteorder::ReadBytesExt;
use ggez::{Context, GameResult};
use ggez::GameError::{InvalidValue, ParseError};
use itertools::Itertools;
use num_derive::FromPrimitive;
use num_traits::{clamp, FromPrimitive};
@ -28,6 +28,7 @@ use crate::scene::title_scene::TitleScene;
use crate::shared_game_state::SharedGameState;
use crate::str;
use crate::weapon::WeaponType;
use crate::framework::error::GameError::ParseError;
/// Engine's text script VM operation codes.
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]

View File

@ -1,12 +1,6 @@
use std::collections::HashMap;
use std::io::{BufReader, Read, Seek, SeekFrom};
use ggez;
use ggez::{Context, GameError, GameResult, graphics};
use ggez::filesystem;
use ggez::graphics::{Drawable, DrawMode, DrawParam, FilterMode, Image, Mesh, mint, Rect};
use ggez::graphics::spritebatch::SpriteBatch;
use ggez::nalgebra::{Point2, Vector2};
use image::RgbaImage;
use itertools::Itertools;
use log::info;
@ -14,6 +8,10 @@ use log::info;
use crate::common;
use crate::common::FILE_TYPES;
use crate::engine_constants::EngineConstants;
use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
use crate::framework::filesystem;
use crate::framework::image::Image;
use crate::settings::Settings;
use crate::shared_game_state::Season;
use crate::str;
@ -21,7 +19,6 @@ use crate::str;
pub static mut G_MAG: f32 = 1.0;
pub struct SizedBatch {
pub batch: SpriteBatch,
width: usize,
height: usize,
real_width: usize,
@ -63,15 +60,15 @@ impl SizedBatch {
#[inline(always)]
pub fn clear(&mut self) {
self.batch.clear();
/*self.batch.clear();*/
}
pub fn add(&mut self, x: f32, y: f32) {
let param = DrawParam::new()
/*let param = DrawParam::new()
.dest(Point2::new(x, y))
.scale(Vector2::new(self.scale_x, self.scale_y));
self.batch.add(param);
self.batch.add(param);*/
}
#[inline(always)]
@ -94,7 +91,7 @@ impl SizedBatch {
y = (y * G_MAG).floor() / G_MAG;
}
let param = DrawParam::new()
/*let param = DrawParam::new()
.src(Rect::new(rect.left as f32 / self.width as f32,
rect.top as f32 / self.height as f32,
(rect.right - rect.left) as f32 / self.width as f32,
@ -102,7 +99,7 @@ impl SizedBatch {
.dest(mint::Point2 { x, y })
.scale(Vector2::new(scale_x, scale_y));
self.batch.add(param);
self.batch.add(param);*/
}
pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
@ -110,7 +107,7 @@ impl SizedBatch {
return;
}
let param = DrawParam::new()
/*let param = DrawParam::new()
.color(color.into())
.src(Rect::new(rect.left as f32 / self.width as f32,
rect.top as f32 / self.height as f32,
@ -119,18 +116,19 @@ impl SizedBatch {
.dest(mint::Point2 { x, y })
.scale(Vector2::new(scale_x, scale_y));
self.batch.add(param);
self.batch.add(param);*/
}
#[inline(always)]
pub fn draw(&mut self, ctx: &mut Context) -> GameResult {
self.draw_filtered(FilterMode::Nearest, ctx)
//self.draw_filtered(FilterMode::Nearest, ctx)
Ok(())
}
pub fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult {
self.batch.set_filter(filter);
/*self.batch.set_filter(filter);
self.batch.draw(ctx, DrawParam::new())?;
self.batch.clear();
self.batch.clear();*/
Ok(())
}
}
@ -213,7 +211,6 @@ impl TextureSet {
let height = (size.h * scale_y) as usize;
Ok(SizedBatch {
batch: SpriteBatch::new(image),
width,
height,
scale_x,
@ -233,14 +230,10 @@ impl TextureSet {
}
pub fn draw_rect(&self, rect: common::Rect, color: [f32; 4], ctx: &mut Context) -> GameResult {
let rect = Mesh::new_rectangle(ctx, DrawMode::fill(), rect.into(), color.into())?;
graphics::draw(ctx, &rect, DrawParam::new())?;
Ok(())
}
pub fn draw_outline_rect(&self, rect: common::Rect, width: f32, color: [f32; 4], ctx: &mut Context) -> GameResult {
let rect = Mesh::new_rectangle(ctx, DrawMode::stroke(width), rect.into(), color.into())?;
graphics::draw(ctx, &rect, DrawParam::new())?;
Ok(())
}
}

View File

@ -2,31 +2,16 @@ use std::time::Instant;
use imgui::{FontConfig, FontSource};
use imgui::sys::*;
use imgui_gfx_renderer::{Renderer, Shaders};
use imgui_gfx_renderer::gfx::format::DepthStencil;
use imgui_gfx_renderer::gfx::format::Rgba8;
use imgui_gfx_renderer::gfx::handle::DepthStencilView;
use imgui_gfx_renderer::gfx::handle::RenderTargetView;
use imgui_gfx_renderer::gfx::memory::Typed;
use imgui_winit_support::{HiDpiMode, WinitPlatform};
use ggez::{Context, GameResult, graphics};
use ggez::GameError::RenderError;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::live_debugger::LiveDebugger;
use crate::scene::Scene;
use crate::shared_game_state::SharedGameState;
mod types {
pub type Resources = gfx_device_gl::Resources;
}
pub struct UI {
pub imgui: imgui::Context,
pub platform: WinitPlatform,
pub renderer: Renderer<Rgba8, types::Resources>,
pub components: Components,
pub main_color: RenderTargetView<types::Resources, Rgba8>,
pub main_depth: DepthStencilView<types::Resources, DepthStencil>,
last_frame: Instant,
}
@ -119,56 +104,18 @@ impl UI {
colors[ImGuiCol_NavWindowingDimBg as usize] = [0.20, 0.20, 0.20, 0.20];
colors[ImGuiCol_ModalWindowDimBg as usize] = [0.20, 0.20, 0.20, 0.35];
let mut platform = WinitPlatform::init(&mut imgui);
platform.attach_window(imgui.io_mut(), graphics::window(ctx).window(), HiDpiMode::Rounded);
let (factory, dev, _, depth, color) = graphics::gfx_objects(ctx);
let shaders = {
let version = dev.get_info().shading_language;
if version.is_embedded {
if version.major >= 3 {
Shaders::GlSlEs300
} else {
Shaders::GlSlEs100
}
} else if version.major >= 4 {
Shaders::GlSl400
} else if version.major >= 3 {
if version.minor >= 2 {
Shaders::GlSl150
} else {
Shaders::GlSl130
}
} else {
Shaders::GlSl110
}
};
let renderer = Renderer::init(&mut imgui, factory, shaders)
.map_err(|e| RenderError(e.to_string()))?;
Ok(Self {
imgui,
platform,
renderer,
components: Components {
live_debugger: LiveDebugger::new(),
},
main_color: RenderTargetView::new(color),
main_depth: DepthStencilView::new(depth),
last_frame: Instant::now(),
})
}
pub fn handle_events(&mut self, ctx: &mut Context, event: &winit::event::Event<()>) {
self.platform.handle_event(self.imgui.io_mut(), graphics::window(ctx).window(), &event);
}
pub fn draw(&mut self, state: &mut SharedGameState, ctx: &mut Context, scene: &mut Box<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;
@ -177,15 +124,7 @@ impl UI {
scene.debug_overlay_draw(&mut self.components, state, ctx, &mut ui)?;
self.platform.prepare_render(&ui, graphics::window(ctx).window());
let draw_data = ui.render();
let (factory, dev, encoder, _, _) = graphics::gfx_objects(ctx);
self.renderer
.render(factory, encoder, &mut self.main_color, draw_data)
.map_err(|e| RenderError(e.to_string()))?;
encoder.flush(dev);
ui.render();*/
Ok(())
}
}