initial commit

This commit is contained in:
Alula 2020-08-18 18:46:07 +02:00
commit b89d54251f
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
23 changed files with 3208 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
.idea/
.vscode/
# Cave Story (copyrighted) data files
data/
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

25
Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
authors = ["Alula <julekonopinska@gmail.com>"]
edition = "2018"
name = "doukutsu-rs"
version = "0.1.0"
[profile.release]
lto = true
panic = 'abort'
[dependencies]
byteorder = "1.3"
ggez = { git = "https://github.com/ggez/ggez", rev = "4f4bdebff463881c36325c7e10520c9a4fd8f75c" }
imgui = "0.4.0"
imgui-ext = "0.3.0"
lazy_static = "1.4.0"
log = "0.4"
maplit = "1.0.2"
num-traits = "0.2.12"
paste = "1.0.0"
pretty_env_logger = "0.4.0"
rodio = { version = "0.11", default-features = false, features = ["flac", "vorbis", "wav"] }
strum = "0.18.0"
strum_macros = "0.18.0"
owning_ref = "0.4.1"

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2020 Alula
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# doukutsu-rs
A re-implementation of Cave Story (Doukutsu Monogatari) engine written in [Rust](https://www.rust-lang.org/), aiming for accuracy and cleaner code.
Later plans might involve turning it into a fully-featured modding tool with live debugging and stuff.
**The project is still in a very early state and nowhere near being playable. Expect lots of breaking changes and bugs**
#### Data files
doukutsu-rs project does not re-distribute any copyrighted files.
The engine should work fine with [CSE2](https://github.com/Clownacy/CSE2)/[NXEngine](https://github.com/nxengine/nxengine-evo) modified freeware data files and [Cave Story+](https://www.nicalis.com/games/cavestory+) (Nicalis commercial release, partial loading is supported but note we're not aiming to fully support it's features) data files.
#### Roadmap
- [x] Tilemap and player rendering
- [ ] Weapons
- [ ] Text scripts (TSC)
- [ ] Making it actually playable
- [ ] Modding enhancements and built-in tools
- [ ] **idk the list is TBD**
#### why rust, it's a hipster language lol
The project is a result of me wanting to build something in a new programming language for memes.
I had an idea of writing my own CS engine long time before and I would've very likely picked C++17/20+SDL2, but after
all I've picked Rust instead because it seemed quite interesting for me.
Would 90% of end-users running this thing care about the programming language software was written in? After all who tf cares if the performance is the same (and maybe a slightly better), but you also get a lot of various benefits?
#### Credits
- Studio Pixel for Cave Story
- [Cave Story Tribute Site](https://cavestory.org) - for LOTS of useful resources related to the game.
- [Clownacy for CSE2](https://github.com/Clownacy/CSE2) - a great and very accurate reference for game's logic used in this project
- [CSMC](https://discord.gg/xRsWpz6) - a helpful Cave Story modding community
- [NXEngine](https://github.com/nxengine/nxengine-evo) - an another OSS rewrite of Cave Story engine.

8
benches/fixed.rs Normal file
View File

@ -0,0 +1,8 @@
use criterion::{black_box, Criterion, criterion_group, criterion_main};
use fpa::I23F9;
fn criterion_benchmark(c: &mut Criterion) {
//c.bench_function("fpa lib", |b| b.iter(|| fpa_lib()));
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

680
src/common.rs Normal file
View File

@ -0,0 +1,680 @@
#[doc(hidden)]
pub use core::convert::Into;
#[doc(hidden)]
pub use core::fmt;
#[doc(hidden)]
pub use core::mem::size_of;
use num_traits::Num;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Left = 0,
Up = 1,
Right = 2,
Bottom = 3,
}
impl Direction {
pub fn from_int(val: usize) -> Option<Direction> {
match val {
0 => { Some(Direction::Left) }
1 => { Some(Direction::Up) }
2 => { Some(Direction::Right) }
3 => { Some(Direction::Bottom) }
_ => { None }
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Rect<T: Num + Copy = isize> {
pub left: T,
pub top: T,
pub right: T,
pub bottom: T,
}
impl<S: Num + Copy> Rect<S> {
pub fn new<T: Num + Copy>(left: T, top: T, right: T, bottom: T) -> Rect<T> {
Rect {
left,
top,
right,
bottom,
}
}
pub fn new_size<T: Num + Copy>(x: T, y: T, width: T, height: T) -> Rect<T> {
Rect {
left: x,
top: y,
right: x.add(width),
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),
}
}
}
#[macro_export]
macro_rules! str {
() => {
String::new()
};
($x:expr) => {
ToString::to_string(&$x)
};
}
// extended version of https://github.com/dzamlo/rust-bitfield
#[macro_export(local_inner_macros)]
macro_rules! bitfield_fields {
(@field $(#[$attribute:meta])* ($($vis:tt)*) $t:ty, $from:ty, $into:ty, _, $setter:ident: $msb:expr,
$lsb:expr, $count:expr) => {
$(#[$attribute])*
#[allow(unknown_lints)]
#[allow(eq_op)]
$($vis)* fn $setter(&mut self, index: usize, value: $from) {
use crate::common::BitRange;
__bitfield_debug_assert!(index < $count);
let width = $msb - $lsb + 1;
let lsb = $lsb + index*width;
let msb = lsb + width - 1;
self.set_bit_range(msb, lsb, crate::common::Into::<$t>::into(value));
}
};
(@field $(#[$attribute:meta])* ($($vis:tt)*) $t:ty, $from:ty, $into:ty, _, $setter:ident: $msb:expr,
$lsb:expr) => {
$(#[$attribute])*
$($vis)* fn $setter(&mut self, value: $from) {
use crate::common::BitRange;
self.set_bit_range($msb, $lsb, crate::common::Into::<$t>::into(value));
}
};
(@field $(#[$attribute:meta])* ($($vis:tt)*) $t:ty, $from:ty, $into:ty, _, $setter:ident: $bit:expr) => {
$(#[$attribute])*
$($vis)* fn $setter(&mut self, value: bool) {
use crate::common::Bit;
self.set_bit($bit, value);
}
};
(@field $(#[$attribute:meta])* ($($vis:tt)*) $t:ty, $from:ty, $into:ty, $getter:ident, _: $msb:expr,
$lsb:expr, $count:expr) => {
$(#[$attribute])*
#[allow(unknown_lints)]
#[allow(eq_op)]
$($vis)* fn $getter(&self, index: usize) -> $into {
use crate::common::BitRange;
__bitfield_debug_assert!(index < $count);
let width = $msb - $lsb + 1;
let lsb = $lsb + index*width;
let msb = lsb + width - 1;
let raw_value: $t = self.bit_range(msb, lsb);
crate::common::Into::into(raw_value)
}
};
(@field $(#[$attribute:meta])* ($($vis:tt)*) $t:ty, $from:ty, $into:ty, $getter:ident, _: $msb:expr,
$lsb:expr) => {
$(#[$attribute])*
$($vis)* fn $getter(&self) -> $into {
use crate::common::BitRange;
let raw_value: $t = self.bit_range($msb, $lsb);
crate::common::Into::into(raw_value)
}
};
(@field $(#[$attribute:meta])* ($($vis:tt)*) $t:ty, $from:ty, $into:ty, $getter:ident, _: $bit:expr) => {
$(#[$attribute])*
$($vis)* fn $getter(&self) -> bool {
use crate::common::Bit;
self.bit($bit)
}
paste::paste! {
$($vis)* fn [<only_ $getter>](&self) -> bool {
use crate::common::Bit;
self.bit_only($bit)
}
}
};
(@field $(#[$attribute:meta])* ($($vis:tt)*) $t:ty, $from:ty, $into:ty, $getter:ident, $setter:ident:
$($exprs:expr),*) => {
bitfield_fields!(@field $(#[$attribute])* ($($vis)*) $t, $from, $into, $getter, _: $($exprs),*);
bitfield_fields!(@field $(#[$attribute])* ($($vis)*) $t, $from, $into, _, $setter: $($exprs),*);
};
($t:ty;) => {};
($default_ty:ty; pub $($rest:tt)*) => {
bitfield_fields!{$default_ty; () pub $($rest)*}
};
($default_ty:ty; #[$attribute:meta] $($rest:tt)*) => {
bitfield_fields!{$default_ty; (#[$attribute]) $($rest)*}
};
($default_ty:ty; ($(#[$attributes:meta])*) #[$attribute:meta] $($rest:tt)*) => {
bitfield_fields!{$default_ty; ($(#[$attributes])* #[$attribute]) $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) pub $t:ty, from into $into:ty, $getter:tt, $setter:tt:
$($exprs:expr),*; $($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* (pub) $t, $into, $into, $getter, $setter: $($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) pub $t:ty, into $into:ty, $getter:tt, $setter:tt:
$($exprs:expr),*; $($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* (pub) $t, $t, $into, $getter, $setter: $($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) pub $t:ty, $getter:tt, $setter:tt: $($exprs:expr),*;
$($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* (pub) $t, $t, $t, $getter, $setter: $($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) pub from into $into:ty, $getter:tt, $setter:tt:
$($exprs:expr),*; $($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* (pub) $default_ty, $into, $into, $getter, $setter:
$($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) pub into $into:ty, $getter:tt, $setter:tt:
$($exprs:expr),*; $($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* (pub) $default_ty, $default_ty, $into, $getter, $setter:
$($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) pub $getter:tt, $setter:tt: $($exprs:expr),*;
$($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* (pub) $default_ty, $default_ty, $default_ty, $getter, $setter:
$($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) $t:ty, from into $into:ty, $getter:tt, $setter:tt:
$($exprs:expr),*; $($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* () $t, $into, $into, $getter, $setter: $($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) $t:ty, into $into:ty, $getter:tt, $setter:tt:
$($exprs:expr),*; $($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* () $t, $t, $into, $getter, $setter: $($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) $t:ty, $getter:tt, $setter:tt: $($exprs:expr),*;
$($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* () $t, $t, $t, $getter, $setter: $($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) from into $into:ty, $getter:tt, $setter:tt:
$($exprs:expr),*; $($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* () $default_ty, $into, $into, $getter, $setter:
$($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) into $into:ty, $getter:tt, $setter:tt:
$($exprs:expr),*; $($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* () $default_ty, $default_ty, $into, $getter, $setter:
$($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; ($(#[$attribute:meta])*) $getter:tt, $setter:tt: $($exprs:expr),*;
$($rest:tt)*) => {
bitfield_fields!{@field $(#[$attribute])* () $default_ty, $default_ty, $default_ty, $getter, $setter:
$($exprs),*}
bitfield_fields!{$default_ty; $($rest)*}
};
($previous_default_ty:ty; $default_ty:ty; $($rest:tt)*) => {
bitfield_fields!{$default_ty; $($rest)*}
};
($default_ty:ty; $($rest:tt)*) => {
bitfield_fields!{$default_ty; () $($rest)*}
};
($($rest:tt)*) => {
bitfield_fields!{SET_A_DEFAULT_TYPE_OR_SPECIFY_THE_TYPE_FOR_EACH_FIELDS; $($rest)*}
}
}
/// Generates a `fmt::Debug` implementation.
///
/// This macros must be called from a `impl Debug for ...` block. It will generate the `fmt` method.
///
/// In most of the case, you will not directly call this macros, but use `bitfield`.
///
/// The syntax is `struct TheNameOfTheStruct` followed by the syntax of `bitfield_fields`.
///
/// The write-only fields are ignored.
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate bitfield;
/// struct FooBar(u32);
/// bitfield_bitrange!{struct FooBar(u32)}
/// impl FooBar{
/// bitfield_fields!{
/// u32;
/// field1, _: 7, 0;
/// field2, _: 31, 24;
/// }
/// }
///
/// impl std::fmt::Debug for FooBar {
/// bitfield_debug!{
/// struct FooBar;
/// field1, _: 7, 0;
/// field2, _: 31, 24;
/// }
/// }
///
/// fn main() {
/// let foobar = FooBar(0x11223344);
/// println!("{:?}", foobar);
/// }
/// ```
#[macro_export(local_inner_macros)]
macro_rules! bitfield_debug {
(struct $name:ident; $($rest:tt)*) => {
fn fmt(&self, f: &mut crate::common::fmt::Formatter) -> crate::common::fmt::Result {
let mut debug_struct = f.debug_struct(__bitfield_stringify!($name));
debug_struct.field(".0", &self.0);
bitfield_debug!{debug_struct, self, $($rest)*}
debug_struct.finish()
}
};
($debug_struct:ident, $self:ident, #[$attribute:meta] $($rest:tt)*) => {
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, pub $($rest:tt)*) => {
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, _, $setter:tt: $($exprs:expr),*; $($rest:tt)*) => {
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, $type:ty; $($rest:tt)*) => {
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, $getter:ident, $setter:tt: $msb:expr, $lsb:expr, $count:expr;
$($rest:tt)*) => {
let mut array = [$self.$getter(0); $count];
for (i, e) in (&mut array).into_iter().enumerate() {
*e = $self.$getter(i);
}
$debug_struct.field(__bitfield_stringify!($getter), &array);
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, $getter:ident, $setter:tt: $($exprs:expr),*; $($rest:tt)*)
=> {
$debug_struct.field(__bitfield_stringify!($getter), &$self.$getter());
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, from into $into:ty, $($rest:tt)*) => {
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, into $into:ty, $($rest:tt)*) => {
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, $type:ty, $($rest:tt)*) => {
bitfield_debug!{$debug_struct, $self, $($rest)*}
};
($debug_struct:ident, $self:ident, ) => {};
}
/// Implements `BitRange` for a tuple struct (or "newtype").
///
/// This macro will generate an implementation of the `BitRange` trait for an existing single
/// element tuple struct.
///
/// The syntax is more or less the same as declaring a "newtype", **without** the attributes,
/// documentation comments and pub keyword.
///
/// The difference with a normal "newtype" is the type in parentheses. If the type is `[t]` (where
/// `t` is any of the unsigned integer type), the "newtype" will be generic and implement
/// `BitRange` for `T: AsMut<[t]> + AsRef<[t]>` (for example a slice, an array or a `Vec`). You can
/// also use `MSB0 [t]`. The difference will be the positions of the bit. You can use the
/// `bits_positions` example to see where each bits is. If the type is neither of this two, the
/// "newtype" will wrap a value of the specified type and implements `BitRange` the same ways as
/// the wrapped type.
///
/// # Examples
///
/// ```rust
/// # #[macro_use] extern crate bitfield;
/// # fn main() {}
/// struct BitField1(u32);
/// bitfield_bitrange!{struct BitField1(u32)}
///
/// struct BitField2<T>(T);
/// bitfield_bitrange!{struct BitField2([u8])}
///
/// struct BitField3<T>(T);
/// bitfield_bitrange!{struct BitField3(MSB0 [u8])}
/// ```
///
#[macro_export(local_inner_macros)]
macro_rules! bitfield_bitrange {
(@impl_bitrange_slice $name:ident, $slice_ty:ty, $bitrange_ty:ty) => {
impl<T: AsMut<[$slice_ty]> + AsRef<[$slice_ty]>> crate::common::BitRange<$bitrange_ty>
for $name<T> {
fn bit_range(&self, msb: usize, lsb: usize) -> $bitrange_ty {
let bit_len = crate::common::size_of::<$slice_ty>()*8;
let value_bit_len = crate::common::size_of::<$bitrange_ty>()*8;
let mut value = 0;
for i in (lsb..=msb).rev() {
value <<= 1;
value |= ((self.0.as_ref()[i/bit_len] >> (i%bit_len)) & 1) as $bitrange_ty;
}
value << (value_bit_len - (msb - lsb + 1)) >> (value_bit_len - (msb - lsb + 1))
}
fn set_bit_range(&mut self, msb: usize, lsb: usize, value: $bitrange_ty) {
let bit_len = crate::common::size_of::<$slice_ty>()*8;
let mut value = value;
for i in lsb..=msb {
self.0.as_mut()[i/bit_len] &= !(1 << (i%bit_len));
self.0.as_mut()[i/bit_len] |= (value & 1) as $slice_ty << (i%bit_len);
value >>= 1;
}
}
}
};
(@impl_bitrange_slice_msb0 $name:ident, $slice_ty:ty, $bitrange_ty:ty) => {
impl<T: AsMut<[$slice_ty]> + AsRef<[$slice_ty]>> crate::common::BitRange<$bitrange_ty>
for $name<T> {
fn bit_range(&self, msb: usize, lsb: usize) -> $bitrange_ty {
let bit_len = crate::common::size_of::<$slice_ty>()*8;
let value_bit_len = crate::common::size_of::<$bitrange_ty>()*8;
let mut value = 0;
for i in lsb..=msb {
value <<= 1;
value |= ((self.0.as_ref()[i/bit_len] >> (bit_len - i%bit_len - 1)) & 1)
as $bitrange_ty;
}
value << (value_bit_len - (msb - lsb + 1)) >> (value_bit_len - (msb - lsb + 1))
}
fn set_bit_range(&mut self, msb: usize, lsb: usize, value: $bitrange_ty) {
let bit_len = crate::common::size_of::<$slice_ty>()*8;
let mut value = value;
for i in (lsb..=msb).rev() {
self.0.as_mut()[i/bit_len] &= !(1 << (bit_len - i%bit_len - 1));
self.0.as_mut()[i/bit_len] |= (value & 1) as $slice_ty
<< (bit_len - i%bit_len - 1);
value >>= 1;
}
}
}
};
(struct $name:ident([$t:ty])) => {
bitfield_bitrange!(@impl_bitrange_slice $name, $t, u8);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, u16);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, u32);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, u64);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, u128);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, i8);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, i16);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, i32);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, i64);
bitfield_bitrange!(@impl_bitrange_slice $name, $t, i128);
};
(struct $name:ident(MSB0 [$t:ty])) => {
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, u8);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, u16);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, u32);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, u64);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, u128);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, i8);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, i16);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, i32);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, i64);
bitfield_bitrange!(@impl_bitrange_slice_msb0 $name, $t, i128);
};
(struct $name:ident($t:ty)) => {
impl<T> crate::common::BitRange<T> for $name where $t: crate::common::BitRange<T> {
fn get(&self) -> T {
self.0.get()
}
fn bit_range(&self, msb: usize, lsb: usize) -> T {
self.0.bit_range(msb, lsb)
}
fn set_bit_range(&mut self, msb: usize, lsb: usize, value: T) {
self.0.set_bit_range(msb, lsb, value);
}
}
};
}
/// Combines `bitfield_bitrange` and `bitfield_fields`.
///
/// The syntax of this macro is the syntax of a tuple struct, including attributes and
/// documentation comments, followed by a semicolon, some optional elements, and finally the fields
/// as described in the `bitfield_fields` documentation.
///
/// The first optional element is `no default BitRange;`. With that, no implementation of
/// `BitRange` will be generated.
///
/// The second optional element is `impl Debug;`. This will generate an implementation of
/// `fmt::Debug` with the `bitfield_debug` macro.
///
/// The difference with calling those macros separately is that `bitfield_fields` is called
/// from an appropriate `impl` block. If you use the non-slice form of `bitfield_bitrange`, the
/// default type for `bitfield_fields` will be set to the wrapped fields.
///
/// See the documentation of these macros for more information on their respective syntax.
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate bitfield;
/// # fn main() {}
/// bitfield!{
/// pub struct BitField1(u16);
/// impl Debug;
/// // The fields default to u16
/// field1, set_field1: 10, 0;
/// pub field2, _ : 12, 3;
/// }
/// ```
///
/// or with a custom `BitRange` implementation :
/// ```rust
/// # #[macro_use] extern crate bitfield;
/// # use bitfield::BitRange;
/// # fn main() {}
/// bitfield!{
/// pub struct BitField1(u16);
/// no default BitRange;
/// impl Debug;
/// u8;
/// field1, set_field1: 10, 0;
/// pub field2, _ : 12, 3;
/// }
/// impl BitRange<u8> for BitField1 {
/// fn bit_range(&self, msb: usize, lsb: usize) -> u8 {
/// let width = msb - lsb + 1;
/// let mask = (1 << width) - 1;
/// ((self.0 >> lsb) & mask) as u8
/// }
/// fn set_bit_range(&mut self, msb: usize, lsb: usize, value: u8) {
/// self.0 = (value as u16) << lsb;
/// }
/// }
/// ```
#[macro_export(local_inner_macros)]
macro_rules! bitfield {
($(#[$attribute:meta])* pub struct $($rest:tt)*) => {
bitfield!($(#[$attribute])* (pub) struct $($rest)*);
};
($(#[$attribute:meta])* struct $($rest:tt)*) => {
bitfield!($(#[$attribute])* () struct $($rest)*);
};
// Force `impl Debug` to always be after `no default BitRange` it the two are present.
// This simplify the rest of the macro.
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident($($type:tt)*); impl Debug; no default BitRange; $($rest:tt)*) => {
bitfield!{$(#[$attribute])* ($($vis)*) struct $name($($type)*); no default BitRange; impl Debug; $($rest)*}
};
// If we have `impl Debug` without `no default BitRange`, we will still match, because when
// we call `bitfield_bitrange`, we add `no default BitRange`.
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident([$t:ty]); no default BitRange; impl Debug; $($rest:tt)*) => {
impl<T: AsMut<[$t]> + AsRef<[$t]> + crate::common::fmt::Debug> crate::common::fmt::Debug for $name<T> {
bitfield_debug!{struct $name; $($rest)*}
}
bitfield!{$(#[$attribute])* ($($vis)*) struct $name([$t]); no default BitRange; $($rest)*}
};
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident([$t:ty]); no default BitRange; $($rest:tt)*) => {
$(#[$attribute])*
$($vis)* struct $name<T>(pub T);
impl<T: AsMut<[$t]> + AsRef<[$t]>> $name<T> {
bitfield_fields!{$($rest)*}
}
};
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident([$t:ty]); $($rest:tt)*) => {
bitfield_bitrange!(struct $name([$t]));
bitfield!{$(#[$attribute])* ($($vis)*) struct $name([$t]); no default BitRange; $($rest)*}
};
// The only difference between the MSB0 version anf the non-MSB0 version, is the BitRange
// implementation. We delegate everything else to the non-MSB0 version of the macro.
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident(MSB0 [$t:ty]); no default BitRange; $($rest:tt)*) => {
bitfield!{$(#[$attribute])* ($($vis)*) struct $name([$t]); no default BitRange; $($rest)*}
};
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident(MSB0 [$t:ty]); $($rest:tt)*) => {
bitfield_bitrange!(struct $name(MSB0 [$t]));
bitfield!{$(#[$attribute])* ($($vis)*) struct $name([$t]); no default BitRange; $($rest)*}
};
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident($t:ty); no default BitRange; impl Debug; $($rest:tt)*) => {
impl crate::common::fmt::Debug for $name {
bitfield_debug!{struct $name; $($rest)*}
}
bitfield!{$(#[$attribute])* ($($vis)*) struct $name($t); no default BitRange; $($rest)*}
};
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident($t:ty); no default BitRange; $($rest:tt)*) => {
$(#[$attribute])*
$($vis)* struct $name(pub $t);
impl $name {
bitfield_fields!{$t; $($rest)*}
}
};
($(#[$attribute:meta])* ($($vis:tt)*) struct $name:ident($t:ty); $($rest:tt)*) => {
bitfield_bitrange!(struct $name($t));
bitfield!{$(#[$attribute])* ($($vis)*) struct $name($t); no default BitRange; $($rest)*}
};
}
/// A trait to get or set ranges of bits.
pub trait BitRange<T> {
fn get(&self) -> T;
/// Get a range of bits.
fn bit_range(&self, msb: usize, lsb: usize) -> T;
/// Set a range of bits.
fn set_bit_range(&mut self, msb: usize, lsb: usize, value: T);
}
/// A trait to get or set a single bit.
///
/// This trait is implemented for all type that implement `BitRange<u8>`.
pub trait Bit {
/// Get a single bit.
fn bit(&self, bit: usize) -> bool;
/// Check if only specific bit is set.
fn bit_only(&self, bit: usize) -> bool;
/// Set a single bit.
fn set_bit(&mut self, bit: usize, value: bool);
}
impl<T: BitRange<u8>> Bit for T {
fn bit(&self, bit: usize) -> bool {
self.bit_range(bit, bit) != 0
}
fn bit_only(&self, bit: usize) -> bool {
self.get() == (1 << bit) as u8
}
fn set_bit(&mut self, bit: usize, value: bool) {
self.set_bit_range(bit, bit, value as u8);
}
}
macro_rules! impl_bitrange_for_u {
($t:ty, $bitrange_ty:ty) => {
impl BitRange<$bitrange_ty> for $t {
#[inline]
fn get(&self) -> $bitrange_ty {
*self as $bitrange_ty
}
#[inline]
fn bit_range(&self, msb: usize, lsb: usize) -> $bitrange_ty {
let bit_len = size_of::<$t>()*8;
let result_bit_len = size_of::<$bitrange_ty>()*8;
let result = ((*self << (bit_len - msb - 1)) >> (bit_len - msb - 1 + lsb))
as $bitrange_ty;
result << (result_bit_len - (msb - lsb + 1)) >> (result_bit_len - (msb - lsb + 1))
}
#[inline]
fn set_bit_range(&mut self, msb: usize, lsb: usize, value: $bitrange_ty) {
let bit_len = size_of::<$t>()*8;
let mask: $t = !(0 as $t)
<< (bit_len - msb - 1)
>> (bit_len - msb - 1 + lsb)
<< (lsb);
*self &= !mask;
*self |= (value as $t << lsb) & mask;
}
}
}
}
macro_rules! impl_bitrange_for_u_combinations {
((),($($bitrange_ty:ty),*)) => {
};
(($t:ty),($($bitrange_ty:ty),*)) => {
$(impl_bitrange_for_u!{$t, $bitrange_ty})*
};
(($t_head:ty, $($t_rest:ty),*),($($bitrange_ty:ty),*)) => {
impl_bitrange_for_u_combinations!{($t_head), ($($bitrange_ty),*)}
impl_bitrange_for_u_combinations!{($($t_rest),*), ($($bitrange_ty),*)}
};
}
impl_bitrange_for_u_combinations! {(u8, u16, u32, u64, u128), (u8, u16, u32, u64, u128)}
impl_bitrange_for_u_combinations! {(u8, u16, u32, u64, u128), (i8, i16, i32, i64, i128)}
// Same as std::stringify but callable from local_inner_macros macros defined inside
// this crate.
#[macro_export]
#[doc(hidden)]
macro_rules! __bitfield_stringify {
($s:ident) => {
stringify!($s)
};
}
// Same as std::debug_assert but callable from local_inner_macros macros defined inside
// this crate.
#[macro_export]
#[doc(hidden)]
macro_rules! __bitfield_debug_assert {
($e:expr) => {
debug_assert!($e)
};
}

0
src/enemy/critter.rs Normal file
View File

33
src/enemy/mod.rs Normal file
View File

@ -0,0 +1,33 @@
use crate::bitfield;
pub mod critter;
bitfield! {
pub struct NPCCond(u16);
impl Debug;
pub damage_boss, set_damage_boss: 4;
pub alive, set_alive: 7;
}
bitfield! {
pub struct NPCFlags(u16);
impl Debug;
pub solid_soft, set_solid_soft: 0;
pub ignore_tile_44, set_ignore_tile_44: 1;
pub invulnerable, set_invulnerable: 2;
pub ignore_solidity, set_ignore_solidity: 3;
pub bouncy, set_bouncy: 4;
pub shootable, set_shootable: 5;
pub solid_hard, set_solid_hard: 6;
pub rear_and_top_not_hurt, set_rear_and_top_not_hurt: 7;
pub event_when_touched, set_event_when_touched: 8;
pub event_when_killed, set_event_when_killed: 9;
pub flag_x400, set_flag_x400: 10;
pub appear_when_flag_set, set_appear_when_flag_set: 11;
pub spawn_in_other_direction, set_spawn_in_other_direction: 12;
pub interactable, set_interactable: 13;
pub hide_when_flag_set, set_hide_when_flag_set: 14;
pub show_damage, set_show_damage: 15;
}

251
src/engine_constants.rs Normal file
View File

@ -0,0 +1,251 @@
use std::collections::HashMap;
use maplit::hashmap;
use crate::common::{Direction, Rect};
use crate::str;
#[derive(Debug, Copy, Clone)]
pub struct PhysicsConsts {
pub max_dash: isize,
pub max_move: isize,
pub gravity_ground: isize,
pub gravity_air: isize,
pub dash_ground: isize,
pub dash_air: isize,
pub resist: isize,
pub jump: isize,
}
#[derive(Debug, Copy, Clone)]
pub struct BoosterConsts {
pub b2_0_up: isize,
pub b2_0_up_nokey: isize,
pub b2_0_down: isize,
pub b2_0_left: isize,
pub b2_0_right: isize,
}
#[derive(Debug, Copy, Clone)]
pub struct MyCharConsts {
pub cond: u16,
pub flags: u32,
pub equip: u16,
pub direction: Direction,
pub view: Rect<usize>,
pub hit: Rect<usize>,
pub life: u16,
pub max_life: u16,
pub unit: u8,
pub air_physics: PhysicsConsts,
pub water_physics: PhysicsConsts,
pub animations_left: [Rect<usize>; 12],
pub animations_right: [Rect<usize>; 12],
}
#[derive(Debug)]
pub struct EngineConstants {
pub my_char: MyCharConsts,
pub booster: BoosterConsts,
pub tex_sizes: HashMap<String, (usize, usize)>,
}
impl Clone for EngineConstants {
fn clone(&self) -> Self {
EngineConstants {
my_char: self.my_char,
booster: self.booster,
tex_sizes: self.tex_sizes.clone(),
}
}
}
impl EngineConstants {
pub fn defaults() -> EngineConstants {
EngineConstants {
my_char: MyCharConsts {
cond: 0x80,
flags: 0,
equip: 0,
direction: Direction::Right,
view: Rect { left: 8 * 0x200, top: 8 * 0x200, right: 8 * 0x200, bottom: 8 * 0x200 },
hit: Rect { left: 5 * 0x200, top: 8 * 0x200, right: 5 * 0x200, bottom: 8 * 0x200 },
life: 3,
max_life: 3,
unit: 0,
air_physics: PhysicsConsts {
max_dash: 0x32c,
max_move: 0x5ff,
gravity_air: 0x20,
gravity_ground: 0x50,
dash_air: 0x20,
dash_ground: 0x55,
resist: 0x33,
jump: 0x500,
},
water_physics: PhysicsConsts {
max_dash: 0x196,
max_move: 0x2ff,
gravity_air: 0x10,
gravity_ground: 0x28,
dash_air: 0x10,
dash_ground: 0x2a,
resist: 0x19,
jump: 0x280,
},
animations_left: [
Rect { left: 0, top: 0, right: 16, bottom: 16 },
Rect { left: 16, top: 0, right: 32, bottom: 16 },
Rect { left: 0, top: 0, right: 16, bottom: 16 },
Rect { left: 32, top: 0, right: 48, bottom: 16 },
Rect { left: 0, top: 0, right: 16, bottom: 16 },
Rect { left: 48, top: 0, right: 64, bottom: 16 },
Rect { left: 64, top: 0, right: 80, bottom: 16 },
Rect { left: 48, top: 0, right: 64, bottom: 16 },
Rect { left: 80, top: 0, right: 96, bottom: 16 },
Rect { left: 48, top: 0, right: 64, bottom: 16 },
Rect { left: 96, top: 0, right: 112, bottom: 16 },
Rect { left: 112, top: 0, right: 128, bottom: 16 },
],
animations_right: [
Rect { left: 0, top: 16, right: 16, bottom: 32 },
Rect { left: 16, top: 16, right: 32, bottom: 32 },
Rect { left: 0, top: 16, right: 16, bottom: 32 },
Rect { left: 32, top: 16, right: 48, bottom: 32 },
Rect { left: 0, top: 16, right: 16, bottom: 32 },
Rect { left: 48, top: 16, right: 64, bottom: 32 },
Rect { left: 64, top: 16, right: 80, bottom: 32 },
Rect { left: 48, top: 16, right: 64, bottom: 32 },
Rect { left: 80, top: 16, right: 96, bottom: 32 },
Rect { left: 48, top: 16, right: 64, bottom: 32 },
Rect { left: 96, top: 16, right: 112, bottom: 32 },
Rect { left: 112, top: 16, right: 128, bottom: 32 },
],
},
booster: BoosterConsts {
b2_0_up: -0x5ff,
b2_0_up_nokey: -0x5ff,
b2_0_down: 0x5ff,
b2_0_left: -0x5ff,
b2_0_right: 0x5ff
},
tex_sizes: hashmap! {
str!("ArmsImage") => (256, 16),
str!("Arms") => (320, 200),
str!("bk0") => (64, 64),
str!("bkBlack") => (64, 64),
str!("bkBlue") => (64, 64),
str!("bkFall") => (64, 64),
str!("bkFog") => (320, 240),
str!("bkFog480fix") => (480, 272), // nxengine
str!("bkGard") => (48, 64),
str!("bkGray") => (64, 64),
str!("bkGreen") => (64, 64),
str!("bkHellish") => (320, 240), // nxengine
str!("bkHellish480fix") => (480, 272), // nxengine
str!("bkLight") => (320, 240), // nxengine
str!("bkLight480fix") => (480, 272), // nxengine
str!("bkMaze") => (64, 64),
str!("bkMoon") => (320, 240),
str!("bkMoon480fix") => (480, 272), // nxengine
str!("bkRed") => (32, 32),
str!("bkSunset") => (320, 240), // nxengine
str!("bkSunset480fix") => (480, 272), // nxengine
str!("bkWater") => (32, 48),
str!("Bullet") => (320, 176),
str!("Caret") => (320, 240),
str!("casts") => (320, 240),
str!("Face") => (288, 240),
str!("Face_0") => (288, 240), // nxengine
str!("Face_1") => (288, 240), // nxengine
str!("Face_2") => (288, 240), // nxengine
str!("Fade") => (256, 32),
str!("ItemImage") => (256, 128),
str!("Loading") => (64, 8),
str!("MyChar") => (200, 64),
str!("Npc/Npc0") => (32, 32),
str!("Npc/NpcAlmo1") => (320, 240),
str!("Npc/NpcAlmo2") => (320, 240),
str!("Npc/NpcBallos") => (320, 240),
str!("Npc/NpcBllg") => (320, 96),
str!("Npc/NpcCemet") => (320, 112),
str!("Npc/NpcCent") => (320, 192),
str!("Npc/NpcCurly") => (256, 80),
str!("Npc/NpcDark") => (160, 64),
str!("Npc/NpcDr") => (320, 240),
str!("Npc/NpcEggs1") => (320, 112),
str!("Npc/NpcEggs2") => (320, 128),
str!("Npc/NpcFrog") => (320, 240),
str!("Npc/NpcGuest") => (320, 184),
str!("Npc/NpcHell") => (320, 160),
str!("Npc/NpcHeri") => (320, 128),
str!("Npc/NpcIronH") => (320, 72),
str!("Npc/NpcIsland") => (320, 80),
str!("Npc/NpcKings") => (96, 48),
str!("Npc/NpcMaze") => (320, 192),
str!("Npc/NpcMiza") => (320, 240),
str!("Npc/NpcMoon") => (320, 128),
str!("Npc/NpcOmg") => (320, 120),
str!("Npc/NpcPlant") => (320, 48),
str!("Npc/NpcPress") => (320, 240),
str!("Npc/NpcPriest") => (320, 240),
str!("Npc/NpcRavil") => (320, 168),
str!("Npc/NpcRed") => (320, 144),
str!("Npc/NpcRegu") => (320, 240),
str!("Npc/NpcSand") => (320, 176),
str!("Npc/NpcStream") => (64, 32),
str!("Npc/NpcSym") => (320, 240),
str!("Npc/NpcToro") => (320, 144),
str!("Npc/NpcTwinD") => (320, 144),
str!("Npc/NpcWeed") => (320, 240),
str!("Npc/NpcX") => (320, 240),
str!("Resource/BITMAP/Credit01") => (160, 240), // cse2
str!("Resource/BITMAP/Credit02") => (160, 240), // cse2
str!("Resource/BITMAP/Credit03") => (160, 240), // cse2
str!("Resource/BITMAP/Credit04") => (160, 240), // cse2
str!("Resource/BITMAP/Credit05") => (160, 240), // cse2
str!("Resource/BITMAP/Credit06") => (160, 240), // cse2
str!("Resource/BITMAP/Credit07") => (160, 240), // cse2
str!("Resource/BITMAP/Credit08") => (160, 240), // cse2
str!("Resource/BITMAP/Credit09") => (160, 240), // cse2
str!("Resource/BITMAP/Credit10") => (160, 240), // cse2
str!("Resource/BITMAP/Credit11") => (160, 240), // cse2
str!("Resource/BITMAP/Credit12") => (160, 240), // cse2
str!("Resource/BITMAP/Credit14") => (160, 240), // cse2
str!("Resource/BITMAP/Credit15") => (160, 240), // cse2
str!("Resource/BITMAP/Credit16") => (160, 240), // cse2
str!("Resource/BITMAP/Credit17") => (160, 240), // cse2
str!("Resource/BITMAP/Credit18") => (160, 240), // cse2
str!("Resource/BITMAP/pixel") => (160, 16), // cse2
str!("Resource/CURSOR/CURSOR_IKA") => (32, 32), // cse2
str!("Resource/CURSOR/CURSOR_NORMAL") => (32, 32), // cse2
str!("StageImage") => (256, 16),
str!("Stage/Prt0") => (32, 32),
str!("Stage/PrtAlmond") => (256, 96),
str!("Stage/PrtBarr") => (256, 88),
str!("Stage/PrtCave") => (256, 80),
str!("Stage/PrtCent") => (256, 128),
str!("Stage/PrtEggIn") => (256, 80),
str!("Stage/PrtEggs") => (256, 240),
str!("Stage/PrtEggX") => (256, 240),
str!("Stage/PrtFall") => (256, 128),
str!("Stage/PrtGard") => (256, 97),
str!("Stage/PrtHell") => (256, 240),
str!("Stage/PrtJail") => (256, 128),
str!("Stage/PrtLabo") => (128, 64),
str!("Stage/PrtMaze") => (256, 160),
str!("Stage/PrtMimi") => (256, 160),
str!("Stage/PrtOside") => (256, 64),
str!("Stage/PrtPens") => (256, 64),
str!("Stage/PrtRiver") => (256, 96),
str!("Stage/PrtSand") => (256, 112),
str!("Stage/PrtStore") => (256, 112),
str!("Stage/PrtWeed") => (256, 128),
str!("Stage/PrtWhite") => (256, 240),
str!("TextBox") => (244, 144),
str!("Title") => (320, 48),
},
}
}
}

10
src/entity.rs Normal file
View File

@ -0,0 +1,10 @@
use ggez::{Context, GameResult};
use crate::frame::Frame;
use crate::SharedGameState;
pub trait GameEntity {
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult;
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult;
}

45
src/frame.rs Normal file
View File

@ -0,0 +1,45 @@
use crate::player::Player;
use crate::stage::Stage;
use crate::SharedGameState;
pub struct Frame {
pub x: isize,
pub y: isize,
pub wait: isize,
}
impl Frame {
pub fn update(&mut self, state: &SharedGameState, player: &Player, stage: &Stage) {
if (stage.map.width - 1) * 16 < state.canvas_size.0 as usize {
self.x = -(((state.canvas_size.0 as isize - ((stage.map.width - 1) * 16) as isize) * 0x200) / 2);
} else {
self.x += (player.target_x - (state.canvas_size.0 as isize * 0x200 / 2) - self.x) / self.wait;
if self.x < 0 {
self.x = 0;
}
let max_x = (((stage.map.width as isize - 1) * 16) - state.canvas_size.0 as isize) * 0x200;
if self.x > max_x {
self.x = max_x;
}
}
if (stage.map.height - 1) * 16 < state.canvas_size.1 as usize {
self.y = -(((state.canvas_size.1 as isize - ((stage.map.height - 1) * 16) as isize) * 0x200) / 2);
} else {
self.y += (player.target_y - (state.canvas_size.1 as isize * 0x200 / 2) - self.y) / self.wait;
if self.y < 0 {
self.y = 0;
}
let max_y = (((stage.map.height as isize - 1) * 16) - state.canvas_size.1 as isize) * 0x200;
if self.y > max_y {
self.y = max_y;
}
}
// todo quake
}
}

258
src/main.rs Normal file
View File

@ -0,0 +1,258 @@
extern crate strum;
#[macro_use]
extern crate strum_macros;
use std::{env, mem};
use std::path;
use ggez::{Context, ContextBuilder, event, GameResult};
use ggez::conf::{WindowMode, WindowSetup};
use ggez::event::{KeyCode, KeyMods};
use ggez::event::winit_event::{ElementState, Event, KeyboardInput, WindowEvent};
use ggez::graphics;
use ggez::graphics::DrawParam;
use ggez::input::keyboard;
use ggez::mint::ColumnMatrix4;
use ggez::nalgebra::Vector2;
use log::*;
use pretty_env_logger::env_logger::Env;
use crate::engine_constants::EngineConstants;
use crate::scene::loading_scene::LoadingScene;
use crate::scene::Scene;
use crate::stage::StageData;
use crate::texture_set::TextureSet;
use crate::sound::SoundManager;
mod common;
mod engine_constants;
mod entity;
mod enemy;
mod frame;
mod map;
mod player;
mod player_hit;
mod scene;
mod stage;
mod sound;
mod text_script;
mod texture_set;
bitfield! {
pub struct KeyState(u16);
impl Debug;
left, set_left: 0;
right, set_right: 1;
up, set_up: 2;
down, set_down: 3;
map, set_map: 4;
jump, set_jump: 5;
fire, set_fire: 6;
weapon_next, set_weapon_next: 7;
weapon_prev, set_weapon_prev: 8;
}
bitfield! {
pub struct GameFlags(u32);
impl Debug;
pub flag_x01, set_flag_x01: 0;
pub control_enabled, set_control_enabled: 1;
pub flag_x04, set_flag_x04: 2;
}
struct Game {
scene: Option<Box<dyn Scene>>,
state: SharedGameState,
scaled_matrix: ColumnMatrix4<f32>,
def_matrix: ColumnMatrix4<f32>,
}
pub struct SharedGameState {
pub flags: GameFlags,
pub key_state: KeyState,
pub key_trigger: KeyState,
pub texture_set: TextureSet,
pub base_path: String,
pub stages: Vec<StageData>,
pub sound_manager: SoundManager,
pub constants: EngineConstants,
pub scale: f32,
pub canvas_size: (f32, f32),
pub screen_size: (f32, f32),
pub next_scene: Option<Box<dyn Scene>>,
key_old: u16,
}
impl SharedGameState {
pub fn update_key_trigger(&mut self) {
let mut trigger = self.key_state.0 ^ self.key_old;
trigger = self.key_state.0 & trigger;
self.key_old = self.key_state.0;
self.key_trigger = KeyState(trigger);
}
}
impl Game {
fn new(ctx: &mut Context) -> GameResult<Game> {
let scale = 2.0;
let screen_size = graphics::drawable_size(ctx);
let canvas_size = (screen_size.0 / scale, screen_size.1 / scale);
let s = Game {
scene: None,
scaled_matrix: DrawParam::new()
.scale(Vector2::new(scale, scale))
.to_matrix(),
def_matrix: DrawParam::new().to_matrix(),
state: SharedGameState {
flags: GameFlags(0),
key_state: KeyState(0),
key_trigger: KeyState(0),
texture_set: TextureSet::new("/"),
base_path: "/".to_string(),
stages: Vec::new(),
sound_manager: SoundManager::new(),
constants: EngineConstants::defaults(),
scale,
screen_size,
canvas_size,
next_scene: None,
key_old: 0,
},
};
Ok(s)
}
fn update(&mut self, ctx: &mut Context) -> GameResult {
if self.scene.is_some() {
self.scene.as_mut().unwrap().tick(&mut self.state, ctx)?;
}
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into());
graphics::set_transform(ctx, self.scaled_matrix);
graphics::apply_transformations(ctx)?;
if self.scene.is_some() {
self.scene.as_ref().unwrap().draw(&mut self.state, ctx)?;
graphics::set_transform(ctx, self.def_matrix);
graphics::apply_transformations(ctx)?;
self.scene.as_ref().unwrap().overlay_draw(&mut self.state, ctx)?;
}
graphics::present(ctx)?;
Ok(())
}
fn key_down_event(&mut self, _ctx: &mut Context, key_code: KeyCode, _key_mod: KeyMods, repeat: bool) {
if repeat { return; }
// todo: proper keymaps?
let state = &mut self.state;
match key_code {
KeyCode::Left => { state.key_state.set_left(true) }
KeyCode::Right => { state.key_state.set_right(true) }
KeyCode::Up => { state.key_state.set_up(true) }
KeyCode::Down => { state.key_state.set_down(true) }
KeyCode::Z => { state.key_state.set_jump(true) }
KeyCode::X => { state.key_state.set_fire(true) }
KeyCode::A => { state.key_state.set_weapon_prev(true) }
KeyCode::S => { state.key_state.set_weapon_next(true) }
_ => {}
}
}
fn key_up_event(&mut self, _ctx: &mut Context, key_code: KeyCode, _key_mod: KeyMods) {
let state = &mut self.state;
match key_code {
KeyCode::Left => { state.key_state.set_left(false) }
KeyCode::Right => { state.key_state.set_right(false) }
KeyCode::Up => { state.key_state.set_up(false) }
KeyCode::Down => { state.key_state.set_down(false) }
KeyCode::Z => { state.key_state.set_jump(false) }
KeyCode::X => { state.key_state.set_fire(false) }
KeyCode::A => { state.key_state.set_weapon_prev(false) }
KeyCode::S => { state.key_state.set_weapon_next(false) }
_ => {}
}
}
}
pub fn main() -> GameResult {
pretty_env_logger::env_logger::init_from_env(Env::default().default_filter_or("info"));
let resource_dir = 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")
};
info!("Resource directory: {:?}", resource_dir);
info!("Initializing engine...");
let cb = ContextBuilder::new("doukutsu-rs", "Alula")
.window_setup(WindowSetup::default().title("Cave Story (doukutsu-rs)"))
.window_mode(WindowMode::default().dimensions(854.0, 480.0))
.add_resource_path(resource_dir);
let (ctx, event_loop) = &mut cb.build()?;
let state = &mut Game::new(ctx)?;
state.state.next_scene = Some(Box::new(LoadingScene::new()));
while ctx.continuing {
ctx.timer_context.tick();
event_loop.poll_events(|event| {
ctx.process_event(&event);
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => event::quit(ctx),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(keycode),
modifiers,
..
},
..
} => {
let repeat = keyboard::is_key_repeated(ctx);
state.key_down_event(ctx, keycode, modifiers.into(), repeat);
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(keycode),
modifiers,
..
},
..
} => {
state.key_up_event(ctx, keycode, modifiers.into());
}
_ => {}
},
_ => {}
}
});
state.update(ctx)?;
state.draw(ctx)?;
if state.state.next_scene.is_some() {
mem::swap(&mut state.scene, &mut state.state.next_scene);
state.state.next_scene = None;
state.scene.as_mut().unwrap().init(&mut state.state, ctx)?;
}
}
Ok(())
}

46
src/map.rs Normal file
View File

@ -0,0 +1,46 @@
use std::io;
use std::io::{Error, ErrorKind};
use byteorder::{LE, ReadBytesExt};
pub struct Map {
pub width: usize,
pub height: usize,
pub tiles: Vec<u8>,
pub attrib: [u8; 0x100],
}
impl Map {
pub fn load_from<R: io::Read>(mut map_data: R, mut attrib_data: R) -> io::Result<Map> {
let mut magic = [0; 3];
map_data.read_exact(&mut magic)?;
if &magic != b"PXM" {
return Err(Error::new(ErrorKind::InvalidData, "Invalid magic"));
}
map_data.read_i8()?; // reserved, alignment?
let width = map_data.read_u16::<LE>()? as usize;
let height = map_data.read_u16::<LE>()? as usize;
let mut tiles = vec![0u8; width * height];
let mut attrib = [0u8; 0x100];
map_data.read_exact(&mut tiles)?;
attrib_data.read_exact(&mut attrib);
let map = Map {
width,
height,
tiles,
attrib,
};
Ok(map)
}
pub fn get_attribute(&self, x: usize, y: usize) -> u8 {
self.attrib[*self.tiles.get(self.width * y + x).unwrap_or_else(|| &0u8) as usize]
}
}

598
src/player.rs Normal file
View File

@ -0,0 +1,598 @@
use ggez::{Context, GameResult};
use num_traits::clamp;
use crate::bitfield;
use crate::common::{Direction, Rect};
use crate::engine_constants::PhysicsConsts;
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::SharedGameState;
use crate::str;
bitfield! {
pub struct Flags(u32);
impl Debug;
pub flag_x01, set_flag_x01: 0;
pub flag_x02, set_flag_x02: 1;
pub flag_x04, set_flag_x04: 2;
pub flag_x08, set_flag_x08: 3;
pub flag_x10, set_flag_x10: 4;
pub flag_x20, set_flag_x20: 5;
pub flag_x40, set_flag_x40: 6;
pub flag_x80, set_flag_x80: 7;
pub underwater, set_underwater: 8; // 0x100
pub flag_x200, set_flag_x200: 9;
pub flag_x400, set_flag_x400: 10;
pub flag_x800, set_flag_x800: 11;
pub force_left, set_force_left: 12; // 0x1000
pub force_up, set_force_up: 13; // 0x2000
pub force_right, set_force_right: 14; // 0x4000
pub force_down, set_force_down: 15; // 0x8000
pub flag_x10000, set_flag_x10000: 16; // 0x10000
pub flag_x20000, set_flag_x20000: 17; // 0x20000
pub flag_x40000, set_flag_x40000: 18; // 0x40000
pub flag_x80000, set_flag_x80000: 19; // 0x80000
}
bitfield! {
pub struct Equip(u16);
impl Debug;
pub has_booster_0_8, set_booster_0_8: 0;
pub has_map, set_map: 1;
pub has_arms_barrier, set_arms_barrier: 2;
pub has_turbocharge, set_turbocharge: 3;
pub has_air_tank, set_air_tank: 4;
pub has_booster_2_0, set_booster_2_0: 5;
pub has_mimiga_mask, set_mimiga_mask: 6;
pub has_whimsical_star, set_whimsical_star: 7;
pub has_nikumaru, set_nikumaru: 8;
// 7 bits wasted, thx pixel
}
bitfield! {
pub struct Cond(u16);
impl Debug;
pub cond_x01, set_cond_x01: 0;
pub cond_x02, set_cond_x02: 1;
pub cond_x04, set_cond_x04: 2;
pub cond_x08, set_cond_x08: 3;
pub cond_x10, set_cond_x10: 4;
pub cond_x20, set_cond_x20: 5;
pub cond_x40, set_cond_x40: 6;
pub visible, set_visible: 7;
}
pub struct Player {
pub x: isize,
pub y: isize,
pub xm: isize,
pub ym: isize,
pub target_x: isize,
pub target_y: isize,
pub cond: Cond,
pub flags: Flags,
pub equip: Equip,
pub direction: Direction,
pub view: Rect<usize>,
pub hit: Rect<usize>,
pub life: u16,
pub max_life: u16,
pub unit: u8,
pub air_physics: PhysicsConsts,
pub water_physics: PhysicsConsts,
index_x: isize,
index_y: isize,
sprash: bool,
ques: bool,
up: bool,
down: bool,
shock: u8,
bubble: u8,
boost_sw: u8,
boost_cnt: isize,
exp_wait: isize,
exp_count: isize,
anim_num: usize,
anim_wait: isize,
anim_rect: Rect<usize>,
tex_player_name: String,
}
impl Player {
pub fn new(state: &mut SharedGameState, ctx: &mut Context) -> GameResult<Player> {
let constants = &state.constants;
let tex_player_name = str!("MyChar");
state.texture_set.ensure_texture_loaded(ctx, &tex_player_name)?;
Ok(Player {
x: 0,
y: 0,
xm: 0,
ym: 0,
target_x: 0,
target_y: 0,
cond: Cond(constants.my_char.cond),
flags: Flags(constants.my_char.flags),
equip: Equip(constants.my_char.equip),
direction: constants.my_char.direction.clone(),
view: constants.my_char.view,
hit: constants.my_char.hit,
life: constants.my_char.life,
max_life: constants.my_char.max_life,
unit: constants.my_char.unit,
air_physics: constants.my_char.air_physics,
water_physics: constants.my_char.water_physics,
index_x: 0,
index_y: 0,
sprash: false,
ques: false,
up: false,
down: false,
shock: 0,
bubble: 0,
boost_sw: 0,
boost_cnt: 0,
exp_wait: 0,
exp_count: 0,
anim_num: 0,
anim_wait: 0,
anim_rect: constants.my_char.animations_right[0],
tex_player_name,
})
}
fn tick_normal(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<()> {
if self.cond.cond_x02() {
return Ok(());
}
let physics = if self.flags.underwater() { &self.water_physics } else { &self.air_physics };
self.ques = false;
if !state.flags.control_enabled() {
self.boost_sw = 0;
}
// todo: split those into separate procedures and refactor (try to not break the logic!)
// ground movement
if self.flags.flag_x08() || self.flags.flag_x10() || self.flags.flag_x20() {
self.boost_sw = 0;
if self.equip.has_booster_0_8() || self.equip.has_booster_2_0() {
self.boost_cnt = 50;
} else {
self.boost_cnt = 0;
}
if state.flags.control_enabled() {
if state.key_trigger.only_down() && state.key_state.only_down() && !self.cond.cond_x01() && state.flags.flag_x04() {
self.cond.set_cond_x01(true);
self.ques = true;
} else {
if state.key_state.left() && self.xm > -physics.max_dash {
self.xm -= physics.dash_ground;
}
if state.key_state.right() && self.xm < physics.max_dash {
self.xm += physics.dash_ground;
}
if state.key_state.left() {
self.direction = Direction::Left;
}
if state.key_state.right() {
self.direction = Direction::Right;
}
}
}
if !self.cond.cond_x20() {
if self.xm < 0 {
if self.xm > -physics.resist {
self.xm = 0;
} else {
self.xm += physics.resist;
}
} else if self.xm > 0 {
if self.xm < physics.resist {
self.xm = 0;
} else {
self.xm -= physics.resist;
}
}
}
} else { // air movement
if state.flags.control_enabled() {
if (self.equip.has_booster_0_8() || self.equip.has_booster_2_0()) && state.key_trigger.jump() && self.boost_cnt != 0 {
if self.equip.has_booster_0_8() {
self.boost_sw = 1;
if self.ym > 0x100 { // 0.5fix9
self.ym /= 2;
}
}
if self.equip.has_booster_2_0() {
if state.key_state.up() {
self.boost_sw = 2;
self.xm = 0;
self.ym = state.constants.booster.b2_0_up;
} else if state.key_state.left() {
self.boost_sw = 2;
self.xm = 0;
self.ym = state.constants.booster.b2_0_left;
} else if state.key_state.right() {
self.boost_sw = 2;
self.xm = 0;
self.ym = state.constants.booster.b2_0_right;
} else if state.key_state.down() {
self.boost_sw = 2;
self.xm = 0;
self.ym = state.constants.booster.b2_0_down;
} else {
self.boost_sw = 2;
self.xm = 0;
self.ym = state.constants.booster.b2_0_up_nokey;
}
}
}
if state.key_state.left() && self.xm > -physics.max_dash {
self.xm -= physics.dash_air;
}
if state.key_state.right() && self.xm < physics.max_dash {
self.xm += physics.dash_air;
}
if state.key_state.left() {
self.direction = Direction::Left;
}
if state.key_state.right() {
self.direction = Direction::Right;
}
}
if self.equip.has_booster_2_0() && self.boost_sw != 0 && !state.key_state.jump() || self.boost_cnt == 0 {
match self.boost_sw {
1 => { self.xm /= 2 }
2 => { self.ym /= 2 }
_ => {}
}
}
if self.boost_cnt == 0 || !state.key_state.jump() {
self.boost_cnt = 0;
}
}
// jumping
if state.flags.control_enabled() {
self.up = state.key_state.up();
self.down = state.key_state.down() && !self.flags.flag_x08();
if state.key_trigger.jump() && (self.flags.flag_x08() || self.flags.flag_x10() || self.flags.flag_x20()) {
if !self.flags.force_up() {
self.ym = -physics.jump;
// todo: PlaySoundObject(15, SOUND_MODE_PLAY);
}
}
}
// stop interacting when moved
if state.flags.control_enabled() && (state.key_state.left() || state.key_state.right() || state.key_state.up() || state.key_state.jump() || state.key_state.fire()) {
self.cond.set_cond_x01(false);
}
// booster losing fuel
if self.boost_sw != 0 && self.boost_cnt != 0 {
self.boost_cnt -= 1;
}
// wind / current forces
if self.flags.force_left() {
self.xm -= 0x88;
}
if self.flags.force_up() {
self.ym -= 0x80;
}
if self.flags.force_right() {
self.xm += 0x80;
}
if self.flags.force_down() {
self.ym += 0x55;
}
if self.equip.has_booster_2_0() && self.boost_sw != 0 {
match self.boost_sw {
1 => {
if self.flags.flag_x01() || self.flags.flag_x04() {
self.ym = -0x100; // -0.5fix9
}
if self.direction == Direction::Left {
self.xm -= 0x20; // 0.1fix9
}
if self.direction == Direction::Right {
self.xm += 0x20; // 0.1fix9
}
// todo: particles and sound
if state.key_trigger.jump() || self.boost_cnt % 3 == 1 {
if self.direction == Direction::Left {
// SetCaret(self.x + 2 * 0x200, self.y + 2 * 0x200, 7, 2);
}
if self.direction == Direction::Right {
// SetCaret(self.x + 2 * 0x200, self.y + 2 * 0x200, 7, 0);
}
// PlaySoundObject(113, SOUND_MODE_PLAY);
}
}
2 => {
self.ym -= 0x20;
// todo: particles and sound
if state.key_trigger.jump() || self.boost_cnt % 3 == 1 {
// SetCaret(self.x, self.y + 6 * 0x200, 7, 3);
// PlaySoundObject(113, SOUND_MODE_PLAY);
}
}
// todo: particles and sound
3 if state.key_trigger.jump() || self.boost_cnt % 3 == 1 => {
// SetCaret(self.x, self.y + 6 * 0x200, 7, 1);
// PlaySoundObject(113, SOUND_MODE_PLAY);
}
_ => {}
}
} else if self.flags.force_up() {
self.ym += physics.gravity_air;
} else if self.equip.has_booster_0_8() && self.boost_sw != 0 && self.ym > -0x400 {
self.ym -= 0x20;
if self.boost_cnt % 3 == 0 {
// SetCaret(self.x, self.y + self.hit.bottom as isize / 2, 7, 3);
// PlaySoundObject(113, SOUND_MODE_PLAY);
}
// bounce off of ceiling
if self.flags.flag_x02() {
self.ym = 0x200; // 1.0fix9
}
} else if self.ym < 0 && state.flags.control_enabled() && state.key_state.jump() {
self.ym += physics.gravity_air;
} else {
self.ym += physics.gravity_ground;
}
if !state.flags.control_enabled() || !state.key_trigger.jump() {
if self.flags.flag_x10() && self.xm < 0 {
self.ym = -self.xm;
}
if self.flags.flag_x20() && self.xm > 0 {
self.ym = self.xm;
}
if (self.flags.flag_x08() && self.flags.flag_x80000() && self.xm < 0)
|| (self.flags.flag_x08() && self.flags.flag_x10000() && self.xm > 0)
|| (self.flags.flag_x08() && self.flags.flag_x20000() && self.flags.flag_x40000()) {
self.ym = 0x400; // 2.0fix9
}
}
let max_move = if self.flags.underwater() && !(self.flags.force_left() || self.flags.force_up() || self.flags.force_right() || self.flags.force_down()) {
self.water_physics.max_move
} else {
self.air_physics.max_move
};
self.xm = clamp(self.xm, -max_move, max_move);
self.ym = clamp(self.ym, -max_move, max_move);
// todo: water splashing
if !self.flags.underwater() {
self.sprash = false;
}
// spike damage
if self.flags.flag_x400() {
//self.damage(10); // todo: borrow checker yells at me
}
// camera
if self.direction == Direction::Left {
self.index_x -= 0x200; // 1.0fix9
if self.index_x < -0x8000 { // -64.0fix9
self.index_x = -0x8000;
}
} else { // possible bug?
self.index_x += 0x200; // 1.0fix9
if self.index_x > 0x8000 { // -64.0fix9
self.index_x = 0x8000;
}
}
if state.flags.control_enabled() && state.key_state.up() {
self.index_x -= 0x200; // 1.0fix9
if self.index_x < -0x8000 { // -64.0fix9
self.index_x = -0x8000;
}
} else if state.flags.control_enabled() && state.key_state.down() {
self.index_x += 0x200; // 1.0fix9
if self.index_x > 0x8000 { // -64.0fix9
self.index_x = 0x8000;
}
} else {
if self.index_y > 0x200 { // 1.0fix9
self.index_y -= 0x200;
}
if self.index_y < -0x200 { // 1.0fix9
self.index_y += 0x200;
}
}
self.target_x = self.x + self.index_x;
self.target_y = self.y + self.index_y;
if self.xm > physics.resist || self.xm < -physics.resist {
self.x += self.xm;
}
self.y += self.ym;
Ok(())
}
fn tick_stream(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<()> {
Ok(())
}
fn tick_animation(&mut self, state: &SharedGameState) {
if self.cond.cond_x02() {
return;
}
if self.flags.flag_x08() {
if self.cond.cond_x01() {
self.anim_num = 11;
} else if state.flags.control_enabled() && state.key_state.up() && (state.key_state.left() || state.key_state.right()) {
self.cond.set_cond_x04(true);
self.anim_wait += 1;
if self.anim_wait > 4 {
self.anim_wait = 0;
self.anim_num += 1;
if self.anim_num == 7 || self.anim_num == 9 {
// PlaySoundObject(24, SOUND_MODE_PLAY); todo
}
}
if self.anim_num > 9 || self.anim_num < 6 {
self.anim_num = 6;
}
} else if state.flags.control_enabled() && (state.key_state.left() || state.key_state.right()) {
self.cond.set_cond_x04(true);
self.anim_wait += 1;
if self.anim_wait > 4 {
self.anim_wait = 0;
self.anim_num += 1;
if self.anim_num == 2 || self.anim_num == 4 {
// PlaySoundObject(24, SOUND_MODE_PLAY); todo
}
}
if self.anim_num > 4 || self.anim_num < 1 {
self.anim_num = 1;
}
} else if state.flags.control_enabled() && state.key_state.up() {
if self.cond.cond_x04() {
// PlaySoundObject(24, SOUND_MODE_PLAY); todo
}
self.cond.set_cond_x04(false);
self.anim_num = 5;
} else {
if self.cond.cond_x04() {
// PlaySoundObject(24, SOUND_MODE_PLAY); todo
}
self.cond.set_cond_x04(false);
self.anim_num = 0;
}
} else if state.key_state.up() {
self.anim_num = 6;
} else if state.key_state.down() {
self.anim_num = 10;
} else {
self.anim_num = if self.ym > 0 { 1 } else { 3 };
}
match self.direction {
Direction::Left => {
self.anim_rect = state.constants.my_char.animations_left[self.anim_num];
}
Direction::Right => {
self.anim_rect = state.constants.my_char.animations_right[self.anim_num];
}
_ => {}
}
}
pub fn damage(&mut self, hp: isize) {}
}
impl GameEntity for Player {
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult<()> {
if !self.cond.visible() {
return Ok(());
}
if self.exp_wait != 0 {
self.exp_wait -= 1;
}
if self.shock != 0 {
self.shock -= 1;
} else if self.exp_count != 0 {
// SetValueView(&self.x, &self.y, self.exp_count); // todo: damage popup
self.exp_count = 0;
}
match self.unit {
0 => {
if state.flags.flag_x04() && state.flags.control_enabled() {
// AirProcess(); // todo
}
self.tick_normal(state, ctx)?;
}
1 => {
self.tick_stream(state, ctx)?;
}
_ => {}
}
self.cond.set_cond_x20(false);
self.tick_animation(state);
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, frame: &Frame) -> GameResult<()> {
if !self.cond.visible() || self.cond.cond_x02() {
return Ok(());
}
// todo draw weapon
let sb = state.texture_set.tex_map.get_mut(&self.tex_player_name);
if sb.is_none() {
return Ok(());
}
let batch = sb.unwrap();
batch.add_rect(
(((self.x - self.view.left as isize) / 0x200) - (frame.x / 0x200)) as f32,
(((self.y - self.view.top as isize) / 0x200) - (frame.y / 0x200)) as f32,
&self.anim_rect,
);
batch.draw(ctx)?;
Ok(())
}
}

312
src/player_hit.rs Normal file
View File

@ -0,0 +1,312 @@
use num_traits::clamp;
use crate::player::Player;
use crate::stage::Stage;
use crate::SharedGameState;
const OFF_X: &[isize; 4] = &[0, 1, 0, 1];
const OFF_Y: &[isize; 4] = &[0, 0, 1, 1];
impl Player {
fn judge_hit_block(&mut self, state: &SharedGameState, x: isize, y: isize) {
// left wall
if (self.y - self.hit.top as isize) < (y * 0x10 + 4) * 0x200
&& self.y + self.hit.bottom as isize > (y * 0x10 - 4) * 0x200
&& (self.x - self.hit.right as isize) < (x * 0x10 + 8) * 0x200
&& (self.x - self.hit.right as isize) > x * 0x10 * 0x200 {
self.x = ((x * 0x10 + 8) * 0x200) + self.hit.right as isize;
if self.xm < -0x180 {
self.xm = -0x180;
}
if !state.key_state.left() && self.xm < 0 {
self.xm = 0;
}
self.flags.set_flag_x01(true);
}
// right wall
if (self.y - self.hit.top as isize) < (y * 0x10 + 4) * 0x200
&& self.y + self.hit.bottom as isize > (y * 0x10 - 4) * 0x200
&& (self.x + self.hit.right as isize) > (x * 0x10 - 8) * 0x200
&& (self.x + self.hit.right as isize) < x * 0x10 * 0x200 {
self.x = ((x * 0x10 - 8) * 0x200) - self.hit.right as isize;
if self.xm > 0x180 {
self.xm = 0x180;
}
if !state.key_state.right() && self.xm > 0 {
self.xm = 0;
}
self.flags.set_flag_x04(true);
}
// ceiling
if (self.x - self.hit.right as isize) < (x * 0x10 + 5) * 0x200
&& (self.x + self.hit.right as isize) > (x * 0x10 - 5) * 0x200
&& (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200
&& (self.y - self.hit.top as isize) > y * 0x10 * 0x200 {
self.y = ((y * 0x10 + 8) * 0x200) + self.hit.top as isize;
if !self.cond.cond_x02() && self.ym < -0x200 {
// PutLittleStar(); todo
}
if self.ym < 0 {
self.ym = 0;
}
self.flags.set_flag_x02(true);
}
// floor
if ((self.x - self.hit.right as isize) < (x * 0x10 + 5) * 0x200)
&& ((self.x + self.hit.right as isize) > (x * 0x10 - 5) * 0x200)
&& ((self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200)
&& ((self.y + self.hit.bottom as isize) < y * 0x10 * 0x200) {
self.y = ((y * 0x10 - 8) * 0x200) - self.hit.bottom as isize;
if self.ym > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.ym > 0 {
self.ym = 0;
}
self.flags.set_flag_x08(true);
}
}
fn judge_hit_triangle_a(&mut self, state: &SharedGameState, x: isize, y: isize) {
if self.x < (x * 0x10 + 8) * 0x200
&& self.x > (x * 0x10 - 8) * 0x200
&& (self.y - self.hit.top as isize) < (y * 0x10 * 0x200) - (self.x - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) + 0x800 + self.hit.top as isize;
if !self.cond.cond_x02() && self.ym < -0x200 {
// PutLittleStar(); todo
}
if self.ym < 0 {
self.ym = 0;
}
self.flags.set_flag_x02(true);
}
}
fn judge_hit_triangle_b(&mut self, state: &SharedGameState, x: isize, y: isize) {
if self.x < (x * 0x10 + 8) * 0x200
&& self.x > (x * 0x10 - 8) * 0x200
&& (self.y - self.hit.top as isize) < (y * 0x10 * 0x200) - (self.x - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) - 0x800 + self.hit.top as isize;
if !self.cond.cond_x02() && self.ym < -0x200 {
// PutLittleStar(); todo
}
if self.ym < 0 {
self.ym = 0;
}
self.flags.set_flag_x02(true);
}
}
fn judge_hit_triangle_c(&mut self, state: &SharedGameState, x: isize, y: isize) {
if self.x < (x * 0x10 + 8) * 0x200
&& self.x > (x * 0x10 - 8) * 0x200
&& (self.y - self.hit.top as isize) < (y * 0x10 * 0x200) + (self.x - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) - 0x800 + self.hit.top as isize;
if !self.cond.cond_x02() && self.ym < -0x200 {
// PutLittleStar(); todo
}
if self.ym < 0 {
self.ym = 0;
}
self.flags.set_flag_x02(true);
}
}
fn judge_hit_triangle_d(&mut self, state: &SharedGameState, x: isize, y: isize) {
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y - self.hit.top as isize) < (y * 0x10 * 0x200) + (self.x - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y + self.hit.bottom as isize) > (y * 0x10 - 8) * 0x200 {
self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) + 0x800 + self.hit.top as isize;
if !self.cond.cond_x02() && self.ym < -0x200 {
// PutLittleStar(); todo
}
if self.ym < 0 {
self.ym = 0;
}
self.flags.set_flag_x02(true);
}
}
fn judge_hit_triangle_e(&mut self, state: &SharedGameState, x: isize, y: isize) {
self.flags.set_flag_x10000(true);
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y + self.hit.bottom as isize) > (y * 0x10 * 0x200) + (self.x - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200 {
self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) - 0x800 - self.hit.bottom as isize;
if self.ym > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.ym > 0 {
self.ym = 0;
}
self.flags.set_flag_x20(true);
self.flags.set_flag_x08(true);
}
}
fn judge_hit_triangle_f(&mut self, state: &SharedGameState, x: isize, y: isize) {
self.flags.set_flag_x20000(true);
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y + self.hit.bottom as isize) > (y * 0x10 * 0x200) + (self.x - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200 {
self.y = (y * 0x10 * 0x200) + ((self.x - x * 0x10 * 0x200) / 2) + 0x800 - self.hit.bottom as isize;
if self.ym > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.ym > 0 {
self.ym = 0;
}
self.flags.set_flag_x20(true);
self.flags.set_flag_x08(true);
}
}
fn judge_hit_triangle_g(&mut self, state: &SharedGameState, x: isize, y: isize) {
self.flags.set_flag_x40000(true);
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y + self.hit.bottom as isize) > (y * 0x10 * 0x200) - (self.x - x * 0x10 * 0x200) / 2 + 0x800
&& (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200 {
self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) + 0x800 - self.hit.bottom as isize;
if self.ym > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.ym > 0 {
self.ym = 0;
}
self.flags.set_flag_x10(true);
self.flags.set_flag_x08(true);
}
}
fn judge_hit_triangle_h(&mut self, state: &SharedGameState, x: isize, y: isize) {
self.flags.set_flag_x80000(true);
if (self.x < (x * 0x10 + 8) * 0x200)
&& (self.x > (x * 0x10 - 8) * 0x200)
&& (self.y + self.hit.bottom as isize) > (y * 0x10 * 0x200) - (self.x - x * 0x10 * 0x200) / 2 - 0x800
&& (self.y - self.hit.top as isize) < (y * 0x10 + 8) * 0x200 {
self.y = (y * 0x10 * 0x200) - ((self.x - x * 0x10 * 0x200) / 2) - 0x800 - self.hit.bottom as isize;
if self.ym > 0x400 {
// PlaySoundObject(23, SOUND_MODE_PLAY); todo
}
if self.ym > 0 {
self.ym = 0;
}
self.flags.set_flag_x10(true);
self.flags.set_flag_x08(true);
}
}
fn judge_hit_water(&mut self, state: &SharedGameState, x: isize, y: isize) {
if (self.x - self.hit.right as isize) < (x * 0x10 + 5) * 0x200
&& (self.x + self.hit.right as isize) > (x * 0x10 - 5) * 0x200
&& (self.y - self.hit.top as isize) < (y * 0x10 + 5) * 0x200
&& (self.y + self.hit.bottom as isize) > y * 0x10 * 0x200 {
self.flags.set_underwater(true);
}
}
pub fn tick_map_collisions(&mut self, state: &SharedGameState, stage: &Stage) {
let x = clamp(self.x / 0x10 / 0x200, 0, stage.map.width as isize);
let y = clamp(self.y / 0x10 / 0x200, 0, stage.map.height as isize);
for (ox, oy) in OFF_X.iter().zip(OFF_Y) {
let attrib = stage.map.get_attribute((x + *ox) as usize, (y + *oy) as usize);
match attrib {
// Block
0x02 | 0x60 => {
self.judge_hit_water(state, x + *ox, y + *oy);
}
0x05 | 0x41 | 0x43 | 0x46 => {
self.judge_hit_block(state, x + *ox, y + *oy);
}
0x50 | 0x70 => {
self.judge_hit_triangle_a(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x51 | 0x71 => {
self.judge_hit_triangle_b(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x52 | 0x72 => {
self.judge_hit_triangle_c(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x53 | 0x73 => {
self.judge_hit_triangle_d(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x54 | 0x74 => {
self.judge_hit_triangle_e(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x55 | 0x75 => {
self.judge_hit_triangle_f(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x56 | 0x76 => {
self.judge_hit_triangle_g(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x57 | 0x77 => {
self.judge_hit_triangle_h(state, x + *ox, y + *oy);
if attrib & 0x20 != 0 { self.judge_hit_water(state, x + *ox, y + *oy); }
}
0x61 => {
self.judge_hit_water(state, x + *ox, y + *oy);
self.judge_hit_block(state, x + *ox, y + *oy);
}
_ => {}
}
}
}
}

303
src/scene/game_scene.rs Normal file
View File

@ -0,0 +1,303 @@
use ggez::{Context, GameResult};
use ggez::nalgebra::clamp;
use log::info;
use crate::common::Rect;
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::player::Player;
use crate::scene::Scene;
use crate::SharedGameState;
use crate::stage::{BackgroundType, Stage};
use crate::str;
use num_traits::abs;
pub struct GameScene {
pub tick: usize,
pub stage: Stage,
pub frame: Frame,
pub player: Player,
tex_tileset_name: String,
tex_background_name: String,
tex_hud_name: String,
life_bar: usize,
life_bar_count: usize,
}
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
pub enum TileLayer {
All,
Background,
Foreground,
}
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
pub enum Alignment {
Left,
Right,
}
impl GameScene {
pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult<GameScene> {
let stage = Stage::load(ctx, &state.stages[id])?;
info!("Loaded stage: {}", stage.data.name);
info!("Map size: {}x{}", stage.map.width, stage.map.height);
let tex_tileset_name = ["Stage/", &stage.data.tileset.filename()].join("");
let tex_background_name = stage.data.background.filename();
let tex_hud_name = str!("TextBox");
state.texture_set.ensure_texture_loaded(ctx, &tex_tileset_name)?;
state.texture_set.ensure_texture_loaded(ctx, &tex_background_name)?;
state.texture_set.ensure_texture_loaded(ctx, &tex_hud_name)?;
Ok(GameScene {
tick: 0,
stage,
player: Player::new(state, ctx)?,
frame: Frame {
x: 0,
y: 0,
wait: 16,
},
tex_tileset_name,
tex_background_name,
tex_hud_name,
life_bar: 3,
life_bar_count: 0,
})
}
fn draw_number(&self, x: f32, y: f32, val: usize, align: Alignment, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let set = state.texture_set.tex_map.get_mut(&self.tex_hud_name);
if set.is_none() {
return Ok(());
}
let batch = set.unwrap();
let n = val.to_string();
let align_offset = if align == Alignment::Right { n.len() as f32 * 8.0 } else { 0.0 };
for (offset, chr) in n.chars().enumerate() {
let idx = chr as usize - '0' as usize;
batch.add_rect(x - align_offset + offset as f32 * 8.0, y, &Rect::<usize>::new_size(idx * 8, 56, 8, 8));
}
batch.draw(ctx)?;
Ok(())
}
fn draw_hud(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let set = state.texture_set.tex_map.get_mut(&self.tex_hud_name);
if set.is_none() {
return Ok(());
}
let batch = set.unwrap();
// todo: max ammo display
// none
batch.add_rect(16.0 + 48.0, 16.0,
&Rect::<usize>::new_size(80, 48, 16, 8));
batch.add_rect(16.0 + 48.0, 24.0,
&Rect::<usize>::new_size(80, 48, 16, 8));
// per
batch.add_rect(16.0 + 32.0, 24.0,
&Rect::<usize>::new_size(72, 48, 8, 8));
// lv
batch.add_rect(16.0, 32.0,
&Rect::<usize>::new_size(80, 80, 16, 8));
// life box
batch.add_rect(16.0, 40.0,
&Rect::<usize>::new_size(0, 40, 64, 8));
// bar
batch.add_rect(40.0, 40.0,
&Rect::<usize>::new_size(0, 32, ((self.life_bar as usize * 40) / self.player.max_life as usize) - 1, 8));
// life
batch.add_rect(40.0, 40.0,
&Rect::<usize>::new_size(0, 24, ((self.player.life as usize * 40) / self.player.max_life as usize) - 1, 8));
batch.draw(ctx)?;
self.draw_number(40.0, 40.0, self.life_bar as usize, Alignment::Right, state, ctx)?;
Ok(())
}
fn draw_background(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let set = state.texture_set.tex_map.get_mut(&self.tex_background_name);
if set.is_none() {
return Ok(());
}
let batch = set.unwrap();
match self.stage.data.background_type {
BackgroundType::Stationary => {
let count_x = state.canvas_size.0 as usize / batch.width() + 1;
let count_y = state.canvas_size.1 as usize / batch.height() + 1;
for y in 0..count_y {
for x in 0..count_x {
batch.add((x * batch.width()) as f32, (y * batch.height()) as f32);
}
}
}
BackgroundType::MoveDistant => {
let off_x = self.frame.x as usize / 2 % (batch.width() * 0x200);
let off_y = self.frame.y as usize / 2 % (batch.height() * 0x200);
let count_x = state.canvas_size.0 as usize / batch.width() + 2;
let count_y = state.canvas_size.1 as usize / batch.height() + 2;
for y in 0..count_y {
for x in 0..count_x {
batch.add((x * batch.width()) as f32 - (off_x / 0x200) as f32,
(y * batch.height()) as f32 - (off_y / 0x200) as f32);
}
}
}
BackgroundType::MoveNear => {}
BackgroundType::Water => {}
BackgroundType::Black => {}
BackgroundType::Autoscroll => {}
BackgroundType::OutsideWind | BackgroundType::Outside => {
let offset = (self.tick % 640) as isize;
batch.add_rect(((state.canvas_size.0 - 320.0) / 2.0).floor(), 0.0,
&Rect::<usize>::new_size(0, 0, 320, 88));
for x in ((-offset / 2)..(state.canvas_size.0 as isize)).step_by(320) {
batch.add_rect(x as f32, 88.0,
&Rect::<usize>::new_size(0, 88, 320, 35));
}
for x in ((-offset % 320)..(state.canvas_size.0 as isize)).step_by(320) {
batch.add_rect(x as f32, 123.0,
&Rect::<usize>::new_size(0, 123, 320, 23));
}
for x in ((-offset * 2)..(state.canvas_size.0 as isize)).step_by(320) {
batch.add_rect(x as f32, 146.0,
&Rect::<usize>::new_size(0, 146, 320, 30));
}
for x in ((-offset * 4)..(state.canvas_size.0 as isize)).step_by(320) {
batch.add_rect(x as f32, 176.0,
&Rect::<usize>::new_size(0, 176, 320, 64));
}
}
}
batch.draw(ctx)?;
Ok(())
}
fn draw_tiles(&self, state: &mut SharedGameState, ctx: &mut Context, layer: TileLayer) -> GameResult {
let set = state.texture_set.tex_map.get_mut(&self.tex_tileset_name);
if set.is_none() {
return Ok(());
}
let mut rect = Rect::<usize>::new(0, 0, 16, 16);
let batch = set.unwrap();
let tile_start_x = clamp(self.frame.x / 0x200 / 16, 0, self.stage.map.width as isize) as usize;
let tile_start_y = clamp(self.frame.y / 0x200 / 16, 0, self.stage.map.height as isize) as usize;
let tile_end_x = clamp((self.frame.x / 0x200 + 8 + state.canvas_size.0 as isize) / 16 + 1, 0, self.stage.map.width as isize) as usize;
let tile_end_y = clamp((self.frame.y / 0x200 + 8 + state.canvas_size.1 as isize) / 16 + 1, 0, self.stage.map.height as isize) as usize;
for y in tile_start_y..tile_end_y {
for x in tile_start_x..tile_end_x {
let tile = *self.stage.map.tiles
.get((y * self.stage.map.width) + x)
.unwrap();
match layer {
TileLayer::Background => {
if self.stage.map.attrib[tile as usize] >= 0x20 {
continue;
}
}
TileLayer::Foreground => {
let attr = self.stage.map.attrib[tile as usize];
if attr < 0x40 || attr >= 0x80 {
continue;
}
}
_ => {}
}
rect.left = (tile as usize % 16) * 16;
rect.top = (tile as usize / 16) * 16;
rect.right = rect.left + 16;
rect.bottom = rect.top + 16;
batch.add_rect((x as f32 * 16.0 - 8.0) - (self.frame.x / 0x200) as f32, (y as f32 * 16.0 - 8.0) - (self.frame.y / 0x200) as f32, &rect);
}
}
batch.draw(ctx)?;
Ok(())
}
}
impl Scene for GameScene {
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
state.sound_manager.play_song(ctx)?;
self.player.x = 700 * 0x200;
self.player.y = 1000 * 0x200;
//self.player.equip.set_booster_2_0(true);
state.flags.set_flag_x01(true);
state.flags.set_control_enabled(true);
Ok(())
}
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
state.update_key_trigger();
if state.flags.flag_x01() {
self.player.tick(state, ctx)?;
self.player.flags.0 = 0;
self.player.tick_map_collisions(state, &self.stage);
self.frame.update(state, &self.player, &self.stage);
}
if state.flags.control_enabled() {
// update health bar
if self.life_bar < self.player.life as usize {
self.life_bar = self.player.life as usize;
}
if self.life_bar > self.player.life as usize {
self.life_bar_count += 1;
if self.life_bar_count > 30 {
self.life_bar -= 1;
}
} else {
self.life_bar_count = 0;
}
}
self.tick = self.tick.wrapping_add(1);
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
self.draw_background(state, ctx)?;
self.draw_tiles(state, ctx, TileLayer::Background)?;
self.player.draw(state, ctx, &self.frame)?;
self.draw_tiles(state, ctx, TileLayer::Foreground)?;
self.draw_hud(state, ctx)?;
self.draw_number(16.0, 50.0, abs(self.player.x) as usize, Alignment::Left, state, ctx)?;
self.draw_number(16.0, 58.0, abs(self.player.y) as usize, Alignment::Left, state, ctx)?;
Ok(())
}
}

View File

@ -0,0 +1,51 @@
use ggez::{Context, GameResult};
use crate::player::Player;
use crate::stage::StageData;
use crate::scene::game_scene::GameScene;
use crate::scene::Scene;
use crate::SharedGameState;
pub struct LoadingScene {
tick: usize,
}
impl LoadingScene {
pub fn new() -> LoadingScene {
LoadingScene {
tick: 0,
}
}
}
impl Scene for LoadingScene {
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
state.texture_set.ensure_texture_loaded(ctx, "Loading")?;
Ok(())
}
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
// deferred to let the loading image draw
if self.tick == 1 {
let stages = StageData::load_stage_table(ctx, "/")?;
state.stages = stages;
state.next_scene = Some(Box::new(GameScene::new(state, ctx, 53)?));
}
self.tick += 1;
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let loading = state.texture_set.tex_map.get_mut("Loading");
if loading.is_some() {
let img = loading.unwrap();
img.add(((state.canvas_size.0 - img.width() as f32) / 2.0).floor(),
((state.canvas_size.1 - img.height() as f32) / 2.0).floor());
img.draw(ctx)?;
}
Ok(())
}
}

16
src/scene/mod.rs Normal file
View File

@ -0,0 +1,16 @@
use ggez::{Context, GameResult};
use crate::SharedGameState;
pub mod game_scene;
pub mod loading_scene;
pub trait Scene {
fn init(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
fn tick(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
fn draw(&self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
fn overlay_draw(&self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult { Ok(()) }
}

29
src/sound/mod.rs Normal file
View File

@ -0,0 +1,29 @@
use std::io::{Cursor, Read};
use ggez::{Context, GameResult};
pub mod pixtone;
pub struct SoundManager {
intro: Cursor<Vec<u8>>,
sloop: Cursor<Vec<u8>>,
}
impl SoundManager {
pub fn new() -> SoundManager {
SoundManager {
intro: Cursor::new(Vec::new()),
sloop: Cursor::new(Vec::new()),
}
}
pub fn play_song(&mut self, ctx: &mut Context) -> GameResult {
self.intro.get_mut().clear();
ggez::filesystem::open(ctx, "/Soundtracks/Arranged/oside_intro.ogg")?.read_to_end(self.intro.get_mut())?;
let sink = rodio::play_once(ctx.audio_context.device(), self.intro.clone())?;
sink.detach();
Ok(())
}
}

57
src/sound/pixtone.rs Normal file
View File

@ -0,0 +1,57 @@
use lazy_static::lazy_static;
lazy_static! {
static ref WAVEFORMS: [[i8; 0x100]; 6] = {
let mut seed = 0;
let mut sine = [0i8; 0x100];
let mut triangle = [0i8; 0x100];
let mut saw_up = [0i8; 0x100];
let mut saw_down = [0i8; 0x100];
let mut square = [0i8; 0x100];
let mut random = [0i8; 0x100];
for i in 0..255 {
seed = (seed * 214013) + 2531011;
sine[i] = (64.0 * (i as f32 * std::f32::consts::PI).sin()) as i8;
triangle[i] = (if (0x40 + i as isize) & 0x80 != 0 { 0x80 - i as isize } else { i as isize }) as i8;
saw_up[i] = (-0x40 + i as isize / 2) as i8;
saw_down[i] = (0x40 - i as isize / 2) as i8;
square[i] = (0x40 - (i as isize & 0x80)) as i8;
random[i] = ((seed >> 16) / 2) as i8;
}
[sine, triangle, saw_up, saw_down, square, random]
};
}
pub struct PixToneData {
}
pub struct PixTone {}
impl PixTone {
}
fn dupa() -> [[i8; 0x100]; 6] {
let mut seed = 0;
let mut sine = [0i8; 0x100];
let mut triangle = [0i8; 0x100];
let mut saw_up = [0i8; 0x100];
let mut saw_down = [0i8; 0x100];
let mut square = [0i8; 0x100];
let mut random = [0i8; 0x100];
for i in 0..255 {
seed = (seed * 214013) + 2531011;
sine[i] = (64.0 * (i as f32 * std::f32::consts::PI).sin()) as i8;
triangle[i] = (if (0x40 + i as isize) & 0x80 != 0 { 0x80 - i as isize } else { i as isize }) as i8;
saw_up[i] = (-0x40 + i as isize / 2) as i8;
saw_down[i] = (0x40 - i as isize / 2) as i8;
square[i] = (0x40 - (i as isize & 0x80)) as i8;
random[i] = ((seed >> 16) / 2) as i8;
}
[sine, triangle, saw_up, saw_down, square, random]
}

280
src/stage.rs Normal file
View File

@ -0,0 +1,280 @@
use std::io::{Cursor, Read};
use std::str::from_utf8;
use byteorder::{LE, ReadBytesExt};
use ggez::{Context, filesystem, GameResult};
use ggez::GameError::ResourceLoadError;
use log::info;
use strum::AsStaticRef;
use crate::map::Map;
#[derive(Debug, EnumIter, AsStaticStr, PartialEq, Eq, Hash, Copy, Clone)]
pub enum NpcType {
#[strum(serialize = "0")]
Zero,
Almo1,
Almo2,
Ballos,
Bllg,
Cemet,
Cent,
Curly,
Dark,
Dr,
Eggs1,
Eggs2,
Frog,
Guest,
Hell,
Heri,
IronH,
Island,
Kings,
Maze,
Miza,
Moon,
Omg,
Plant,
Press,
Priest,
Ravil,
Red,
Regu,
Sand,
Stream,
Sym,
Toro,
TwinD,
Weed,
X,
}
impl NpcType {
pub fn filename(&self) -> String {
["Npc", self.as_static()].join("")
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Tileset {
name: String,
}
impl Clone for Tileset {
fn clone(&self) -> Self {
Tileset {
name: self.name.clone(),
}
}
}
impl Tileset {
pub fn new(name: &str) -> Tileset {
Tileset {
name: name.to_owned(),
}
}
pub fn filename(&self) -> String {
["Prt", &self.name].join("")
}
pub fn orig_width(&self) -> usize {
// todo: move to json or something?
if self.name == "Labo" {
return 128;
}
256
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Background {
name: String,
}
impl Clone for Background {
fn clone(&self) -> Self {
Background {
name: self.name.clone(),
}
}
}
impl Background {
pub fn new(name: &str) -> Background {
Background {
name: name.to_owned(),
}
}
pub fn filename(&self) -> String {
self.name.to_owned()
}
}
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
pub enum BackgroundType {
Stationary,
MoveDistant,
MoveNear,
Water,
Black,
Autoscroll,
OutsideWind,
Outside,
}
impl BackgroundType {
pub fn new(id: u8) -> BackgroundType {
match id {
0 => { BackgroundType::Stationary }
1 => { BackgroundType::MoveDistant }
2 => { BackgroundType::MoveNear }
3 => { BackgroundType::Water }
4 => { BackgroundType::Black }
5 => { BackgroundType::Autoscroll }
6 => { BackgroundType::OutsideWind }
7 => { BackgroundType::Outside }
_ => { BackgroundType::Stationary }
}
}
}
#[derive(Debug)]
pub struct StageData {
pub name: String,
pub map: String,
pub boss: String,
pub boss_no: usize,
pub tileset: Tileset,
pub background: Background,
pub background_type: BackgroundType,
pub npc: NpcType,
}
impl Clone for StageData {
fn clone(&self) -> Self {
StageData {
name: self.name.clone(),
map: self.map.clone(),
boss: self.boss.clone(),
boss_no: self.boss_no,
tileset: self.tileset.clone(),
background: self.background.clone(),
background_type: self.background_type,
npc: self.npc,
}
}
}
impl StageData {
pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult<Vec<StageData>> {
let stage_tbl_path = [root, "stage.tbl"].join("");
let mrmap_bin_path = [root, "mrmap.bin"].join("");
if filesystem::exists(ctx, &stage_tbl_path) {
let mut stages = Vec::new();
info!("Loading stage table from {}", &stage_tbl_path);
let mut data = Vec::new();
filesystem::open(ctx, stage_tbl_path)?.read_to_end(&mut data)?;
let count = data.len() / 0xe5;
let mut f = Cursor::new(data);
for i in 0..count {}
return Ok(stages);
} else if filesystem::exists(ctx, &mrmap_bin_path) {
let mut stages = Vec::new();
info!("Loading stage table from {}", &mrmap_bin_path);
let mut data = Vec::new();
let mut fh = filesystem::open(ctx, &mrmap_bin_path)?;
let count = fh.read_u32::<LE>()?;
fh.read_to_end(&mut data)?;
if data.len() < count as usize * 0x74 {
return Err(ResourceLoadError("Specified stage table size is bigger than actual number of entries.".to_string()));
}
let mut f = Cursor::new(data);
for _ in 0..count {
let mut map_buf = Box::new(vec![0u8; 0x10]);
let mut boss_buf = Box::new(vec![0u8; 0x10]);
let mut name_buf = Box::new(vec![0u8; 0x22]);
let mut ts_buf = vec![0u8; 0x10];
let mut back_buf = vec![0u8; 0x10];
let mut npc_buf = vec![0u8; 0x10];
f.read_exact(&mut ts_buf)?;
f.read_exact(&mut map_buf)?;
let bg_type = f.read_u8()?;
f.read_exact(&mut back_buf)?;
f.read_exact(&mut npc_buf)?;
f.read_exact(&mut boss_buf)?;
let boss_no = f.read_u8()? as usize;
f.read_exact(&mut name_buf)?;
let tileset = from_utf8(&ts_buf)
.map_err(|_| ResourceLoadError("UTF-8 error in tileset field".to_string()))?
.trim_matches('\0').to_owned();
let map = from_utf8(&map_buf)
.map_err(|_| ResourceLoadError("UTF-8 error in map field".to_string()))?
.trim_matches('\0').to_owned();
let background = from_utf8(&back_buf)
.map_err(|_| ResourceLoadError("UTF-8 error in background field".to_string()))?
.trim_matches('\0').to_owned();
let boss = from_utf8(&boss_buf)
.map_err(|_| ResourceLoadError("UTF-8 error in boss field".to_string()))?
.trim_matches('\0').to_owned();
let name = from_utf8(&name_buf)
.map_err(|_| ResourceLoadError("UTF-8 error in name field".to_string()))?
.trim_matches('\0').to_owned();
let stage = StageData {
name: name.clone(),
map: map.clone(),
boss: boss.clone(),
boss_no,
tileset: Tileset::new(&tileset),
background: Background::new(&background),
background_type: BackgroundType::new(bg_type),
npc: NpcType::Zero,
};
stages.push(stage);
}
return Ok(stages);
}
Err(ResourceLoadError("No stage table found.".to_string()))
}
}
pub struct Stage {
pub map: Map,
pub data: StageData,
}
impl Stage {
pub fn load(ctx: &mut Context, data: &StageData) -> GameResult<Stage> {
let map_file = filesystem::open(ctx, ["/Stage/", &data.map, ".pxm"].join(""))?;
let attrib_file = filesystem::open(ctx, ["/Stage/", &data.tileset.name, ".pxa"].join(""))?;
let map = Map::load_from(map_file, attrib_file)?;
let stage = Stage {
map,
data: data.clone(),
};
Ok(stage)
}
}

12
src/text_script.rs Normal file
View File

@ -0,0 +1,12 @@
use ggez::{Context, GameResult};
struct TextScript {
}
impl TextScript {
pub fn load(ctx: &mut Context, filename: &str) -> GameResult<TextScript> {
let tsc = TextScript {};
Ok(tsc)
}
}

132
src/texture_set.rs Normal file
View File

@ -0,0 +1,132 @@
use std::collections::HashMap;
use ggez::{Context, GameError, GameResult};
use ggez::filesystem;
use ggez::graphics::{Drawable, DrawParam, FilterMode, Image, Rect};
use ggez::graphics::spritebatch::SpriteBatch;
use ggez::nalgebra::{Point2, Vector2};
use log::info;
use crate::common;
use crate::str;
pub struct SizedBatch {
pub batch: SpriteBatch,
width: usize,
height: usize,
real_width: usize,
real_height: usize,
scale_x: f32,
scale_y: f32,
}
impl SizedBatch {
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
pub fn real_dimensions(&self) -> (usize, usize) {
(self.real_width, self.real_height)
}
pub fn to_rect(&self) -> common::Rect<usize> {
common::Rect::<usize>::new(0, 0, self.width, self.height)
}
pub fn clear(&mut self) {
self.batch.clear();
}
pub fn add(&mut self, x: f32, y: f32) {
let param = DrawParam::new()
.dest(Point2::new(x, y))
.scale(Vector2::new(self.scale_x, self.scale_y));
self.batch.add(param);
}
pub fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<usize>) {
let param = DrawParam::new()
.src(Rect::new(rect.left as f32 / self.width as f32,
rect.top as f32 / self.height as f32,
(rect.right - rect.left) as f32 / self.width as f32,
(rect.bottom - rect.top) as f32 / self.height as f32))
.dest(Point2::new(x, y))
.scale(Vector2::new(self.scale_x, self.scale_y));
self.batch.add(param);
}
pub fn draw(&mut self, ctx: &mut Context) -> GameResult {
self.batch.set_filter(FilterMode::Nearest);
self.batch.draw(ctx, DrawParam::new())?;
self.batch.clear();
Ok(())
}
}
pub struct TextureSet {
pub tex_map: HashMap<String, SizedBatch>,
base_path: String,
}
static FILE_TYPES: [&str; 3] = [".png", ".bmp", ".pbm"];
impl TextureSet {
pub fn new(base_path: &str) -> TextureSet {
TextureSet {
tex_map: HashMap::new(),
base_path: str!(base_path),
}
}
pub fn load_texture(&self, ctx: &mut Context, path: &str) -> GameResult<SizedBatch> {
let path = FILE_TYPES
.iter()
.map(|ext| [&self.base_path, path, ext].join(""))
.find(|path| filesystem::exists(ctx, path))
.ok_or_else(|| GameError::ResourceLoadError(format!("Texture {:?} does not exist.", path)))?;
info!("Loading texture: {}", path);
let image = Image::new(ctx, path)?;
let size = image.dimensions();
assert_ne!(size.w, 0.0, "size.w == 0");
assert_ne!(size.h, 0.0, "size.h == 0");
let scale_x = 1.0;
let scale_y = 1.0;
let width = (size.w / scale_x) as usize;
let height = (size.h / scale_y) as usize;
Ok(SizedBatch {
batch: SpriteBatch::new(image),
width,
height,
scale_x,
scale_y,
real_width: size.w as usize,
real_height: size.h as usize,
})
}
pub fn ensure_texture_loaded(&mut self, ctx: &mut Context, name: &str) -> GameResult {
if self.tex_map.contains_key(name) {
return Ok(());
}
let batch = self.load_texture(ctx, name)?;
self.tex_map.insert(str!(name), batch);
Ok(())
}
}