refactoring

This commit is contained in:
Alula 2021-10-15 16:36:05 +02:00
parent d147242199
commit 164b2bf295
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
36 changed files with 1400 additions and 1389 deletions

View File

@ -33,12 +33,13 @@ category = "Game"
osx_minimum_system_version = "10.12"
[features]
default = ["scripting", "backend-sdl", "render-opengl", "ogg-playback", "exe", "netplay"]
default = ["default-base", "backend-sdl", "render-opengl", "exe"]
default-base = ["scripting-lua", "ogg-playback", "netplay"]
ogg-playback = ["lewton"]
backend-sdl = ["sdl2", "sdl2-sys"]
backend-glutin = ["winit", "glutin", "render-opengl"]
render-opengl = []
scripting = ["lua-ffi"]
scripting-lua = ["lua-ffi"]
netplay = ["tokio", "serde_cbor"]
editor = []
hooks = ["libc"]

View File

@ -11,4 +11,4 @@ crate-type = ["cdylib"]
ndk = "0.3"
ndk-glue = "0.3"
ndk-sys = "0.2"
doukutsu-rs = { path = "../", default-features = false, features = ["android", "backend-glutin", "ogg-playback", "scripting"] }
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-glutin"] }

View File

@ -1,11 +1,10 @@
use std::collections::HashMap;
use std::io;
use byteorder::{LE, ReadBytesExt};
use byteorder::{ReadBytesExt, LE};
use crate::framework::error::GameError::ResourceLoadError;
use crate::framework::error::GameResult;
use crate::str;
#[derive(Debug)]
pub struct BmChar {
@ -43,7 +42,7 @@ impl BMFont {
data.read_exact(&mut magic)?;
if magic != MAGIC {
return Err(ResourceLoadError(str!( "Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
while let Ok(block_type) = data.read_u8() {
@ -80,30 +79,16 @@ impl BMFont {
let chnl = data.read_u8()?;
if let Some(chr) = std::char::from_u32(id) {
chars.insert(chr, BmChar {
x,
y,
width,
height,
xoffset,
yoffset,
xadvance,
page,
chnl,
});
chars.insert(chr, BmChar { x, y, width, height, xoffset, yoffset, xadvance, page, chnl });
}
}
}
_ => { return Err(ResourceLoadError(str!( "Unknown block type."))); }
_ => {
return Err(ResourceLoadError("Unknown block type.".to_owned()));
}
}
}
Ok(Self {
pages,
font_size,
line_height,
base,
chars,
})
Ok(Self { pages, font_size, line_height, base, chars })
}
}

View File

@ -2,13 +2,12 @@ use std::collections::HashSet;
use std::path::PathBuf;
use crate::bmfont::BMFont;
use crate::common::{FILE_TYPES, Rect};
use crate::common::{Rect, FILE_TYPES};
use crate::engine_constants::EngineConstants;
use crate::framework::context::Context;
use crate::framework::error::GameError::ResourceLoadError;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::str;
use crate::texture_set::TextureSet;
pub struct BMFontRenderer {
@ -21,7 +20,7 @@ impl BMFontRenderer {
let root = PathBuf::from(root);
let full_path = &root.join(PathBuf::from(desc_path));
let desc_stem =
full_path.file_stem().ok_or_else(|| ResourceLoadError(str!("Cannot extract the file stem.")))?;
full_path.file_stem().ok_or_else(|| ResourceLoadError("Cannot extract the file stem.".to_owned()))?;
let stem = full_path.parent().unwrap_or(full_path).join(desc_stem);
let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?;
@ -101,7 +100,16 @@ impl BMFontRenderer {
texture_set: &mut TextureSet,
ctx: &mut Context,
) -> GameResult {
self.draw_colored_text_scaled(iter.clone(), x + scale, y + scale, scale, (0, 0, 0, 150), constants, texture_set, ctx)?;
self.draw_colored_text_scaled(
iter.clone(),
x + scale,
y + scale,
scale,
(0, 0, 0, 150),
constants,
texture_set,
ctx,
)?;
self.draw_colored_text_scaled(iter, x, y, scale, color, constants, texture_set, ctx)
}

View File

@ -21,40 +21,40 @@ lazy_static! {
}
bitfield! {
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Flag(u32);
impl Debug;
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Flag(u32);
impl Debug;
/// Set if left wall was hit. (corresponds to flag & 0x01)
pub hit_left_wall, set_hit_left_wall: 0;
/// Set if top wall was hit. (corresponds to flag & 0x02)
pub hit_top_wall, set_hit_top_wall: 1;
/// Set if right wall was hit. (corresponds to flag & 0x04)
pub hit_right_wall, set_hit_right_wall: 2;
/// Set if bottom wall was hit. (corresponds to flag & 0x08)
pub hit_bottom_wall, set_hit_bottom_wall: 3;
/// Set if entity stays on right slope. (corresponds to flag & 0x10)
pub hit_right_slope, set_hit_right_slope: 4;
/// Set if entity stays on left slope. (corresponds to flag & 0x20)
pub hit_left_slope, set_hit_left_slope: 5;
/// Unknown purpose (corresponds to flag & 0x40)
pub flag_x40, set_flag_x40: 6;
/// Unknown purpose (corresponds to flag & 0x80)
pub flag_x80, set_flag_x80: 7;
/// Set if entity is in water. (corresponds to flag & 0x100)
pub in_water, set_in_water: 8;
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
pub force_left, set_force_left: 12; // 0x1000
pub force_up, set_force_up: 13; // 0x2000
pub force_right, set_force_right: 14; // 0x4000
pub force_down, set_force_down: 15; // 0x8000
pub hit_left_higher_half, set_hit_left_higher_half: 16; // 0x10000
pub hit_left_lower_half, set_hit_left_lower_half: 17; // 0x20000
pub hit_right_lower_half, set_hit_right_lower_half: 18; // 0x40000
pub hit_right_higher_half, set_hit_right_higher_half: 19; // 0x80000
/// Set if left wall was hit. (corresponds to flag & 0x01)
pub hit_left_wall, set_hit_left_wall: 0;
/// Set if top wall was hit. (corresponds to flag & 0x02)
pub hit_top_wall, set_hit_top_wall: 1;
/// Set if right wall was hit. (corresponds to flag & 0x04)
pub hit_right_wall, set_hit_right_wall: 2;
/// Set if bottom wall was hit. (corresponds to flag & 0x08)
pub hit_bottom_wall, set_hit_bottom_wall: 3;
/// Set if entity stays on right slope. (corresponds to flag & 0x10)
pub hit_right_slope, set_hit_right_slope: 4;
/// Set if entity stays on left slope. (corresponds to flag & 0x20)
pub hit_left_slope, set_hit_left_slope: 5;
/// Unknown purpose (corresponds to flag & 0x40)
pub flag_x40, set_flag_x40: 6;
/// Unknown purpose (corresponds to flag & 0x80)
pub flag_x80, set_flag_x80: 7;
/// Set if entity is in water. (corresponds to flag & 0x100)
pub in_water, set_in_water: 8;
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
pub force_left, set_force_left: 12; // 0x1000
pub force_up, set_force_up: 13; // 0x2000
pub force_right, set_force_right: 14; // 0x4000
pub force_down, set_force_down: 15; // 0x8000
pub hit_left_higher_half, set_hit_left_higher_half: 16; // 0x10000
pub hit_left_lower_half, set_hit_left_lower_half: 17; // 0x20000
pub hit_right_lower_half, set_hit_right_lower_half: 18; // 0x40000
pub hit_right_higher_half, set_hit_right_higher_half: 19; // 0x80000
}
impl Flag {
@ -68,79 +68,79 @@ impl Flag {
}
bitfield! {
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Equipment(u16);
impl Debug;
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Equipment(u16);
impl Debug;
pub has_booster_0_8, set_booster_0_8: 0; // 0x01 / 0001
pub has_map, set_map: 1; // 0x02 / 0002
pub has_arms_barrier, set_arms_barrier: 2; // 0x04 / 0004
pub has_turbocharge, set_turbocharge: 3; // 0x08 / 0008
pub has_air_tank, set_air_tank: 4; // 0x10 / 0016
pub has_booster_2_0, set_booster_2_0: 5; // 0x20 / 0032
pub has_mimiga_mask, set_mimiga_mask: 6; // 0x40 / 0064
pub has_whimsical_star, set_whimsical_star: 7; // 0x080 / 0128
pub has_nikumaru, set_nikumaru: 8; // 0x100 / 0256
// for custom equips
pub unused_1, set_unused_1: 9; // 0x200 / 0512
pub unused_2, set_unused_2: 10; // 0x400 / 1024
pub unused_3, set_unused_3: 11; // 0x800 / 2048
pub unused_4, set_unused_4: 12; // 0x1000 / 4096
pub unused_5, set_unused_5: 13; // 0x2000 / 8192
// bit 14 and 15 aren't accessible via TSC without abusing overflows (won't work in strict mode)
pub unused_6, set_unused_6: 14; // 0x4000 / @384
pub unused_7, set_unused_7: 15; // 0x8000 / P768
pub has_booster_0_8, set_booster_0_8: 0; // 0x01 / 0001
pub has_map, set_map: 1; // 0x02 / 0002
pub has_arms_barrier, set_arms_barrier: 2; // 0x04 / 0004
pub has_turbocharge, set_turbocharge: 3; // 0x08 / 0008
pub has_air_tank, set_air_tank: 4; // 0x10 / 0016
pub has_booster_2_0, set_booster_2_0: 5; // 0x20 / 0032
pub has_mimiga_mask, set_mimiga_mask: 6; // 0x40 / 0064
pub has_whimsical_star, set_whimsical_star: 7; // 0x080 / 0128
pub has_nikumaru, set_nikumaru: 8; // 0x100 / 0256
// for custom equips
pub unused_1, set_unused_1: 9; // 0x200 / 0512
pub unused_2, set_unused_2: 10; // 0x400 / 1024
pub unused_3, set_unused_3: 11; // 0x800 / 2048
pub unused_4, set_unused_4: 12; // 0x1000 / 4096
pub unused_5, set_unused_5: 13; // 0x2000 / 8192
// bit 14 and 15 aren't accessible via TSC without abusing overflows (won't work in strict mode)
pub unused_6, set_unused_6: 14; // 0x4000 / @384
pub unused_7, set_unused_7: 15; // 0x8000 / P768
}
bitfield! {
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Condition(u16);
impl Debug;
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Condition(u16);
impl Debug;
pub interacted, set_interacted: 0; // 0x01
pub hidden, set_hidden: 1; // 0x02
pub fallen, set_fallen: 2; // 0x04
pub explode_die, set_explode_die: 3; // 0x08
pub damage_boss, set_damage_boss: 4; // 0x10
pub increase_acceleration, set_increase_acceleration: 5; // 0x20
pub cond_x40, set_cond_x40: 6; // 0x40
pub alive, set_alive: 7; // 0x80
pub interacted, set_interacted: 0; // 0x01
pub hidden, set_hidden: 1; // 0x02
pub fallen, set_fallen: 2; // 0x04
pub explode_die, set_explode_die: 3; // 0x08
pub damage_boss, set_damage_boss: 4; // 0x10
pub increase_acceleration, set_increase_acceleration: 5; // 0x20
pub cond_x40, set_cond_x40: 6; // 0x40
pub alive, set_alive: 7; // 0x80
// engine specific flags
pub drs_novanish, set_drs_novanish: 14;
pub drs_boss, set_drs_boss: 15;
// engine specific flags
pub drs_novanish, set_drs_novanish: 14;
pub drs_boss, set_drs_boss: 15;
}
bitfield! {
#[derive(Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub struct ControlFlags(u16);
impl Debug;
#[derive(Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub struct ControlFlags(u16);
impl Debug;
pub tick_world, set_tick_world: 0; // 0x01
pub control_enabled, set_control_enabled: 1; // 0x02
pub interactions_disabled, set_interactions_disabled: 2; // 0x04
pub credits_running, set_credits_running: 3; // 0x08
pub tick_world, set_tick_world: 0; // 0x01
pub control_enabled, set_control_enabled: 1; // 0x02
pub interactions_disabled, set_interactions_disabled: 2; // 0x04
pub credits_running, set_credits_running: 3; // 0x08
// engine specific flags
pub friendly_fire, set_friendly_fire: 14;
// engine specific flags
pub friendly_fire, set_friendly_fire: 14;
}
bitfield! {
#[derive(Clone, Copy)]
#[repr(C)]
pub struct BulletFlag(u16);
impl Debug;
pub flag_x01, set_flag_x01: 0; // 0x01
pub flag_x02, set_flag_x02: 1; // 0x02
pub no_collision_checks, set_no_collision_checks: 2; // 0x04
pub bounce_from_walls, set_bounce_from_walls: 3; // 0x08
pub flag_x10, set_flag_x10: 4; // 0x10
pub flag_x20, set_flag_x20: 5; // 0x20
pub can_destroy_snack, set_can_destroy_snack: 6; // 0x40
pub flag_x80, set_flag_x80: 7; // 0x80
#[derive(Clone, Copy)]
#[repr(C)]
pub struct BulletFlag(u16);
impl Debug;
pub flag_x01, set_flag_x01: 0; // 0x01
pub flag_x02, set_flag_x02: 1; // 0x02
pub no_collision_checks, set_no_collision_checks: 2; // 0x04
pub bounce_from_walls, set_bounce_from_walls: 3; // 0x08
pub flag_x10, set_flag_x10: 4; // 0x10
pub flag_x20, set_flag_x20: 5; // 0x20
pub can_destroy_snack, set_can_destroy_snack: 6; // 0x40
pub flag_x80, set_flag_x80: 7; // 0x80
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View File

@ -8,7 +8,7 @@ use crate::input::touch_controls::TouchControlType;
use crate::inventory::Inventory;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
use crate::text_script::ScriptMode;
use crate::scripting::tsc::text_script::ScriptMode;
use crate::weapon::{WeaponLevel, WeaponType};
#[derive(Copy, Clone, PartialEq, Eq)]

View File

@ -7,7 +7,7 @@ use crate::frame::Frame;
use crate::input::touch_controls::TouchControlType;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
use crate::text_script::ScriptMode;
use crate::scripting::tsc::text_script::ScriptMode;
pub struct StageSelect {
pub current_teleport_slot: u8,

View File

@ -6,10 +6,9 @@ use crate::case_insensitive_hashmap;
use crate::common::{BulletFlag, Color, Rect};
use crate::engine_constants::npcs::NPCConsts;
use crate::player::ControlMode;
use crate::sound::pixtone::{Channel, PixToneParameters, Waveform, Envelope};
use crate::scripting::tsc::text_script::TextScriptEncoding;
use crate::sound::pixtone::{Channel, Envelope, PixToneParameters, Waveform};
use crate::sound::SoundManager;
use crate::str;
use crate::text_script::TextScriptEncoding;
mod npcs;
@ -308,7 +307,7 @@ impl EngineConstants {
intro_player_pos: (3, 3),
new_game_stage: 13,
new_game_event: 200,
new_game_player_pos: (10, 8)
new_game_player_pos: (10, 8),
},
player: PlayerConsts {
life: 3,
@ -1421,7 +1420,7 @@ impl EngineConstants {
text_speed_fast: 1,
},
title: TitleConsts {
intro_text: "Studio Pixel presents".to_string(),
intro_text: "Studio Pixel presents".to_owned(),
logo_rect: Rect { left: 0, top: 0, right: 144, bottom: 40 },
menu_left_top: Rect { left: 0, top: 0, right: 8, bottom: 8 },
menu_right_top: Rect { left: 236, top: 0, right: 244, bottom: 8 },
@ -1434,59 +1433,59 @@ impl EngineConstants {
menu_right: Rect { left: 236, top: 8, right: 244, bottom: 16 },
},
inventory_dim_color: Color::from_rgba(0, 0, 0, 0),
font_path: "csfont.fnt".to_string(),
font_path: "csfont.fnt".to_owned(),
font_scale: 1.0,
font_space_offset: 0.0,
soundtracks: HashMap::new(),
music_table: vec![
"xxxx".to_string(),
"wanpaku".to_string(),
"anzen".to_string(),
"gameover".to_string(),
"gravity".to_string(),
"weed".to_string(),
"mdown2".to_string(),
"fireeye".to_string(),
"vivi".to_string(),
"mura".to_string(),
"fanfale1".to_string(),
"ginsuke".to_string(),
"cemetery".to_string(),
"plant".to_string(),
"kodou".to_string(),
"fanfale3".to_string(),
"fanfale2".to_string(),
"dr".to_string(),
"escape".to_string(),
"jenka".to_string(),
"maze".to_string(),
"access".to_string(),
"ironh".to_string(),
"grand".to_string(),
"curly".to_string(),
"oside".to_string(),
"requiem".to_string(),
"wanpak2".to_string(),
"quiet".to_string(),
"lastcave".to_string(),
"balcony".to_string(),
"lastbtl".to_string(),
"lastbt3".to_string(),
"ending".to_string(),
"zonbie".to_string(),
"bdown".to_string(),
"hell".to_string(),
"jenka2".to_string(),
"marine".to_string(),
"ballos".to_string(),
"toroko".to_string(),
"white".to_string(),
"kaze".to_string(),
"xxxx".to_owned(),
"wanpaku".to_owned(),
"anzen".to_owned(),
"gameover".to_owned(),
"gravity".to_owned(),
"weed".to_owned(),
"mdown2".to_owned(),
"fireeye".to_owned(),
"vivi".to_owned(),
"mura".to_owned(),
"fanfale1".to_owned(),
"ginsuke".to_owned(),
"cemetery".to_owned(),
"plant".to_owned(),
"kodou".to_owned(),
"fanfale3".to_owned(),
"fanfale2".to_owned(),
"dr".to_owned(),
"escape".to_owned(),
"jenka".to_owned(),
"maze".to_owned(),
"access".to_owned(),
"ironh".to_owned(),
"grand".to_owned(),
"curly".to_owned(),
"oside".to_owned(),
"requiem".to_owned(),
"wanpak2".to_owned(),
"quiet".to_owned(),
"lastcave".to_owned(),
"balcony".to_owned(),
"lastbtl".to_owned(),
"lastbt3".to_owned(),
"ending".to_owned(),
"zonbie".to_owned(),
"bdown".to_owned(),
"hell".to_owned(),
"jenka2".to_owned(),
"marine".to_owned(),
"ballos".to_owned(),
"toroko".to_owned(),
"white".to_owned(),
"kaze".to_owned(),
],
organya_paths: vec![
"/org/".to_string(), // NXEngine
"/base/Org/".to_string(), // CS+
"/Resource/ORG/".to_string(), // CSE2E
"/org/".to_owned(), // NXEngine
"/base/Org/".to_owned(), // CS+
"/Resource/ORG/".to_owned(), // CSE2E
],
}
}
@ -1496,15 +1495,15 @@ impl EngineConstants {
self.is_cs_plus = true;
self.supports_og_textures = true;
self.tex_sizes.insert(str!("Caret"), (320, 320));
self.tex_sizes.insert(str!("MyChar"), (200, 384));
self.tex_sizes.insert(str!("Npc/NpcRegu"), (320, 410));
self.tex_sizes.insert("Caret".to_owned(), (320, 320));
self.tex_sizes.insert("MyChar".to_owned(), (200, 384));
self.tex_sizes.insert("Npc/NpcRegu".to_owned(), (320, 410));
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 50 };
self.font_path = str!("csfont.fnt");
self.font_path = "csfont.fnt".to_owned();
self.font_scale = 0.5;
self.font_space_offset = 2.0;
self.soundtracks.insert("Remastered".to_string(), "/base/Ogg11/".to_string());
self.soundtracks.insert("New".to_string(), "/base/Ogg/".to_string());
self.soundtracks.insert("Remastered".to_owned(), "/base/Ogg11/".to_owned());
self.soundtracks.insert("New".to_owned(), "/base/Ogg/".to_owned());
let typewriter_sample = PixToneParameters {
// fx2 (CS+)
@ -1539,8 +1538,8 @@ impl EngineConstants {
self.is_switch = true;
self.supports_og_textures = true;
self.tex_sizes.insert(str!("bkMoon"), (427, 240));
self.tex_sizes.insert(str!("bkFog"), (427, 240));
self.tex_sizes.insert("bkMoon".to_owned(), (427, 240));
self.tex_sizes.insert("bkFog".to_owned(), (427, 240));
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 62 };
self.inventory_dim_color = Color::from_rgba(0, 0, 32, 150);
self.textscript.encoding = TextScriptEncoding::UTF8;
@ -1549,11 +1548,9 @@ impl EngineConstants {
self.textscript.text_shadow = true;
self.textscript.text_speed_normal = 1;
self.textscript.text_speed_fast = 0;
self.soundtracks.insert("Famitracks".to_string(), "/base/ogg17/".to_string());
self.soundtracks.insert("Ridiculon".to_string(), "/base/ogg_ridic/".to_string());
self.soundtracks.insert("Famitracks".to_owned(), "/base/ogg17/".to_owned());
self.soundtracks.insert("Ridiculon".to_owned(), "/base/ogg_ridic/".to_owned());
}
pub fn apply_constant_json_files(&mut self) {
}
pub fn apply_constant_json_files(&mut self) {}
}

View File

@ -1,8 +1,5 @@
#[macro_use]
extern crate log;
#[cfg_attr(feature = "scripting", macro_use)]
#[cfg(feature = "scripting")]
extern crate lua_ffi;
extern crate strum;
#[macro_use]
extern crate strum_macros;
@ -55,7 +52,6 @@ mod player;
mod profile;
mod rng;
mod scene;
#[cfg(feature = "scripting")]
mod scripting;
mod settings;
#[cfg(feature = "backend-gfx")]
@ -63,7 +59,6 @@ mod shaders;
mod shared_game_state;
mod sound;
mod stage;
mod text_script;
mod texture_set;
mod weapon;
@ -281,7 +276,7 @@ pub fn init(options: LaunchOptions) -> GameResult {
let game = UnsafeCell::new(Game::new(&mut context)?);
let state_ref = unsafe { &mut *((&mut *game.get()).state.get()) };
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
{
state_ref.lua.update_refs(unsafe { (&*game.get()).state.get() }, &mut context as *mut Context);
}

View File

@ -5,7 +5,7 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::scene::game_scene::GameScene;
use crate::shared_game_state::SharedGameState;
use crate::text_script::TextScriptExecutionState;
use crate::scripting::tsc::text_script::TextScriptExecutionState;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
@ -121,7 +121,7 @@ impl LiveDebugger {
self.flags_visible = !self.flags_visible;
}
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
{
ui.same_line(0.0);
if ui.button(im_str!("Reload Scripts"), [0.0, 0.0]) {

View File

@ -5,16 +5,6 @@ pub use core::fmt;
#[doc(hidden)]
pub use core::mem::size_of;
#[macro_export]
macro_rules! str {
() => {
String::new()
};
($x:expr) => {
ToString::to_string(&$x)
};
}
// extended version of https://github.com/dzamlo/rust-bitfield
#[macro_export(local_inner_macros)]

View File

@ -13,7 +13,6 @@ use crate::framework::error::{GameError, GameResult};
use crate::framework::filesystem;
use crate::shared_game_state::TileSize;
use crate::stage::{PxPackScroll, PxPackStageData, StageData};
use crate::str;
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
@ -43,7 +42,7 @@ impl Map {
map_data.read_exact(&mut magic)?;
if &magic != b"PXM" {
return Err(ResourceLoadError(str!("Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
let version = map_data.read_u8()?;
@ -80,7 +79,7 @@ impl Map {
// based on https://github.com/tilderain/pxEdit/blob/kero/pxMap.py
if &magic != b"PXPACK121127a**\0" {
return Err(ResourceLoadError(str!("Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
fn read_string<R: io::Read>(map_data: &mut R) -> GameResult<String> {
@ -153,7 +152,7 @@ impl Map {
map_data.read_exact(&mut magic)?;
if &magic != b"pxMAP01\0" {
return Err(ResourceLoadError(str!("Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
let width_fg = map_data.read_u16::<LE>()?;
@ -170,7 +169,7 @@ impl Map {
map_data.read_exact(&mut magic)?;
if &magic != b"pxMAP01\0" {
return Err(ResourceLoadError(str!("Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
let width_mg = map_data.read_u16::<LE>()?;
@ -188,7 +187,7 @@ impl Map {
map_data.read_exact(&mut magic)?;
if &magic != b"pxMAP01\0" {
return Err(ResourceLoadError(str!("Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
let width_bg = map_data.read_u16::<LE>()?;
@ -214,7 +213,7 @@ impl Map {
attrib_data.read_exact(&mut magic)?;
if &magic != b"pxMAP01\0" {
return Err(ResourceLoadError(str!("Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
attrib_data.read_u16::<LE>()?;
@ -475,7 +474,7 @@ impl NPCData {
data.read_exact(&mut magic)?;
if &magic != b"PXE" {
return Err(ResourceLoadError(str!("Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
let version = data.read_u8()?;

View File

@ -20,7 +20,6 @@ use crate::player::Player;
use crate::rng::Xoroshiro32PlusPlus;
use crate::shared_game_state::SharedGameState;
use crate::stage::Stage;
use crate::str;
use crate::weapon::bullet::BulletManager;
pub mod ai;
@ -199,7 +198,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu
) -> GameResult {
#[allow(unused_assignments)]
let mut npc_hook_ran = false;
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
{
npc_hook_ran = state.lua.try_run_npc_hook(self.id, self.npc_type);
}
@ -655,9 +654,9 @@ impl NPCTable {
pub fn new() -> NPCTable {
NPCTable {
entries: Vec::new(),
tileset_name: str!("Stage/Prt0"),
tex_npc1_name: str!("Npc/Npc0"),
tex_npc2_name: str!("Npc/Npc0"),
tileset_name: "Stage/Prt0".to_owned(),
tex_npc1_name: "Npc/Npc0".to_owned(),
tex_npc2_name: "Npc/Npc0".to_owned(),
}
}

View File

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

View File

@ -10,7 +10,6 @@ use crate::framework::error::GameResult;
use crate::player::ControlMode;
use crate::scene::game_scene::GameScene;
use crate::shared_game_state::SharedGameState;
use crate::str;
use crate::weapon::{WeaponLevel, WeaponType};
pub struct WeaponData {
@ -294,7 +293,7 @@ impl GameProfile {
pub fn load_from_save<R: io::Read>(mut data: R) -> GameResult<GameProfile> {
// Do041220
if data.read_u64::<BE>()? != 0x446f303431323230 {
return Err(ResourceLoadError(str!("Invalid magic")));
return Err(ResourceLoadError("Invalid magic".to_owned()));
}
let current_map = data.read_u32::<LE>()?;
@ -354,7 +353,7 @@ impl GameProfile {
data.read_exact(&mut map_flags)?;
if data.read_u32::<BE>()? != 0x464c4147 {
return Err(ResourceLoadError(str!("Invalid FLAG signature")));
return Err(ResourceLoadError("Invalid FLAG signature".to_owned()));
}
let mut flags = [0u8; 1000];

View File

@ -32,7 +32,7 @@ use crate::scene::title_scene::TitleScene;
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::scripting::tsc::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
use crate::texture_set::SpriteBatch;
use crate::weapon::bullet::BulletManager;
use crate::weapon::{Weapon, WeaponType};
@ -1776,7 +1776,7 @@ impl Scene for GameScene {
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?);
state.textscript_vm.suspend = false;
state.tile_size = self.stage.map.tile_size;
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
state.lua.set_game_scene(self as *mut _);
self.player1.controller = state.settings.create_player1_controller();
@ -1948,7 +1948,7 @@ impl Scene for GameScene {
self.flash.tick(state, ())?;
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
state.lua.scene_tick();
if state.control_flags.tick_world() {

View File

@ -6,7 +6,7 @@ use crate::scene::no_data_scene::NoDataScene;
use crate::scene::Scene;
use crate::shared_game_state::SharedGameState;
use crate::stage::StageData;
use crate::text_script::TextScript;
use crate::scripting::tsc::text_script::TextScript;
pub struct LoadingScene {
tick: usize,

View File

@ -1,13 +1,15 @@
use std::io::Read;
use lua_ffi::c_str;
use lua_ffi::ffi::luaL_Reg;
use lua_ffi::lua_method;
use lua_ffi::{c_int, LuaObject, State};
use crate::common::{Direction, Rect};
use crate::framework::filesystem;
use crate::rng::RNG;
use crate::scripting::{check_status, LuaScriptingState, DRS_RUNTIME_GLOBAL};
use crate::scene::game_scene::LightingMode;
use crate::scripting::lua::{check_status, LuaScriptingState, DRS_RUNTIME_GLOBAL};
pub struct Doukutsu {
pub ptr: *mut LuaScriptingState,

232
src/scripting/lua/mod.rs Normal file
View File

@ -0,0 +1,232 @@
use std::io::Read;
use std::ptr::null_mut;
use lua_ffi::lua_fn;
use lua_ffi::ffi::lua_State;
use lua_ffi::types::LuaValue;
use lua_ffi::{c_int, State, ThreadStatus};
use crate::common::Rect;
use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
use crate::framework::filesystem;
use crate::framework::filesystem::File;
use crate::scene::game_scene::GameScene;
use crate::scripting::lua::doukutsu::Doukutsu;
use crate::shared_game_state::SharedGameState;
mod doukutsu;
mod scene;
pub struct LuaScriptingState {
state: Option<State>,
state_ptr: *mut SharedGameState,
ctx_ptr: *mut Context,
game_scene: *mut GameScene,
}
pub(in crate::scripting) static DRS_API_GLOBAL: &str = "__doukutsu_rs";
pub(in crate::scripting) static DRS_RUNTIME_GLOBAL: &str = "__doukutsu_rs_runtime_dont_touch";
static BOOT_SCRIPT: &str = include_str!("boot.lua");
pub(in crate::scripting) fn check_status(status: ThreadStatus, state: &mut State) -> GameResult {
match status {
ThreadStatus::Ok | ThreadStatus::Yield => {
return Ok(());
}
_ => {}
}
let error = state.to_str(-1).unwrap_or("???");
match status {
ThreadStatus::RuntimeError => Err(GameError::EventLoopError(format!("Lua Runtime Error: {}", error))),
ThreadStatus::SyntaxError => Err(GameError::EventLoopError(format!("Lua Syntax Error: {}", error))),
ThreadStatus::MemoryError => Err(GameError::EventLoopError(format!("Lua Memory Error: {}", error))),
ThreadStatus::MsgHandlerError => {
Err(GameError::EventLoopError(format!("Lua Message Handler Error: {}", error)))
}
ThreadStatus::FileError => Err(GameError::EventLoopError(format!("Lua File Error: {}", error))),
ThreadStatus::Unknown => Err(GameError::EventLoopError(format!("Unknown Error: {}", error))),
_ => Ok(()),
}
}
fn print(state: &mut State) -> c_int {
if let Some(msg) = state.to_str(1) {
log::info!("[Lua] {}", msg);
}
0
}
impl LuaScriptingState {
pub fn new() -> LuaScriptingState {
LuaScriptingState { state: None, state_ptr: null_mut(), ctx_ptr: null_mut(), game_scene: null_mut() }
}
pub fn update_refs(&mut self, state: *mut SharedGameState, ctx: *mut Context) {
self.state_ptr = state;
self.ctx_ptr = ctx;
}
pub fn set_game_scene(&mut self, game_scene: *mut GameScene) {
self.game_scene = game_scene;
}
fn load_script(mut state: &mut State, path: &str, mut script: File) -> bool {
let mut buf = Vec::new();
let res = script.read_to_end(&mut buf);
if let Err(err) = res {
log::warn!("Error reading script {}: {}", path, err);
return false;
}
let name = format!("@{}", path);
let res = state.load_buffer(&buf, &name);
let res = check_status(res, &mut state);
if let Err(err) = res {
log::warn!("Error loading script {}: {}", path, err);
return false;
}
state.get_global(DRS_RUNTIME_GLOBAL);
state.get_field(-1, "_initializeScript");
state.push_value(-3);
let res = state.pcall(1, 0, 0);
if let Err((_, err)) = res {
log::warn!("Error evaluating script {}: {}", path, err);
return false;
}
log::info!("Successfully loaded Lua script: {}", path);
true
}
pub fn reload_scripts(&mut self, ctx: &mut Context) -> GameResult {
let mut state = State::new();
state.open_libs();
state.push(lua_fn!(print));
state.set_global("print");
state.push(Doukutsu { ptr: self as *mut LuaScriptingState });
state.set_global(DRS_API_GLOBAL);
log::info!("Initializing Lua scripting engine...");
let res = state.do_string(BOOT_SCRIPT);
check_status(res, &mut state)?;
if filesystem::exists(ctx, "/drs-scripts/") {
let mut script_count = 0;
let files = filesystem::read_dir(ctx, "/drs-scripts/")?
.filter(|f| f.to_string_lossy().to_lowercase().ends_with(".lua"));
for file in files {
let path = file.clone();
match filesystem::open(ctx, file) {
Ok(script) => {
if LuaScriptingState::load_script(&mut state, path.to_string_lossy().as_ref(), script) {
script_count += 1;
}
}
Err(err) => {
log::warn!("Error opening script {:?}: {}", path, err);
}
}
}
if script_count > 0 {
log::info!("{} Lua scripts have been loaded.", script_count);
}
}
let modcs_path = "/Scripts/main.lua";
if filesystem::exists(ctx, modcs_path) {
log::info!("Loading ModCS main script...");
match filesystem::open(ctx, modcs_path) {
Ok(script) => {
if !LuaScriptingState::load_script(&mut state, modcs_path, script) {
log::warn!("Error loading ModCS main script.");
}
}
Err(err) => {
log::warn!("Error opening script {:?}: {}", modcs_path, err);
}
}
}
self.state = Some(state);
Ok(())
}
}
impl LuaValue for Rect<u16> {
fn push_val(self, l: *mut lua_State) {
unsafe {
lua_ffi::ffi::lua_newtable(l);
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 1);
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 2);
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 3);
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 4);
}
}
}
impl LuaValue for Rect<i16> {
fn push_val(self, l: *mut lua_State) {
unsafe {
lua_ffi::ffi::lua_newtable(l);
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 1);
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 2);
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 3);
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 4);
}
}
}
impl LuaValue for Rect<i32> {
fn push_val(self, l: *mut lua_State) {
unsafe {
lua_ffi::ffi::lua_newtable(l);
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 1);
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 2);
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 3);
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 4);
}
}
}
impl LuaValue for Rect<f32> {
fn push_val(self, l: *mut lua_State) {
unsafe {
lua_ffi::ffi::lua_newtable(l);
lua_ffi::ffi::lua_pushnumber(l, self.left as f64);
lua_ffi::ffi::lua_rawseti(l, -2, 1);
lua_ffi::ffi::lua_pushnumber(l, self.top as f64);
lua_ffi::ffi::lua_rawseti(l, -2, 2);
lua_ffi::ffi::lua_pushnumber(l, self.right as f64);
lua_ffi::ffi::lua_rawseti(l, -2, 3);
lua_ffi::ffi::lua_pushnumber(l, self.bottom as f64);
lua_ffi::ffi::lua_rawseti(l, -2, 4);
}
}
}

View File

@ -1,8 +1,10 @@
use lua_ffi::c_str;
use lua_ffi::ffi::luaL_Reg;
use lua_ffi::lua_method;
use lua_ffi::{c_int, LuaObject, State};
use crate::scene::game_scene::GameScene;
use crate::scripting::{LuaScriptingState, DRS_RUNTIME_GLOBAL};
use crate::scripting::lua::{LuaScriptingState, DRS_RUNTIME_GLOBAL};
pub struct LuaGameScene {
valid_reference: bool,

View File

@ -1,235 +1,3 @@
use std::io::Read;
use std::ptr::null_mut;
use crate::framework::context::Context;
use crate::framework::error::{GameResult, GameError};
use lua_ffi::{c_int, State, ThreadStatus};
use crate::scene::game_scene::GameScene;
use crate::scripting::doukutsu::Doukutsu;
use crate::shared_game_state::SharedGameState;
use crate::framework::filesystem::File;
use crate::framework::filesystem;
use lua_ffi::types::LuaValue;
use crate::common::Rect;
use lua_ffi::ffi::lua_State;
mod doukutsu;
mod scene;
pub struct LuaScriptingState {
state: Option<State>,
state_ptr: *mut SharedGameState,
ctx_ptr: *mut Context,
game_scene: *mut GameScene,
}
pub(in crate::scripting) static DRS_API_GLOBAL: &str = "__doukutsu_rs";
pub(in crate::scripting) static DRS_RUNTIME_GLOBAL: &str = "__doukutsu_rs_runtime_dont_touch";
static BOOT_SCRIPT: &str = include_str!("boot.lua");
pub(in crate::scripting) fn check_status(status: ThreadStatus, state: &mut State) -> GameResult {
match status {
ThreadStatus::Ok | ThreadStatus::Yield => { return Ok(()); }
_ => {}
}
let error = state.to_str(-1).unwrap_or("???");
match status {
ThreadStatus::RuntimeError => Err(GameError::EventLoopError(format!("Lua Runtime Error: {}", error))),
ThreadStatus::SyntaxError => Err(GameError::EventLoopError(format!("Lua Syntax Error: {}", error))),
ThreadStatus::MemoryError => Err(GameError::EventLoopError(format!("Lua Memory Error: {}", error))),
ThreadStatus::MsgHandlerError => Err(GameError::EventLoopError(format!("Lua Message Handler Error: {}", error))),
ThreadStatus::FileError => Err(GameError::EventLoopError(format!("Lua File Error: {}", error))),
ThreadStatus::Unknown => Err(GameError::EventLoopError(format!("Unknown Error: {}", error))),
_ => Ok(())
}
}
fn print(state: &mut State) -> c_int {
if let Some(msg) = state.to_str(1) {
log::info!("[Lua] {}", msg);
}
0
}
impl LuaScriptingState {
pub fn new() -> LuaScriptingState {
LuaScriptingState {
state: None,
state_ptr: null_mut(),
ctx_ptr: null_mut(),
game_scene: null_mut(),
}
}
pub fn update_refs(&mut self, state: *mut SharedGameState, ctx: *mut Context) {
self.state_ptr = state;
self.ctx_ptr = ctx;
}
pub fn set_game_scene(&mut self, game_scene: *mut GameScene) {
self.game_scene = game_scene;
}
fn load_script(mut state: &mut State, path: &str, mut script: File) -> bool {
let mut buf = Vec::new();
let res = script.read_to_end(&mut buf);
if let Err(err) = res {
log::warn!("Error reading script {}: {}", path, err);
return false;
}
let name = format!("@{}", path);
let res = state.load_buffer(&buf, &name);
let res = check_status(res, &mut state);
if let Err(err) = res {
log::warn!("Error loading script {}: {}", path, err);
return false;
}
state.get_global(DRS_RUNTIME_GLOBAL);
state.get_field(-1, "_initializeScript");
state.push_value(-3);
let res = state.pcall(1, 0, 0);
if let Err((_, err)) = res {
log::warn!("Error evaluating script {}: {}", path, err);
return false;
}
log::info!("Successfully loaded Lua script: {}", path);
true
}
pub fn reload_scripts(&mut self, ctx: &mut Context) -> GameResult {
let mut state = State::new();
state.open_libs();
state.push(lua_fn!(print));
state.set_global("print");
state.push(Doukutsu { ptr: self as *mut LuaScriptingState });
state.set_global(DRS_API_GLOBAL);
log::info!("Initializing Lua scripting engine...");
let res = state.do_string(BOOT_SCRIPT);
check_status(res, &mut state)?;
if filesystem::exists(ctx, "/drs-scripts/") {
let mut script_count = 0;
let files = filesystem::read_dir(ctx, "/drs-scripts/")?
.filter(|f| f.to_string_lossy().to_lowercase().ends_with(".lua"));
for file in files {
let path = file.clone();
match filesystem::open(ctx, file) {
Ok(script) => {
if LuaScriptingState::load_script(&mut state, path.to_string_lossy().as_ref(), script) {
script_count += 1;
}
}
Err(err) => {
log::warn!("Error opening script {:?}: {}", path, err);
}
}
}
if script_count > 0 {
log::info!("{} Lua scripts have been loaded.", script_count);
}
}
let modcs_path = "/Scripts/main.lua";
if filesystem::exists(ctx, modcs_path) {
log::info!("Loading ModCS main script...");
match filesystem::open(ctx, modcs_path) {
Ok(script) => {
if !LuaScriptingState::load_script(&mut state, modcs_path, script) {
log::warn!("Error loading ModCS main script.");
}
}
Err(err) => {
log::warn!("Error opening script {:?}: {}", modcs_path, err);
}
}
}
self.state = Some(state);
Ok(())
}
}
impl LuaValue for Rect<u16> {
fn push_val(self, l: *mut lua_State) {
unsafe {
lua_ffi::ffi::lua_newtable(l);
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 1);
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 2);
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 3);
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 4);
}
}
}
impl LuaValue for Rect<i16> {
fn push_val(self, l: *mut lua_State) {
unsafe {
lua_ffi::ffi::lua_newtable(l);
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 1);
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 2);
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 3);
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 4);
}
}
}
impl LuaValue for Rect<i32> {
fn push_val(self, l: *mut lua_State) {
unsafe {
lua_ffi::ffi::lua_newtable(l);
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 1);
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 2);
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 3);
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
lua_ffi::ffi::lua_rawseti(l, -2, 4);
}
}
}
impl LuaValue for Rect<f32> {
fn push_val(self, l: *mut lua_State) {
unsafe {
lua_ffi::ffi::lua_newtable(l);
lua_ffi::ffi::lua_pushnumber(l, self.left as f64);
lua_ffi::ffi::lua_rawseti(l, -2, 1);
lua_ffi::ffi::lua_pushnumber(l, self.top as f64);
lua_ffi::ffi::lua_rawseti(l, -2, 2);
lua_ffi::ffi::lua_pushnumber(l, self.right as f64);
lua_ffi::ffi::lua_rawseti(l, -2, 3);
lua_ffi::ffi::lua_pushnumber(l, self.bottom as f64);
lua_ffi::ffi::lua_rawseti(l, -2, 4);
}
}
}
#[cfg(feature = "scripting-lua")]
pub mod lua;
pub mod tsc;

View File

@ -0,0 +1,103 @@
use std::io::{Cursor, Read};
use crate::encoding::{read_cur_shift_jis, read_cur_wtf8};
use crate::framework::error::GameError::ParseError;
use crate::framework::error::GameResult;
use crate::scripting::tsc::opcodes::OpCode;
use crate::scripting::tsc::text_script::TextScriptEncoding;
pub fn put_varint(val: i32, out: &mut Vec<u8>) {
let mut x = ((val as u32) >> 31) ^ ((val as u32) << 1);
loop {
let mut n = (x & 0x7f) as u8;
x >>= 7;
if x != 0 {
n |= 0x80;
}
out.push(n);
if x == 0 {
break;
}
}
}
pub fn read_cur_varint(cursor: &mut Cursor<&Vec<u8>>) -> GameResult<i32> {
let mut result = 0u32;
for o in 0..5 {
let mut n = [0u8];
cursor.read_exact(&mut n)?;
let [n] = n;
result |= (n as u32 & 0x7f) << (o * 7);
if n & 0x80 == 0 {
break;
}
}
Ok(((result << 31) ^ (result >> 1)) as i32)
}
#[allow(unused)]
pub fn read_varint<I: Iterator<Item = u8>>(iter: &mut I) -> GameResult<i32> {
let mut result = 0u32;
for o in 0..5 {
let n = iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
result |= (n as u32 & 0x7f) << (o * 7);
if n & 0x80 == 0 {
break;
}
}
Ok(((result << 31) ^ (result >> 1)) as i32)
}
pub fn put_string(buffer: &mut Vec<u8>, out: &mut Vec<u8>, encoding: TextScriptEncoding) {
if buffer.len() == 0 {
return;
}
let mut cursor: Cursor<&Vec<u8>> = Cursor::new(buffer);
let mut tmp_buf = Vec::new();
let mut remaining = buffer.len() as u32;
let mut chars = 0;
while remaining > 0 {
let (consumed, chr) = match encoding {
TextScriptEncoding::UTF8 => read_cur_wtf8(&mut cursor, remaining),
TextScriptEncoding::ShiftJIS => read_cur_shift_jis(&mut cursor, remaining),
};
remaining -= consumed;
chars += 1;
put_varint(chr as i32, &mut tmp_buf);
}
buffer.clear();
put_varint(OpCode::_STR as i32, out);
put_varint(chars, out);
out.append(&mut tmp_buf);
}
#[test]
fn test_varint() {
for n in -4000..=4000 {
let mut out = Vec::new();
put_varint(n, &mut out);
let result = read_varint(&mut out.iter().copied()).unwrap();
assert_eq!(result, n);
let mut cur = Cursor::new(&out);
let result = read_cur_varint(&mut cur).unwrap();
assert_eq!(result, n);
}
}

View File

@ -0,0 +1,314 @@
use std::collections::HashMap;
use std::iter::Peekable;
use std::str::FromStr;
use itertools::Itertools;
use crate::encoding::read_cur_wtf8;
use crate::framework::error::GameError::ParseError;
use crate::framework::error::GameResult;
use crate::scripting::tsc::bytecode_utils::{put_string, put_varint};
use crate::scripting::tsc::opcodes::OpCode;
use crate::scripting::tsc::parse_utils::{expect_char, read_number, skip_until};
use crate::scripting::tsc::text_script::{TextScript, TextScriptEncoding};
impl TextScript {
/// Compiles a decrypted text script data into internal bytecode.
pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult<TextScript> {
let mut event_map = HashMap::new();
let mut iter = data.iter().copied().peekable();
let mut last_event = 0;
while let Some(&chr) = iter.peek() {
match chr {
b'#' => {
iter.next();
let event_num = read_number(&mut iter)? as u16;
if iter.peek().is_some() {
skip_until(b'\n', &mut iter)?;
iter.next();
}
last_event = event_num;
if event_map.contains_key(&event_num) {
if strict {
return Err(ParseError(format!("Event {} has been defined twice.", event_num)));
}
match skip_until(b'#', &mut iter).ok() {
Some(_) => {
continue;
}
None => {
break;
}
}
}
let bytecode = TextScript::compile_event(&mut iter, strict, encoding)?;
log::info!("Successfully compiled event #{} ({} bytes generated).", event_num, bytecode.len());
event_map.insert(event_num, bytecode);
}
b'\r' | b'\n' | b' ' | b'\t' => {
iter.next();
}
n => {
// CS+ boss rush is the buggiest shit ever.
if !strict && last_event == 0 {
iter.next();
continue;
}
return Err(ParseError(format!("Unexpected token in event {}: {}", last_event, n as char)));
}
}
}
Ok(TextScript { event_map })
}
fn compile_event<I: Iterator<Item = u8>>(
iter: &mut Peekable<I>,
strict: bool,
encoding: TextScriptEncoding,
) -> GameResult<Vec<u8>> {
let mut bytecode = Vec::new();
let mut char_buf = Vec::with_capacity(16);
let mut allow_next_event = true;
while let Some(&chr) = iter.peek() {
match chr {
b'#' if allow_next_event => {
if !char_buf.is_empty() {
put_string(&mut char_buf, &mut bytecode, encoding);
}
// some events end without <END marker.
put_varint(OpCode::_END as i32, &mut bytecode);
break;
}
b'<' => {
allow_next_event = false;
if char_buf.len() > 2 {
if let Some(&c) = char_buf.last() {
if c == b'\n' {
let _ = char_buf.pop();
}
}
}
if !char_buf.is_empty() {
put_string(&mut char_buf, &mut bytecode, encoding);
}
iter.next();
let n = iter
.next_tuple::<(u8, u8, u8)>()
.map(|t| [t.0, t.1, t.2])
.ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
let code = String::from_utf8_lossy(&n);
TextScript::compile_code(code.as_ref(), strict, iter, &mut bytecode)?;
}
b'\r' => {
iter.next();
}
b'\n' => {
allow_next_event = true;
char_buf.push(chr);
iter.next();
}
_ => {
allow_next_event = false;
char_buf.push(chr);
iter.next();
}
}
}
Ok(bytecode)
}
fn compile_code<I: Iterator<Item = u8>>(
code: &str,
strict: bool,
iter: &mut Peekable<I>,
out: &mut Vec<u8>,
) -> GameResult {
let instr = OpCode::from_str(code).map_err(|_| ParseError(format!("Unknown opcode: {}", code)))?;
match instr {
// Zero operand codes
OpCode::AEp
| OpCode::CAT
| OpCode::CIL
| OpCode::CLO
| OpCode::CLR
| OpCode::CPS
| OpCode::CRE
| OpCode::CSS
| OpCode::END
| OpCode::ESC
| OpCode::FLA
| OpCode::FMU
| OpCode::FRE
| OpCode::HMC
| OpCode::INI
| OpCode::KEY
| OpCode::LDP
| OpCode::MLP
| OpCode::MM0
| OpCode::MNA
| OpCode::MS2
| OpCode::MS3
| OpCode::MSG
| OpCode::NOD
| OpCode::PRI
| OpCode::RMU
| OpCode::SAT
| OpCode::SLP
| OpCode::SMC
| OpCode::SPS
| OpCode::STC
| OpCode::SVP
| OpCode::TUR
| OpCode::WAS
| OpCode::ZAM
| OpCode::HM2
| OpCode::POP
| OpCode::KE2
| OpCode::FR2 => {
put_varint(instr as i32, out);
}
// One operand codes
OpCode::BOA
| OpCode::BSL
| OpCode::FOB
| OpCode::FOM
| OpCode::QUA
| OpCode::UNI
| OpCode::MYB
| OpCode::MYD
| OpCode::FAI
| OpCode::FAO
| OpCode::WAI
| OpCode::FAC
| OpCode::GIT
| OpCode::NUM
| OpCode::DNA
| OpCode::DNP
| OpCode::FLm
| OpCode::FLp
| OpCode::MPp
| OpCode::SKm
| OpCode::SKp
| OpCode::EQp
| OpCode::EQm
| OpCode::MLp
| OpCode::ITp
| OpCode::ITm
| OpCode::AMm
| OpCode::UNJ
| OpCode::MPJ
| OpCode::YNJ
| OpCode::EVE
| OpCode::XX1
| OpCode::SIL
| OpCode::LIp
| OpCode::SOU
| OpCode::CMU
| OpCode::SSS
| OpCode::ACH
| OpCode::S2MV
| OpCode::S2PJ
| OpCode::PSH => {
let operand = read_number(iter)?;
put_varint(instr as i32, out);
put_varint(operand as i32, out);
}
// Two operand codes
OpCode::FON
| OpCode::MOV
| OpCode::AMp
| OpCode::NCJ
| OpCode::ECJ
| OpCode::FLJ
| OpCode::ITJ
| OpCode::SKJ
| OpCode::AMJ
| OpCode::SMP
| OpCode::PSp
| OpCode::IpN
| OpCode::FFm => {
let operand_a = read_number(iter)?;
if strict {
expect_char(b':', iter)?;
} else {
iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
}
let operand_b = read_number(iter)?;
put_varint(instr as i32, out);
put_varint(operand_a as i32, out);
put_varint(operand_b as i32, out);
}
// Three operand codes
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP | OpCode::INJ => {
let operand_a = read_number(iter)?;
if strict {
expect_char(b':', iter)?;
} else {
iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
}
let operand_b = read_number(iter)?;
if strict {
expect_char(b':', iter)?;
} else {
iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
}
let operand_c = read_number(iter)?;
put_varint(instr as i32, out);
put_varint(operand_a as i32, out);
put_varint(operand_b as i32, out);
put_varint(operand_c as i32, out);
}
// Four operand codes
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
let operand_a = read_number(iter)?;
if strict {
expect_char(b':', iter)?;
} else {
iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
}
let operand_b = read_number(iter)?;
if strict {
expect_char(b':', iter)?;
} else {
iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
}
let operand_c = read_number(iter)?;
if strict {
expect_char(b':', iter)?;
} else {
iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?;
}
let operand_d = read_number(iter)?;
put_varint(instr as i32, out);
put_varint(operand_a as i32, out);
put_varint(operand_b as i32, out);
put_varint(operand_c as i32, out);
put_varint(operand_d as i32, out);
}
OpCode::_NOP | OpCode::_UNI | OpCode::_STR | OpCode::_END => {
unreachable!()
}
}
Ok(())
}
}

View File

@ -0,0 +1,3 @@
pub struct CreditScript {
}

View File

@ -0,0 +1,186 @@
use std::io::Cursor;
use num_traits::FromPrimitive;
use crate::framework::error::GameError::InvalidValue;
use crate::framework::error::GameResult;
use crate::scripting::tsc::bytecode_utils::read_cur_varint;
use crate::scripting::tsc::opcodes::OpCode;
use crate::scripting::tsc::text_script::TextScript;
impl TextScript {
pub fn decompile_event(&self, id: u16) -> GameResult<String> {
if let Some(bytecode) = self.event_map.get(&id) {
let mut result = String::new();
let mut cursor = Cursor::new(bytecode);
while let Ok(op_num) = read_cur_varint(&mut cursor) {
let op_maybe: Option<OpCode> = FromPrimitive::from_i32(op_num);
if let Some(op) = op_maybe {
match op {
// Zero operand codes
OpCode::AEp
| OpCode::CAT
| OpCode::CIL
| OpCode::CLO
| OpCode::CLR
| OpCode::CPS
| OpCode::CRE
| OpCode::CSS
| OpCode::END
| OpCode::ESC
| OpCode::FLA
| OpCode::FMU
| OpCode::FRE
| OpCode::HMC
| OpCode::INI
| OpCode::KEY
| OpCode::LDP
| OpCode::MLP
| OpCode::MM0
| OpCode::MNA
| OpCode::MS2
| OpCode::MS3
| OpCode::MSG
| OpCode::NOD
| OpCode::PRI
| OpCode::RMU
| OpCode::SAT
| OpCode::SLP
| OpCode::SMC
| OpCode::SPS
| OpCode::STC
| OpCode::SVP
| OpCode::TUR
| OpCode::WAS
| OpCode::ZAM
| OpCode::HM2
| OpCode::POP
| OpCode::KE2
| OpCode::FR2 => {
result.push_str(format!("{:?}()\n", op).as_str());
}
// One operand codes
OpCode::BOA
| OpCode::BSL
| OpCode::FOB
| OpCode::FOM
| OpCode::QUA
| OpCode::UNI
| OpCode::MYB
| OpCode::MYD
| OpCode::FAI
| OpCode::FAO
| OpCode::WAI
| OpCode::FAC
| OpCode::GIT
| OpCode::NUM
| OpCode::DNA
| OpCode::DNP
| OpCode::FLm
| OpCode::FLp
| OpCode::MPp
| OpCode::SKm
| OpCode::SKp
| OpCode::EQp
| OpCode::EQm
| OpCode::MLp
| OpCode::ITp
| OpCode::ITm
| OpCode::AMm
| OpCode::UNJ
| OpCode::MPJ
| OpCode::YNJ
| OpCode::EVE
| OpCode::XX1
| OpCode::SIL
| OpCode::LIp
| OpCode::SOU
| OpCode::CMU
| OpCode::SSS
| OpCode::ACH
| OpCode::S2MV
| OpCode::S2PJ
| OpCode::PSH => {
let par_a = read_cur_varint(&mut cursor)?;
result.push_str(format!("{:?}({})\n", op, par_a).as_str());
}
// Two operand codes
OpCode::FON
| OpCode::MOV
| OpCode::AMp
| OpCode::NCJ
| OpCode::ECJ
| OpCode::FLJ
| OpCode::ITJ
| OpCode::SKJ
| OpCode::AMJ
| OpCode::SMP
| OpCode::PSp
| OpCode::IpN
| OpCode::FFm => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
result.push_str(format!("{:?}({}, {})\n", op, par_a, par_b).as_str());
}
// Three operand codes
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP | OpCode::INJ => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
let par_c = read_cur_varint(&mut cursor)?;
result.push_str(format!("{:?}({}, {}, {})\n", op, par_a, par_b, par_c).as_str());
}
// Four operand codes
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
let par_c = read_cur_varint(&mut cursor)?;
let par_d = read_cur_varint(&mut cursor)?;
result.push_str(format!("{:?}({}, {}, {}, {})\n", op, par_a, par_b, par_c, par_d).as_str());
}
OpCode::_STR => {
let len = read_cur_varint(&mut cursor)?;
result.push_str(format!("%string(len = {}, value = \"", len).as_str());
for _ in 0..len {
let chr = std::char::from_u32(read_cur_varint(&mut cursor)? as u32).unwrap_or('?');
match chr {
'\n' => {
result.push_str("\\n");
}
'\r' => {
result.push_str("\\r");
}
'\t' => {
result.push_str("\\t");
}
'\u{0000}'..='\u{001f}' | '\u{0080}'..='\u{ffff}' => {
result.push_str(chr.escape_unicode().to_string().as_str());
}
_ => {
result.push(chr);
}
}
}
result.push_str("\")\n");
}
OpCode::_NOP => result.push_str("%no_op()\n"),
OpCode::_UNI => result.push_str("%unimplemented()\n"),
OpCode::_END => result.push_str("%end_marker()\n"),
}
} else {
break;
}
}
Ok(result)
} else {
Err(InvalidValue("Unknown script.".to_string()))
}
}
}

7
src/scripting/tsc/mod.rs Normal file
View File

@ -0,0 +1,7 @@
mod bytecode_utils;
mod compiler;
pub mod credit_script;
mod decompiler;
mod opcodes;
mod parse_utils;
pub mod text_script;

View File

@ -0,0 +1,269 @@
use num_derive::FromPrimitive;
/// Engine's text script VM operation codes.
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]
#[repr(i32)]
pub enum OpCode {
// ---- Internal opcodes (used by bytecode, no TSC representation)
/// internal: no operation
_NOP = 0,
/// internal: unimplemented
_UNI,
/// internal: string marker
_STR,
/// internal: implicit END marker
_END,
// ---- Vanilla opcodes ----
/// <BOAxxxx, Starts boss animation
BOA,
/// <BSLxxxx, Starts boss fight
BSL,
/// <FOBxxxx:yyyy, Focuses on boss part xxxx and sets speed to yyyy ticks
FOB,
/// <FOMxxxx, Focuses on player and sets speed to xxxx
FOM,
/// <FONxxxx:yyyy, Focuses on NPC tagged with event xxxx and sets speed to yyyy
FON,
/// <FLA, Flashes screen
FLA,
/// <QUAxxxx, Starts quake for xxxx ticks
QUA,
/// <UNIxxxx, Sets player movement mode (0 = normal, 1 = main artery)
UNI,
/// <HMC, Hides the player
HMC,
/// <SMC, Shows the player
SMC,
/// <MM0, Halts horizontal movement
MM0,
/// <MOVxxxx:yyyy, Moves the player to tile (xxxx,yyyy)
MOV,
/// <MYBxxxx, Bumps the player from direction xxxx
MYB,
/// <MYDxxxx, Makes the player face direction xxxx
MYD,
/// <TRAxxxx:yyyy:zzzz:wwww, Travels to map xxxx, starts event yyyy, places the player at tile (zzzz,wwww)
TRA,
/// <END, Ends the current event
END,
/// <FRE, Starts world ticking and unlocks player controls.
FRE,
/// <FAIxxxx, Fades in with direction xxxx
FAI,
/// <FAOxxxx, Fades out with direction xxxx
FAO,
/// <WAIxxxx, Waits for xxxx frames
WAI,
/// <WASs, Waits until the player is standing
WAS,
/// <KEY, Locks out the player controls.
KEY,
/// <PRI, Stops world ticking and locks out player controls.
PRI,
/// <NOD, Waits for input
NOD,
/// <CAT, Instantly displays the text, works for entire event
CAT,
/// <SAT, Same as <CAT
SAT,
/// <TUR, Instantly displays the text, works until <MSG/2/3 or <END
TUR,
/// <CLO, Closes the text box
CLO,
/// <CLR, Clears the text box
CLR,
/// <FACxxxx, Shows the face xxxx in text box, 0 to hide,
/// CS+ Switch extensions:
/// - add 0100 to display talking animation (requires faceanm.dat)
/// - add 1000 to the number to display the face in opposite direction.
/// Note that those extensions are enabled on every mod by default.
FAC,
/// <GITxxxx, Shows the item xxxx above text box, 0 to hide
GIT,
/// <MS2, Displays text on top of the screen without background.
MS2,
/// <MS3, Displays text on top of the screen with background.
MS3,
/// <MSG, Displays text on bottom of the screen with background.
MSG,
/// <NUMxxxx, Displays a value from AM+, buggy in vanilla.
NUM,
/// <ANPxxxx:yyyy:zzzz, Changes the animation state of NPC tagged with
/// event xxxx to yyyy and set the direction to zzzz
ANP,
/// <CNPxxxx:yyyy:zzzz, Changes the NPC tagged with event xxxx to type yyyy
/// and makes it face direction zzzz
CNP,
/// <INPxxxx:yyyy:zzzz, Same as <CNP, but also sets NPC flag event_when_touched (0x100)
INP,
/// <MNPxxxx:yyyy:zzzz:wwww, Moves NPC tagged with event xxxx to tile position (xxxx,yyyy)
/// and makes it face direction zzzz
MNP,
/// <DNAxxxx, Deletes all NPCs of type xxxx
DNA,
/// <DNPxxxx, Deletes all NPCs of type xxxx
DNP,
SNP,
/// <FL-xxxx, Sets the flag xxxx to false
#[strum(serialize = "FL-")]
FLm,
/// <FL+xxxx, Sets the flag xxxx to true
#[strum(serialize = "FL+")]
FLp,
/// <MP-xxxx, Sets the map xxxx to true
#[strum(serialize = "MP+")]
MPp,
/// <SK-xxxx, Sets the skip flag xxx to false
#[strum(serialize = "SK-")]
SKm,
/// <SK+xxxx, Sets the skip flag xxx to true
#[strum(serialize = "SK+")]
SKp,
/// <EQ+xxxx, Sets specified bits in equip bitfield
#[strum(serialize = "EQ+")]
EQp,
/// <EQ-xxxx, Unsets specified bits in equip bitfield
#[strum(serialize = "EQ-")]
EQm,
/// <ML+xxxx, Adds xxxx to maximum health.
#[strum(serialize = "ML+")]
MLp,
/// <IT+xxxx, Adds item xxxx to players inventory.
#[strum(serialize = "IT+")]
ITp,
/// <IT-xxxx, Removes item xxxx to players inventory.
#[strum(serialize = "IT-")]
ITm,
/// <AM+xxxx:yyyy, Adds weapon xxxx with yyyy ammo (0 = infinite) to players inventory.
#[strum(serialize = "AM+")]
AMp,
/// <AM-xxxx, Removes weapon xxxx from players inventory.
#[strum(serialize = "AM-")]
AMm,
/// <TAMxxxx:yyyy:zzzz, Trades weapon xxxx for weapon yyyy with zzzz ammo
TAM,
/// <UNJxxxx, Jumps to event xxxx if no damage has been taken
UNJ,
/// <NCJxxxx:yyyy, Jumps to event xxxx if NPC of type yyyy is alive
NCJ,
/// <ECJxxxx:yyyy, Jumps to event xxxx if NPC tagged with event yyyy is alive
ECJ,
/// <FLJxxxx:yyyy, Jumps to event yyyy if flag xxxx is set
FLJ,
/// <FLJxxxx:yyyy, Jumps to event xxxx if player has item yyyy
ITJ,
/// <MPJxxxx, Jumps to event xxxx if map flag for current stage is set
MPJ,
/// <YNJxxxx, Jumps to event xxxx if prompt response is No, otherwise continues event execution
YNJ,
/// <MPJxxxx, Jumps to event xxxx if skip flag for is set
SKJ,
/// <EVExxxx, Jumps to event xxxx
EVE,
/// <AMJyyyy, Jumps to event xxxx player has weapon yyyy
AMJ,
/// <MLP, Displays the map of current stage
MLP,
/// <MLP, Displays the name of current stage
MNA,
/// <CMPxxxx:yyyy:zzzz, Sets the tile at (xxxx,yyyy) to type zzzz
CMP,
/// <SMPxxxx:yyyy:zzzz, Subtracts 1 from tile type at (xxxx,yyyy)
SMP,
/// <CRE, Shows credits
CRE,
/// <XX1xxxx, Shows falling island
XX1,
/// <CIL, Hides credits illustration
CIL,
/// <SILxxxx, Shows credits illustration xxxx
SIL,
/// <ESC, Exits to title screen
ESC,
/// <INI, Exits to "Studio Pixel presents" screen
INI,
/// <LDP, Loads a saved game
LDP,
/// <PS+xxxx:yyyy, Sets teleporter slot xxxx to event number yyyy
#[strum(serialize = "PS+")]
PSp,
/// <SLP, Shows the teleporter menu
SLP,
/// <ZAM, Resets the experience and level of all weapons
ZAM,
/// <AE+, Refills ammunition
#[strum(serialize = "AE+")]
AEp,
/// <LI+xxxx, Recovers xxxx health
#[strum(serialize = "LI+")]
LIp,
/// <SVP, Saves the current game
SVP,
/// <STC, Saves the state of Nikumaru counter
STC,
/// <SOUxxxx, Plays sound effect xxxx
SOU,
/// <CMUxxxx, Changes BGM to xxxx
CMU,
/// <FMU, Fades the BGM
FMU,
/// <RMU, Restores the music state of BGM played before current one
RMU,
/// <CPS, Stops the propeller sound
CPS,
/// <SPS, Starts the propeller sound
SPS,
/// <CSS, Stops the stream sound
CSS,
/// <SSSxxxx, Starts the stream sound at volume xxxx
SSS,
// ---- Cave Story+ specific opcodes ----
/// <ACHxxxx, triggers a Steam achievement. No-op in EGS/Humble Bundle version.
ACH,
// ---- Cave Story+ (Switch) specific opcodes ----
/// <HM2, HMC only for executor player.
HM2,
/// <2MVxxxx, Put another player near the player who executed the event.
/// 0000 - puts player on left side of executor player
/// 0001 - puts player on right side of executor player
/// 0002-0010 - unused
/// 0011.. - the first 3 digits are distance in pixels, the last digit is a flag
/// - if it's 1 put the player on right side of the player, otherwise put it on left
#[strum(serialize = "2MV")]
S2MV,
/// <2PJ, jump to event if in multiplayer mode.
#[strum(serialize = "2PJ")]
S2PJ,
/// <INJxxxx:yyyy:zzzz, Jumps to event zzzz if amount of item xxxx equals yyyy
INJ,
/// <I+Nxxxx:yyyy, Adds item xxxx with maximum amount of yyyy
#[strum(serialize = "I+N")]
IpN,
/// <FF-xxxx:yyyy, Sets first flag in range xxxx-yyyy to false
#[strum(serialize = "FF-")]
FFm,
/// <PSHxxxx, Pushes text script state to stack and starts event xxxx
PSH,
/// <POP, Restores text script state from stack and resumes previous event.
POP,
/// <KEY related to player 2?
KE2,
/// <FRE related to player 2?
FR2,
// ---- Custom opcodes, for use by modders ----
}

View File

@ -0,0 +1,37 @@
use std::iter::Peekable;
use crate::framework::error::GameError::ParseError;
use crate::framework::error::GameResult;
pub fn expect_char<I: Iterator<Item = u8>>(expect: u8, iter: &mut I) -> GameResult {
let res = iter.next();
match res {
Some(n) if n == expect => Ok(()),
Some(n) => Err(ParseError(format!("Expected {}, found {}", expect as char, n as char))),
None => Err(ParseError("Script unexpectedly ended.".to_string())),
}
}
pub fn skip_until<I: Iterator<Item = u8>>(expect: u8, iter: &mut Peekable<I>) -> GameResult {
while let Some(&chr) = iter.peek() {
if chr == expect {
return Ok(());
} else {
iter.next();
}
}
Err(ParseError("Script unexpectedly ended.".to_string()))
}
/// Reads a 4 digit TSC formatted number from iterator.
/// Intentionally does no '0'..'9' range checking, since it was often exploited by modders.
pub fn read_number<I: Iterator<Item = u8>>(iter: &mut Peekable<I>) -> GameResult<i32> {
Some(0)
.and_then(|result| iter.next().map(|v| result + 1000 * v.wrapping_sub(b'0') as i32))
.and_then(|result| iter.next().map(|v| result + 100 * v.wrapping_sub(b'0') as i32))
.and_then(|result| iter.next().map(|v| result + 10 * v.wrapping_sub(b'0') as i32))
.and_then(|result| iter.next().map(|v| result + v.wrapping_sub(b'0') as i32))
.ok_or_else(|| ParseError("Script unexpectedly ended.".to_string()))
}

File diff suppressed because it is too large Load Diff

View File

@ -23,13 +23,12 @@ use crate::rng::XorShift;
use crate::scene::game_scene::GameScene;
use crate::scene::title_scene::TitleScene;
use crate::scene::Scene;
#[cfg(feature = "scripting")]
use crate::scripting::LuaScriptingState;
#[cfg(feature = "scripting-lua")]
use crate::scripting::lua::LuaScriptingState;
use crate::settings::Settings;
use crate::sound::SoundManager;
use crate::stage::StageData;
use crate::str;
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::texture_set::TextureSet;
#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
@ -143,7 +142,7 @@ pub struct SharedGameState {
pub constants: EngineConstants,
pub font: BMFontRenderer,
pub texture_set: TextureSet,
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
pub lua: LuaScriptingState,
pub sound_manager: SoundManager,
pub settings: Settings,
@ -223,7 +222,7 @@ impl SharedGameState {
teleporter_slots: Vec::with_capacity(8),
carets: Vec::with_capacity(32),
touch_controls: TouchControls::new(),
base_path: str!(base_path),
base_path: base_path.to_owned(),
npc_table: NPCTable::new(),
npc_super_pos: (0, 0),
npc_curly_target: (0, 0),
@ -243,7 +242,7 @@ impl SharedGameState {
constants,
font,
texture_set,
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
lua: LuaScriptingState::new(),
sound_manager,
settings,
@ -290,7 +289,7 @@ impl SharedGameState {
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
self.reset();
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
self.lua.reload_scripts(ctx)?;
let mut next_scene = GameScene::new(self, ctx, self.constants.game.new_game_stage as usize)?;
@ -309,7 +308,7 @@ impl SharedGameState {
}
pub fn start_intro(&mut self, ctx: &mut Context) -> GameResult {
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
self.lua.reload_scripts(ctx)?;
let start_stage_id = self.constants.game.intro_stage as usize;
@ -356,7 +355,7 @@ impl SharedGameState {
profile.apply(self, &mut next_scene, ctx);
#[cfg(feature = "scripting")]
#[cfg(feature = "scripting-lua")]
self.lua.reload_scripts(ctx)?;
self.next_scene = Some(Box::new(next_scene));

View File

@ -24,7 +24,6 @@ use crate::sound::org_playback::{OrgPlaybackEngine, SavedOrganyaPlaybackState};
use crate::sound::organya::Song;
use crate::sound::pixtone::{PixToneParameters, PixTonePlayback};
use crate::sound::wave_bank::SoundBank;
use crate::str;
mod fir;
#[cfg(feature = "ogg-playback")]
@ -73,7 +72,7 @@ impl SoundManager {
let host = cpal::default_host();
let device =
host.default_output_device().ok_or_else(|| AudioError(str!("Error initializing audio device.")))?;
host.default_output_device().ok_or_else(|| AudioError("Error initializing audio device.".to_owned()))?;
let config = device.default_output_config()?;
let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/organya-wavetable-doukutsu.bin")?)?;
@ -284,7 +283,7 @@ impl SoundManager {
}
if speed <= 0.0 {
return Err(InvalidValue(str!("Speed must be bigger than 0.0!")));
return Err(InvalidValue("Speed must be bigger than 0.0!".to_owned()));
}
self.tx.send(PlaybackMessage::SetSpeed(speed))?;

View File

@ -12,7 +12,7 @@ use crate::framework::error::GameError::ResourceLoadError;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::map::{Map, NPCData};
use crate::text_script::TextScript;
use crate::scripting::tsc::text_script::TextScript;
use crate::common::Color;
#[derive(Debug, PartialEq, Eq, Hash)]

View File

@ -15,7 +15,6 @@ use crate::framework::filesystem;
use crate::framework::graphics::{create_texture, FilterMode};
use crate::settings::Settings;
use crate::shared_game_state::Season;
use crate::str;
pub static mut I_MAG: f32 = 1.0;
pub static mut G_MAG: f32 = 1.0;
@ -412,7 +411,7 @@ impl TextureSet {
if !self.tex_map.contains_key(name) {
let batch = self.load_texture(ctx, constants, name)?;
self.tex_map.insert(str!(name), batch);
self.tex_map.insert(name.to_owned(), batch);
}
Ok(self.tex_map.get_mut(name).unwrap())