hell
This commit is contained in:
parent
4b147527bc
commit
6656240c8d
|
@ -39,7 +39,7 @@ backend-sdl = ["sdl2", "sdl2-sys"]
|
|||
backend-glutin = ["winit", "glutin", "render-opengl"]
|
||||
render-opengl = []
|
||||
scripting-lua = ["lua-ffi"]
|
||||
netplay = ["laminar", "bincode"]
|
||||
netplay = ["laminar"]
|
||||
editor = []
|
||||
hooks = ["libc"]
|
||||
exe = []
|
||||
|
@ -52,7 +52,7 @@ android = []
|
|||
#sdl2 = { path = "./3rdparty/rust-sdl2", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||
#sdl2-sys = { path = "./3rdparty/rust-sdl2/sdl2-sys", optional = true, features = ["bundled", "static-link"] }
|
||||
bitvec = "0.20"
|
||||
bincode = { version = "2.0.0-rc.1", optional = true }
|
||||
bincode = { version = "2.0.0-rc.1" }
|
||||
byteorder = "1.4"
|
||||
case_insensitive_hashmap = "1.0.0"
|
||||
chrono = "0.4"
|
||||
|
@ -60,6 +60,7 @@ crossbeam-channel = "0.5"
|
|||
cpal = "0.13"
|
||||
directories = "3"
|
||||
downcast = "0.11"
|
||||
ed25519-dalek = "1.0"
|
||||
funty = "=1.1.0" # https://github.com/bitvecto-rs/bitvec/issues/105
|
||||
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] }
|
||||
imgui = "0.8.0"
|
||||
|
@ -75,6 +76,7 @@ num-derive = "0.3.2"
|
|||
num-traits = "0.2.12"
|
||||
paste = "1.0.0"
|
||||
pelite = "0.9.1"
|
||||
rand = "0.8"
|
||||
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
|
||||
sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "95bcf63768abf422527f86da41da910649b9fcc9", optional = true, features = ["bundled", "static-link"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
@ -146,7 +146,7 @@ impl BMFontRenderer {
|
|||
iter.clone(),
|
||||
x + 1.0,
|
||||
y + 1.0,
|
||||
(0, 0, 0, 150),
|
||||
(20, 20, 20, 150),
|
||||
constants,
|
||||
texture_set,
|
||||
rect_map,
|
||||
|
@ -182,7 +182,7 @@ impl BMFontRenderer {
|
|||
x + scale,
|
||||
y + scale,
|
||||
scale,
|
||||
(0, 0, 0, 150),
|
||||
(20, 20, 20, 150),
|
||||
constants,
|
||||
texture_set,
|
||||
ctx,
|
||||
|
@ -208,7 +208,7 @@ impl BMFontRenderer {
|
|||
x + scale,
|
||||
y + scale,
|
||||
scale,
|
||||
(0, 0, 0, 150),
|
||||
(20, 20, 20, 150),
|
||||
constants,
|
||||
texture_set,
|
||||
rect_map,
|
||||
|
@ -259,12 +259,70 @@ impl BMFontRenderer {
|
|||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
let mut sprite_rects: Vec<(f32, f32, &Rect<u16>)> = Vec::new();
|
||||
let mut curr_color = color;
|
||||
let mut color_flag = false;
|
||||
|
||||
static COLOR_MAP: [(u8, u8, u8, u8); 16] = [
|
||||
(20, 20, 20, 255),
|
||||
(20, 20, 255, 255),
|
||||
(20, 255, 20, 255),
|
||||
(20, 255, 255, 255),
|
||||
(255, 20, 20, 255),
|
||||
(255, 20, 255, 255),
|
||||
(255, 255, 20, 255),
|
||||
(255, 255, 255, 255),
|
||||
(160, 160, 160, 255),
|
||||
(160, 160, 255, 255),
|
||||
(160, 255, 160, 255),
|
||||
(160, 255, 255, 255),
|
||||
(255, 160, 160, 255),
|
||||
(255, 160, 255, 255),
|
||||
(255, 255, 160, 255),
|
||||
(255, 255, 255, 255),
|
||||
];
|
||||
|
||||
#[inline]
|
||||
fn mult_color(c1: (u8, u8, u8, u8), c2: (u8, u8, u8, u8)) -> (u8, u8, u8, u8) {
|
||||
let r = c1.0 as i32 * c2.0 as i32 / 255;
|
||||
let g = c1.1 as i32 * c2.1 as i32 / 255;
|
||||
let b = c1.2 as i32 * c2.2 as i32 / 255;
|
||||
let a = c1.3 as i32 * c2.3 as i32 / 255;
|
||||
|
||||
(r as u8, g as u8, b as u8, a as u8)
|
||||
}
|
||||
|
||||
if self.pages.len() == 1 {
|
||||
let batch = texture_set.get_or_load_batch(ctx, constants, self.pages.get(0).unwrap())?;
|
||||
let mut offset_x = x;
|
||||
|
||||
for chr in iter {
|
||||
if color_flag {
|
||||
unsafe {
|
||||
match chr {
|
||||
'0'..='9' => {
|
||||
curr_color = mult_color(color, *COLOR_MAP.get_unchecked(chr as usize - '0' as usize))
|
||||
}
|
||||
'a'..='f' => {
|
||||
curr_color =
|
||||
mult_color(color, *COLOR_MAP.get_unchecked(chr as usize - 'a' as usize + 10))
|
||||
}
|
||||
'A'..='F' => {
|
||||
curr_color =
|
||||
mult_color(color, *COLOR_MAP.get_unchecked(chr as usize - 'A' as usize + 10))
|
||||
}
|
||||
_ => curr_color = color,
|
||||
}
|
||||
}
|
||||
|
||||
color_flag = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if chr == '§' {
|
||||
color_flag = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(glyph) = self.font.chars.get(&chr) {
|
||||
if let Some(rect) = rect_map.get(&chr) {
|
||||
sprite_rects.push((
|
||||
|
@ -277,7 +335,7 @@ impl BMFontRenderer {
|
|||
batch.add_rect_scaled_tinted(
|
||||
offset_x + (glyph.xoffset as f32 * constants.font_scale),
|
||||
y + (glyph.yoffset as f32 * constants.font_scale),
|
||||
color,
|
||||
curr_color,
|
||||
constants.font_scale * scale,
|
||||
constants.font_scale * scale,
|
||||
&Rect::new_size(glyph.x as u16, glyph.y as u16, glyph.width as u16, glyph.height as u16),
|
||||
|
@ -307,10 +365,39 @@ impl BMFontRenderer {
|
|||
continue;
|
||||
};
|
||||
|
||||
curr_color = color;
|
||||
|
||||
let batch = texture_set.get_or_load_batch(ctx, constants, page_tex)?;
|
||||
let mut offset_x = x;
|
||||
|
||||
for (chr, glyph) in chars.iter() {
|
||||
if color_flag {
|
||||
unsafe {
|
||||
match *chr {
|
||||
'0'..='9' => {
|
||||
curr_color =
|
||||
mult_color(color, *COLOR_MAP.get_unchecked(*chr as usize - '0' as usize))
|
||||
}
|
||||
'a'..='f' => {
|
||||
curr_color =
|
||||
mult_color(color, *COLOR_MAP.get_unchecked(*chr as usize - 'a' as usize + 10))
|
||||
}
|
||||
'A'..='F' => {
|
||||
curr_color =
|
||||
mult_color(color, *COLOR_MAP.get_unchecked(*chr as usize - 'A' as usize + 10))
|
||||
}
|
||||
_ => curr_color = color,
|
||||
}
|
||||
}
|
||||
color_flag = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if *chr == '§' {
|
||||
color_flag = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(rect) = rect_map.get(&chr) {
|
||||
sprite_rects.push((
|
||||
offset_x,
|
||||
|
@ -323,7 +410,7 @@ impl BMFontRenderer {
|
|||
batch.add_rect_scaled_tinted(
|
||||
offset_x + (glyph.xoffset as f32 * constants.font_scale),
|
||||
y + (glyph.yoffset as f32 * constants.font_scale),
|
||||
color,
|
||||
curr_color,
|
||||
constants.font_scale * scale,
|
||||
constants.font_scale * scale,
|
||||
&Rect::new_size(
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::fmt;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use bincode::de::Decoder;
|
||||
use bincode::enc::Encoder;
|
||||
use bincode::error::{DecodeError, EncodeError};
|
||||
use lazy_static::lazy_static;
|
||||
use num_traits::{abs, Num};
|
||||
use serde::de::{SeqAccess, Visitor};
|
||||
|
@ -22,7 +25,7 @@ lazy_static! {
|
|||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, bincode::Encode, bincode::Decode)]
|
||||
#[repr(C)]
|
||||
pub struct Flag(u32);
|
||||
impl Debug;
|
||||
|
@ -69,7 +72,7 @@ impl Flag {
|
|||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, bincode::Encode, bincode::Decode)]
|
||||
#[repr(C)]
|
||||
pub struct Equipment(u16);
|
||||
impl Debug;
|
||||
|
@ -95,7 +98,7 @@ bitfield! {
|
|||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, bincode::Encode, bincode::Decode)]
|
||||
#[repr(C)]
|
||||
pub struct Condition(u16);
|
||||
impl Debug;
|
||||
|
@ -115,7 +118,7 @@ bitfield! {
|
|||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, bincode::Encode, bincode::Decode)]
|
||||
#[repr(C)]
|
||||
pub struct ControlFlags(u16);
|
||||
impl Debug;
|
||||
|
@ -133,7 +136,7 @@ bitfield! {
|
|||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, bincode::Encode, bincode::Decode)]
|
||||
#[repr(C)]
|
||||
pub struct BulletFlag(u8);
|
||||
impl Debug;
|
||||
|
@ -152,7 +155,7 @@ bitfield! {
|
|||
pub flag_x80, set_flag_x80: 7; // 0x80, nowhere in code?
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, bincode::Encode, bincode::Decode)]
|
||||
#[repr(u8)]
|
||||
pub enum FadeDirection {
|
||||
Left = 0,
|
||||
|
@ -185,7 +188,7 @@ impl FadeDirection {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone, bincode::Encode, bincode::Decode)]
|
||||
#[repr(u8)]
|
||||
pub enum FadeState {
|
||||
Visible,
|
||||
|
@ -194,7 +197,7 @@ pub enum FadeState {
|
|||
FadeOut(i8, FadeDirection),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, bincode::Decode, bincode::Encode)]
|
||||
#[repr(u8)]
|
||||
pub enum Direction {
|
||||
Left = 0,
|
||||
|
@ -325,6 +328,28 @@ impl<T: Num + PartialOrd + Copy + Serialize> Default for Rect<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Num + PartialOrd + Copy + bincode::Encode> bincode::Encode for Rect<T> {
|
||||
fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
|
||||
bincode::Encode::encode(&self.left, encoder)?;
|
||||
bincode::Encode::encode(&self.top, encoder)?;
|
||||
bincode::Encode::encode(&self.right, encoder)?;
|
||||
bincode::Encode::encode(&self.bottom, encoder)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Num + PartialOrd + Copy + bincode::Decode> bincode::Decode for Rect<T> {
|
||||
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
|
||||
let left: T = bincode::Decode::decode(decoder)?;
|
||||
let top: T = bincode::Decode::decode(decoder)?;
|
||||
let right: T = bincode::Decode::decode(decoder)?;
|
||||
let bottom: T = bincode::Decode::decode(decoder)?;
|
||||
|
||||
Ok(Rect { left, top, right, bottom })
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! rect_deserialize {
|
||||
($num_type: ident) => {
|
||||
impl<'de> Deserialize<'de> for Rect<$num_type> {
|
||||
|
@ -401,7 +426,7 @@ pub fn get_timestamp() -> u64 {
|
|||
/// 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)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, bincode::Decode, bincode::Encode)]
|
||||
pub struct Color {
|
||||
/// Red component
|
||||
pub r: f32,
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
use std::collections::LinkedList;
|
||||
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
struct MessageData {
|
||||
content: String,
|
||||
fade: u8,
|
||||
}
|
||||
|
||||
pub struct Chat {
|
||||
messages: LinkedList<MessageData>,
|
||||
max_messages: usize,
|
||||
}
|
||||
|
||||
impl Chat {
|
||||
pub fn new() -> Chat {
|
||||
Chat { messages: LinkedList::new(), max_messages: 50 }
|
||||
}
|
||||
|
||||
pub fn push_message(&mut self, content: String) {
|
||||
self.messages.push_front(MessageData { content, fade: 150 });
|
||||
|
||||
while self.messages.len() > self.max_messages {
|
||||
self.messages.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameEntity<()> for Chat {
|
||||
fn tick(&mut self, state: &mut SharedGameState, custom: ()) -> GameResult {
|
||||
for message in self.messages.iter_mut() {
|
||||
if message.fade > 0 {
|
||||
message.fade -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context, _frame: &Frame) -> GameResult {
|
||||
let mut ctr = 8;
|
||||
let font_height = state.font.line_height(&state.constants);
|
||||
|
||||
for message in self.messages.iter() {
|
||||
if message.fade > 0 {
|
||||
let fade = message.fade.min(50);
|
||||
|
||||
state.font.draw_colored_text_with_shadow_scaled(
|
||||
message.content.chars(),
|
||||
2.0,
|
||||
2.0 + ctr as f32 * font_height,
|
||||
1.0,
|
||||
(255, 255, 255, 5 + fade * 5),
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
|
||||
if ctr > 0 {
|
||||
ctr -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
pub mod background;
|
||||
pub mod boss_life_bar;
|
||||
pub mod chat;
|
||||
pub mod credits;
|
||||
pub mod draw_common;
|
||||
pub mod fade;
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::framework::context::Context;
|
|||
use crate::framework::error::GameResult;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, bincode::Encode, bincode::Decode)]
|
||||
pub struct NumberPopup {
|
||||
pub value: i16,
|
||||
pub x: i32,
|
||||
|
|
|
@ -9,7 +9,8 @@ use crate::framework::error::GameResult;
|
|||
use crate::framework::filesystem;
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::framework::vfs::OpenOptions;
|
||||
use crate::input::replay_player_controller::{KeyState, ReplayController};
|
||||
use crate::input::player_controller::KeyState;
|
||||
use crate::input::replay_player_controller::ReplayController;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::{ReplayState, SharedGameState};
|
||||
|
||||
|
@ -105,18 +106,18 @@ impl GameEntity<(&mut Context, &mut Player)> for Replay {
|
|||
ReplayState::Recording => {
|
||||
// This mimics the KeyState bitfield
|
||||
let inputs = player.controller.move_left() as u16
|
||||
+ ((player.controller.move_right() as u16) << 1)
|
||||
+ ((player.controller.move_up() as u16) << 2)
|
||||
+ ((player.controller.move_down() as u16) << 3)
|
||||
+ ((player.controller.trigger_map() as u16) << 4)
|
||||
+ ((player.controller.trigger_inventory() as u16) << 5)
|
||||
+ (((player.controller.jump() || player.controller.trigger_menu_ok()) as u16) << 6)
|
||||
+ (((player.controller.shoot() || player.controller.trigger_menu_back()) as u16) << 7)
|
||||
+ ((player.controller.next_weapon() as u16) << 8)
|
||||
+ ((player.controller.prev_weapon() as u16) << 9)
|
||||
+ ((player.controller.trigger_menu_ok() as u16) << 11)
|
||||
+ ((player.controller.skip() as u16) << 12)
|
||||
+ ((player.controller.strafe() as u16) << 13);
|
||||
| ((player.controller.move_right() as u16) << 1)
|
||||
| ((player.controller.move_up() as u16) << 2)
|
||||
| ((player.controller.move_down() as u16) << 3)
|
||||
| ((player.controller.trigger_map() as u16) << 4)
|
||||
| ((player.controller.trigger_inventory() as u16) << 5)
|
||||
| (((player.controller.jump() || player.controller.trigger_menu_ok()) as u16) << 6)
|
||||
| (((player.controller.shoot() || player.controller.trigger_menu_back()) as u16) << 7)
|
||||
| ((player.controller.next_weapon() as u16) << 8)
|
||||
| ((player.controller.prev_weapon() as u16) << 9)
|
||||
| ((player.controller.trigger_menu_ok() as u16) << 11)
|
||||
| ((player.controller.skip() as u16) << 12)
|
||||
| ((player.controller.strafe() as u16) << 13);
|
||||
|
||||
self.keylist.push(inputs);
|
||||
}
|
||||
|
|
|
@ -149,7 +149,6 @@ impl<T> From<SendError<T>> for GameError {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
impl From<bincode::error::DecodeError> for GameError {
|
||||
fn from(s: bincode::error::DecodeError) -> Self {
|
||||
let errstr = format!("Decode error: {}", s);
|
||||
|
@ -157,7 +156,6 @@ impl From<bincode::error::DecodeError> for GameError {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
impl From<bincode::error::EncodeError> for GameError {
|
||||
fn from(s: bincode::error::EncodeError) -> Self {
|
||||
let errstr = format!("Encode error: {}", s);
|
||||
|
|
|
@ -160,10 +160,29 @@ impl PlayerController for CombinedPlayerController {
|
|||
}
|
||||
|
||||
fn move_analog_x(&self) -> f64 {
|
||||
self.controllers.iter().fold(0.0, |acc, cont| acc + cont.move_analog_x()) / self.controllers.len() as f64
|
||||
self.controllers.iter().fold(0.0, |acc, cont| acc + cont.move_analog_x()).clamp(-1.0, 1.0)
|
||||
}
|
||||
|
||||
fn move_analog_y(&self) -> f64 {
|
||||
self.controllers.iter().fold(0.0, |acc, cont| acc + cont.move_analog_y()) / self.controllers.len() as f64
|
||||
self.controllers.iter().fold(0.0, |acc, cont| acc + cont.move_analog_y()).clamp(-1.0, 1.0)
|
||||
}
|
||||
|
||||
fn dump_state(&self) -> (u16, u16, u16) {
|
||||
let mut state = (0, 0, 0);
|
||||
|
||||
for c in self.controllers.iter() {
|
||||
let s = c.dump_state();
|
||||
state.0 |= s.0;
|
||||
state.1 |= s.1;
|
||||
state.2 |= s.2;
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: (u16, u16, u16)) {
|
||||
for c in self.controllers.iter_mut() {
|
||||
c.set_state(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,162 +1,190 @@
|
|||
use crate::bitfield;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::input::player_controller::PlayerController;
|
||||
use crate::input::player_controller::{KeyState, PlayerController};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
/// A no-op implementation of player controller.
|
||||
#[derive(Clone)]
|
||||
pub struct DummyPlayerController(bool);
|
||||
pub struct DummyPlayerController {
|
||||
state: KeyState,
|
||||
old_state: KeyState,
|
||||
trigger: KeyState,
|
||||
}
|
||||
|
||||
impl DummyPlayerController {
|
||||
pub fn new() -> DummyPlayerController {
|
||||
DummyPlayerController(true)
|
||||
DummyPlayerController { state: KeyState(0), old_state: KeyState(0), trigger: KeyState(0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerController for DummyPlayerController {
|
||||
fn is_enabled(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool) {
|
||||
self.0 = enabled;
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_trigger(&mut self) {}
|
||||
fn update_trigger(&mut self) {
|
||||
let mut trigger = self.state.0 ^ self.old_state.0;
|
||||
trigger &= self.state.0;
|
||||
self.old_state = self.state;
|
||||
self.trigger = KeyState(trigger);
|
||||
}
|
||||
|
||||
fn move_up(&self) -> bool {
|
||||
false
|
||||
self.state.up()
|
||||
}
|
||||
|
||||
fn move_left(&self) -> bool {
|
||||
false
|
||||
self.state.left()
|
||||
}
|
||||
|
||||
fn move_down(&self) -> bool {
|
||||
false
|
||||
self.state.down()
|
||||
}
|
||||
|
||||
fn move_right(&self) -> bool {
|
||||
false
|
||||
self.state.right()
|
||||
}
|
||||
|
||||
fn prev_weapon(&self) -> bool {
|
||||
false
|
||||
self.state.prev_weapon()
|
||||
}
|
||||
|
||||
fn next_weapon(&self) -> bool {
|
||||
false
|
||||
self.state.next_weapon()
|
||||
}
|
||||
|
||||
fn map(&self) -> bool {
|
||||
false
|
||||
self.state.map()
|
||||
}
|
||||
|
||||
fn inventory(&self) -> bool {
|
||||
false
|
||||
self.state.inventory()
|
||||
}
|
||||
|
||||
fn jump(&self) -> bool {
|
||||
false
|
||||
self.state.jump()
|
||||
}
|
||||
|
||||
fn shoot(&self) -> bool {
|
||||
false
|
||||
self.state.shoot()
|
||||
}
|
||||
|
||||
fn skip(&self) -> bool {
|
||||
false
|
||||
self.state.skip()
|
||||
}
|
||||
|
||||
fn strafe(&self) -> bool {
|
||||
false
|
||||
self.state.strafe()
|
||||
}
|
||||
|
||||
fn trigger_up(&self) -> bool {
|
||||
false
|
||||
self.trigger.up()
|
||||
}
|
||||
|
||||
fn trigger_left(&self) -> bool {
|
||||
false
|
||||
self.trigger.left()
|
||||
}
|
||||
|
||||
fn trigger_down(&self) -> bool {
|
||||
false
|
||||
self.trigger.down()
|
||||
}
|
||||
|
||||
fn trigger_right(&self) -> bool {
|
||||
false
|
||||
self.trigger.right()
|
||||
}
|
||||
|
||||
fn trigger_prev_weapon(&self) -> bool {
|
||||
false
|
||||
self.trigger.prev_weapon()
|
||||
}
|
||||
|
||||
fn trigger_next_weapon(&self) -> bool {
|
||||
false
|
||||
self.trigger.next_weapon()
|
||||
}
|
||||
|
||||
fn trigger_map(&self) -> bool {
|
||||
false
|
||||
self.trigger.map()
|
||||
}
|
||||
|
||||
fn trigger_inventory(&self) -> bool {
|
||||
false
|
||||
self.trigger.inventory()
|
||||
}
|
||||
|
||||
fn trigger_jump(&self) -> bool {
|
||||
false
|
||||
self.trigger.jump()
|
||||
}
|
||||
|
||||
fn trigger_shoot(&self) -> bool {
|
||||
false
|
||||
self.trigger.shoot()
|
||||
}
|
||||
|
||||
fn trigger_skip(&self) -> bool {
|
||||
false
|
||||
self.trigger.skip()
|
||||
}
|
||||
|
||||
fn trigger_strafe(&self) -> bool {
|
||||
false
|
||||
self.trigger.strafe()
|
||||
}
|
||||
|
||||
fn trigger_menu_ok(&self) -> bool {
|
||||
false
|
||||
self.trigger.jump() || self.trigger.enter()
|
||||
}
|
||||
|
||||
fn trigger_menu_back(&self) -> bool {
|
||||
false
|
||||
self.trigger.shoot() || self.trigger.escape()
|
||||
}
|
||||
|
||||
fn trigger_menu_pause(&self) -> bool {
|
||||
false
|
||||
self.trigger.escape()
|
||||
}
|
||||
|
||||
fn look_up(&self) -> bool {
|
||||
false
|
||||
self.state.up()
|
||||
}
|
||||
|
||||
fn look_left(&self) -> bool {
|
||||
false
|
||||
self.state.left()
|
||||
}
|
||||
|
||||
fn look_down(&self) -> bool {
|
||||
false
|
||||
self.state.down()
|
||||
}
|
||||
|
||||
fn look_right(&self) -> bool {
|
||||
false
|
||||
self.state.right()
|
||||
}
|
||||
|
||||
fn move_analog_x(&self) -> f64 {
|
||||
0.0
|
||||
if self.state.left() && self.state.right() {
|
||||
0.0
|
||||
} else if self.state.left() {
|
||||
-1.0
|
||||
} else if self.state.right() {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn move_analog_y(&self) -> f64 {
|
||||
0.0
|
||||
if self.state.up() && self.state.down() {
|
||||
0.0
|
||||
} else if self.state.up() {
|
||||
-1.0
|
||||
} else if self.state.down() {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_state(&self) -> (u16, u16, u16) {
|
||||
(self.state.0, self.old_state.0, self.trigger.0)
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: (u16, u16, u16)) {
|
||||
self.state = KeyState(state.0);
|
||||
self.old_state = KeyState(state.1);
|
||||
self.trigger = KeyState(state.2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,31 +2,10 @@ use crate::bitfield;
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::gamepad::{self, Button, PlayerControllerInputType};
|
||||
use crate::input::player_controller::PlayerController;
|
||||
use crate::input::player_controller::{KeyState, PlayerController};
|
||||
use crate::player::TargetPlayer;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KeyState(u16);
|
||||
impl Debug;
|
||||
|
||||
pub left, set_left: 0;
|
||||
pub right, set_right: 1;
|
||||
pub up, set_up: 2;
|
||||
pub down, set_down: 3;
|
||||
pub map, set_map: 4;
|
||||
pub inventory, set_inventory: 5;
|
||||
pub jump, set_jump: 6;
|
||||
pub shoot, set_shoot: 7;
|
||||
pub next_weapon, set_next_weapon: 8;
|
||||
pub prev_weapon, set_prev_weapon: 9;
|
||||
pub escape, set_escape: 10;
|
||||
pub enter, set_enter: 11;
|
||||
pub skip, set_skip: 12;
|
||||
pub strafe, set_strafe: 13;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GamepadController {
|
||||
gamepad_id: u32,
|
||||
|
@ -225,4 +204,14 @@ impl PlayerController for GamepadController {
|
|||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_state(&self) -> (u16, u16, u16) {
|
||||
(self.state.0, self.old_state.0, self.trigger.0)
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: (u16, u16, u16)) {
|
||||
self.state = KeyState(state.0);
|
||||
self.old_state = KeyState(state.1);
|
||||
self.trigger = KeyState(state.2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,68 +3,31 @@ use crate::framework::context::Context;
|
|||
use crate::framework::error::GameResult;
|
||||
use crate::framework::keyboard;
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::input::player_controller::PlayerController;
|
||||
use crate::input::player_controller::{KeyState, PlayerController};
|
||||
use crate::player::TargetPlayer;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KeyState(u16);
|
||||
impl Debug;
|
||||
|
||||
pub left, set_left: 0;
|
||||
pub right, set_right: 1;
|
||||
pub up, set_up: 2;
|
||||
pub down, set_down: 3;
|
||||
pub map, set_map: 4;
|
||||
pub inventory, set_inventory: 5;
|
||||
pub jump, set_jump: 6;
|
||||
pub shoot, set_shoot: 7;
|
||||
pub next_weapon, set_next_weapon: 8;
|
||||
pub prev_weapon, set_prev_weapon: 9;
|
||||
pub escape, set_escape: 10;
|
||||
pub enter, set_enter: 11;
|
||||
pub skip, set_skip: 12;
|
||||
pub strafe, set_strafe: 13;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct KeyboardController {
|
||||
target: TargetPlayer,
|
||||
state: KeyState,
|
||||
old_state: KeyState,
|
||||
trigger: KeyState,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl KeyboardController {
|
||||
pub fn new(target: TargetPlayer) -> KeyboardController {
|
||||
KeyboardController { target, state: KeyState(0), old_state: KeyState(0), trigger: KeyState(0), enabled: true }
|
||||
KeyboardController { target, state: KeyState(0), old_state: KeyState(0), trigger: KeyState(0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerController for KeyboardController {
|
||||
fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let keymap = match self.target {
|
||||
TargetPlayer::Player1 => &state.settings.player1_key_map,
|
||||
TargetPlayer::Player2 => &state.settings.player2_key_map,
|
||||
};
|
||||
|
||||
if !self.enabled {
|
||||
self.state = KeyState(0);
|
||||
self.old_state = KeyState(0);
|
||||
self.trigger = KeyState(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.state.set_left(keyboard::is_key_pressed(ctx, keymap.left));
|
||||
self.state.set_up(keyboard::is_key_pressed(ctx, keymap.up));
|
||||
self.state.set_right(keyboard::is_key_pressed(ctx, keymap.right));
|
||||
|
@ -237,4 +200,14 @@ impl PlayerController for KeyboardController {
|
|||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_state(&self) -> (u16, u16, u16) {
|
||||
(self.state.0, self.old_state.0, self.trigger.0)
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: (u16, u16, u16)) {
|
||||
self.state = KeyState(state.0);
|
||||
self.old_state = KeyState(state.1);
|
||||
self.trigger = KeyState(state.2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
use crate::bitfield;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
pub trait PlayerController: PlayerControllerClone {
|
||||
fn is_enabled(&self) -> bool;
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool);
|
||||
|
||||
fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult;
|
||||
|
||||
fn update_trigger(&mut self);
|
||||
|
@ -101,6 +98,32 @@ pub trait PlayerController: PlayerControllerClone {
|
|||
/// Returns movement analog stick state in Y axis within (-1.0..=1.0) range
|
||||
/// In case of non-analog controllers this should return -1.0, 0.0 or 1.0, depending on keys pressed.
|
||||
fn move_analog_y(&self) -> f64;
|
||||
|
||||
fn dump_state(&self) -> (u16, u16, u16);
|
||||
|
||||
fn set_state(&mut self, state: (u16, u16, u16));
|
||||
}
|
||||
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KeyState(u16);
|
||||
impl Debug;
|
||||
|
||||
pub left, set_left: 0;
|
||||
pub right, set_right: 1;
|
||||
pub up, set_up: 2;
|
||||
pub down, set_down: 3;
|
||||
pub map, set_map: 4;
|
||||
pub inventory, set_inventory: 5;
|
||||
pub jump, set_jump: 6;
|
||||
pub shoot, set_shoot: 7;
|
||||
pub next_weapon, set_next_weapon: 8;
|
||||
pub prev_weapon, set_prev_weapon: 9;
|
||||
pub escape, set_escape: 10;
|
||||
pub enter, set_enter: 11;
|
||||
pub skip, set_skip: 12;
|
||||
pub strafe, set_strafe: 13;
|
||||
}
|
||||
|
||||
pub trait PlayerControllerClone {
|
||||
|
|
|
@ -1,38 +1,15 @@
|
|||
use crate::bitfield;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::input::player_controller::PlayerController;
|
||||
use crate::input::player_controller::{KeyState, PlayerController};
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
bitfield! {
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KeyState(u16);
|
||||
impl Debug;
|
||||
|
||||
pub left, set_left: 0;
|
||||
pub right, set_right: 1;
|
||||
pub up, set_up: 2;
|
||||
pub down, set_down: 3;
|
||||
pub map, set_map: 4;
|
||||
pub inventory, set_inventory: 5;
|
||||
pub jump, set_jump: 6;
|
||||
pub shoot, set_shoot: 7;
|
||||
pub next_weapon, set_next_weapon: 8;
|
||||
pub prev_weapon, set_prev_weapon: 9;
|
||||
pub escape, set_escape: 10;
|
||||
pub enter, set_enter: 11;
|
||||
pub skip, set_skip: 12;
|
||||
pub strafe, set_strafe: 13;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ReplayController {
|
||||
//target: TargetPlayer,
|
||||
pub state: KeyState,
|
||||
pub old_state: KeyState,
|
||||
trigger: KeyState,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl ReplayController {
|
||||
|
@ -42,20 +19,11 @@ impl ReplayController {
|
|||
state: KeyState(0),
|
||||
old_state: KeyState(0),
|
||||
trigger: KeyState(0),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerController for ReplayController {
|
||||
fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -214,4 +182,14 @@ impl PlayerController for ReplayController {
|
|||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_state(&self) -> (u16, u16, u16) {
|
||||
(self.state.0, self.old_state.0, self.trigger.0)
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: (u16, u16, u16)) {
|
||||
self.state = KeyState(state.0);
|
||||
self.old_state = KeyState(state.1);
|
||||
self.trigger = KeyState(state.2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::common::Rect;
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::screen_insets_scaled;
|
||||
use crate::input::player_controller::PlayerController;
|
||||
use crate::input::player_controller::{KeyState, PlayerController};
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
|
@ -14,51 +14,16 @@ pub struct TouchPlayerController {
|
|||
old_state: KeyState,
|
||||
trigger: KeyState,
|
||||
prev_touch_len: usize,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct KeyState(u16);
|
||||
impl Debug;
|
||||
|
||||
pub left, set_left: 0;
|
||||
pub right, set_right: 1;
|
||||
pub up, set_up: 2;
|
||||
pub down, set_down: 3;
|
||||
pub map, _: 4;
|
||||
pub inventory, set_inventory: 5;
|
||||
pub jump, set_jump: 6;
|
||||
pub shoot, set_shoot: 7;
|
||||
pub next_weapon, _: 8;
|
||||
pub prev_weapon, _: 9;
|
||||
pub pause, set_pause: 10;
|
||||
}
|
||||
|
||||
impl TouchPlayerController {
|
||||
pub fn new() -> TouchPlayerController {
|
||||
TouchPlayerController { state: KeyState(0), old_state: KeyState(0), trigger: KeyState(0), prev_touch_len: 0, enabled: true }
|
||||
TouchPlayerController { state: KeyState(0), old_state: KeyState(0), trigger: KeyState(0), prev_touch_len: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl PlayerController for TouchPlayerController {
|
||||
fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
if !self.enabled {
|
||||
self.state = KeyState(0);
|
||||
self.old_state = KeyState(0);
|
||||
self.trigger = KeyState(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match state.touch_controls.control_type {
|
||||
TouchControlType::None => {}
|
||||
TouchControlType::Dialog => {
|
||||
|
@ -250,8 +215,8 @@ impl PlayerController for TouchPlayerController {
|
|||
.is_some(),
|
||||
);
|
||||
|
||||
self.state.set_pause(
|
||||
self.state.pause() || state.touch_controls.point_in(Rect::new_size(0, 0, 40, 40)).is_some(),
|
||||
self.state.set_escape(
|
||||
self.state.escape() || state.touch_controls.point_in(Rect::new_size(0, 0, 40, 40)).is_some(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -375,7 +340,7 @@ impl PlayerController for TouchPlayerController {
|
|||
}
|
||||
|
||||
fn trigger_menu_pause(&self) -> bool {
|
||||
self.trigger.pause()
|
||||
self.trigger.escape()
|
||||
}
|
||||
|
||||
fn look_up(&self) -> bool {
|
||||
|
@ -417,4 +382,14 @@ impl PlayerController for TouchPlayerController {
|
|||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_state(&self) -> (u16, u16, u16) {
|
||||
(self.state.0, self.old_state.0, self.trigger.0)
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: (u16, u16, u16)) {
|
||||
self.state = KeyState(state.0);
|
||||
self.old_state = KeyState(state.1);
|
||||
self.trigger = KeyState(state.2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ mod i18n;
|
|||
mod input;
|
||||
mod inventory;
|
||||
mod live_debugger;
|
||||
#[cfg(feature = "netplay")]
|
||||
mod netplay;
|
||||
mod macros;
|
||||
mod map;
|
||||
mod menu;
|
||||
|
|
|
@ -210,6 +210,7 @@ impl LiveDebugger {
|
|||
scene.player2.life = scene.player1.max_life;
|
||||
}
|
||||
|
||||
scene.local_player = game_scene.local_player;
|
||||
state.textscript_vm.suspend = true;
|
||||
state.textscript_vm.state = TextScriptExecutionState::Running(94, 0);
|
||||
state.next_scene = Some(Box::new(scene));
|
||||
|
|
|
@ -17,7 +17,7 @@ use crate::stage::{PxPackScroll, PxPackStageData, StageData};
|
|||
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
||||
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct Map {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
|
|
|
@ -11,29 +11,19 @@ use std::time::{Duration, Instant};
|
|||
use crossbeam_channel::{Receiver, Sender};
|
||||
use laminar::{Packet, Socket, SocketEvent};
|
||||
|
||||
use crate::input::dummy_player_controller::DummyPlayerController;
|
||||
use crate::netplay::common::{make_socket_config, SenderExt};
|
||||
use crate::netplay::future::RSFuture;
|
||||
use crate::netplay::protocol::{DRSPacket, ServerInfo};
|
||||
use crate::{GameError, GameResult};
|
||||
use crate::netplay::protocol::{DRSPacket, PlayerInfo, PlayerMove, ServerInfo};
|
||||
use crate::player::{Player, TargetPlayer};
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::scripting::tsc::text_script::TextScriptLine;
|
||||
use crate::{Context, GameError, GameResult, SharedGameState};
|
||||
|
||||
pub struct Client {
|
||||
master_addr: SocketAddr,
|
||||
peers: Vec<SocketAddr>,
|
||||
thread: JoinHandle<()>,
|
||||
sender: Sender<Packet>,
|
||||
state: ConnectionState,
|
||||
server_info_tasks: Arc<Mutex<VecDeque<(Instant, Sender<GameResult<ServerInfo>>)>>>,
|
||||
}
|
||||
pub struct FutureStruct<T>(RefCell<Option<GameResult<T>>>, RefCell<Option<Receiver<GameResult<T>>>>);
|
||||
|
||||
pub enum ConnectionState {
|
||||
Connecting,
|
||||
Connected,
|
||||
Failed,
|
||||
}
|
||||
|
||||
pub struct ServerInfoFuture(RefCell<Option<GameResult<ServerInfo>>>, RefCell<Option<Receiver<GameResult<ServerInfo>>>>);
|
||||
|
||||
impl RSFuture for ServerInfoFuture {
|
||||
type Output = GameResult<ServerInfo>;
|
||||
impl<T> RSFuture for FutureStruct<T> {
|
||||
type Output = GameResult<T>;
|
||||
|
||||
fn poll(&self) -> Option<Ref<Self::Output>> {
|
||||
let mut destroy = false;
|
||||
|
@ -57,76 +47,378 @@ impl RSFuture for ServerInfoFuture {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct FutureTasks<T>(Arc<Mutex<VecDeque<(Instant, Sender<GameResult<T>>)>>>);
|
||||
|
||||
impl<T> FutureTasks<T> {
|
||||
pub fn new() -> FutureTasks<T> {
|
||||
FutureTasks(Arc::new(Mutex::new(VecDeque::new())))
|
||||
}
|
||||
|
||||
pub fn process_timeout(&self) {
|
||||
let mut deque = self.0.deref().lock().unwrap();
|
||||
let n = deque.len();
|
||||
for _ in 0..n {
|
||||
if let Some((inst, tx)) = deque.pop_front() {
|
||||
if inst.elapsed().as_secs() > 30 {
|
||||
let _ = tx.send(Err(GameError::NetworkError("Timed out.".to_owned())));
|
||||
continue;
|
||||
}
|
||||
|
||||
deque.push_back((inst, tx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for FutureTasks<T> {
|
||||
fn clone(&self) -> Self {
|
||||
FutureTasks(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
master_addr: SocketAddr,
|
||||
peers: Arc<Mutex<Vec<SocketAddr>>>,
|
||||
thread: JoinHandle<()>,
|
||||
sender: Sender<Packet>,
|
||||
game_packet_queue: Receiver<DRSPacket>,
|
||||
control_packet_queue: Sender<DRSPacket>,
|
||||
state: Arc<Mutex<ConnectionState>>,
|
||||
server_info_tasks: FutureTasks<ServerInfo>,
|
||||
join_tasks: FutureTasks<()>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum ConnectionState {
|
||||
PreHello,
|
||||
Connecting,
|
||||
Connected,
|
||||
Failed,
|
||||
}
|
||||
|
||||
// this code is a horror already but I'm too lazy to scrap it and rewrite
|
||||
|
||||
impl Client {
|
||||
pub fn new(ip: &str) -> GameResult<Client> {
|
||||
let mut socket = Socket::bind_any()?;
|
||||
let mut socket = Socket::bind_any_with_config(make_socket_config())?;
|
||||
|
||||
let (pq_tx, pq_rx) = crossbeam_channel::bounded(512);
|
||||
let (cq_tx, cq_rx) = crossbeam_channel::bounded(8);
|
||||
let cl_sender = socket.get_packet_sender();
|
||||
let cl_server_info_tasks = FutureTasks::new();
|
||||
let cl_join_tasks = FutureTasks::new();
|
||||
|
||||
let cl_peers = Arc::new(Mutex::new(Vec::new()));
|
||||
let cl_state = Arc::new(Mutex::new(ConnectionState::PreHello));
|
||||
|
||||
let sender = cl_sender.clone();
|
||||
let cl_server_info_tasks: Arc<Mutex<VecDeque<(Instant, Sender<GameResult<ServerInfo>>)>>> =
|
||||
Arc::new(Mutex::new(VecDeque::new()));
|
||||
let server_info_tasks = cl_server_info_tasks.clone();
|
||||
let join_tasks = cl_join_tasks.clone();
|
||||
let peers = cl_peers.clone();
|
||||
let state = cl_state.clone();
|
||||
|
||||
let addr = SocketAddr::from_str(ip)?;
|
||||
cl_sender.send_reliable(addr, DRSPacket::KeepAlive);
|
||||
|
||||
let thread = thread::spawn(move || {
|
||||
let receiver = socket.get_event_receiver();
|
||||
let mut challenge = [0u8; 32];
|
||||
let mut last_keepalive = Instant::now();
|
||||
|
||||
loop {
|
||||
socket.manual_poll(Instant::now());
|
||||
sleep(Duration::from_millis(1));
|
||||
|
||||
let now = Instant::now();
|
||||
if last_keepalive.duration_since(now).as_secs() > 5 {
|
||||
sender.send_reliable(addr, DRSPacket::KeepAlive);
|
||||
last_keepalive = now;
|
||||
}
|
||||
|
||||
let mut state_locked = state.lock().unwrap();
|
||||
while let Ok(packet) = receiver.try_recv() {
|
||||
if let SocketEvent::Packet(p) = packet {
|
||||
if let Ok(p) = DRSPacket::decode(p.payload()) {
|
||||
match p {
|
||||
DRSPacket::ServerInfoResponse(info) => {
|
||||
let mut deque = server_info_tasks.deref().lock().unwrap();
|
||||
while let Some((_, tx)) = deque.pop_front() {
|
||||
let _ = tx.send(Ok(info.clone()));
|
||||
match DRSPacket::decode(p.payload()) {
|
||||
Ok(p) => match *state_locked {
|
||||
ConnectionState::PreHello => {
|
||||
if let DRSPacket::Hello(data) = p {
|
||||
challenge.copy_from_slice(&data.challenge);
|
||||
log::info!("Received challenge: {:?}", challenge);
|
||||
}
|
||||
|
||||
*state_locked = ConnectionState::Connecting;
|
||||
}
|
||||
ConnectionState::Connecting => match p {
|
||||
DRSPacket::ServerInfoResponse(info) => {
|
||||
let mut deque = server_info_tasks.0.deref().lock().unwrap();
|
||||
while let Some((_, tx)) = deque.pop_front() {
|
||||
let _ = tx.send(Ok(info.clone()));
|
||||
}
|
||||
}
|
||||
DRSPacket::ConnectResponse(_) => {
|
||||
let mut deque = join_tasks.0.deref().lock().unwrap();
|
||||
while let Some((_, tx)) = deque.pop_front() {
|
||||
let _ = tx.send(Ok(()));
|
||||
}
|
||||
|
||||
let _ = pq_tx.send(p);
|
||||
*state_locked = ConnectionState::Connected;
|
||||
}
|
||||
DRSPacket::Kicked(reason) => {
|
||||
log::info!("Kicked from the server: {}", reason);
|
||||
return;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
ConnectionState::Connected => {
|
||||
let _ = pq_tx.send(p);
|
||||
|
||||
while let Ok(packet) = cq_rx.try_recv() {
|
||||
sender.send_reliable(addr, packet);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
ConnectionState::Failed => (),
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to decode a packet: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut deque = server_info_tasks.deref().lock().unwrap();
|
||||
let n = deque.len();
|
||||
for _ in 0..n {
|
||||
if let Some((inst, tx)) = deque.pop_front() {
|
||||
if inst.elapsed().as_secs() > 30 {
|
||||
let _ = tx.send(Err(GameError::NetworkError("Timed out.".to_owned())));
|
||||
continue;
|
||||
}
|
||||
|
||||
deque.push_back((inst, tx));
|
||||
if *state_locked != ConnectionState::PreHello {
|
||||
while let Ok(packet) = cq_rx.try_recv() {
|
||||
sender.send_reliable(addr, packet);
|
||||
}
|
||||
}
|
||||
|
||||
server_info_tasks.process_timeout();
|
||||
join_tasks.process_timeout();
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Client {
|
||||
master_addr: addr,
|
||||
peers: Vec::new(),
|
||||
peers: cl_peers,
|
||||
thread,
|
||||
sender: cl_sender,
|
||||
state: ConnectionState::Connecting,
|
||||
game_packet_queue: pq_rx,
|
||||
control_packet_queue: cq_tx,
|
||||
state: cl_state,
|
||||
server_info_tasks: cl_server_info_tasks,
|
||||
join_tasks: cl_join_tasks,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_server_info(&mut self) -> ServerInfoFuture {
|
||||
pub fn fetch_server_info(&mut self) -> FutureStruct<ServerInfo> {
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
|
||||
{
|
||||
let mut tasks = self.server_info_tasks.lock().unwrap();
|
||||
let mut tasks = self.server_info_tasks.0.lock().unwrap();
|
||||
tasks.push_back((Instant::now(), tx));
|
||||
}
|
||||
|
||||
let _ = self
|
||||
.sender
|
||||
.send(Packet::reliable_unordered(self.master_addr, DRSPacket::ServerInfoRequest.encode_to_vec()));
|
||||
log::info!("Fetch server info...");
|
||||
let _ = self.control_packet_queue.send(DRSPacket::ServerInfoRequest);
|
||||
|
||||
ServerInfoFuture(RefCell::new(None), RefCell::new(Some(rx)))
|
||||
FutureStruct(RefCell::new(None), RefCell::new(Some(rx)))
|
||||
}
|
||||
|
||||
pub fn join(&mut self, player_name: String) -> FutureStruct<()> {
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
|
||||
{
|
||||
let mut tasks = self.join_tasks.0.lock().unwrap();
|
||||
tasks.push_back((Instant::now(), tx));
|
||||
}
|
||||
|
||||
let player_info = PlayerInfo { name: player_name, public_key: [0u8; 32], challenge_signature: [0u8; 64] };
|
||||
|
||||
log::info!("Join...");
|
||||
let _ = self.control_packet_queue.send(DRSPacket::Connect(player_info));
|
||||
|
||||
FutureStruct(RefCell::new(None), RefCell::new(Some(rx)))
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> ConnectionState {
|
||||
*self.state.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn process(&mut self, state: &mut SharedGameState, game_scene: &mut GameScene, ctx: &mut Context) {
|
||||
let mut skip_move = false;
|
||||
|
||||
while let Ok(packet) = self.game_packet_queue.try_recv() {
|
||||
match packet {
|
||||
DRSPacket::ConnectResponse(target) => {
|
||||
log::info!("Connected, local player = {}", target.index());
|
||||
game_scene.player1.controller = Box::new(DummyPlayerController::new());
|
||||
game_scene.player2.controller = Box::new(DummyPlayerController::new());
|
||||
|
||||
let player =
|
||||
if target == TargetPlayer::Player1 { &mut game_scene.player1 } else { &mut game_scene.player2 };
|
||||
|
||||
game_scene.local_player = target;
|
||||
player.cond.set_alive(true);
|
||||
player.controller = state.settings.create_player1_controller();
|
||||
}
|
||||
DRSPacket::Kicked(reason) => {
|
||||
log::info!("Kicked from the server: {}", reason);
|
||||
return;
|
||||
}
|
||||
DRSPacket::ChatMessage(message) => {
|
||||
log::info!("Chat: {}", message);
|
||||
let chat = state.chat.clone();
|
||||
chat.borrow_mut().push_message(message);
|
||||
}
|
||||
DRSPacket::SyncStageData(data) => {
|
||||
let mut new_scene = GameScene::from_stage(state, ctx, data.stage, data.stage_id as usize).unwrap();
|
||||
let (pos_x, pos_y) = data.player_pos;
|
||||
|
||||
new_scene.inventory_player1 = game_scene.inventory_player1.clone();
|
||||
new_scene.inventory_player2 = game_scene.inventory_player2.clone();
|
||||
new_scene.player1 = game_scene.player1.clone();
|
||||
new_scene.player1.vel_x = 0;
|
||||
new_scene.player1.vel_y = 0;
|
||||
new_scene.player1.x = pos_x;
|
||||
new_scene.player1.y = pos_y;
|
||||
new_scene.player2 = game_scene.player2.clone();
|
||||
new_scene.player2.vel_x = 0;
|
||||
new_scene.player2.vel_y = 0;
|
||||
new_scene.player2.x = pos_x;
|
||||
new_scene.player2.y = pos_y;
|
||||
// Reset player interaction flag upon TRA
|
||||
new_scene.player1.cond.set_interacted(false);
|
||||
new_scene.player2.cond.set_interacted(false);
|
||||
// Reset ground collision for WAS / WaitStanding
|
||||
new_scene.player1.flags.set_hit_bottom_wall(false);
|
||||
new_scene.player2.flags.set_hit_bottom_wall(false);
|
||||
new_scene.frame.wait = game_scene.frame.wait;
|
||||
new_scene.nikumaru = game_scene.nikumaru;
|
||||
new_scene.local_player = game_scene.local_player;
|
||||
|
||||
let skip = state.textscript_vm.flags.cutscene_skip();
|
||||
state.control_flags.set_tick_world(true);
|
||||
state.control_flags.set_interactions_disabled(true);
|
||||
state.textscript_vm.flags.0 = 0;
|
||||
state.textscript_vm.flags.set_cutscene_skip(skip);
|
||||
state.textscript_vm.face = 0;
|
||||
state.textscript_vm.item = 0;
|
||||
state.textscript_vm.current_line = TextScriptLine::Line1;
|
||||
state.textscript_vm.line_1.clear();
|
||||
state.textscript_vm.line_2.clear();
|
||||
state.textscript_vm.line_3.clear();
|
||||
state.textscript_vm.suspend = true;
|
||||
state.next_scene = Some(Box::new(new_scene));
|
||||
|
||||
return; // process remaining packets on new stage
|
||||
}
|
||||
DRSPacket::SyncTSCScripts(scripts) => {
|
||||
state.textscript_vm.scripts.replace(scripts);
|
||||
}
|
||||
DRSPacket::SyncTSC(data) => {
|
||||
state.textscript_vm.state = data.state;
|
||||
state.textscript_vm.stack = data.stack;
|
||||
state.textscript_vm.flags = data.flags;
|
||||
state.textscript_vm.mode = data.mode;
|
||||
state.textscript_vm.executor_player = data.executor_player;
|
||||
state.textscript_vm.strict_mode = data.strict_mode;
|
||||
state.textscript_vm.suspend = data.suspend;
|
||||
state.textscript_vm.reset_invincibility = data.reset_invincibility;
|
||||
state.textscript_vm.numbers = data.numbers;
|
||||
state.textscript_vm.face = data.face;
|
||||
state.textscript_vm.item = data.item;
|
||||
state.textscript_vm.current_line = data.current_line;
|
||||
state.textscript_vm.line_1 = data.line_1;
|
||||
state.textscript_vm.line_2 = data.line_2;
|
||||
state.textscript_vm.line_3 = data.line_3;
|
||||
state.textscript_vm.current_illustration = data.current_illustration;
|
||||
state.textscript_vm.illustration_state = data.illustration_state;
|
||||
state.textscript_vm.prev_char = data.prev_char;
|
||||
state.fade_state = data.fade_state;
|
||||
|
||||
let _ = state.sound_manager.play_song(
|
||||
data.current_song as usize,
|
||||
&state.constants,
|
||||
&state.settings,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
DRSPacket::SyncNPC(npc) => {
|
||||
if let Some(npc_ref) = game_scene.npc_list.get_npc(npc.id as usize) {
|
||||
*npc_ref = npc;
|
||||
}
|
||||
}
|
||||
DRSPacket::SyncControlFlags(flags) => {
|
||||
state.control_flags = flags;
|
||||
}
|
||||
DRSPacket::SyncPlayer(data) => {
|
||||
let player = if data.target == TargetPlayer::Player1 {
|
||||
&mut game_scene.player1
|
||||
} else {
|
||||
&mut game_scene.player2
|
||||
};
|
||||
|
||||
player.life = data.life;
|
||||
player.max_life = data.max_life;
|
||||
player.control_mode = data.control_mode;
|
||||
player.question = data.question;
|
||||
player.popup = data.popup;
|
||||
player.shock_counter = data.shock_counter;
|
||||
player.xp_counter = data.xp_counter;
|
||||
player.current_weapon = data.current_weapon;
|
||||
player.stars = data.stars;
|
||||
player.damage = data.damage;
|
||||
player.air_counter = data.air_counter;
|
||||
player.air = data.air;
|
||||
}
|
||||
DRSPacket::Move(data) => {
|
||||
let player = if data.target == TargetPlayer::Player1 {
|
||||
&mut game_scene.player1
|
||||
} else {
|
||||
&mut game_scene.player2
|
||||
};
|
||||
|
||||
player.x = data.x;
|
||||
player.y = data.y;
|
||||
player.vel_x = data.vel_x;
|
||||
player.vel_y = data.vel_y;
|
||||
player.direction = data.direction;
|
||||
player.cond = data.cond;
|
||||
|
||||
if data.target == game_scene.local_player {
|
||||
skip_move = true;
|
||||
} else {
|
||||
player.controller.set_state((data.state, data.old_state, data.trigger));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if !skip_move {
|
||||
let p = if game_scene.local_player == TargetPlayer::Player1 {
|
||||
&game_scene.player1
|
||||
} else {
|
||||
&game_scene.player2
|
||||
};
|
||||
let (state, old_state, trigger) = p.controller.dump_state();
|
||||
let Player { x, y, vel_x, vel_y, direction, cond, .. } = *p;
|
||||
|
||||
self.sender.send_unreliable(
|
||||
self.master_addr,
|
||||
DRSPacket::Move(PlayerMove {
|
||||
target: game_scene.local_player, // ignored
|
||||
x,
|
||||
y,
|
||||
vel_x,
|
||||
vel_y,
|
||||
state,
|
||||
old_state,
|
||||
trigger,
|
||||
direction,
|
||||
cond,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
use laminar::{Config, Packet};
|
||||
|
||||
use crate::netplay::protocol::DRSPacket;
|
||||
|
||||
pub fn make_socket_config() -> Config {
|
||||
let mut config = Config::default();
|
||||
config.idle_connection_timeout = Duration::new(30, 0);
|
||||
config
|
||||
}
|
||||
|
||||
pub trait SenderExt {
|
||||
fn kick(&self, addr: SocketAddr, reason: &'static str) {
|
||||
self.send_reliable(addr, DRSPacket::Kicked(reason.to_owned()));
|
||||
}
|
||||
|
||||
fn send_reliable(&self, addr: SocketAddr, packet: DRSPacket);
|
||||
|
||||
fn send_unreliable(&self, addr: SocketAddr, packet: DRSPacket);
|
||||
|
||||
fn broadcast_reliable<I>(&self, addrs: I, packet: DRSPacket) where I: Iterator<Item = SocketAddr>;
|
||||
|
||||
fn broadcast_unreliable<I>(&self, addrs: I, packet: DRSPacket) where I: Iterator<Item = SocketAddr>;
|
||||
}
|
||||
|
||||
impl SenderExt for Sender<Packet> {
|
||||
fn send_reliable(&self, addr: SocketAddr, packet: DRSPacket) {
|
||||
let _ = self.send(Packet::reliable_sequenced(addr, packet.encode_to_vec(), Some(0)));
|
||||
}
|
||||
|
||||
fn send_unreliable(&self, addr: SocketAddr, packet: DRSPacket) {
|
||||
let _ = self.send(Packet::unreliable_sequenced(addr, packet.encode_to_vec(), Some(0)));
|
||||
}
|
||||
|
||||
fn broadcast_reliable<I>(&self, addrs: I, packet: DRSPacket) where I: Iterator<Item=SocketAddr> {
|
||||
let payload = packet.encode_to_vec();
|
||||
for addr in addrs {
|
||||
let _ = self.send(Packet::reliable_sequenced(addr, payload.clone(), Some(0)));
|
||||
}
|
||||
}
|
||||
|
||||
fn broadcast_unreliable<I>(&self, addrs: I, packet: DRSPacket) where I: Iterator<Item=SocketAddr> {
|
||||
let payload = packet.encode_to_vec();
|
||||
for addr in addrs {
|
||||
let _ = self.send(Packet::unreliable_sequenced(addr, payload.clone(), Some(0)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod client;
|
||||
pub mod common;
|
||||
pub mod future;
|
||||
pub mod protocol;
|
||||
pub mod server;
|
||||
|
|
|
@ -1,29 +1,113 @@
|
|||
use crate::GameResult;
|
||||
use crate::common::{Condition, ControlFlags, Direction, FadeState};
|
||||
use crate::player::{ControlMode, TargetPlayer};
|
||||
use crate::scripting::tsc::text_script::{
|
||||
IllustrationState, Scripts, TextScriptExecutionState, TextScriptFlags, TextScriptLine,
|
||||
};
|
||||
use crate::stage::Stage;
|
||||
use crate::{GameResult, ScriptMode};
|
||||
use crate::components::number_popup::NumberPopup;
|
||||
use crate::npc::NPC;
|
||||
|
||||
#[derive(bincode::Encode, bincode::Decode, Clone, Debug)]
|
||||
#[derive(Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct ServerInfo {
|
||||
pub motd: String,
|
||||
// online/max
|
||||
pub players: (u16, u16),
|
||||
}
|
||||
|
||||
#[derive(bincode::Encode, bincode::Decode, Clone, Debug)]
|
||||
#[derive(Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct PlayerInfo {
|
||||
pub name: String,
|
||||
pub public_key: String,
|
||||
pub signed_challenge: String,
|
||||
pub public_key: [u8; 32],
|
||||
pub challenge_signature: [u8; 64],
|
||||
}
|
||||
|
||||
#[derive(bincode::Encode, bincode::Decode, Clone, Debug)]
|
||||
#[derive(Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct HelloData {
|
||||
pub challenge: String,
|
||||
pub challenge: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(bincode::Encode, bincode::Decode, Clone, Debug)]
|
||||
#[derive(Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct StageData {
|
||||
pub stage_id: u32,
|
||||
pub stage: Stage,
|
||||
pub player_pos: (i32, i32),
|
||||
}
|
||||
|
||||
#[derive(Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct PlayerMove {
|
||||
pub target: TargetPlayer,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub vel_x: i32,
|
||||
pub vel_y: i32,
|
||||
pub state: u16,
|
||||
pub old_state: u16,
|
||||
pub trigger: u16,
|
||||
pub direction: Direction,
|
||||
pub cond: Condition,
|
||||
}
|
||||
|
||||
#[derive(Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct TextScriptData {
|
||||
pub state: TextScriptExecutionState,
|
||||
pub stack: Vec<TextScriptExecutionState>,
|
||||
pub flags: TextScriptFlags,
|
||||
pub mode: ScriptMode,
|
||||
pub executor_player: TargetPlayer,
|
||||
pub strict_mode: bool,
|
||||
pub suspend: bool,
|
||||
pub reset_invincibility: bool,
|
||||
pub numbers: [u16; 4],
|
||||
pub face: u16,
|
||||
pub item: u16,
|
||||
pub current_line: TextScriptLine,
|
||||
pub line_1: Vec<char>,
|
||||
pub line_2: Vec<char>,
|
||||
pub line_3: Vec<char>,
|
||||
pub current_illustration: Option<String>,
|
||||
pub illustration_state: IllustrationState,
|
||||
pub prev_char: char,
|
||||
pub fade_state: FadeState,
|
||||
pub current_song: u32,
|
||||
pub prev_song: u32,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct PlayerData {
|
||||
pub target: TargetPlayer,
|
||||
pub life: u16,
|
||||
pub max_life: u16,
|
||||
pub control_mode: ControlMode,
|
||||
pub question: bool,
|
||||
pub popup: NumberPopup,
|
||||
pub shock_counter: u8,
|
||||
pub xp_counter: u8,
|
||||
pub current_weapon: u8,
|
||||
pub stars: u8,
|
||||
pub damage: u16,
|
||||
pub air_counter: u16,
|
||||
pub air: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, bincode::Decode, bincode::Encode)]
|
||||
pub enum DRSPacket {
|
||||
KeepAlive,
|
||||
Hello(HelloData),
|
||||
Kicked(String),
|
||||
ChatMessage(String),
|
||||
ServerInfoRequest,
|
||||
ServerInfoResponse(ServerInfo),
|
||||
Connect(PlayerInfo),
|
||||
ConnectResponse(TargetPlayer),
|
||||
Move(PlayerMove),
|
||||
SyncControlFlags(ControlFlags),
|
||||
SyncStageData(StageData),
|
||||
SyncTSCScripts(Scripts),
|
||||
SyncTSC(TextScriptData),
|
||||
SyncPlayer(PlayerData),
|
||||
SyncNPC(NPC),
|
||||
}
|
||||
|
||||
impl DRSPacket {
|
||||
|
@ -38,6 +122,6 @@ impl DRSPacket {
|
|||
}
|
||||
|
||||
pub fn encode_to_vec(&self) -> Vec<u8> {
|
||||
bincode::encode_to_vec(self, bincode::config::standard()).unwrap()
|
||||
bincode::encode_to_vec(self, bincode::config::standard()).unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,163 @@
|
|||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
use std::thread;
|
||||
use std::thread::{sleep, JoinHandle};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use laminar::{Packet, Socket, SocketEvent};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use laminar::{Socket, SocketEvent};
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::{RngCore, SeedableRng};
|
||||
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::netplay::protocol::{DRSPacket, ServerInfo};
|
||||
use crate::netplay::common::{make_socket_config, SenderExt};
|
||||
use crate::netplay::protocol::{DRSPacket, HelloData, PlayerData, PlayerMove, ServerInfo, StageData, TextScriptData};
|
||||
use crate::netplay::server_config::ServerConfiguration;
|
||||
use crate::player::TargetPlayer;
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::SharedGameState;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
enum DeliveryType {
|
||||
Reliable,
|
||||
Unreliable,
|
||||
}
|
||||
|
||||
enum SyncMessage {
|
||||
SyncStageToPlayer(TargetPlayer),
|
||||
SyncNewPlayer(TargetPlayer),
|
||||
PlayerLeft(TargetPlayer),
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
thread: JoinHandle<()>,
|
||||
_thread: JoinHandle<()>,
|
||||
game_packet_queue: Receiver<DRSPacket>,
|
||||
sync_message_queue: Receiver<SyncMessage>,
|
||||
broadcast_packet_queue: Sender<(DRSPacket, DeliveryType)>,
|
||||
send_packet_queue: Sender<(TargetPlayer, DRSPacket, DeliveryType)>,
|
||||
tick: usize,
|
||||
}
|
||||
|
||||
struct PlayerState {
|
||||
target: TargetPlayer,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn start(config: ServerConfiguration) -> GameResult<Server> {
|
||||
let mut socket = Socket::bind(config.bind_to)?;
|
||||
let mut socket = Socket::bind_with_config(config.bind_to, make_socket_config())?;
|
||||
let (pq_tx, pq_rx) = crossbeam_channel::bounded(2048);
|
||||
let (bq_tx, bq_rx) = crossbeam_channel::bounded::<(DRSPacket, DeliveryType)>(2048);
|
||||
let (sq_tx, sq_rx) = crossbeam_channel::bounded::<(TargetPlayer, DRSPacket, DeliveryType)>(2048);
|
||||
let (mq_tx, mq_rx) = crossbeam_channel::bounded::<SyncMessage>(32);
|
||||
|
||||
log::info!("Listening on {:?}.", socket.local_addr()?);
|
||||
|
||||
let thread = thread::spawn(move || {
|
||||
let receiver = socket.get_event_receiver();
|
||||
let sender = socket.get_packet_sender();
|
||||
let mut players = HashMap::<SocketAddr, PlayerState>::new();
|
||||
let mut rng = ThreadRng::default();
|
||||
|
||||
loop {
|
||||
let mut test = [0u8; 4];
|
||||
if rng.try_fill_bytes(&mut test).is_ok() {
|
||||
break;
|
||||
}
|
||||
|
||||
log::warn!("The system RNG is not ready, waiting for initialization...");
|
||||
sleep(Duration::from_millis(1000));
|
||||
}
|
||||
|
||||
loop {
|
||||
socket.manual_poll(Instant::now());
|
||||
sleep(Duration::from_millis(1));
|
||||
|
||||
while let Ok((packet, delivery)) = bq_rx.try_recv() {
|
||||
match delivery {
|
||||
DeliveryType::Reliable => {
|
||||
sender.broadcast_reliable(players.keys().into_iter().copied(), packet);
|
||||
}
|
||||
DeliveryType::Unreliable => {
|
||||
sender.broadcast_unreliable(players.keys().into_iter().copied(), packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Ok((target, packet, delivery)) = sq_rx.try_recv() {
|
||||
let entry = players.iter().find(|(_, state)| state.target == target);
|
||||
|
||||
if let Some(entry) = entry {
|
||||
match delivery {
|
||||
DeliveryType::Reliable => {
|
||||
sender.send_reliable(*entry.0, packet);
|
||||
}
|
||||
DeliveryType::Unreliable => {
|
||||
sender.send_unreliable(*entry.0, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Ok(packet) = receiver.try_recv() {
|
||||
match packet {
|
||||
SocketEvent::Packet(p) => {
|
||||
let player = players.get(&p.addr());
|
||||
|
||||
if let Ok(payload) = DRSPacket::decode(p.payload()) {
|
||||
match payload {
|
||||
DRSPacket::KeepAlive => {
|
||||
sender.send_reliable(p.addr(), DRSPacket::KeepAlive);
|
||||
}
|
||||
DRSPacket::ServerInfoRequest => {
|
||||
let _ = sender.send(Packet::reliable_sequenced(
|
||||
sender.send_reliable(
|
||||
p.addr(),
|
||||
DRSPacket::ServerInfoResponse(ServerInfo {
|
||||
motd: "A doukutsu-rs server".to_string(),
|
||||
players: (21, 37),
|
||||
})
|
||||
.encode_to_vec(),
|
||||
Some(0),
|
||||
));
|
||||
players: (0, 2),
|
||||
}),
|
||||
);
|
||||
}
|
||||
DRSPacket::Connect(info) => {
|
||||
if player.is_some() {
|
||||
sender.kick(p.addr(), "Invalid state.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if players.len() == 2 {
|
||||
sender.kick(p.addr(), "Too many players are connected right now.");
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut target = TargetPlayer::Player2;
|
||||
for (_, state) in players.iter() {
|
||||
if state.target == TargetPlayer::Player2 {
|
||||
target = TargetPlayer::Player1;
|
||||
}
|
||||
}
|
||||
|
||||
players.insert(p.addr(), PlayerState { target, name: info.name.clone() });
|
||||
|
||||
sender.send_reliable(p.addr(), DRSPacket::ConnectResponse(target));
|
||||
|
||||
sender.broadcast_reliable(
|
||||
players.keys().into_iter().copied(),
|
||||
DRSPacket::ChatMessage(format!(
|
||||
"§2(§a+§2) §7Player §f{} §7joined the game.",
|
||||
info.name
|
||||
)),
|
||||
);
|
||||
|
||||
let _ = mq_tx.send(SyncMessage::SyncNewPlayer(target));
|
||||
let _ = mq_tx.send(SyncMessage::SyncStageToPlayer(target));
|
||||
}
|
||||
DRSPacket::Move(mut plr_move) => {
|
||||
if let Some(player) = player {
|
||||
plr_move.target = player.target;
|
||||
let _ = pq_tx.send(DRSPacket::Move(plr_move));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
@ -48,20 +165,239 @@ impl Server {
|
|||
}
|
||||
SocketEvent::Connect(addr) => {
|
||||
log::info!("Client {:?} connected.", addr);
|
||||
let mut challenge = [0u8; 32];
|
||||
if rng.try_fill_bytes(&mut challenge).is_err() {
|
||||
log::error!("Failed to generate challenge, the RNG is not ready?");
|
||||
continue;
|
||||
}
|
||||
|
||||
sender.send_reliable(addr, DRSPacket::Hello(HelloData { challenge }))
|
||||
}
|
||||
SocketEvent::Timeout(addr) => {
|
||||
log::info!("Client {:?} timed out.", addr);
|
||||
}
|
||||
SocketEvent::Disconnect(addr) => {
|
||||
log::info!("Client {:?} disconnected.", addr);
|
||||
let info = players.remove(&addr);
|
||||
|
||||
if let Some(info) = info {
|
||||
sender.broadcast_reliable(
|
||||
players.keys().into_iter().copied(),
|
||||
DRSPacket::ChatMessage(format!(
|
||||
"§4(§c-§4) §7Player §f{} §7left the game.",
|
||||
info.name
|
||||
)),
|
||||
);
|
||||
let _ = mq_tx.send(SyncMessage::PlayerLeft(info.target));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Server { thread })
|
||||
Ok(Server {
|
||||
_thread: thread,
|
||||
game_packet_queue: pq_rx,
|
||||
sync_message_queue: mq_rx,
|
||||
broadcast_packet_queue: bq_tx,
|
||||
send_packet_queue: sq_tx,
|
||||
tick: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process(&mut self, state: &mut SharedGameState) {}
|
||||
pub fn process(&mut self, state: &mut SharedGameState, game_scene: &mut GameScene) {
|
||||
self.tick = self.tick.wrapping_add(1);
|
||||
|
||||
while let Ok(packet) = self.game_packet_queue.try_recv() {
|
||||
match packet {
|
||||
DRSPacket::Move(plr_move) => {
|
||||
let player = if plr_move.target == TargetPlayer::Player1 {
|
||||
&mut game_scene.player1
|
||||
} else {
|
||||
&mut game_scene.player2
|
||||
};
|
||||
player.x = plr_move.x;
|
||||
player.y = plr_move.y;
|
||||
player.vel_x = plr_move.vel_x;
|
||||
player.vel_y = plr_move.vel_y;
|
||||
player.direction = plr_move.direction;
|
||||
player.cond = plr_move.cond;
|
||||
player.controller.set_state((plr_move.state, plr_move.old_state, plr_move.trigger));
|
||||
|
||||
let other_player = if plr_move.target == TargetPlayer::Player1 {
|
||||
TargetPlayer::Player2
|
||||
} else {
|
||||
TargetPlayer::Player1
|
||||
};
|
||||
|
||||
let _ = self.send_packet_queue.send((
|
||||
other_player,
|
||||
DRSPacket::Move(plr_move),
|
||||
DeliveryType::Unreliable,
|
||||
));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
while let Ok(msg) = self.sync_message_queue.try_recv() {
|
||||
match msg {
|
||||
SyncMessage::SyncStageToPlayer(target) => {
|
||||
self.sync_transfer_stage(state, game_scene, Some(target));
|
||||
self.sync_players(state, game_scene);
|
||||
}
|
||||
SyncMessage::SyncNewPlayer(target) => {
|
||||
if target == TargetPlayer::Player2 {
|
||||
game_scene.add_player2(state);
|
||||
}
|
||||
|
||||
{
|
||||
let player = if target == TargetPlayer::Player1 {
|
||||
&mut game_scene.player1
|
||||
} else {
|
||||
&mut game_scene.player2
|
||||
};
|
||||
player.cond.set_alive(true);
|
||||
}
|
||||
|
||||
self.sync_players(state, game_scene);
|
||||
}
|
||||
SyncMessage::PlayerLeft(target) => {
|
||||
{
|
||||
let player = if target == TargetPlayer::Player1 {
|
||||
&mut game_scene.player1
|
||||
} else {
|
||||
&mut game_scene.player2
|
||||
};
|
||||
|
||||
player.cond.set_alive(false);
|
||||
}
|
||||
|
||||
self.sync_players(state, game_scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.tick % 300 == 50 {
|
||||
for npc in game_scene.npc_list.iter_alive() {
|
||||
let _ = self.broadcast_packet_queue.send((DRSPacket::SyncNPC(npc.clone()), DeliveryType::Reliable));
|
||||
}
|
||||
}
|
||||
|
||||
if self.tick % 10 == 0 {
|
||||
self.sync_tsc(state, game_scene, None);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_players(&mut self, state: &mut SharedGameState, game_scene: &mut GameScene) {
|
||||
let players = [TargetPlayer::Player1, TargetPlayer::Player2];
|
||||
for target in players {
|
||||
let player =
|
||||
if target == TargetPlayer::Player1 { &mut game_scene.player1 } else { &mut game_scene.player2 };
|
||||
|
||||
let (state, old_state, trigger) = player.controller.dump_state();
|
||||
|
||||
let sync_packet = DRSPacket::SyncPlayer(PlayerData {
|
||||
target,
|
||||
life: player.life,
|
||||
max_life: player.max_life,
|
||||
control_mode: player.control_mode,
|
||||
question: player.question,
|
||||
popup: player.popup,
|
||||
shock_counter: player.shock_counter,
|
||||
xp_counter: player.xp_counter,
|
||||
current_weapon: player.current_weapon,
|
||||
stars: player.stars,
|
||||
damage: player.damage,
|
||||
air_counter: player.air_counter,
|
||||
air: player.air,
|
||||
});
|
||||
|
||||
let move_packet = DRSPacket::Move(PlayerMove {
|
||||
target,
|
||||
x: player.x,
|
||||
y: player.y,
|
||||
vel_x: player.vel_x,
|
||||
vel_y: player.vel_y,
|
||||
state,
|
||||
old_state,
|
||||
trigger,
|
||||
direction: player.direction,
|
||||
cond: player.cond,
|
||||
});
|
||||
|
||||
let _ = self.broadcast_packet_queue.send((sync_packet, DeliveryType::Reliable));
|
||||
let _ = self.broadcast_packet_queue.send((move_packet, DeliveryType::Reliable));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_tsc(&mut self, state: &mut SharedGameState, game_scene: &mut GameScene, target: Option<TargetPlayer>) {
|
||||
let tsc_packet = DRSPacket::SyncTSC(TextScriptData {
|
||||
state: state.textscript_vm.state,
|
||||
stack: state.textscript_vm.stack.clone(),
|
||||
flags: state.textscript_vm.flags,
|
||||
mode: state.textscript_vm.mode,
|
||||
executor_player: state.textscript_vm.executor_player,
|
||||
strict_mode: state.textscript_vm.strict_mode,
|
||||
suspend: state.textscript_vm.suspend,
|
||||
reset_invincibility: state.textscript_vm.reset_invincibility,
|
||||
numbers: state.textscript_vm.numbers,
|
||||
face: state.textscript_vm.face,
|
||||
item: state.textscript_vm.item,
|
||||
current_line: state.textscript_vm.current_line,
|
||||
line_1: state.textscript_vm.line_1.clone(),
|
||||
line_2: state.textscript_vm.line_2.clone(),
|
||||
line_3: state.textscript_vm.line_3.clone(),
|
||||
current_illustration: state.textscript_vm.current_illustration.clone(),
|
||||
illustration_state: state.textscript_vm.illustration_state.clone(),
|
||||
prev_char: state.textscript_vm.prev_char,
|
||||
fade_state: state.fade_state,
|
||||
prev_song: state.sound_manager.prev_song() as u32,
|
||||
current_song: state.sound_manager.current_song() as u32,
|
||||
});
|
||||
|
||||
if let Some(target) = target {
|
||||
let _ = self.send_packet_queue.send((target, tsc_packet, DeliveryType::Unreliable));
|
||||
} else {
|
||||
let _ = self.broadcast_packet_queue.send((tsc_packet, DeliveryType::Unreliable));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_transfer_stage(
|
||||
&mut self,
|
||||
state: &mut SharedGameState,
|
||||
game_scene: &mut GameScene,
|
||||
target: Option<TargetPlayer>,
|
||||
) {
|
||||
let stage_packet = DRSPacket::SyncStageData(StageData {
|
||||
stage_id: game_scene.stage_id as u32,
|
||||
stage: game_scene.stage.clone(),
|
||||
player_pos: (game_scene.player1.x, game_scene.player1.y),
|
||||
});
|
||||
|
||||
let tsc_packet = DRSPacket::SyncTSCScripts(state.textscript_vm.scripts.deref().borrow().clone());
|
||||
|
||||
let ctrlf_packet = DRSPacket::SyncControlFlags(state.control_flags);
|
||||
|
||||
if let Some(target) = target {
|
||||
let _ = self.send_packet_queue.send((target, stage_packet, DeliveryType::Reliable));
|
||||
let _ = self.send_packet_queue.send((target, tsc_packet, DeliveryType::Reliable));
|
||||
let _ = self.send_packet_queue.send((target, ctrlf_packet, DeliveryType::Reliable));
|
||||
|
||||
for npc in game_scene.npc_list.iter_alive() {
|
||||
let _ = self.send_packet_queue.send((target, DRSPacket::SyncNPC(npc.clone()), DeliveryType::Reliable));
|
||||
}
|
||||
} else {
|
||||
let _ = self.broadcast_packet_queue.send((stage_packet, DeliveryType::Reliable));
|
||||
let _ = self.broadcast_packet_queue.send((tsc_packet, DeliveryType::Reliable));
|
||||
let _ = self.broadcast_packet_queue.send((ctrlf_packet, DeliveryType::Reliable));
|
||||
|
||||
for npc in game_scene.npc_list.iter_alive() {
|
||||
let _ = self.broadcast_packet_queue.send((DRSPacket::SyncNPC(npc.clone()), DeliveryType::Reliable));
|
||||
}
|
||||
}
|
||||
|
||||
self.sync_tsc(state, game_scene, target);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
pub struct ServerConfiguration {
|
||||
#[serde(default = "default_bind")]
|
||||
pub bind_to: String,
|
||||
#[serde(default = "default_max_players")]
|
||||
pub max_players: u16,
|
||||
}
|
||||
|
||||
impl Default for ServerConfiguration {
|
||||
fn default() -> Self {
|
||||
ServerConfiguration { bind_to: default_bind() }
|
||||
ServerConfiguration { bind_to: default_bind(), max_players: default_max_players() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,3 +16,7 @@ impl Default for ServerConfiguration {
|
|||
fn default_bind() -> String {
|
||||
"0.0.0.0:21075".to_string()
|
||||
}
|
||||
|
||||
fn default_max_players() -> u16 {
|
||||
2
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ pub mod list;
|
|||
pub mod utils;
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, bincode::Encode, bincode::Decode)]
|
||||
pub struct NPCFlag(u16);
|
||||
impl Debug;
|
||||
/// Represented by 0x01
|
||||
|
@ -68,7 +68,7 @@ bitfield! {
|
|||
pub show_damage, set_show_damage: 15;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialOrd, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialOrd, PartialEq, bincode::Encode, bincode::Decode)]
|
||||
#[repr(u8)]
|
||||
pub enum NPCLayer {
|
||||
Background = 0,
|
||||
|
@ -77,7 +77,7 @@ pub enum NPCLayer {
|
|||
}
|
||||
|
||||
/// Represents an NPC object.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, bincode::Encode, bincode::Decode)]
|
||||
#[repr(C)]
|
||||
pub struct NPC {
|
||||
pub id: u16,
|
||||
|
|
|
@ -20,16 +20,17 @@ use crate::rng::RNG;
|
|||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
mod player_hit;
|
||||
mod player_list;
|
||||
pub mod skin;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, bincode::Decode, bincode::Encode)]
|
||||
#[repr(u8)]
|
||||
pub enum ControlMode {
|
||||
Normal = 0,
|
||||
IronHead,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, bincode::Decode, bincode::Encode)]
|
||||
pub enum TargetPlayer {
|
||||
Player1,
|
||||
Player2,
|
||||
|
@ -924,7 +925,7 @@ impl GameEntity<&NPCList> for Player {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
if state.textscript_vm.reset_invicibility && state.constants.textscript.reset_invicibility_on_any_script {
|
||||
if state.textscript_vm.reset_invincibility && state.constants.textscript.reset_invicibility_on_any_script {
|
||||
self.shock_counter = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ impl RNG for XorShift {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, bincode::Encode, bincode::Decode)]
|
||||
pub struct Xoroshiro32PlusPlus(Cell<(u16, u16)>);
|
||||
|
||||
impl Xoroshiro32PlusPlus {
|
||||
|
|
|
@ -3,10 +3,11 @@ use std::collections::HashMap;
|
|||
use std::ops::{Deref, Range};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crossbeam_channel::tick;
|
||||
use log::info;
|
||||
|
||||
use crate::caret::CaretType;
|
||||
use crate::common::{interpolate_fix9_scale, Color, Direction, Rect};
|
||||
use crate::common::{Color, Direction, interpolate_fix9_scale, Rect};
|
||||
use crate::components::background::Background;
|
||||
use crate::components::boss_life_bar::BossLifeBar;
|
||||
use crate::components::credits::Credits;
|
||||
|
@ -26,32 +27,32 @@ use crate::components::water_renderer::{WaterLayer, WaterRenderer};
|
|||
use crate::components::whimsical_star::WhimsicalStar;
|
||||
use crate::entity::GameEntity;
|
||||
use crate::frame::{Frame, UpdateTarget};
|
||||
use crate::framework::{filesystem, graphics};
|
||||
use crate::framework::backend::SpriteBatchCommand;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::{draw_rect, BlendMode, FilterMode};
|
||||
use crate::framework::graphics::{BlendMode, draw_rect, FilterMode};
|
||||
use crate::framework::ui::Components;
|
||||
use crate::framework::{filesystem, graphics};
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::inventory::{Inventory, TakeExperienceResult};
|
||||
use crate::map::WaterParams;
|
||||
use crate::menu::pause_menu::PauseMenu;
|
||||
use crate::npc::{NPC, NPCLayer};
|
||||
use crate::npc::boss::BossNPC;
|
||||
use crate::npc::list::NPCList;
|
||||
use crate::npc::{NPCLayer, NPC};
|
||||
use crate::physics::{PhysicalEntity, OFFSETS};
|
||||
use crate::physics::{OFFSETS, PhysicalEntity};
|
||||
use crate::player::{ControlMode, Player, TargetPlayer};
|
||||
use crate::rng::RNG;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::scripting::tsc::credit_script::CreditScriptVM;
|
||||
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::settings::ControllerType;
|
||||
use crate::shared_game_state::{Language, PlayerCount, ReplayState, SharedGameState, TileSize};
|
||||
use crate::stage::{BackgroundType, Stage, StageTexturePaths};
|
||||
use crate::texture_set::SpriteBatch;
|
||||
use crate::weapon::bullet::BulletManager;
|
||||
use crate::weapon::{Weapon, WeaponType};
|
||||
use crate::weapon::bullet::BulletManager;
|
||||
|
||||
pub struct GameScene {
|
||||
pub tick: u32,
|
||||
|
@ -87,6 +88,7 @@ pub struct GameScene {
|
|||
pub pause_menu: PauseMenu,
|
||||
pub stage_textures: Rc<RefCell<StageTexturePaths>>,
|
||||
pub replay: Replay,
|
||||
pub local_player: TargetPlayer,
|
||||
map_name_counter: u16,
|
||||
skip_counter: u16,
|
||||
inventory_dim: f32,
|
||||
|
@ -174,12 +176,14 @@ impl GameScene {
|
|||
skip_counter: 0,
|
||||
inventory_dim: 0.0,
|
||||
replay: Replay::new(),
|
||||
local_player: TargetPlayer::Player1,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn display_map_name(&mut self, ticks: u16) {
|
||||
self.map_name_counter = ticks;
|
||||
}
|
||||
|
||||
pub fn add_player2(&mut self, state: &mut SharedGameState) {
|
||||
self.player2.cond.set_alive(true);
|
||||
self.player2.cond.set_hidden(self.player1.cond.hidden());
|
||||
|
@ -1329,7 +1333,7 @@ impl GameScene {
|
|||
};
|
||||
self.player1.tick(state, &self.npc_list)?;
|
||||
self.player2.tick(state, &self.npc_list)?;
|
||||
state.textscript_vm.reset_invicibility = false;
|
||||
state.textscript_vm.reset_invincibility = false;
|
||||
|
||||
self.whimsical_star.tick(state, (&self.player1, &mut self.bullet_manager))?;
|
||||
|
||||
|
@ -1616,17 +1620,34 @@ impl GameScene {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn local_player(&mut self) -> &Player {
|
||||
if self.local_player == TargetPlayer::Player1 {
|
||||
&self.player1
|
||||
} else {
|
||||
&self.player2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scene for GameScene {
|
||||
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let mut is_server = false;
|
||||
#[cfg(feature = "netplay")]
|
||||
{
|
||||
is_server = state.server.is_some();
|
||||
}
|
||||
|
||||
if state.mod_path.is_some() && state.replay_state == ReplayState::Recording {
|
||||
self.replay.initialize_recording(state);
|
||||
}
|
||||
if state.player_count == PlayerCount::Two {
|
||||
self.add_player2(state);
|
||||
} else {
|
||||
self.drop_player2();
|
||||
|
||||
if !state.is_netplay() {
|
||||
if state.player_count == PlayerCount::Two {
|
||||
self.add_player2(state);
|
||||
} else {
|
||||
self.drop_player2();
|
||||
}
|
||||
}
|
||||
|
||||
if state.mod_path.is_some() && state.replay_state == ReplayState::Playback {
|
||||
|
@ -1645,8 +1666,10 @@ impl Scene for GameScene {
|
|||
#[cfg(feature = "scripting-lua")]
|
||||
state.lua.set_game_scene(self as *mut _);
|
||||
|
||||
self.player1.controller = state.settings.create_player1_controller();
|
||||
self.player2.controller = state.settings.create_player2_controller();
|
||||
if !state.is_netplay() {
|
||||
self.player1.controller = state.settings.create_player1_controller();
|
||||
self.player2.controller = state.settings.create_player2_controller();
|
||||
}
|
||||
|
||||
let npcs = self.stage.load_npcs(&state.constants.base_paths, ctx)?;
|
||||
for npc_data in npcs.iter() {
|
||||
|
@ -1716,17 +1739,30 @@ impl Scene for GameScene {
|
|||
self.pause_menu.init(state, ctx)?;
|
||||
self.whimsical_star.init(&self.player1);
|
||||
|
||||
#[cfg(feature = "netplay")]
|
||||
{
|
||||
let state_ref = unsafe { &mut *(state as *mut SharedGameState) };
|
||||
if let Some(server) = &mut state.server {
|
||||
server.sync_transfer_stage(state_ref, self, None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
let chat = state.chat.clone();
|
||||
chat.borrow_mut().tick(state, ())?;
|
||||
|
||||
#[cfg(feature = "netplay")]
|
||||
{
|
||||
let state_ref = unsafe { &mut *(state as *mut SharedGameState) };
|
||||
if let Some(server) = &mut state.server {
|
||||
server.process(state_ref);
|
||||
}
|
||||
{
|
||||
let state_ref = unsafe { &mut *(state as *mut SharedGameState) };
|
||||
if let Some(server) = &mut state.server {
|
||||
server.process(state_ref, self);
|
||||
} else if let Some(client) = &mut state.client {
|
||||
client.process(state_ref, self, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if !self.pause_menu.is_paused() && state.replay_state == ReplayState::Playback {
|
||||
self.replay.tick(state, (ctx, &mut self.player1))?;
|
||||
|
@ -1754,18 +1790,18 @@ impl Scene for GameScene {
|
|||
state.next_scene = Some(Box::new(TitleScene::new()));
|
||||
}
|
||||
|
||||
if self.player1.controller.trigger_menu_ok() {
|
||||
if self.local_player().controller.trigger_menu_ok() {
|
||||
state.next_scene = Some(Box::new(TitleScene::new()));
|
||||
}
|
||||
}
|
||||
|
||||
if self.player1.controller.trigger_menu_pause() {
|
||||
if self.local_player().controller.trigger_menu_pause() {
|
||||
self.pause_menu.pause(state);
|
||||
}
|
||||
|
||||
if self.pause_menu.is_paused() {
|
||||
self.pause_menu.tick(state, ctx)?;
|
||||
|
||||
|
||||
if !state.is_netplay() {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -1785,7 +1821,7 @@ impl Scene for GameScene {
|
|||
if !state.control_flags.control_enabled() && !state.textscript_vm.flags.cutscene_skip() =>
|
||||
{
|
||||
state.touch_controls.control_type = TouchControlType::Dialog;
|
||||
if self.player1.controller.skip() {
|
||||
if self.local_player().controller.skip() {
|
||||
self.skip_counter += 1;
|
||||
if self.skip_counter >= CUTSCENE_SKIP_WAIT {
|
||||
state.textscript_vm.flags.set_cutscene_skip(true);
|
||||
|
@ -2290,6 +2326,9 @@ impl Scene for GameScene {
|
|||
)?;
|
||||
}
|
||||
|
||||
let chat = state.chat.clone();
|
||||
chat.borrow().draw(state, ctx, &self.frame)?;
|
||||
|
||||
self.replay.draw(state, ctx, &self.frame)?;
|
||||
|
||||
self.pause_menu.draw(state, ctx)?;
|
||||
|
|
|
@ -4,12 +4,13 @@ use crate::common::Color;
|
|||
use crate::components::background::Background;
|
||||
use crate::frame::Frame;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::ui::Components;
|
||||
use crate::map::Map;
|
||||
use crate::netplay::client::{Client, ServerInfoFuture};
|
||||
use crate::netplay::client::{Client, FutureStruct};
|
||||
use crate::netplay::future::RSFuture;
|
||||
use crate::netplay::protocol::ServerInfo;
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::stage::{BackgroundType, NpcType, Stage, StageData, StageTexturePaths, Tileset};
|
||||
|
@ -22,8 +23,8 @@ pub struct NetplayScene {
|
|||
textures: StageTexturePaths,
|
||||
player_name: String,
|
||||
ip: String,
|
||||
client: Option<Client>,
|
||||
info_future: Option<ServerInfoFuture>,
|
||||
info_future: Option<FutureStruct<ServerInfo>>,
|
||||
join_future: Option<FutureStruct<()>>,
|
||||
}
|
||||
|
||||
impl NetplayScene {
|
||||
|
@ -32,6 +33,7 @@ impl NetplayScene {
|
|||
map: Map { width: 0, height: 0, tiles: vec![], attrib: [0; 0x100], tile_size: TileSize::Tile16x16 },
|
||||
data: StageData {
|
||||
name: "".to_string(),
|
||||
name_jp: "".to_string(),
|
||||
map: "".to_string(),
|
||||
boss_no: 0,
|
||||
tileset: Tileset { name: "0".to_string() },
|
||||
|
@ -53,8 +55,8 @@ impl NetplayScene {
|
|||
textures,
|
||||
player_name: "Quote".to_owned(),
|
||||
ip: "127.0.0.1:21075".to_owned(),
|
||||
client: None,
|
||||
info_future: None,
|
||||
join_future: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,26 +66,50 @@ impl Scene for NetplayScene {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn tick(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
|
||||
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
self.background.tick()?;
|
||||
|
||||
let mut clear = false;
|
||||
if let Some(future) = &mut self.info_future {
|
||||
if let Some(info) = future.poll() {
|
||||
match info.as_ref() {
|
||||
Ok(info) => {
|
||||
log::info!("Got server info: {} {}/{}", info.motd, info.players.0, info.players.1);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to get server info: {}", e);
|
||||
{
|
||||
let mut clear = false;
|
||||
if let Some(future) = &mut self.info_future {
|
||||
if let Some(info) = future.poll() {
|
||||
match info.as_ref() {
|
||||
Ok(info) => {
|
||||
log::info!("Got server info: {} {}/{}", info.motd, info.players.0, info.players.1);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to get server info: {}", e);
|
||||
}
|
||||
}
|
||||
clear = true;
|
||||
}
|
||||
clear = true;
|
||||
}
|
||||
|
||||
if clear {
|
||||
self.info_future = None;
|
||||
}
|
||||
}
|
||||
|
||||
if clear {
|
||||
self.info_future = None;
|
||||
{
|
||||
let mut clear = false;
|
||||
if let Some(future) = &mut self.join_future {
|
||||
if let Some(info) = future.poll() {
|
||||
match info.as_ref() {
|
||||
Ok(_) => {
|
||||
state.next_scene = Some(Box::new(GameScene::new(state, ctx, 0)?));
|
||||
log::info!("Joined.");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to join the game: {}", e);
|
||||
}
|
||||
}
|
||||
clear = true;
|
||||
}
|
||||
}
|
||||
|
||||
if clear {
|
||||
self.join_future = None;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -111,12 +137,14 @@ impl Scene for NetplayScene {
|
|||
InputText::new(frame, "Address", &mut self.ip).build();
|
||||
|
||||
{
|
||||
let _t = frame.begin_disabled(self.client.is_some());
|
||||
let _t = frame.begin_disabled(state.client.is_some());
|
||||
if frame.button("Connect") {
|
||||
match Client::new(&self.ip) {
|
||||
Ok(mut c) => {
|
||||
self.info_future = Some(c.get_server_info());
|
||||
self.client = Some(c);
|
||||
Ok(c) => {
|
||||
let mut c = Box::new(c);
|
||||
//self.info_future = Some(c.get_server_info());
|
||||
self.join_future = Some(c.join(self.player_name.clone()));
|
||||
state.client = Some(c);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to create client: {}", e);
|
||||
|
|
|
@ -30,8 +30,8 @@ use crate::shared_game_state::SharedGameState;
|
|||
use crate::weapon::WeaponType;
|
||||
|
||||
bitfield! {
|
||||
#[derive(Debug, PartialEq, Copy, Clone, bincode::Encode, bincode::Decode)]
|
||||
pub struct TextScriptFlags(u16);
|
||||
impl Debug;
|
||||
pub render, set_render: 0;
|
||||
pub background_visible, set_background_visible: 1;
|
||||
pub fast, set_fast: 4;
|
||||
|
@ -47,7 +47,7 @@ pub enum TextScriptEncoding {
|
|||
ShiftJIS,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone, bincode::Encode, bincode::Decode)]
|
||||
#[repr(u8)]
|
||||
pub enum TextScriptLine {
|
||||
Line1 = 0,
|
||||
|
@ -55,14 +55,14 @@ pub enum TextScriptLine {
|
|||
Line3,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone, bincode::Encode, bincode::Decode)]
|
||||
#[repr(u8)]
|
||||
pub enum ConfirmSelection {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone, bincode::Encode, bincode::Decode)]
|
||||
#[repr(u8)]
|
||||
pub enum ScriptMode {
|
||||
Map,
|
||||
|
@ -82,7 +82,7 @@ impl Not for ConfirmSelection {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone, bincode::Encode, bincode::Decode)]
|
||||
pub enum TextScriptExecutionState {
|
||||
Ended,
|
||||
Running(u16, u32),
|
||||
|
@ -100,7 +100,7 @@ pub enum TextScriptExecutionState {
|
|||
Reset,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Copy, Clone, bincode::Encode, bincode::Decode)]
|
||||
pub enum IllustrationState {
|
||||
Hidden,
|
||||
Shown,
|
||||
|
@ -121,8 +121,8 @@ pub struct TextScriptVM {
|
|||
/// while parsing no one noticed them.
|
||||
pub strict_mode: bool,
|
||||
pub suspend: bool,
|
||||
/// Requires `constants.textscript.reset_invicibility_on_any_script`
|
||||
pub reset_invicibility: bool,
|
||||
/// Requires `constants.textscript.reset_invincibility_on_any_script`
|
||||
pub reset_invincibility: bool,
|
||||
pub numbers: [u16; 4],
|
||||
pub face: u16,
|
||||
pub item: u16,
|
||||
|
@ -132,10 +132,11 @@ pub struct TextScriptVM {
|
|||
pub line_3: Vec<char>,
|
||||
pub current_illustration: Option<String>,
|
||||
pub illustration_state: IllustrationState,
|
||||
prev_char: char,
|
||||
pub prev_char: char,
|
||||
pub substitution_rect_map: HashMap<char, Rect<u16>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, bincode::Encode, bincode::Decode)]
|
||||
pub struct Scripts {
|
||||
/// Head.tsc - shared part of map scripts
|
||||
pub global_script: TextScript,
|
||||
|
@ -189,7 +190,7 @@ impl TextScriptVM {
|
|||
executor_player: TargetPlayer::Player1,
|
||||
strict_mode: false,
|
||||
suspend: true,
|
||||
reset_invicibility: false,
|
||||
reset_invincibility: false,
|
||||
numbers: [0; 4],
|
||||
face: 0,
|
||||
item: 0,
|
||||
|
@ -264,7 +265,7 @@ impl TextScriptVM {
|
|||
|
||||
pub fn start_script(&mut self, event_num: u16) {
|
||||
self.reset();
|
||||
self.reset_invicibility = true;
|
||||
self.reset_invincibility = true;
|
||||
self.state = TextScriptExecutionState::Running(event_num, 0);
|
||||
|
||||
log::info!("Started script: #{:04}", event_num);
|
||||
|
@ -1148,6 +1149,7 @@ impl TextScriptVM {
|
|||
new_scene.frame.wait = game_scene.frame.wait;
|
||||
new_scene.nikumaru = game_scene.nikumaru;
|
||||
new_scene.replay = game_scene.replay.clone();
|
||||
new_scene.local_player = game_scene.local_player;
|
||||
|
||||
let skip = state.textscript_vm.flags.cutscene_skip();
|
||||
state.control_flags.set_tick_world(true);
|
||||
|
@ -1787,6 +1789,7 @@ impl TextScriptVM {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(bincode::Encode, bincode::Decode)]
|
||||
pub struct TextScript {
|
||||
pub(in crate::scripting::tsc) event_map: HashMap<u16, Vec<u8>>,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::{cmp, ops::Div};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use bitvec::vec::BitVec;
|
||||
use chrono::{Datelike, Local};
|
||||
|
@ -7,6 +9,7 @@ use chrono::{Datelike, Local};
|
|||
use crate::bmfont_renderer::BMFontRenderer;
|
||||
use crate::caret::{Caret, CaretType};
|
||||
use crate::common::{ControlFlags, Direction, FadeState};
|
||||
use crate::components::chat::Chat;
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::framework::backend::BackendTexture;
|
||||
|
@ -250,7 +253,7 @@ pub enum ReplayState {
|
|||
Playback,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, bincode::Decode, bincode::Encode)]
|
||||
pub enum TileSize {
|
||||
Tile8x8,
|
||||
Tile16x16,
|
||||
|
@ -283,6 +286,7 @@ pub struct SharedGameState {
|
|||
/// RNG used by graphics effects that aren't dependent on game's state.
|
||||
pub effect_rng: XorShift,
|
||||
pub tile_size: TileSize,
|
||||
pub chat: Rc<RefCell<Chat>>,
|
||||
pub quake_counter: u16,
|
||||
pub super_quake_counter: u16,
|
||||
pub teleporter_slots: Vec<(u16, u16)>,
|
||||
|
@ -424,6 +428,7 @@ impl SharedGameState {
|
|||
game_rng: XorShift::new(chrono::Local::now().timestamp() as i32),
|
||||
effect_rng: XorShift::new(123),
|
||||
tile_size: TileSize::Tile16x16,
|
||||
chat: Rc::new(RefCell::new(Chat::new())),
|
||||
quake_counter: 0,
|
||||
super_quake_counter: 0,
|
||||
teleporter_slots: Vec::with_capacity(8),
|
||||
|
|
|
@ -255,7 +255,13 @@ impl SoundManager {
|
|||
settings: &Settings,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
if self.current_song_id == song_id || self.no_audio {
|
||||
if self.current_song_id == song_id {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.no_audio {
|
||||
self.prev_song_id = self.current_song_id;
|
||||
self.current_song_id = song_id;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -429,6 +435,10 @@ impl SoundManager {
|
|||
self.current_song_id
|
||||
}
|
||||
|
||||
pub fn prev_song(&self) -> usize {
|
||||
self.prev_song_id
|
||||
}
|
||||
|
||||
pub fn set_sample_params_from_file<R: io::Read>(&mut self, id: u8, data: R) -> GameResult {
|
||||
if self.no_audio {
|
||||
return Ok(());
|
||||
|
|
16
src/stage.rs
16
src/stage.rs
|
@ -16,7 +16,7 @@ use crate::map::{Map, NPCData};
|
|||
use crate::scripting::tsc::text_script::TextScript;
|
||||
use crate::GameError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, bincode::Decode, bincode::Encode)]
|
||||
pub struct NpcType {
|
||||
name: String,
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ impl NpcType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, bincode::Decode, bincode::Encode)]
|
||||
pub struct Tileset {
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ impl Tileset {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, bincode::Decode, bincode::Encode)]
|
||||
pub struct Background {
|
||||
name: String,
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ impl Background {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
#[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone, bincode::Decode, bincode::Encode)]
|
||||
pub enum BackgroundType {
|
||||
TiledStatic,
|
||||
TiledParallax,
|
||||
|
@ -132,7 +132,7 @@ impl From<u8> for BackgroundType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, bincode::Decode, bincode::Encode)]
|
||||
pub enum PxPackScroll {
|
||||
Normal,
|
||||
ThreeQuarters,
|
||||
|
@ -181,7 +181,7 @@ impl PxPackScroll {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct PxPackStageData {
|
||||
pub tileset_fg: String,
|
||||
pub tileset_mg: String,
|
||||
|
@ -196,7 +196,7 @@ pub struct PxPackStageData {
|
|||
pub offset_bg: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, bincode::Decode, bincode::Encode)]
|
||||
pub struct StageData {
|
||||
pub name: String,
|
||||
pub name_jp: String,
|
||||
|
@ -538,7 +538,7 @@ impl StageData {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone, bincode::Decode, bincode::Encode)]
|
||||
pub struct Stage {
|
||||
pub map: Map,
|
||||
pub data: StageData,
|
||||
|
|
Loading…
Reference in New Issue