mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-04-05 19:34:12 +00:00
refactoring
This commit is contained in:
parent
d147242199
commit
164b2bf295
Cargo.toml
drsandroid
src
bmfont.rsbmfont_renderer.rscommon.rs
components
engine_constants
lib.rslive_debugger.rsmacros.rsmap.rsnpc
player
profile.rsscene
scripting
shared_game_state.rssound
stage.rstexture_set.rs
|
@ -33,12 +33,13 @@ category = "Game"
|
||||||
osx_minimum_system_version = "10.12"
|
osx_minimum_system_version = "10.12"
|
||||||
|
|
||||||
[features]
|
[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"]
|
ogg-playback = ["lewton"]
|
||||||
backend-sdl = ["sdl2", "sdl2-sys"]
|
backend-sdl = ["sdl2", "sdl2-sys"]
|
||||||
backend-glutin = ["winit", "glutin", "render-opengl"]
|
backend-glutin = ["winit", "glutin", "render-opengl"]
|
||||||
render-opengl = []
|
render-opengl = []
|
||||||
scripting = ["lua-ffi"]
|
scripting-lua = ["lua-ffi"]
|
||||||
netplay = ["tokio", "serde_cbor"]
|
netplay = ["tokio", "serde_cbor"]
|
||||||
editor = []
|
editor = []
|
||||||
hooks = ["libc"]
|
hooks = ["libc"]
|
||||||
|
|
|
@ -11,4 +11,4 @@ crate-type = ["cdylib"]
|
||||||
ndk = "0.3"
|
ndk = "0.3"
|
||||||
ndk-glue = "0.3"
|
ndk-glue = "0.3"
|
||||||
ndk-sys = "0.2"
|
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"] }
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use byteorder::{LE, ReadBytesExt};
|
use byteorder::{ReadBytesExt, LE};
|
||||||
|
|
||||||
use crate::framework::error::GameError::ResourceLoadError;
|
use crate::framework::error::GameError::ResourceLoadError;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::str;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BmChar {
|
pub struct BmChar {
|
||||||
|
@ -43,7 +42,7 @@ impl BMFont {
|
||||||
data.read_exact(&mut magic)?;
|
data.read_exact(&mut magic)?;
|
||||||
|
|
||||||
if magic != 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() {
|
while let Ok(block_type) = data.read_u8() {
|
||||||
|
@ -80,30 +79,16 @@ impl BMFont {
|
||||||
let chnl = data.read_u8()?;
|
let chnl = data.read_u8()?;
|
||||||
|
|
||||||
if let Some(chr) = std::char::from_u32(id) {
|
if let Some(chr) = std::char::from_u32(id) {
|
||||||
chars.insert(chr, BmChar {
|
chars.insert(chr, BmChar { x, y, width, height, xoffset, yoffset, xadvance, page, chnl });
|
||||||
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 {
|
Ok(Self { pages, font_size, line_height, base, chars })
|
||||||
pages,
|
|
||||||
font_size,
|
|
||||||
line_height,
|
|
||||||
base,
|
|
||||||
chars,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,12 @@ use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::bmfont::BMFont;
|
use crate::bmfont::BMFont;
|
||||||
use crate::common::{FILE_TYPES, Rect};
|
use crate::common::{Rect, FILE_TYPES};
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
use crate::framework::context::Context;
|
use crate::framework::context::Context;
|
||||||
use crate::framework::error::GameError::ResourceLoadError;
|
use crate::framework::error::GameError::ResourceLoadError;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::filesystem;
|
use crate::framework::filesystem;
|
||||||
use crate::str;
|
|
||||||
use crate::texture_set::TextureSet;
|
use crate::texture_set::TextureSet;
|
||||||
|
|
||||||
pub struct BMFontRenderer {
|
pub struct BMFontRenderer {
|
||||||
|
@ -21,7 +20,7 @@ impl BMFontRenderer {
|
||||||
let root = PathBuf::from(root);
|
let root = PathBuf::from(root);
|
||||||
let full_path = &root.join(PathBuf::from(desc_path));
|
let full_path = &root.join(PathBuf::from(desc_path));
|
||||||
let desc_stem =
|
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 stem = full_path.parent().unwrap_or(full_path).join(desc_stem);
|
||||||
|
|
||||||
let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?;
|
let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?;
|
||||||
|
@ -101,7 +100,16 @@ impl BMFontRenderer {
|
||||||
texture_set: &mut TextureSet,
|
texture_set: &mut TextureSet,
|
||||||
ctx: &mut Context,
|
ctx: &mut Context,
|
||||||
) -> GameResult {
|
) -> 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)
|
self.draw_colored_text_scaled(iter, x, y, scale, color, constants, texture_set, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
184
src/common.rs
184
src/common.rs
|
@ -21,40 +21,40 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Flag(u32);
|
pub struct Flag(u32);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
|
||||||
/// Set if left wall was hit. (corresponds to flag & 0x01)
|
/// Set if left wall was hit. (corresponds to flag & 0x01)
|
||||||
pub hit_left_wall, set_hit_left_wall: 0;
|
pub hit_left_wall, set_hit_left_wall: 0;
|
||||||
/// Set if top wall was hit. (corresponds to flag & 0x02)
|
/// Set if top wall was hit. (corresponds to flag & 0x02)
|
||||||
pub hit_top_wall, set_hit_top_wall: 1;
|
pub hit_top_wall, set_hit_top_wall: 1;
|
||||||
/// Set if right wall was hit. (corresponds to flag & 0x04)
|
/// Set if right wall was hit. (corresponds to flag & 0x04)
|
||||||
pub hit_right_wall, set_hit_right_wall: 2;
|
pub hit_right_wall, set_hit_right_wall: 2;
|
||||||
/// Set if bottom wall was hit. (corresponds to flag & 0x08)
|
/// Set if bottom wall was hit. (corresponds to flag & 0x08)
|
||||||
pub hit_bottom_wall, set_hit_bottom_wall: 3;
|
pub hit_bottom_wall, set_hit_bottom_wall: 3;
|
||||||
/// Set if entity stays on right slope. (corresponds to flag & 0x10)
|
/// Set if entity stays on right slope. (corresponds to flag & 0x10)
|
||||||
pub hit_right_slope, set_hit_right_slope: 4;
|
pub hit_right_slope, set_hit_right_slope: 4;
|
||||||
/// Set if entity stays on left slope. (corresponds to flag & 0x20)
|
/// Set if entity stays on left slope. (corresponds to flag & 0x20)
|
||||||
pub hit_left_slope, set_hit_left_slope: 5;
|
pub hit_left_slope, set_hit_left_slope: 5;
|
||||||
/// Unknown purpose (corresponds to flag & 0x40)
|
/// Unknown purpose (corresponds to flag & 0x40)
|
||||||
pub flag_x40, set_flag_x40: 6;
|
pub flag_x40, set_flag_x40: 6;
|
||||||
/// Unknown purpose (corresponds to flag & 0x80)
|
/// Unknown purpose (corresponds to flag & 0x80)
|
||||||
pub flag_x80, set_flag_x80: 7;
|
pub flag_x80, set_flag_x80: 7;
|
||||||
/// Set if entity is in water. (corresponds to flag & 0x100)
|
/// Set if entity is in water. (corresponds to flag & 0x100)
|
||||||
pub in_water, set_in_water: 8;
|
pub in_water, set_in_water: 8;
|
||||||
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
|
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
|
||||||
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
|
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
|
||||||
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
|
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
|
||||||
pub force_left, set_force_left: 12; // 0x1000
|
pub force_left, set_force_left: 12; // 0x1000
|
||||||
pub force_up, set_force_up: 13; // 0x2000
|
pub force_up, set_force_up: 13; // 0x2000
|
||||||
pub force_right, set_force_right: 14; // 0x4000
|
pub force_right, set_force_right: 14; // 0x4000
|
||||||
pub force_down, set_force_down: 15; // 0x8000
|
pub force_down, set_force_down: 15; // 0x8000
|
||||||
pub hit_left_higher_half, set_hit_left_higher_half: 16; // 0x10000
|
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_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_lower_half, set_hit_right_lower_half: 18; // 0x40000
|
||||||
pub hit_right_higher_half, set_hit_right_higher_half: 19; // 0x80000
|
pub hit_right_higher_half, set_hit_right_higher_half: 19; // 0x80000
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flag {
|
impl Flag {
|
||||||
|
@ -68,79 +68,79 @@ impl Flag {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Equipment(u16);
|
pub struct Equipment(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
|
||||||
pub has_booster_0_8, set_booster_0_8: 0; // 0x01 / 0001
|
pub has_booster_0_8, set_booster_0_8: 0; // 0x01 / 0001
|
||||||
pub has_map, set_map: 1; // 0x02 / 0002
|
pub has_map, set_map: 1; // 0x02 / 0002
|
||||||
pub has_arms_barrier, set_arms_barrier: 2; // 0x04 / 0004
|
pub has_arms_barrier, set_arms_barrier: 2; // 0x04 / 0004
|
||||||
pub has_turbocharge, set_turbocharge: 3; // 0x08 / 0008
|
pub has_turbocharge, set_turbocharge: 3; // 0x08 / 0008
|
||||||
pub has_air_tank, set_air_tank: 4; // 0x10 / 0016
|
pub has_air_tank, set_air_tank: 4; // 0x10 / 0016
|
||||||
pub has_booster_2_0, set_booster_2_0: 5; // 0x20 / 0032
|
pub has_booster_2_0, set_booster_2_0: 5; // 0x20 / 0032
|
||||||
pub has_mimiga_mask, set_mimiga_mask: 6; // 0x40 / 0064
|
pub has_mimiga_mask, set_mimiga_mask: 6; // 0x40 / 0064
|
||||||
pub has_whimsical_star, set_whimsical_star: 7; // 0x080 / 0128
|
pub has_whimsical_star, set_whimsical_star: 7; // 0x080 / 0128
|
||||||
pub has_nikumaru, set_nikumaru: 8; // 0x100 / 0256
|
pub has_nikumaru, set_nikumaru: 8; // 0x100 / 0256
|
||||||
// for custom equips
|
// for custom equips
|
||||||
pub unused_1, set_unused_1: 9; // 0x200 / 0512
|
pub unused_1, set_unused_1: 9; // 0x200 / 0512
|
||||||
pub unused_2, set_unused_2: 10; // 0x400 / 1024
|
pub unused_2, set_unused_2: 10; // 0x400 / 1024
|
||||||
pub unused_3, set_unused_3: 11; // 0x800 / 2048
|
pub unused_3, set_unused_3: 11; // 0x800 / 2048
|
||||||
pub unused_4, set_unused_4: 12; // 0x1000 / 4096
|
pub unused_4, set_unused_4: 12; // 0x1000 / 4096
|
||||||
pub unused_5, set_unused_5: 13; // 0x2000 / 8192
|
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)
|
// 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_6, set_unused_6: 14; // 0x4000 / @384
|
||||||
pub unused_7, set_unused_7: 15; // 0x8000 / P768
|
pub unused_7, set_unused_7: 15; // 0x8000 / P768
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Condition(u16);
|
pub struct Condition(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
|
||||||
pub interacted, set_interacted: 0; // 0x01
|
pub interacted, set_interacted: 0; // 0x01
|
||||||
pub hidden, set_hidden: 1; // 0x02
|
pub hidden, set_hidden: 1; // 0x02
|
||||||
pub fallen, set_fallen: 2; // 0x04
|
pub fallen, set_fallen: 2; // 0x04
|
||||||
pub explode_die, set_explode_die: 3; // 0x08
|
pub explode_die, set_explode_die: 3; // 0x08
|
||||||
pub damage_boss, set_damage_boss: 4; // 0x10
|
pub damage_boss, set_damage_boss: 4; // 0x10
|
||||||
pub increase_acceleration, set_increase_acceleration: 5; // 0x20
|
pub increase_acceleration, set_increase_acceleration: 5; // 0x20
|
||||||
pub cond_x40, set_cond_x40: 6; // 0x40
|
pub cond_x40, set_cond_x40: 6; // 0x40
|
||||||
pub alive, set_alive: 7; // 0x80
|
pub alive, set_alive: 7; // 0x80
|
||||||
|
|
||||||
// engine specific flags
|
// engine specific flags
|
||||||
pub drs_novanish, set_drs_novanish: 14;
|
pub drs_novanish, set_drs_novanish: 14;
|
||||||
pub drs_boss, set_drs_boss: 15;
|
pub drs_boss, set_drs_boss: 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct ControlFlags(u16);
|
pub struct ControlFlags(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
|
||||||
pub tick_world, set_tick_world: 0; // 0x01
|
pub tick_world, set_tick_world: 0; // 0x01
|
||||||
pub control_enabled, set_control_enabled: 1; // 0x02
|
pub control_enabled, set_control_enabled: 1; // 0x02
|
||||||
pub interactions_disabled, set_interactions_disabled: 2; // 0x04
|
pub interactions_disabled, set_interactions_disabled: 2; // 0x04
|
||||||
pub credits_running, set_credits_running: 3; // 0x08
|
pub credits_running, set_credits_running: 3; // 0x08
|
||||||
|
|
||||||
// engine specific flags
|
// engine specific flags
|
||||||
pub friendly_fire, set_friendly_fire: 14;
|
pub friendly_fire, set_friendly_fire: 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct BulletFlag(u16);
|
pub struct BulletFlag(u16);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
pub flag_x01, set_flag_x01: 0; // 0x01
|
pub flag_x01, set_flag_x01: 0; // 0x01
|
||||||
pub flag_x02, set_flag_x02: 1; // 0x02
|
pub flag_x02, set_flag_x02: 1; // 0x02
|
||||||
pub no_collision_checks, set_no_collision_checks: 2; // 0x04
|
pub no_collision_checks, set_no_collision_checks: 2; // 0x04
|
||||||
pub bounce_from_walls, set_bounce_from_walls: 3; // 0x08
|
pub bounce_from_walls, set_bounce_from_walls: 3; // 0x08
|
||||||
pub flag_x10, set_flag_x10: 4; // 0x10
|
pub flag_x10, set_flag_x10: 4; // 0x10
|
||||||
pub flag_x20, set_flag_x20: 5; // 0x20
|
pub flag_x20, set_flag_x20: 5; // 0x20
|
||||||
pub can_destroy_snack, set_can_destroy_snack: 6; // 0x40
|
pub can_destroy_snack, set_can_destroy_snack: 6; // 0x40
|
||||||
pub flag_x80, set_flag_x80: 7; // 0x80
|
pub flag_x80, set_flag_x80: 7; // 0x80
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::input::touch_controls::TouchControlType;
|
||||||
use crate::inventory::Inventory;
|
use crate::inventory::Inventory;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::text_script::ScriptMode;
|
use crate::scripting::tsc::text_script::ScriptMode;
|
||||||
use crate::weapon::{WeaponLevel, WeaponType};
|
use crate::weapon::{WeaponLevel, WeaponType};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::frame::Frame;
|
||||||
use crate::input::touch_controls::TouchControlType;
|
use crate::input::touch_controls::TouchControlType;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::text_script::ScriptMode;
|
use crate::scripting::tsc::text_script::ScriptMode;
|
||||||
|
|
||||||
pub struct StageSelect {
|
pub struct StageSelect {
|
||||||
pub current_teleport_slot: u8,
|
pub current_teleport_slot: u8,
|
||||||
|
|
|
@ -6,10 +6,9 @@ use crate::case_insensitive_hashmap;
|
||||||
use crate::common::{BulletFlag, Color, Rect};
|
use crate::common::{BulletFlag, Color, Rect};
|
||||||
use crate::engine_constants::npcs::NPCConsts;
|
use crate::engine_constants::npcs::NPCConsts;
|
||||||
use crate::player::ControlMode;
|
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::sound::SoundManager;
|
||||||
use crate::str;
|
|
||||||
use crate::text_script::TextScriptEncoding;
|
|
||||||
|
|
||||||
mod npcs;
|
mod npcs;
|
||||||
|
|
||||||
|
@ -308,7 +307,7 @@ impl EngineConstants {
|
||||||
intro_player_pos: (3, 3),
|
intro_player_pos: (3, 3),
|
||||||
new_game_stage: 13,
|
new_game_stage: 13,
|
||||||
new_game_event: 200,
|
new_game_event: 200,
|
||||||
new_game_player_pos: (10, 8)
|
new_game_player_pos: (10, 8),
|
||||||
},
|
},
|
||||||
player: PlayerConsts {
|
player: PlayerConsts {
|
||||||
life: 3,
|
life: 3,
|
||||||
|
@ -1421,7 +1420,7 @@ impl EngineConstants {
|
||||||
text_speed_fast: 1,
|
text_speed_fast: 1,
|
||||||
},
|
},
|
||||||
title: TitleConsts {
|
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 },
|
logo_rect: Rect { left: 0, top: 0, right: 144, bottom: 40 },
|
||||||
menu_left_top: Rect { left: 0, top: 0, right: 8, bottom: 8 },
|
menu_left_top: Rect { left: 0, top: 0, right: 8, bottom: 8 },
|
||||||
menu_right_top: Rect { left: 236, top: 0, right: 244, 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 },
|
menu_right: Rect { left: 236, top: 8, right: 244, bottom: 16 },
|
||||||
},
|
},
|
||||||
inventory_dim_color: Color::from_rgba(0, 0, 0, 0),
|
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_scale: 1.0,
|
||||||
font_space_offset: 0.0,
|
font_space_offset: 0.0,
|
||||||
soundtracks: HashMap::new(),
|
soundtracks: HashMap::new(),
|
||||||
music_table: vec![
|
music_table: vec![
|
||||||
"xxxx".to_string(),
|
"xxxx".to_owned(),
|
||||||
"wanpaku".to_string(),
|
"wanpaku".to_owned(),
|
||||||
"anzen".to_string(),
|
"anzen".to_owned(),
|
||||||
"gameover".to_string(),
|
"gameover".to_owned(),
|
||||||
"gravity".to_string(),
|
"gravity".to_owned(),
|
||||||
"weed".to_string(),
|
"weed".to_owned(),
|
||||||
"mdown2".to_string(),
|
"mdown2".to_owned(),
|
||||||
"fireeye".to_string(),
|
"fireeye".to_owned(),
|
||||||
"vivi".to_string(),
|
"vivi".to_owned(),
|
||||||
"mura".to_string(),
|
"mura".to_owned(),
|
||||||
"fanfale1".to_string(),
|
"fanfale1".to_owned(),
|
||||||
"ginsuke".to_string(),
|
"ginsuke".to_owned(),
|
||||||
"cemetery".to_string(),
|
"cemetery".to_owned(),
|
||||||
"plant".to_string(),
|
"plant".to_owned(),
|
||||||
"kodou".to_string(),
|
"kodou".to_owned(),
|
||||||
"fanfale3".to_string(),
|
"fanfale3".to_owned(),
|
||||||
"fanfale2".to_string(),
|
"fanfale2".to_owned(),
|
||||||
"dr".to_string(),
|
"dr".to_owned(),
|
||||||
"escape".to_string(),
|
"escape".to_owned(),
|
||||||
"jenka".to_string(),
|
"jenka".to_owned(),
|
||||||
"maze".to_string(),
|
"maze".to_owned(),
|
||||||
"access".to_string(),
|
"access".to_owned(),
|
||||||
"ironh".to_string(),
|
"ironh".to_owned(),
|
||||||
"grand".to_string(),
|
"grand".to_owned(),
|
||||||
"curly".to_string(),
|
"curly".to_owned(),
|
||||||
"oside".to_string(),
|
"oside".to_owned(),
|
||||||
"requiem".to_string(),
|
"requiem".to_owned(),
|
||||||
"wanpak2".to_string(),
|
"wanpak2".to_owned(),
|
||||||
"quiet".to_string(),
|
"quiet".to_owned(),
|
||||||
"lastcave".to_string(),
|
"lastcave".to_owned(),
|
||||||
"balcony".to_string(),
|
"balcony".to_owned(),
|
||||||
"lastbtl".to_string(),
|
"lastbtl".to_owned(),
|
||||||
"lastbt3".to_string(),
|
"lastbt3".to_owned(),
|
||||||
"ending".to_string(),
|
"ending".to_owned(),
|
||||||
"zonbie".to_string(),
|
"zonbie".to_owned(),
|
||||||
"bdown".to_string(),
|
"bdown".to_owned(),
|
||||||
"hell".to_string(),
|
"hell".to_owned(),
|
||||||
"jenka2".to_string(),
|
"jenka2".to_owned(),
|
||||||
"marine".to_string(),
|
"marine".to_owned(),
|
||||||
"ballos".to_string(),
|
"ballos".to_owned(),
|
||||||
"toroko".to_string(),
|
"toroko".to_owned(),
|
||||||
"white".to_string(),
|
"white".to_owned(),
|
||||||
"kaze".to_string(),
|
"kaze".to_owned(),
|
||||||
],
|
],
|
||||||
organya_paths: vec![
|
organya_paths: vec![
|
||||||
"/org/".to_string(), // NXEngine
|
"/org/".to_owned(), // NXEngine
|
||||||
"/base/Org/".to_string(), // CS+
|
"/base/Org/".to_owned(), // CS+
|
||||||
"/Resource/ORG/".to_string(), // CSE2E
|
"/Resource/ORG/".to_owned(), // CSE2E
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1496,15 +1495,15 @@ impl EngineConstants {
|
||||||
|
|
||||||
self.is_cs_plus = true;
|
self.is_cs_plus = true;
|
||||||
self.supports_og_textures = true;
|
self.supports_og_textures = true;
|
||||||
self.tex_sizes.insert(str!("Caret"), (320, 320));
|
self.tex_sizes.insert("Caret".to_owned(), (320, 320));
|
||||||
self.tex_sizes.insert(str!("MyChar"), (200, 384));
|
self.tex_sizes.insert("MyChar".to_owned(), (200, 384));
|
||||||
self.tex_sizes.insert(str!("Npc/NpcRegu"), (320, 410));
|
self.tex_sizes.insert("Npc/NpcRegu".to_owned(), (320, 410));
|
||||||
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 50 };
|
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_scale = 0.5;
|
||||||
self.font_space_offset = 2.0;
|
self.font_space_offset = 2.0;
|
||||||
self.soundtracks.insert("Remastered".to_string(), "/base/Ogg11/".to_string());
|
self.soundtracks.insert("Remastered".to_owned(), "/base/Ogg11/".to_owned());
|
||||||
self.soundtracks.insert("New".to_string(), "/base/Ogg/".to_string());
|
self.soundtracks.insert("New".to_owned(), "/base/Ogg/".to_owned());
|
||||||
|
|
||||||
let typewriter_sample = PixToneParameters {
|
let typewriter_sample = PixToneParameters {
|
||||||
// fx2 (CS+)
|
// fx2 (CS+)
|
||||||
|
@ -1539,8 +1538,8 @@ impl EngineConstants {
|
||||||
|
|
||||||
self.is_switch = true;
|
self.is_switch = true;
|
||||||
self.supports_og_textures = true;
|
self.supports_og_textures = true;
|
||||||
self.tex_sizes.insert(str!("bkMoon"), (427, 240));
|
self.tex_sizes.insert("bkMoon".to_owned(), (427, 240));
|
||||||
self.tex_sizes.insert(str!("bkFog"), (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.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 62 };
|
||||||
self.inventory_dim_color = Color::from_rgba(0, 0, 32, 150);
|
self.inventory_dim_color = Color::from_rgba(0, 0, 32, 150);
|
||||||
self.textscript.encoding = TextScriptEncoding::UTF8;
|
self.textscript.encoding = TextScriptEncoding::UTF8;
|
||||||
|
@ -1549,11 +1548,9 @@ impl EngineConstants {
|
||||||
self.textscript.text_shadow = true;
|
self.textscript.text_shadow = true;
|
||||||
self.textscript.text_speed_normal = 1;
|
self.textscript.text_speed_normal = 1;
|
||||||
self.textscript.text_speed_fast = 0;
|
self.textscript.text_speed_fast = 0;
|
||||||
self.soundtracks.insert("Famitracks".to_string(), "/base/ogg17/".to_string());
|
self.soundtracks.insert("Famitracks".to_owned(), "/base/ogg17/".to_owned());
|
||||||
self.soundtracks.insert("Ridiculon".to_string(), "/base/ogg_ridic/".to_string());
|
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) {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
#[cfg_attr(feature = "scripting", macro_use)]
|
|
||||||
#[cfg(feature = "scripting")]
|
|
||||||
extern crate lua_ffi;
|
|
||||||
extern crate strum;
|
extern crate strum;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate strum_macros;
|
extern crate strum_macros;
|
||||||
|
@ -55,7 +52,6 @@ mod player;
|
||||||
mod profile;
|
mod profile;
|
||||||
mod rng;
|
mod rng;
|
||||||
mod scene;
|
mod scene;
|
||||||
#[cfg(feature = "scripting")]
|
|
||||||
mod scripting;
|
mod scripting;
|
||||||
mod settings;
|
mod settings;
|
||||||
#[cfg(feature = "backend-gfx")]
|
#[cfg(feature = "backend-gfx")]
|
||||||
|
@ -63,7 +59,6 @@ mod shaders;
|
||||||
mod shared_game_state;
|
mod shared_game_state;
|
||||||
mod sound;
|
mod sound;
|
||||||
mod stage;
|
mod stage;
|
||||||
mod text_script;
|
|
||||||
mod texture_set;
|
mod texture_set;
|
||||||
mod weapon;
|
mod weapon;
|
||||||
|
|
||||||
|
@ -281,7 +276,7 @@ pub fn init(options: LaunchOptions) -> GameResult {
|
||||||
|
|
||||||
let game = UnsafeCell::new(Game::new(&mut context)?);
|
let game = UnsafeCell::new(Game::new(&mut context)?);
|
||||||
let state_ref = unsafe { &mut *((&mut *game.get()).state.get()) };
|
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);
|
state_ref.lua.update_refs(unsafe { (&*game.get()).state.get() }, &mut context as *mut Context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::framework::context::Context;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::scene::game_scene::GameScene;
|
use crate::scene::game_scene::GameScene;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::text_script::TextScriptExecutionState;
|
use crate::scripting::tsc::text_script::TextScriptExecutionState;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
@ -121,7 +121,7 @@ impl LiveDebugger {
|
||||||
self.flags_visible = !self.flags_visible;
|
self.flags_visible = !self.flags_visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
{
|
{
|
||||||
ui.same_line(0.0);
|
ui.same_line(0.0);
|
||||||
if ui.button(im_str!("Reload Scripts"), [0.0, 0.0]) {
|
if ui.button(im_str!("Reload Scripts"), [0.0, 0.0]) {
|
||||||
|
|
|
@ -5,16 +5,6 @@ pub use core::fmt;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use core::mem::size_of;
|
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
|
// extended version of https://github.com/dzamlo/rust-bitfield
|
||||||
|
|
||||||
#[macro_export(local_inner_macros)]
|
#[macro_export(local_inner_macros)]
|
||||||
|
|
15
src/map.rs
15
src/map.rs
|
@ -13,7 +13,6 @@ use crate::framework::error::{GameError, GameResult};
|
||||||
use crate::framework::filesystem;
|
use crate::framework::filesystem;
|
||||||
use crate::shared_game_state::TileSize;
|
use crate::shared_game_state::TileSize;
|
||||||
use crate::stage::{PxPackScroll, PxPackStageData, StageData};
|
use crate::stage::{PxPackScroll, PxPackStageData, StageData};
|
||||||
use crate::str;
|
|
||||||
|
|
||||||
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
||||||
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
|
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
|
||||||
|
@ -43,7 +42,7 @@ impl Map {
|
||||||
map_data.read_exact(&mut magic)?;
|
map_data.read_exact(&mut magic)?;
|
||||||
|
|
||||||
if &magic != b"PXM" {
|
if &magic != b"PXM" {
|
||||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = map_data.read_u8()?;
|
let version = map_data.read_u8()?;
|
||||||
|
@ -80,7 +79,7 @@ impl Map {
|
||||||
// based on https://github.com/tilderain/pxEdit/blob/kero/pxMap.py
|
// based on https://github.com/tilderain/pxEdit/blob/kero/pxMap.py
|
||||||
|
|
||||||
if &magic != b"PXPACK121127a**\0" {
|
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> {
|
fn read_string<R: io::Read>(map_data: &mut R) -> GameResult<String> {
|
||||||
|
@ -153,7 +152,7 @@ impl Map {
|
||||||
map_data.read_exact(&mut magic)?;
|
map_data.read_exact(&mut magic)?;
|
||||||
|
|
||||||
if &magic != b"pxMAP01\0" {
|
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>()?;
|
let width_fg = map_data.read_u16::<LE>()?;
|
||||||
|
@ -170,7 +169,7 @@ impl Map {
|
||||||
map_data.read_exact(&mut magic)?;
|
map_data.read_exact(&mut magic)?;
|
||||||
|
|
||||||
if &magic != b"pxMAP01\0" {
|
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>()?;
|
let width_mg = map_data.read_u16::<LE>()?;
|
||||||
|
@ -188,7 +187,7 @@ impl Map {
|
||||||
map_data.read_exact(&mut magic)?;
|
map_data.read_exact(&mut magic)?;
|
||||||
|
|
||||||
if &magic != b"pxMAP01\0" {
|
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>()?;
|
let width_bg = map_data.read_u16::<LE>()?;
|
||||||
|
@ -214,7 +213,7 @@ impl Map {
|
||||||
attrib_data.read_exact(&mut magic)?;
|
attrib_data.read_exact(&mut magic)?;
|
||||||
|
|
||||||
if &magic != b"pxMAP01\0" {
|
if &magic != b"pxMAP01\0" {
|
||||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
attrib_data.read_u16::<LE>()?;
|
attrib_data.read_u16::<LE>()?;
|
||||||
|
@ -475,7 +474,7 @@ impl NPCData {
|
||||||
data.read_exact(&mut magic)?;
|
data.read_exact(&mut magic)?;
|
||||||
|
|
||||||
if &magic != b"PXE" {
|
if &magic != b"PXE" {
|
||||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = data.read_u8()?;
|
let version = data.read_u8()?;
|
||||||
|
|
|
@ -20,7 +20,6 @@ use crate::player::Player;
|
||||||
use crate::rng::Xoroshiro32PlusPlus;
|
use crate::rng::Xoroshiro32PlusPlus;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::stage::Stage;
|
use crate::stage::Stage;
|
||||||
use crate::str;
|
|
||||||
use crate::weapon::bullet::BulletManager;
|
use crate::weapon::bullet::BulletManager;
|
||||||
|
|
||||||
pub mod ai;
|
pub mod ai;
|
||||||
|
@ -199,7 +198,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu
|
||||||
) -> GameResult {
|
) -> GameResult {
|
||||||
#[allow(unused_assignments)]
|
#[allow(unused_assignments)]
|
||||||
let mut npc_hook_ran = false;
|
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);
|
npc_hook_ran = state.lua.try_run_npc_hook(self.id, self.npc_type);
|
||||||
}
|
}
|
||||||
|
@ -655,9 +654,9 @@ impl NPCTable {
|
||||||
pub fn new() -> NPCTable {
|
pub fn new() -> NPCTable {
|
||||||
NPCTable {
|
NPCTable {
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
tileset_name: str!("Stage/Prt0"),
|
tileset_name: "Stage/Prt0".to_owned(),
|
||||||
tex_npc1_name: str!("Npc/Npc0"),
|
tex_npc1_name: "Npc/Npc0".to_owned(),
|
||||||
tex_npc2_name: str!("Npc/Npc0"),
|
tex_npc2_name: "Npc/Npc0".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
use crate::player::Player;
|
pub struct RemotePlayerList {}
|
||||||
|
|
||||||
pub struct RemotePlayerList {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RemotePlayerList {
|
impl RemotePlayerList {
|
||||||
pub fn new() -> RemotePlayerList {
|
pub fn new() -> RemotePlayerList {
|
||||||
RemotePlayerList {
|
RemotePlayerList {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ use crate::framework::error::GameResult;
|
||||||
use crate::player::ControlMode;
|
use crate::player::ControlMode;
|
||||||
use crate::scene::game_scene::GameScene;
|
use crate::scene::game_scene::GameScene;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::str;
|
|
||||||
use crate::weapon::{WeaponLevel, WeaponType};
|
use crate::weapon::{WeaponLevel, WeaponType};
|
||||||
|
|
||||||
pub struct WeaponData {
|
pub struct WeaponData {
|
||||||
|
@ -294,7 +293,7 @@ impl GameProfile {
|
||||||
pub fn load_from_save<R: io::Read>(mut data: R) -> GameResult<GameProfile> {
|
pub fn load_from_save<R: io::Read>(mut data: R) -> GameResult<GameProfile> {
|
||||||
// Do041220
|
// Do041220
|
||||||
if data.read_u64::<BE>()? != 0x446f303431323230 {
|
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>()?;
|
let current_map = data.read_u32::<LE>()?;
|
||||||
|
@ -354,7 +353,7 @@ impl GameProfile {
|
||||||
data.read_exact(&mut map_flags)?;
|
data.read_exact(&mut map_flags)?;
|
||||||
|
|
||||||
if data.read_u32::<BE>()? != 0x464c4147 {
|
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];
|
let mut flags = [0u8; 1000];
|
||||||
|
|
|
@ -32,7 +32,7 @@ use crate::scene::title_scene::TitleScene;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||||
use crate::stage::{BackgroundType, Stage};
|
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::texture_set::SpriteBatch;
|
||||||
use crate::weapon::bullet::BulletManager;
|
use crate::weapon::bullet::BulletManager;
|
||||||
use crate::weapon::{Weapon, WeaponType};
|
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.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?);
|
||||||
state.textscript_vm.suspend = false;
|
state.textscript_vm.suspend = false;
|
||||||
state.tile_size = self.stage.map.tile_size;
|
state.tile_size = self.stage.map.tile_size;
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
state.lua.set_game_scene(self as *mut _);
|
state.lua.set_game_scene(self as *mut _);
|
||||||
|
|
||||||
self.player1.controller = state.settings.create_player1_controller();
|
self.player1.controller = state.settings.create_player1_controller();
|
||||||
|
@ -1948,7 +1948,7 @@ impl Scene for GameScene {
|
||||||
|
|
||||||
self.flash.tick(state, ())?;
|
self.flash.tick(state, ())?;
|
||||||
|
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
state.lua.scene_tick();
|
state.lua.scene_tick();
|
||||||
|
|
||||||
if state.control_flags.tick_world() {
|
if state.control_flags.tick_world() {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::scene::no_data_scene::NoDataScene;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
use crate::stage::StageData;
|
use crate::stage::StageData;
|
||||||
use crate::text_script::TextScript;
|
use crate::scripting::tsc::text_script::TextScript;
|
||||||
|
|
||||||
pub struct LoadingScene {
|
pub struct LoadingScene {
|
||||||
tick: usize,
|
tick: usize,
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
use lua_ffi::c_str;
|
||||||
use lua_ffi::ffi::luaL_Reg;
|
use lua_ffi::ffi::luaL_Reg;
|
||||||
|
use lua_ffi::lua_method;
|
||||||
use lua_ffi::{c_int, LuaObject, State};
|
use lua_ffi::{c_int, LuaObject, State};
|
||||||
|
|
||||||
use crate::common::{Direction, Rect};
|
use crate::common::{Direction, Rect};
|
||||||
use crate::framework::filesystem;
|
use crate::framework::filesystem;
|
||||||
use crate::rng::RNG;
|
use crate::rng::RNG;
|
||||||
use crate::scripting::{check_status, LuaScriptingState, DRS_RUNTIME_GLOBAL};
|
|
||||||
use crate::scene::game_scene::LightingMode;
|
use crate::scene::game_scene::LightingMode;
|
||||||
|
use crate::scripting::lua::{check_status, LuaScriptingState, DRS_RUNTIME_GLOBAL};
|
||||||
|
|
||||||
pub struct Doukutsu {
|
pub struct Doukutsu {
|
||||||
pub ptr: *mut LuaScriptingState,
|
pub ptr: *mut LuaScriptingState,
|
232
src/scripting/lua/mod.rs
Normal file
232
src/scripting/lua/mod.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
|
use lua_ffi::c_str;
|
||||||
use lua_ffi::ffi::luaL_Reg;
|
use lua_ffi::ffi::luaL_Reg;
|
||||||
|
use lua_ffi::lua_method;
|
||||||
use lua_ffi::{c_int, LuaObject, State};
|
use lua_ffi::{c_int, LuaObject, State};
|
||||||
|
|
||||||
use crate::scene::game_scene::GameScene;
|
use crate::scene::game_scene::GameScene;
|
||||||
use crate::scripting::{LuaScriptingState, DRS_RUNTIME_GLOBAL};
|
use crate::scripting::lua::{LuaScriptingState, DRS_RUNTIME_GLOBAL};
|
||||||
|
|
||||||
pub struct LuaGameScene {
|
pub struct LuaGameScene {
|
||||||
valid_reference: bool,
|
valid_reference: bool,
|
|
@ -1,235 +1,3 @@
|
||||||
use std::io::Read;
|
#[cfg(feature = "scripting-lua")]
|
||||||
use std::ptr::null_mut;
|
pub mod lua;
|
||||||
|
pub mod tsc;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
103
src/scripting/tsc/bytecode_utils.rs
Normal file
103
src/scripting/tsc/bytecode_utils.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
314
src/scripting/tsc/compiler.rs
Normal file
314
src/scripting/tsc/compiler.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
3
src/scripting/tsc/credit_script.rs
Normal file
3
src/scripting/tsc/credit_script.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub struct CreditScript {
|
||||||
|
|
||||||
|
}
|
186
src/scripting/tsc/decompiler.rs
Normal file
186
src/scripting/tsc/decompiler.rs
Normal 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
7
src/scripting/tsc/mod.rs
Normal 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;
|
269
src/scripting/tsc/opcodes.rs
Normal file
269
src/scripting/tsc/opcodes.rs
Normal 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 ----
|
||||||
|
}
|
37
src/scripting/tsc/parse_utils.rs
Normal file
37
src/scripting/tsc/parse_utils.rs
Normal 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
|
@ -23,13 +23,12 @@ use crate::rng::XorShift;
|
||||||
use crate::scene::game_scene::GameScene;
|
use crate::scene::game_scene::GameScene;
|
||||||
use crate::scene::title_scene::TitleScene;
|
use crate::scene::title_scene::TitleScene;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
use crate::scripting::LuaScriptingState;
|
use crate::scripting::lua::LuaScriptingState;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::sound::SoundManager;
|
use crate::sound::SoundManager;
|
||||||
use crate::stage::StageData;
|
use crate::stage::StageData;
|
||||||
use crate::str;
|
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||||
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
|
||||||
use crate::texture_set::TextureSet;
|
use crate::texture_set::TextureSet;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
@ -143,7 +142,7 @@ pub struct SharedGameState {
|
||||||
pub constants: EngineConstants,
|
pub constants: EngineConstants,
|
||||||
pub font: BMFontRenderer,
|
pub font: BMFontRenderer,
|
||||||
pub texture_set: TextureSet,
|
pub texture_set: TextureSet,
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
pub lua: LuaScriptingState,
|
pub lua: LuaScriptingState,
|
||||||
pub sound_manager: SoundManager,
|
pub sound_manager: SoundManager,
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
|
@ -223,7 +222,7 @@ impl SharedGameState {
|
||||||
teleporter_slots: Vec::with_capacity(8),
|
teleporter_slots: Vec::with_capacity(8),
|
||||||
carets: Vec::with_capacity(32),
|
carets: Vec::with_capacity(32),
|
||||||
touch_controls: TouchControls::new(),
|
touch_controls: TouchControls::new(),
|
||||||
base_path: str!(base_path),
|
base_path: base_path.to_owned(),
|
||||||
npc_table: NPCTable::new(),
|
npc_table: NPCTable::new(),
|
||||||
npc_super_pos: (0, 0),
|
npc_super_pos: (0, 0),
|
||||||
npc_curly_target: (0, 0),
|
npc_curly_target: (0, 0),
|
||||||
|
@ -243,7 +242,7 @@ impl SharedGameState {
|
||||||
constants,
|
constants,
|
||||||
font,
|
font,
|
||||||
texture_set,
|
texture_set,
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
lua: LuaScriptingState::new(),
|
lua: LuaScriptingState::new(),
|
||||||
sound_manager,
|
sound_manager,
|
||||||
settings,
|
settings,
|
||||||
|
@ -290,7 +289,7 @@ impl SharedGameState {
|
||||||
|
|
||||||
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
|
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
|
||||||
self.reset();
|
self.reset();
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
self.lua.reload_scripts(ctx)?;
|
self.lua.reload_scripts(ctx)?;
|
||||||
|
|
||||||
let mut next_scene = GameScene::new(self, ctx, self.constants.game.new_game_stage as usize)?;
|
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 {
|
pub fn start_intro(&mut self, ctx: &mut Context) -> GameResult {
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
self.lua.reload_scripts(ctx)?;
|
self.lua.reload_scripts(ctx)?;
|
||||||
|
|
||||||
let start_stage_id = self.constants.game.intro_stage as usize;
|
let start_stage_id = self.constants.game.intro_stage as usize;
|
||||||
|
@ -356,7 +355,7 @@ impl SharedGameState {
|
||||||
|
|
||||||
profile.apply(self, &mut next_scene, ctx);
|
profile.apply(self, &mut next_scene, ctx);
|
||||||
|
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting-lua")]
|
||||||
self.lua.reload_scripts(ctx)?;
|
self.lua.reload_scripts(ctx)?;
|
||||||
|
|
||||||
self.next_scene = Some(Box::new(next_scene));
|
self.next_scene = Some(Box::new(next_scene));
|
||||||
|
|
|
@ -24,7 +24,6 @@ use crate::sound::org_playback::{OrgPlaybackEngine, SavedOrganyaPlaybackState};
|
||||||
use crate::sound::organya::Song;
|
use crate::sound::organya::Song;
|
||||||
use crate::sound::pixtone::{PixToneParameters, PixTonePlayback};
|
use crate::sound::pixtone::{PixToneParameters, PixTonePlayback};
|
||||||
use crate::sound::wave_bank::SoundBank;
|
use crate::sound::wave_bank::SoundBank;
|
||||||
use crate::str;
|
|
||||||
|
|
||||||
mod fir;
|
mod fir;
|
||||||
#[cfg(feature = "ogg-playback")]
|
#[cfg(feature = "ogg-playback")]
|
||||||
|
@ -73,7 +72,7 @@ impl SoundManager {
|
||||||
|
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let device =
|
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 config = device.default_output_config()?;
|
||||||
|
|
||||||
let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/organya-wavetable-doukutsu.bin")?)?;
|
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 {
|
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))?;
|
self.tx.send(PlaybackMessage::SetSpeed(speed))?;
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::framework::error::GameError::ResourceLoadError;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::filesystem;
|
use crate::framework::filesystem;
|
||||||
use crate::map::{Map, NPCData};
|
use crate::map::{Map, NPCData};
|
||||||
use crate::text_script::TextScript;
|
use crate::scripting::tsc::text_script::TextScript;
|
||||||
use crate::common::Color;
|
use crate::common::Color;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
|
|
@ -15,7 +15,6 @@ use crate::framework::filesystem;
|
||||||
use crate::framework::graphics::{create_texture, FilterMode};
|
use crate::framework::graphics::{create_texture, FilterMode};
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::shared_game_state::Season;
|
use crate::shared_game_state::Season;
|
||||||
use crate::str;
|
|
||||||
|
|
||||||
pub static mut I_MAG: f32 = 1.0;
|
pub static mut I_MAG: f32 = 1.0;
|
||||||
pub static mut G_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) {
|
if !self.tex_map.contains_key(name) {
|
||||||
let batch = self.load_texture(ctx, constants, 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())
|
Ok(self.tex_map.get_mut(name).unwrap())
|
||||||
|
|
Loading…
Reference in a new issue