This commit is contained in:
Alula 2022-08-01 02:32:56 +02:00
parent 4b147527bc
commit 6656240c8d
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
33 changed files with 1392 additions and 361 deletions

View File

@ -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"] }

View File

@ -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(

View File

@ -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,

73
src/components/chat.rs Normal file
View File

@ -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(())
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -47,6 +47,8 @@ mod i18n;
mod input;
mod inventory;
mod live_debugger;
#[cfg(feature = "netplay")]
mod netplay;
mod macros;
mod map;
mod menu;

View File

@ -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));

View File

@ -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,

View File

@ -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,
}),
);
}
}
}

51
src/netplay/common.rs Normal file
View File

@ -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)));
}
}
}

View File

@ -1,4 +1,5 @@
pub mod client;
pub mod common;
pub mod future;
pub mod protocol;
pub mod server;

View File

@ -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()
}
}

View File

@ -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);
}
}

View File

@ -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
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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 {

View File

@ -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)?;

View File

@ -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);

View File

@ -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>>,
}

View File

@ -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),

View File

@ -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(());

View File

@ -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,