1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-01-06 02:56:41 +00:00

netplay shit, visual tweaks

This commit is contained in:
Alula 2021-10-14 06:54:11 +02:00
parent 01e35a09eb
commit 68cf299e96
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
25 changed files with 418 additions and 106 deletions

View file

@ -17,8 +17,8 @@ bench = false
required-features = ["exe"]
[profile.release]
lto = 'thin'
panic = 'abort'
lto = "off"
panic = "abort"
[profile.dev.package."*"]
opt-level = 3
@ -33,13 +33,13 @@ category = "Game"
osx_minimum_system_version = "10.12"
[features]
default = ["scripting", "backend-sdl", "render-opengl", "ogg-playback", "exe"]
default = ["scripting", "backend-sdl", "render-opengl", "ogg-playback", "exe", "netplay"]
ogg-playback = ["lewton"]
backend-sdl = ["sdl2", "sdl2-sys"]
backend-glutin = ["winit", "glutin", "render-opengl"]
render-opengl = []
scripting = ["lua-ffi"]
netplay = []
netplay = ["tokio", "serde_cbor"]
editor = []
hooks = ["libc"]
exe = []
@ -73,11 +73,12 @@ sdl2 = { version = "=0.34.2", optional = true, features = ["unsafe_textures", "b
sdl2-sys = { version = "=0.34.2", optional = true, features = ["bundled", "static-link"] }
serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_cbor = { version = "0.11.2", optional = true }
serde_json = "1.0"
serde_yaml = "0.8"
simple_logger = { version = "1.13" }
strum = "0.20"
strum_macros = "0.20"
tokio = { version = "1.12.0", features = ["net"], optional = true }
# remove and replace when drain_filter is in stable
vec_mut_scan = "0.4"
webbrowser = "0.5.5"

View file

@ -6,7 +6,7 @@ plugins {
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
//ndkVersion "21.3.6528147"
ndkVersion "22.1.7171670"
defaultConfig {
applicationId "io.github.doukutsu_rs"
@ -65,6 +65,7 @@ dependencies {
}
println("cargo target: ${project.buildDir.getAbsolutePath()}/rust-target")
println("ndk dir: ${android.ndkDirectory}")
cargoNdk {
targets = [

View file

@ -1,5 +1,7 @@
#[cfg(target_os = "android")]
#[cfg_attr(target_os = "android", ndk_glue::main())]
pub fn android_main() {
doukutsu_rs::init().unwrap();
let options = doukutsu_rs::LaunchOptions { server_mode: false };
doukutsu_rs::init(options).unwrap();
}

View file

@ -260,10 +260,12 @@ pub struct Rect<T: Num + PartialOrd + Copy = isize> {
}
impl<T: Num + PartialOrd + Copy> Rect<T> {
#[inline(always)]
pub fn new(left: T, top: T, right: T, bottom: T) -> Rect<T> {
Rect { left, top, right, bottom }
}
#[inline(always)]
pub fn new_size(x: T, y: T, width: T, height: T) -> Rect<T> {
Rect { left: x, top: y, right: x.add(width), bottom: y.add(height) }
}

View file

@ -18,6 +18,7 @@ pub struct HUD {
max_ammo: u16,
xp: u16,
max_xp: u16,
xp_bar_counter: u8,
max_level: bool,
life: u16,
max_life: u16,
@ -42,6 +43,7 @@ impl HUD {
max_ammo: 0,
xp: 0,
max_xp: 0,
xp_bar_counter: 0,
max_level: false,
life: 0,
max_life: 0,
@ -66,6 +68,7 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
self.max_ammo = max_ammo;
self.xp = xp;
self.max_xp = max_xp;
self.xp_bar_counter = if player.xp_counter != 0 { self.xp_bar_counter.wrapping_add(1) } else { 0 };
self.max_level = max_level;
self.life = player.life;
@ -138,7 +141,7 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
pos_x -= 48;
}
let wtype = unsafe { *self.weapon_types.get_unchecked(a) };
let wtype = self.weapon_types[a];
if wtype != 0 {
rect = Rect::new_size(pos_x + weapon_offset - 4, 16 - 4, 24, 24);
@ -203,6 +206,10 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(0, 80, bar_width, 8));
}
if (self.xp_bar_counter & 0x02) != 0 {
batch.add_rect(bar_offset + weap_x + 24.0, 32.0 + top, &Rect::new_size(40, 80, 40, 8));
}
if self.max_life != 0 {
let yellow_bar_width = (self.life_bar as f32 / self.max_life as f32 * 39.0) as u16;
let bar_width = (self.life as f32 / self.max_life as f32 * 39.0) as u16;
@ -252,8 +259,7 @@ impl GameEntity<(&Player, &mut Inventory)> for HUD {
pos_x -= 96.0 + self.weapon_count as f32 * 16.0;
}
let wtype = unsafe { *self.weapon_types.get_unchecked(a) };
let wtype = self.weapon_types[a];
if wtype != 0 {
rect.left = wtype as u16 * 16;
rect.right = rect.left + 16;

View file

@ -480,7 +480,7 @@ impl EngineConstants {
question_right_rect: Rect { left: 48, top: 64, right: 64, bottom: 80 },
},
world: WorldConsts { snack_rect: Rect { left: 256, top: 48, right: 272, bottom: 64 } },
npc: serde_yaml::from_str("dummy: \"lol\"").unwrap(),
npc: serde_json::from_str("{}").unwrap(),
weapon: WeaponConsts {
bullet_table: vec![
// Null

View file

@ -75,7 +75,11 @@ pub trait BackendTexture {
}
#[allow(unreachable_code)]
pub fn init_backend() -> GameResult<Box<dyn Backend>> {
pub fn init_backend(headless: bool) -> GameResult<Box<dyn Backend>> {
if headless {
return crate::framework::backend_null::NullBackend::new();
}
#[cfg(all(feature = "backend-glutin"))]
{
return crate::framework::backend_glutin::GlutinBackend::new();

View file

@ -50,9 +50,9 @@ impl BackendEventLoop for NullEventLoop {
game.loops = 0;
state_ref.frame_time = 0.0;
}
std::thread::sleep(std::time::Duration::from_millis(5));
std::thread::sleep(std::time::Duration::from_millis(10));
//game.draw(ctx).unwrap();
game.draw(ctx).unwrap();
}
}

View file

@ -5,6 +5,7 @@ use crate::framework::keyboard::KeyboardContext;
use crate::Game;
pub struct Context {
pub headless: bool,
pub(crate) filesystem: Filesystem,
pub(crate) renderer: Option<Box<dyn BackendRenderer>>,
pub(crate) keyboard_context: KeyboardContext,
@ -15,6 +16,7 @@ pub struct Context {
impl Context {
pub fn new() -> Context {
Context {
headless: false,
filesystem: Filesystem::new(),
renderer: None,
keyboard_context: KeyboardContext::new(),
@ -24,7 +26,7 @@ impl Context {
}
pub fn run(&mut self, game: &mut Game) -> GameResult {
let backend = init_backend()?;
let backend = init_backend(self.headless)?;
let mut event_loop = backend.create_event_loop()?;
self.renderer = Some(event_loop.new_renderer()?);

View file

@ -93,9 +93,9 @@ impl From<std::string::FromUtf8Error> for GameError {
}
}
impl From<serde_yaml::Error> for GameError {
fn from(e: serde_yaml::Error) -> Self {
let errstr = format!("Yaml error: {:?}", e);
impl From<serde_json::Error> for GameError {
fn from(e: serde_json::Error) -> Self {
let errstr = format!("JSON error: {:?}", e);
GameError::ParseError(errstr)
}
}

View file

@ -67,6 +67,10 @@ mod text_script;
mod texture_set;
mod weapon;
pub struct LaunchOptions {
pub server_mode: bool,
}
lazy_static! {
pub static ref GAME_SUSPENDED: Mutex<bool> = Mutex::new(false);
}
@ -142,6 +146,12 @@ impl Game {
fn draw(&mut self, ctx: &mut Context) -> GameResult {
let state_ref = unsafe { &mut *self.state.get() };
if ctx.headless {
self.loops = 0;
state_ref.frame_time = 1.0;
return Ok(());
}
if state_ref.timing_mode != TimingMode::FrameSynchronized {
let mut elapsed = self.start_time.elapsed().as_nanos();
@ -185,7 +195,7 @@ impl Game {
}
}
pub fn init() -> GameResult {
pub fn init(options: LaunchOptions) -> GameResult {
let _ = simple_logger::init_with_level(log::Level::Info);
#[cfg(not(target_os = "android"))]
@ -264,6 +274,11 @@ pub fn init() -> GameResult {
}
}
if options.server_mode {
log::info!("Running in server mode...");
context.headless = true;
}
let game = UnsafeCell::new(Game::new(&mut context)?);
let state_ref = unsafe { &mut *((&mut *game.get()).state.get()) };
#[cfg(feature = "scripting")]

View file

@ -3,7 +3,18 @@
use std::process::exit;
fn main() {
let result = doukutsu_rs::init();
let args = std::env::args();
let mut options = doukutsu_rs::LaunchOptions {
server_mode: false
};
for arg in args {
if arg == "--server-mode" {
options.server_mode = true;
}
}
let result = doukutsu_rs::init(options);
#[cfg(target_os = "windows")]
unsafe {
@ -28,7 +39,7 @@ fn main() {
}
if let Err(e) = result {
println!("Initialization error: {}", e);
eprintln!("Initialization error: {}", e);
exit(1);
}
}

View file

@ -0,0 +1,3 @@
pub mod packets;
pub mod server;
pub mod server_config;

8
src/netplay/packets.rs Normal file
View file

@ -0,0 +1,8 @@
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(tag = "i")]
pub enum DRSPacket {
#[serde(rename = "\x01")]
Ping(u16),
#[serde(rename = "\x02")]
Pong(u16),
}

34
src/netplay/server.rs Normal file
View file

@ -0,0 +1,34 @@
use tokio::net::UdpSocket;
use crate::framework::error::GameResult;
use crate::netplay::server_config::ServerConfiguration;
pub struct Server {
}
impl Server {
pub fn start(config: ServerConfiguration) -> GameResult<Server> {
let context = ServerContext::new(config);
Ok(Server {
})
}
}
struct ServerContext {
config: ServerConfiguration,
}
impl ServerContext {
pub fn new(config: ServerConfiguration) -> ServerContext {
ServerContext {
config
}
}
pub async fn run(self) {
let socket = UdpSocket::bind(&self.config.bind_to).await.unwrap();
}
}

View file

@ -0,0 +1,10 @@
#[derive(serde::Serialize, serde::Deserialize)]
pub struct ServerConfiguration {
#[serde(default = "default_bind")]
pub bind_to: String,
}
// 'RS' = 0x5253 = 21075
fn default_bind() -> String {
"0.0.0.0:21075".to_string()
}

View file

@ -4,7 +4,8 @@ use num_derive::FromPrimitive;
use num_traits::clamp;
use crate::caret::CaretType;
use crate::common::{Condition, Direction, Equipment, Flag, interpolate_fix9_scale, Rect};
use crate::common::{interpolate_fix9_scale, Condition, Direction, Equipment, Flag, Rect};
use crate::components::number_popup::NumberPopup;
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::framework::context::Context;
@ -13,13 +14,13 @@ use crate::input::dummy_player_controller::DummyPlayerController;
use crate::input::player_controller::PlayerController;
use crate::npc::list::NPCList;
use crate::npc::NPC;
use crate::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin};
use crate::player::skin::basic::BasicPlayerSkin;
use crate::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin};
use crate::rng::RNG;
use crate::shared_game_state::SharedGameState;
use crate::components::number_popup::NumberPopup;
mod player_hit;
pub mod player_list;
pub mod skin;
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive)]
@ -42,6 +43,14 @@ impl TargetPlayer {
}
}
#[derive(PartialEq, Eq, Copy, Clone)]
enum BoosterSwitch {
None,
Up,
Sides,
Down,
}
#[derive(Clone)]
pub struct Player {
pub x: i32,
@ -68,6 +77,7 @@ pub struct Player {
pub up: bool,
pub down: bool,
pub shock_counter: u8,
pub xp_counter: u8,
pub current_weapon: u8,
pub stars: u8,
pub damage: u16,
@ -79,7 +89,7 @@ pub struct Player {
weapon_offset_y: i8,
splash: bool,
tick: u8,
booster_switch: u8,
booster_switch: BoosterSwitch,
damage_counter: u16,
damage_taken: i16,
pub anim_num: u16,
@ -121,8 +131,9 @@ impl Player {
current_weapon: 0,
weapon_offset_y: 0,
shock_counter: 0,
xp_counter: 0,
tick: 0,
booster_switch: 0,
booster_switch: BoosterSwitch::None,
stars: 0,
damage: 0,
air_counter: 0,
@ -185,12 +196,12 @@ impl Player {
self.question = false;
if !state.control_flags.control_enabled() {
self.booster_switch = 0;
self.booster_switch = BoosterSwitch::None;
}
// ground movement
if self.flags.hit_bottom_wall() || self.flags.hit_right_slope() || self.flags.hit_left_slope() {
self.booster_switch = 0;
self.booster_switch = BoosterSwitch::None;
if state.settings.infinite_booster {
self.booster_fuel = u32::MAX;
@ -258,31 +269,30 @@ impl Player {
if state.control_flags.control_enabled() {
if self.controller.trigger_jump() && self.booster_fuel != 0 {
if self.equip.has_booster_0_8() {
self.booster_switch = 1;
self.booster_switch = BoosterSwitch::Sides;
if self.vel_y > 0x100 {
// 0.5fix9
self.vel_y /= 2;
}
} else if state.settings.infinite_booster || self.equip.has_booster_2_0() {
if self.controller.move_up() {
self.booster_switch = 2;
self.booster_switch = BoosterSwitch::Up;
self.vel_x = 0;
self.vel_y = state.constants.booster.b2_0_up;
} else if self.controller.move_left() {
self.booster_switch = 1;
self.booster_switch = BoosterSwitch::Sides;
self.vel_x = state.constants.booster.b2_0_left;
self.vel_y = 0;
} else if self.controller.move_right() {
self.booster_switch = 1;
self.booster_switch = BoosterSwitch::Sides;
self.vel_x = state.constants.booster.b2_0_right;
self.vel_y = 0;
} else if self.controller.move_down() {
self.booster_switch = 3;
self.booster_switch = BoosterSwitch::Down;
self.vel_x = 0;
self.vel_y = state.constants.booster.b2_0_down;
} else {
self.booster_switch = 2;
self.booster_switch = BoosterSwitch::Up;
self.vel_x = 0;
self.vel_y = state.constants.booster.b2_0_up_nokey;
}
@ -307,18 +317,18 @@ impl Player {
}
if (state.settings.infinite_booster || self.equip.has_booster_2_0())
&& self.booster_switch != 0
&& self.booster_switch != BoosterSwitch::None
&& (!self.controller.jump() || self.booster_fuel == 0)
{
match self.booster_switch {
1 => self.vel_x /= 2,
2 => self.vel_y /= 2,
_ => {}
BoosterSwitch::Sides => self.vel_x /= 2,
BoosterSwitch::Up => self.vel_y /= 2,
_ => (),
}
}
if self.booster_fuel == 0 || !self.controller.jump() {
self.booster_switch = 0;
self.booster_switch = BoosterSwitch::None;
}
}
@ -348,7 +358,7 @@ impl Player {
}
// booster losing fuel
if self.booster_switch != 0 && self.booster_fuel != 0 {
if self.booster_switch != BoosterSwitch::None && self.booster_fuel != 0 {
self.booster_fuel -= 1;
}
@ -367,18 +377,18 @@ impl Player {
self.vel_y += 0x55;
}
if (state.settings.infinite_booster || self.equip.has_booster_2_0()) && self.booster_switch != 0 {
if (state.settings.infinite_booster || self.equip.has_booster_2_0()) && self.booster_switch != BoosterSwitch::None {
match self.booster_switch {
1 => {
BoosterSwitch::Sides => {
if self.flags.hit_left_wall() || self.flags.hit_right_wall() {
self.vel_y = -0x100; // -0.5fix9
self.vel_y = -0x100;
}
if self.direction == Direction::Left {
self.vel_x -= 0x20; // 0.1fix9
self.vel_x -= 0x20;
}
if self.direction == Direction::Right {
self.vel_x += 0x20; // 0.1fix9
self.vel_x += 0x20;
}
if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 {
@ -393,7 +403,7 @@ impl Player {
state.sound_manager.play_sfx(113);
}
}
2 => {
BoosterSwitch::Up => {
self.vel_y -= 0x20;
if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 {
@ -401,7 +411,7 @@ impl Player {
state.sound_manager.play_sfx(113);
}
}
3 if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 => {
BoosterSwitch::Down if self.controller.trigger_jump() || self.booster_fuel % 3 == 1 => {
state.create_caret(self.x, self.y + 0xc00, CaretType::Exhaust, Direction::Up);
state.sound_manager.play_sfx(113);
}
@ -409,7 +419,7 @@ impl Player {
}
} else if self.flags.force_up() {
self.vel_y += physics.gravity_air;
} else if self.equip.has_booster_0_8() && self.booster_switch != 0 && self.vel_y > -0x400 {
} else if self.equip.has_booster_0_8() && self.booster_switch != BoosterSwitch::None && self.vel_y > -0x400 {
self.vel_y -= 0x20;
if self.booster_fuel % 3 == 0 {
@ -725,13 +735,16 @@ impl GameEntity<&NPCList> for Player {
self.damage_counter -= 1;
}
if self.xp_counter != 0 {
self.xp_counter -= 1;
}
if self.shock_counter != 0 {
self.shock_counter -= 1;
} else if self.damage_taken != 0 {
self.damage_taken = 0;
}
// todo: add additional control modes like NXEngine has such as noclip?
match self.control_mode {
ControlMode::Normal => self.tick_normal(state, npc_list)?,
ControlMode::IronHead => self.tick_ironhead(state)?,
@ -813,7 +826,8 @@ impl GameEntity<&NPCList> for Player {
self.prev_y - self.display_bounds.top as i32,
self.y - self.display_bounds.top as i32,
state.frame_time,
) + self.weapon_offset_y as f32 + gun_off_y as f32
) + self.weapon_offset_y as f32
+ gun_off_y as f32
- frame_y,
&self.weapon_rect,
);

13
src/player/player_list.rs Normal file
View file

@ -0,0 +1,13 @@
use crate::player::Player;
pub struct RemotePlayerList {
}
impl RemotePlayerList {
pub fn new() -> RemotePlayerList {
RemotePlayerList {
}
}
}

View file

@ -33,7 +33,7 @@ use crate::scene::Scene;
use crate::shared_game_state::{SharedGameState, TileSize};
use crate::stage::{BackgroundType, Stage};
use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
use crate::texture_set::SizedBatch;
use crate::texture_set::SpriteBatch;
use crate::weapon::bullet::BulletManager;
use crate::weapon::{Weapon, WeaponType};
@ -661,7 +661,7 @@ impl GameScene {
Ok(())
}
fn draw_light(&self, x: f32, y: f32, size: f32, color: (u8, u8, u8), batch: &mut SizedBatch) {
fn draw_light(&self, x: f32, y: f32, size: f32, color: (u8, u8, u8), batch: &mut Box<dyn SpriteBatch>) {
batch.add_rect_scaled_tinted(
x - size * 32.0,
y - size * 32.0,
@ -680,7 +680,7 @@ impl GameScene {
(br, bg, bb): (u8, u8, u8),
att: f32,
angle: Range<i32>,
batch: &mut SizedBatch,
batch: &mut Box<dyn SpriteBatch>,
) {
let px = world_point_x as f32 / 512.0;
let py = world_point_y as f32 / 512.0;
@ -2001,11 +2001,12 @@ impl Scene for GameScene {
}
}
self.inventory_dim += 0.1 * if state.textscript_vm.mode == ScriptMode::Inventory {
state.frame_time as f32
} else {
-(state.frame_time as f32)
};
self.inventory_dim += 0.1
* if state.textscript_vm.mode == ScriptMode::Inventory {
state.frame_time as f32
} else {
-(state.frame_time as f32)
};
self.inventory_dim = self.inventory_dim.clamp(0.0, 1.0);

View file

@ -37,7 +37,12 @@ impl LoadingScene {
let stage_select_script = TextScript::load_from(stage_select_tsc, &state.constants)?;
state.textscript_vm.set_stage_select_script(stage_select_script);
state.start_intro(ctx)?;
if ctx.headless {
log::info!("Headless mode detected, skipping intro and loading last saved game.");
state.load_or_start_game(ctx)?;
} else {
state.start_intro(ctx)?;
}
Ok(())
}

View file

@ -43,7 +43,7 @@ pub struct Settings {
fn default_true() -> bool { true }
#[inline(always)]
fn current_version() -> u32 { 2 }
fn current_version() -> u32 { 3 }
#[inline(always)]
fn default_interpolation() -> InterpolationMode { InterpolationMode::Linear }
@ -55,9 +55,9 @@ fn default_speed() -> f64 {
impl Settings {
pub fn load(ctx: &Context) -> GameResult<Settings> {
if let Ok(file) = user_open(ctx, "/settings.yml") {
match serde_yaml::from_reader::<_, Settings>(file) {
Ok(settings) => return Ok(settings),
if let Ok(file) = user_open(ctx, "/settings.json") {
match serde_json::from_reader::<_, Settings>(file) {
Ok(settings) => return Ok(settings.upgrade()),
Err(err) => log::warn!("Failed to deserialize settings: {}", err),
}
}
@ -65,9 +65,25 @@ impl Settings {
Ok(Settings::default())
}
fn upgrade(mut self) -> Self {
let initial_version = self.version;
if self.version == 2 {
self.version = 3;
self.light_cone = true;
}
if self.version != initial_version {
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
}
self
}
pub fn save(&self, ctx: &Context) -> GameResult {
let file = user_create(ctx, "/settings.yml")?;
serde_yaml::to_writer(file, self)?;
let file = user_create(ctx, "/settings.json")?;
serde_json::to_writer(file, self)?;
Ok(())
}
@ -87,7 +103,7 @@ impl Settings {
impl Default for Settings {
fn default() -> Self {
Settings {
version: 2,
version: current_version(),
seasonal_textures: true,
original_textures: false,
shader_effects: true,

View file

@ -41,6 +41,7 @@ pub struct SoundManager {
tx: Sender<PlaybackMessage>,
prev_song_id: usize,
current_song_id: usize,
no_audio: bool,
}
enum SongFormat {
@ -64,6 +65,12 @@ impl SoundManager {
pub fn new(ctx: &mut Context) -> GameResult<SoundManager> {
let (tx, rx): (Sender<PlaybackMessage>, Receiver<PlaybackMessage>) = mpsc::channel();
if ctx.headless {
log::info!("Running in headless mode, skipping initialization.");
return Ok(SoundManager { tx: tx.clone(), prev_song_id: 0, current_song_id: 0, no_audio: true });
}
let host = cpal::default_host();
let device =
host.default_output_device().ok_or_else(|| AudioError(str!("Error initializing audio device.")))?;
@ -81,22 +88,34 @@ impl SoundManager {
}
});
Ok(SoundManager { tx: tx.clone(), prev_song_id: 0, current_song_id: 0 })
Ok(SoundManager { tx: tx.clone(), prev_song_id: 0, current_song_id: 0, no_audio: false })
}
pub fn play_sfx(&self, id: u8) {
if self.no_audio {
return;
}
let _ = self.tx.send(PlaybackMessage::PlaySample(id));
}
pub fn loop_sfx(&self, id: u8) {
if self.no_audio {
return;
}
let _ = self.tx.send(PlaybackMessage::LoopSample(id));
}
pub fn stop_sfx(&self, id: u8) {
if self.no_audio {
return;
}
let _ = self.tx.send(PlaybackMessage::StopSample(id));
}
pub fn set_org_interpolation(&self, interpolation: InterpolationMode) {
if self.no_audio {
return;
}
let _ = self.tx.send(PlaybackMessage::SetOrgInterpolation(interpolation));
}
@ -107,7 +126,7 @@ impl SoundManager {
settings: &Settings,
ctx: &mut Context,
) -> GameResult {
if self.current_song_id == song_id {
if self.current_song_id == song_id || self.no_audio {
return Ok(());
}
@ -157,7 +176,8 @@ impl SoundManager {
self.prev_song_id = self.current_song_id;
self.current_song_id = song_id;
self.tx.send(PlaybackMessage::SetOrgInterpolation(settings.organya_interpolation))?;
self.tx
.send(PlaybackMessage::SetOrgInterpolation(settings.organya_interpolation))?;
self.tx.send(PlaybackMessage::SaveState)?;
self.tx.send(PlaybackMessage::PlayOrganyaSong(Box::new(org)))?;
@ -237,6 +257,10 @@ impl SoundManager {
}
pub fn save_state(&mut self) -> GameResult {
if self.no_audio {
return Ok(());
}
self.tx.send(PlaybackMessage::SaveState)?;
self.prev_song_id = self.current_song_id;
@ -244,6 +268,10 @@ impl SoundManager {
}
pub fn restore_state(&mut self) -> GameResult {
if self.no_audio {
return Ok(());
}
self.tx.send(PlaybackMessage::RestoreState)?;
self.current_song_id = self.prev_song_id;
@ -251,9 +279,14 @@ impl SoundManager {
}
pub fn set_speed(&self, speed: f32) -> GameResult {
if self.no_audio {
return Ok(());
}
if speed <= 0.0 {
return Err(InvalidValue(str!("Speed must be bigger than 0.0!")));
}
self.tx.send(PlaybackMessage::SetSpeed(speed))?;
Ok(())
@ -264,6 +297,10 @@ impl SoundManager {
}
pub fn set_sample_params_from_file<R: io::Read>(&self, id: u8, data: R) -> GameResult {
if self.no_audio {
return Ok(());
}
let mut reader = BufReader::new(data).lines();
let mut params = PixToneParameters::empty();
@ -325,6 +362,10 @@ impl SoundManager {
}
pub fn set_sample_params(&self, id: u8, params: PixToneParameters) -> GameResult {
if self.no_audio {
return Ok(());
}
self.tx.send(PlaybackMessage::SetSampleParams(id, params))?;
Ok(())

View file

@ -1845,8 +1845,6 @@ impl TextScript {
/// Compiles a decrypted text script data into internal bytecode.
pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult<TextScript> {
log::info!("data: {}", String::from_utf8_lossy(data));
let mut event_map = HashMap::new();
let mut iter = data.iter().copied().peekable();
let mut last_event = 0;

View file

@ -20,6 +20,117 @@ use crate::str;
pub static mut I_MAG: f32 = 1.0;
pub static mut G_MAG: f32 = 1.0;
pub trait SpriteBatch {
fn width(&self) -> usize;
fn height(&self) -> usize;
fn dimensions(&self) -> (usize, usize);
fn real_dimensions(&self) -> (usize, usize);
fn scale(&self) -> (f32, f32);
fn has_glow_layer(&self) -> bool;
fn has_normal_layer(&self) -> bool;
fn to_rect(&self) -> common::Rect<usize>;
fn clear(&mut self);
fn add(&mut self, x: f32, y: f32);
fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<u16>);
fn add_rect_flip(&mut self, x: f32, y: f32, flip_x: bool, flip_y: bool, rect: &common::Rect<u16>);
fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>);
fn add_rect_scaled(&mut self, x: f32, y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>);
fn add_rect_scaled_tinted(
&mut self,
x: f32,
y: f32,
color: (u8, u8, u8, u8),
scale_x: f32,
scale_y: f32,
rect: &common::Rect<u16>,
);
fn draw(&mut self, ctx: &mut Context) -> GameResult;
fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult;
}
pub struct DummyBatch;
impl SpriteBatch for DummyBatch {
fn width(&self) -> usize {
1
}
fn height(&self) -> usize {
1
}
fn dimensions(&self) -> (usize, usize) {
(1, 1)
}
fn real_dimensions(&self) -> (usize, usize) {
(1, 1)
}
fn scale(&self) -> (f32, f32) {
(1.0, 1.0)
}
fn has_glow_layer(&self) -> bool {
false
}
fn has_normal_layer(&self) -> bool {
false
}
fn to_rect(&self) -> Rect<usize> {
Rect::new(0, 0, 1, 1)
}
fn clear(&mut self) {}
fn add(&mut self, _x: f32, _y: f32) {}
fn add_rect(&mut self, _x: f32, _y: f32, _rect: &Rect<u16>) {}
fn add_rect_flip(&mut self, _x: f32, _y: f32, _flip_x: bool, _flip_y: bool, _rect: &Rect<u16>) {}
fn add_rect_tinted(&mut self, _x: f32, _y: f32, _color: (u8, u8, u8, u8), _rect: &Rect<u16>) {}
fn add_rect_scaled(&mut self, _x: f32, _y: f32, _scale_x: f32, _scale_y: f32, _rect: &Rect<u16>) {}
fn add_rect_scaled_tinted(
&mut self,
_x: f32,
_y: f32,
_color: (u8, u8, u8, u8),
_scale_x: f32,
_scale_y: f32,
_rect: &Rect<u16>,
) {
}
fn draw(&mut self, _ctx: &mut Context) -> GameResult {
Ok(())
}
fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult {
Ok(())
}
}
pub struct SizedBatch {
batch: Box<dyn BackendTexture>,
width: usize,
@ -32,53 +143,53 @@ pub struct SizedBatch {
has_normal_layer: bool,
}
impl SizedBatch {
impl SpriteBatch for SizedBatch {
#[inline(always)]
pub fn width(&self) -> usize {
fn width(&self) -> usize {
self.width
}
#[inline(always)]
pub fn height(&self) -> usize {
fn height(&self) -> usize {
self.height
}
#[inline(always)]
pub fn dimensions(&self) -> (usize, usize) {
fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
#[inline(always)]
pub fn real_dimensions(&self) -> (usize, usize) {
fn real_dimensions(&self) -> (usize, usize) {
(self.real_width, self.real_height)
}
#[inline(always)]
pub fn scale(&self) -> (f32, f32) {
fn scale(&self) -> (f32, f32) {
(self.scale_x, self.scale_y)
}
#[inline(always)]
pub fn has_glow_layer(&self) -> bool {
fn has_glow_layer(&self) -> bool {
self.has_glow_layer
}
#[inline(always)]
pub fn has_normal_layer(&self) -> bool {
fn has_normal_layer(&self) -> bool {
self.has_normal_layer
}
#[inline(always)]
pub fn to_rect(&self) -> common::Rect<usize> {
fn to_rect(&self) -> common::Rect<usize> {
common::Rect::<usize>::new(0, 0, self.width, self.height)
}
#[inline(always)]
pub fn clear(&mut self) {
fn clear(&mut self) {
self.batch.clear();
}
pub fn add(&mut self, x: f32, y: f32) {
fn add(&mut self, x: f32, y: f32) {
let mag = unsafe { I_MAG };
self.batch.add(SpriteBatchCommand::DrawRect(
@ -93,11 +204,11 @@ impl SizedBatch {
}
#[inline(always)]
pub fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<u16>) {
fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<u16>) {
self.add_rect_scaled(x, y, 1.0, 1.0, rect)
}
pub fn add_rect_flip(&mut self, x: f32, y: f32, flip_x: bool, flip_y: bool, rect: &common::Rect<u16>) {
fn add_rect_flip(&mut self, x: f32, y: f32, flip_x: bool, flip_y: bool, rect: &common::Rect<u16>) {
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
return;
}
@ -123,11 +234,11 @@ impl SizedBatch {
}
#[inline(always)]
pub fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>) {
fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>) {
self.add_rect_scaled_tinted(x, y, color, 1.0, 1.0, rect)
}
pub fn add_rect_scaled(&mut self, x: f32, y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
fn add_rect_scaled(&mut self, x: f32, y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
if (rect.right.saturating_sub(rect.left)) == 0 || (rect.bottom.saturating_sub(rect.top)) == 0 {
return;
}
@ -150,7 +261,7 @@ impl SizedBatch {
));
}
pub fn add_rect_scaled_tinted(
fn add_rect_scaled_tinted(
&mut self,
x: f32,
y: f32,
@ -178,11 +289,11 @@ impl SizedBatch {
}
#[inline(always)]
pub fn draw(&mut self, ctx: &mut Context) -> GameResult {
fn draw(&mut self, ctx: &mut Context) -> GameResult {
self.draw_filtered(FilterMode::Nearest, ctx)
}
pub fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult {
fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult {
//self.batch.set_filter(filter);
self.batch.draw()?;
self.batch.clear();
@ -191,13 +302,18 @@ impl SizedBatch {
}
pub struct TextureSet {
pub tex_map: HashMap<String, SizedBatch>,
pub tex_map: HashMap<String, Box<dyn SpriteBatch>>,
pub paths: Vec<String>,
dummy_batch: Box<dyn SpriteBatch>,
}
impl TextureSet {
pub fn new(base_path: &str) -> TextureSet {
TextureSet { tex_map: HashMap::new(), paths: vec![base_path.to_string(), "".to_string()] }
TextureSet {
tex_map: HashMap::new(),
paths: vec![base_path.to_string(), "".to_string()],
dummy_batch: Box::new(DummyBatch),
}
}
pub fn apply_seasonal_content(&mut self, season: Season, settings: &Settings) {
@ -239,14 +355,17 @@ impl TextureSet {
create_texture(ctx, width as u16, height as u16, &img)
}
pub fn load_texture(&self, ctx: &mut Context, constants: &EngineConstants, name: &str) -> GameResult<SizedBatch> {
pub fn load_texture(
&self,
ctx: &mut Context,
constants: &EngineConstants,
name: &str,
) -> GameResult<Box<dyn SpriteBatch>> {
let path = self
.paths
.iter()
.find_map(|s| {
FILE_TYPES.iter().map(|ext| [s, name, ext].join("")).find(|path| {
filesystem::exists(ctx, path)
})
FILE_TYPES.iter().map(|ext| [s, name, ext].join("")).find(|path| filesystem::exists(ctx, path))
})
.ok_or_else(|| GameError::ResourceLoadError(format!("Texture {} does not exist.", name)))?;
@ -254,25 +373,21 @@ impl TextureSet {
.paths
.iter()
.find_map(|s| {
FILE_TYPES.iter().map(|ext| [s, name, ".glow", ext].join("")).find(|path| {
filesystem::exists(ctx, path)
})
}).is_some();
FILE_TYPES.iter().map(|ext| [s, name, ".glow", ext].join("")).find(|path| filesystem::exists(ctx, path))
})
.is_some();
info!("Loading texture: {}", path);
let batch = self.load_image(ctx, &path)?;
let size = batch.dimensions();
assert_ne!(size.0 as isize, 0, "size.width == 0");
assert_ne!(size.1 as isize, 0, "size.height == 0");
let orig_dimensions = constants.tex_sizes.get(name).unwrap_or_else(|| &size);
let scale = orig_dimensions.0 as f32 / size.0 as f32;
let width = (size.0 as f32 * scale) as usize;
let height = (size.1 as f32 * scale) as usize;
Ok(SizedBatch {
Ok(Box::new(SizedBatch {
batch,
width,
height,
@ -282,7 +397,7 @@ impl TextureSet {
real_height: size.1 as usize,
has_glow_layer,
has_normal_layer: false,
})
}))
}
pub fn get_or_load_batch(
@ -290,7 +405,11 @@ impl TextureSet {
ctx: &mut Context,
constants: &EngineConstants,
name: &str,
) -> GameResult<&mut SizedBatch> {
) -> GameResult<&mut Box<dyn SpriteBatch>> {
if ctx.headless {
return Ok(&mut self.dummy_batch);
}
if !self.tex_map.contains_key(name) {
let batch = self.load_texture(ctx, constants, name)?;
self.tex_map.insert(str!(name), batch);

View file

@ -136,6 +136,12 @@ impl Weapon {
state.create_caret(player.x, player.y, CaretType::LevelUp, Direction::Left);
}
}
player.xp_counter = if self.wtype != WeaponType::Spur {
30
} else {
10
};
}
pub fn reset_xp(&mut self) {