mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-03-24 02:49:21 +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"
|
||||
|
||||
[features]
|
||||
default = ["scripting", "backend-sdl", "render-opengl", "ogg-playback", "exe", "netplay"]
|
||||
default = ["default-base", "backend-sdl", "render-opengl", "exe"]
|
||||
default-base = ["scripting-lua", "ogg-playback", "netplay"]
|
||||
ogg-playback = ["lewton"]
|
||||
backend-sdl = ["sdl2", "sdl2-sys"]
|
||||
backend-glutin = ["winit", "glutin", "render-opengl"]
|
||||
render-opengl = []
|
||||
scripting = ["lua-ffi"]
|
||||
scripting-lua = ["lua-ffi"]
|
||||
netplay = ["tokio", "serde_cbor"]
|
||||
editor = []
|
||||
hooks = ["libc"]
|
||||
|
|
|
@ -11,4 +11,4 @@ crate-type = ["cdylib"]
|
|||
ndk = "0.3"
|
||||
ndk-glue = "0.3"
|
||||
ndk-sys = "0.2"
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["android", "backend-glutin", "ogg-playback", "scripting"] }
|
||||
doukutsu-rs = { path = "../", default-features = false, features = ["default-base", "backend-glutin"] }
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use byteorder::{LE, ReadBytesExt};
|
||||
use byteorder::{ReadBytesExt, LE};
|
||||
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::str;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BmChar {
|
||||
|
@ -43,7 +42,7 @@ impl BMFont {
|
|||
data.read_exact(&mut magic)?;
|
||||
|
||||
if magic != MAGIC {
|
||||
return Err(ResourceLoadError(str!( "Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
while let Ok(block_type) = data.read_u8() {
|
||||
|
@ -80,30 +79,16 @@ impl BMFont {
|
|||
let chnl = data.read_u8()?;
|
||||
|
||||
if let Some(chr) = std::char::from_u32(id) {
|
||||
chars.insert(chr, BmChar {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
xoffset,
|
||||
yoffset,
|
||||
xadvance,
|
||||
page,
|
||||
chnl,
|
||||
});
|
||||
chars.insert(chr, BmChar { x, y, width, height, xoffset, yoffset, xadvance, page, chnl });
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => { return Err(ResourceLoadError(str!( "Unknown block type."))); }
|
||||
_ => {
|
||||
return Err(ResourceLoadError("Unknown block type.".to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
pages,
|
||||
font_size,
|
||||
line_height,
|
||||
base,
|
||||
chars,
|
||||
})
|
||||
Ok(Self { pages, font_size, line_height, base, chars })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,12 @@ use std::collections::HashSet;
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::bmfont::BMFont;
|
||||
use crate::common::{FILE_TYPES, Rect};
|
||||
use crate::common::{Rect, FILE_TYPES};
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameError::ResourceLoadError;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::str;
|
||||
use crate::texture_set::TextureSet;
|
||||
|
||||
pub struct BMFontRenderer {
|
||||
|
@ -21,7 +20,7 @@ impl BMFontRenderer {
|
|||
let root = PathBuf::from(root);
|
||||
let full_path = &root.join(PathBuf::from(desc_path));
|
||||
let desc_stem =
|
||||
full_path.file_stem().ok_or_else(|| ResourceLoadError(str!("Cannot extract the file stem.")))?;
|
||||
full_path.file_stem().ok_or_else(|| ResourceLoadError("Cannot extract the file stem.".to_owned()))?;
|
||||
let stem = full_path.parent().unwrap_or(full_path).join(desc_stem);
|
||||
|
||||
let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?;
|
||||
|
@ -101,7 +100,16 @@ impl BMFontRenderer {
|
|||
texture_set: &mut TextureSet,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.draw_colored_text_scaled(iter.clone(), x + scale, y + scale, scale, (0, 0, 0, 150), constants, texture_set, ctx)?;
|
||||
self.draw_colored_text_scaled(
|
||||
iter.clone(),
|
||||
x + scale,
|
||||
y + scale,
|
||||
scale,
|
||||
(0, 0, 0, 150),
|
||||
constants,
|
||||
texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
self.draw_colored_text_scaled(iter, x, y, scale, color, constants, texture_set, ctx)
|
||||
}
|
||||
|
||||
|
|
184
src/common.rs
184
src/common.rs
|
@ -21,40 +21,40 @@ lazy_static! {
|
|||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Flag(u32);
|
||||
impl Debug;
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Flag(u32);
|
||||
impl Debug;
|
||||
|
||||
/// Set if left wall was hit. (corresponds to flag & 0x01)
|
||||
pub hit_left_wall, set_hit_left_wall: 0;
|
||||
/// Set if top wall was hit. (corresponds to flag & 0x02)
|
||||
pub hit_top_wall, set_hit_top_wall: 1;
|
||||
/// Set if right wall was hit. (corresponds to flag & 0x04)
|
||||
pub hit_right_wall, set_hit_right_wall: 2;
|
||||
/// Set if bottom wall was hit. (corresponds to flag & 0x08)
|
||||
pub hit_bottom_wall, set_hit_bottom_wall: 3;
|
||||
/// Set if entity stays on right slope. (corresponds to flag & 0x10)
|
||||
pub hit_right_slope, set_hit_right_slope: 4;
|
||||
/// Set if entity stays on left slope. (corresponds to flag & 0x20)
|
||||
pub hit_left_slope, set_hit_left_slope: 5;
|
||||
/// Unknown purpose (corresponds to flag & 0x40)
|
||||
pub flag_x40, set_flag_x40: 6;
|
||||
/// Unknown purpose (corresponds to flag & 0x80)
|
||||
pub flag_x80, set_flag_x80: 7;
|
||||
/// Set if entity is in water. (corresponds to flag & 0x100)
|
||||
pub in_water, set_in_water: 8;
|
||||
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
|
||||
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
|
||||
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
|
||||
pub force_left, set_force_left: 12; // 0x1000
|
||||
pub force_up, set_force_up: 13; // 0x2000
|
||||
pub force_right, set_force_right: 14; // 0x4000
|
||||
pub force_down, set_force_down: 15; // 0x8000
|
||||
pub hit_left_higher_half, set_hit_left_higher_half: 16; // 0x10000
|
||||
pub hit_left_lower_half, set_hit_left_lower_half: 17; // 0x20000
|
||||
pub hit_right_lower_half, set_hit_right_lower_half: 18; // 0x40000
|
||||
pub hit_right_higher_half, set_hit_right_higher_half: 19; // 0x80000
|
||||
/// Set if left wall was hit. (corresponds to flag & 0x01)
|
||||
pub hit_left_wall, set_hit_left_wall: 0;
|
||||
/// Set if top wall was hit. (corresponds to flag & 0x02)
|
||||
pub hit_top_wall, set_hit_top_wall: 1;
|
||||
/// Set if right wall was hit. (corresponds to flag & 0x04)
|
||||
pub hit_right_wall, set_hit_right_wall: 2;
|
||||
/// Set if bottom wall was hit. (corresponds to flag & 0x08)
|
||||
pub hit_bottom_wall, set_hit_bottom_wall: 3;
|
||||
/// Set if entity stays on right slope. (corresponds to flag & 0x10)
|
||||
pub hit_right_slope, set_hit_right_slope: 4;
|
||||
/// Set if entity stays on left slope. (corresponds to flag & 0x20)
|
||||
pub hit_left_slope, set_hit_left_slope: 5;
|
||||
/// Unknown purpose (corresponds to flag & 0x40)
|
||||
pub flag_x40, set_flag_x40: 6;
|
||||
/// Unknown purpose (corresponds to flag & 0x80)
|
||||
pub flag_x80, set_flag_x80: 7;
|
||||
/// Set if entity is in water. (corresponds to flag & 0x100)
|
||||
pub in_water, set_in_water: 8;
|
||||
pub weapon_hit_block, set_weapon_hit_block: 9; // 0x200
|
||||
pub hit_by_spike, set_hit_by_spike: 10; // 0x400
|
||||
pub water_splash_facing_right, set_water_splash_facing_right: 11; // 0x800
|
||||
pub force_left, set_force_left: 12; // 0x1000
|
||||
pub force_up, set_force_up: 13; // 0x2000
|
||||
pub force_right, set_force_right: 14; // 0x4000
|
||||
pub force_down, set_force_down: 15; // 0x8000
|
||||
pub hit_left_higher_half, set_hit_left_higher_half: 16; // 0x10000
|
||||
pub hit_left_lower_half, set_hit_left_lower_half: 17; // 0x20000
|
||||
pub hit_right_lower_half, set_hit_right_lower_half: 18; // 0x40000
|
||||
pub hit_right_higher_half, set_hit_right_higher_half: 19; // 0x80000
|
||||
}
|
||||
|
||||
impl Flag {
|
||||
|
@ -68,79 +68,79 @@ impl Flag {
|
|||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Equipment(u16);
|
||||
impl Debug;
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Equipment(u16);
|
||||
impl Debug;
|
||||
|
||||
pub has_booster_0_8, set_booster_0_8: 0; // 0x01 / 0001
|
||||
pub has_map, set_map: 1; // 0x02 / 0002
|
||||
pub has_arms_barrier, set_arms_barrier: 2; // 0x04 / 0004
|
||||
pub has_turbocharge, set_turbocharge: 3; // 0x08 / 0008
|
||||
pub has_air_tank, set_air_tank: 4; // 0x10 / 0016
|
||||
pub has_booster_2_0, set_booster_2_0: 5; // 0x20 / 0032
|
||||
pub has_mimiga_mask, set_mimiga_mask: 6; // 0x40 / 0064
|
||||
pub has_whimsical_star, set_whimsical_star: 7; // 0x080 / 0128
|
||||
pub has_nikumaru, set_nikumaru: 8; // 0x100 / 0256
|
||||
// for custom equips
|
||||
pub unused_1, set_unused_1: 9; // 0x200 / 0512
|
||||
pub unused_2, set_unused_2: 10; // 0x400 / 1024
|
||||
pub unused_3, set_unused_3: 11; // 0x800 / 2048
|
||||
pub unused_4, set_unused_4: 12; // 0x1000 / 4096
|
||||
pub unused_5, set_unused_5: 13; // 0x2000 / 8192
|
||||
// bit 14 and 15 aren't accessible via TSC without abusing overflows (won't work in strict mode)
|
||||
pub unused_6, set_unused_6: 14; // 0x4000 / @384
|
||||
pub unused_7, set_unused_7: 15; // 0x8000 / P768
|
||||
pub has_booster_0_8, set_booster_0_8: 0; // 0x01 / 0001
|
||||
pub has_map, set_map: 1; // 0x02 / 0002
|
||||
pub has_arms_barrier, set_arms_barrier: 2; // 0x04 / 0004
|
||||
pub has_turbocharge, set_turbocharge: 3; // 0x08 / 0008
|
||||
pub has_air_tank, set_air_tank: 4; // 0x10 / 0016
|
||||
pub has_booster_2_0, set_booster_2_0: 5; // 0x20 / 0032
|
||||
pub has_mimiga_mask, set_mimiga_mask: 6; // 0x40 / 0064
|
||||
pub has_whimsical_star, set_whimsical_star: 7; // 0x080 / 0128
|
||||
pub has_nikumaru, set_nikumaru: 8; // 0x100 / 0256
|
||||
// for custom equips
|
||||
pub unused_1, set_unused_1: 9; // 0x200 / 0512
|
||||
pub unused_2, set_unused_2: 10; // 0x400 / 1024
|
||||
pub unused_3, set_unused_3: 11; // 0x800 / 2048
|
||||
pub unused_4, set_unused_4: 12; // 0x1000 / 4096
|
||||
pub unused_5, set_unused_5: 13; // 0x2000 / 8192
|
||||
// bit 14 and 15 aren't accessible via TSC without abusing overflows (won't work in strict mode)
|
||||
pub unused_6, set_unused_6: 14; // 0x4000 / @384
|
||||
pub unused_7, set_unused_7: 15; // 0x8000 / P768
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Condition(u16);
|
||||
impl Debug;
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Condition(u16);
|
||||
impl Debug;
|
||||
|
||||
pub interacted, set_interacted: 0; // 0x01
|
||||
pub hidden, set_hidden: 1; // 0x02
|
||||
pub fallen, set_fallen: 2; // 0x04
|
||||
pub explode_die, set_explode_die: 3; // 0x08
|
||||
pub damage_boss, set_damage_boss: 4; // 0x10
|
||||
pub increase_acceleration, set_increase_acceleration: 5; // 0x20
|
||||
pub cond_x40, set_cond_x40: 6; // 0x40
|
||||
pub alive, set_alive: 7; // 0x80
|
||||
pub interacted, set_interacted: 0; // 0x01
|
||||
pub hidden, set_hidden: 1; // 0x02
|
||||
pub fallen, set_fallen: 2; // 0x04
|
||||
pub explode_die, set_explode_die: 3; // 0x08
|
||||
pub damage_boss, set_damage_boss: 4; // 0x10
|
||||
pub increase_acceleration, set_increase_acceleration: 5; // 0x20
|
||||
pub cond_x40, set_cond_x40: 6; // 0x40
|
||||
pub alive, set_alive: 7; // 0x80
|
||||
|
||||
// engine specific flags
|
||||
pub drs_novanish, set_drs_novanish: 14;
|
||||
pub drs_boss, set_drs_boss: 15;
|
||||
// engine specific flags
|
||||
pub drs_novanish, set_drs_novanish: 14;
|
||||
pub drs_boss, set_drs_boss: 15;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
pub struct ControlFlags(u16);
|
||||
impl Debug;
|
||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
pub struct ControlFlags(u16);
|
||||
impl Debug;
|
||||
|
||||
pub tick_world, set_tick_world: 0; // 0x01
|
||||
pub control_enabled, set_control_enabled: 1; // 0x02
|
||||
pub interactions_disabled, set_interactions_disabled: 2; // 0x04
|
||||
pub credits_running, set_credits_running: 3; // 0x08
|
||||
pub tick_world, set_tick_world: 0; // 0x01
|
||||
pub control_enabled, set_control_enabled: 1; // 0x02
|
||||
pub interactions_disabled, set_interactions_disabled: 2; // 0x04
|
||||
pub credits_running, set_credits_running: 3; // 0x08
|
||||
|
||||
// engine specific flags
|
||||
pub friendly_fire, set_friendly_fire: 14;
|
||||
// engine specific flags
|
||||
pub friendly_fire, set_friendly_fire: 14;
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct BulletFlag(u16);
|
||||
impl Debug;
|
||||
pub flag_x01, set_flag_x01: 0; // 0x01
|
||||
pub flag_x02, set_flag_x02: 1; // 0x02
|
||||
pub no_collision_checks, set_no_collision_checks: 2; // 0x04
|
||||
pub bounce_from_walls, set_bounce_from_walls: 3; // 0x08
|
||||
pub flag_x10, set_flag_x10: 4; // 0x10
|
||||
pub flag_x20, set_flag_x20: 5; // 0x20
|
||||
pub can_destroy_snack, set_can_destroy_snack: 6; // 0x40
|
||||
pub flag_x80, set_flag_x80: 7; // 0x80
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct BulletFlag(u16);
|
||||
impl Debug;
|
||||
pub flag_x01, set_flag_x01: 0; // 0x01
|
||||
pub flag_x02, set_flag_x02: 1; // 0x02
|
||||
pub no_collision_checks, set_no_collision_checks: 2; // 0x04
|
||||
pub bounce_from_walls, set_bounce_from_walls: 3; // 0x08
|
||||
pub flag_x10, set_flag_x10: 4; // 0x10
|
||||
pub flag_x20, set_flag_x20: 5; // 0x20
|
||||
pub can_destroy_snack, set_can_destroy_snack: 6; // 0x40
|
||||
pub flag_x80, set_flag_x80: 7; // 0x80
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::input::touch_controls::TouchControlType;
|
|||
use crate::inventory::Inventory;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::text_script::ScriptMode;
|
||||
use crate::scripting::tsc::text_script::ScriptMode;
|
||||
use crate::weapon::{WeaponLevel, WeaponType};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::frame::Frame;
|
|||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::player::Player;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::text_script::ScriptMode;
|
||||
use crate::scripting::tsc::text_script::ScriptMode;
|
||||
|
||||
pub struct StageSelect {
|
||||
pub current_teleport_slot: u8,
|
||||
|
|
|
@ -6,10 +6,9 @@ use crate::case_insensitive_hashmap;
|
|||
use crate::common::{BulletFlag, Color, Rect};
|
||||
use crate::engine_constants::npcs::NPCConsts;
|
||||
use crate::player::ControlMode;
|
||||
use crate::sound::pixtone::{Channel, PixToneParameters, Waveform, Envelope};
|
||||
use crate::scripting::tsc::text_script::TextScriptEncoding;
|
||||
use crate::sound::pixtone::{Channel, Envelope, PixToneParameters, Waveform};
|
||||
use crate::sound::SoundManager;
|
||||
use crate::str;
|
||||
use crate::text_script::TextScriptEncoding;
|
||||
|
||||
mod npcs;
|
||||
|
||||
|
@ -308,7 +307,7 @@ impl EngineConstants {
|
|||
intro_player_pos: (3, 3),
|
||||
new_game_stage: 13,
|
||||
new_game_event: 200,
|
||||
new_game_player_pos: (10, 8)
|
||||
new_game_player_pos: (10, 8),
|
||||
},
|
||||
player: PlayerConsts {
|
||||
life: 3,
|
||||
|
@ -1421,7 +1420,7 @@ impl EngineConstants {
|
|||
text_speed_fast: 1,
|
||||
},
|
||||
title: TitleConsts {
|
||||
intro_text: "Studio Pixel presents".to_string(),
|
||||
intro_text: "Studio Pixel presents".to_owned(),
|
||||
logo_rect: Rect { left: 0, top: 0, right: 144, bottom: 40 },
|
||||
menu_left_top: Rect { left: 0, top: 0, right: 8, bottom: 8 },
|
||||
menu_right_top: Rect { left: 236, top: 0, right: 244, bottom: 8 },
|
||||
|
@ -1434,59 +1433,59 @@ impl EngineConstants {
|
|||
menu_right: Rect { left: 236, top: 8, right: 244, bottom: 16 },
|
||||
},
|
||||
inventory_dim_color: Color::from_rgba(0, 0, 0, 0),
|
||||
font_path: "csfont.fnt".to_string(),
|
||||
font_path: "csfont.fnt".to_owned(),
|
||||
font_scale: 1.0,
|
||||
font_space_offset: 0.0,
|
||||
soundtracks: HashMap::new(),
|
||||
music_table: vec![
|
||||
"xxxx".to_string(),
|
||||
"wanpaku".to_string(),
|
||||
"anzen".to_string(),
|
||||
"gameover".to_string(),
|
||||
"gravity".to_string(),
|
||||
"weed".to_string(),
|
||||
"mdown2".to_string(),
|
||||
"fireeye".to_string(),
|
||||
"vivi".to_string(),
|
||||
"mura".to_string(),
|
||||
"fanfale1".to_string(),
|
||||
"ginsuke".to_string(),
|
||||
"cemetery".to_string(),
|
||||
"plant".to_string(),
|
||||
"kodou".to_string(),
|
||||
"fanfale3".to_string(),
|
||||
"fanfale2".to_string(),
|
||||
"dr".to_string(),
|
||||
"escape".to_string(),
|
||||
"jenka".to_string(),
|
||||
"maze".to_string(),
|
||||
"access".to_string(),
|
||||
"ironh".to_string(),
|
||||
"grand".to_string(),
|
||||
"curly".to_string(),
|
||||
"oside".to_string(),
|
||||
"requiem".to_string(),
|
||||
"wanpak2".to_string(),
|
||||
"quiet".to_string(),
|
||||
"lastcave".to_string(),
|
||||
"balcony".to_string(),
|
||||
"lastbtl".to_string(),
|
||||
"lastbt3".to_string(),
|
||||
"ending".to_string(),
|
||||
"zonbie".to_string(),
|
||||
"bdown".to_string(),
|
||||
"hell".to_string(),
|
||||
"jenka2".to_string(),
|
||||
"marine".to_string(),
|
||||
"ballos".to_string(),
|
||||
"toroko".to_string(),
|
||||
"white".to_string(),
|
||||
"kaze".to_string(),
|
||||
"xxxx".to_owned(),
|
||||
"wanpaku".to_owned(),
|
||||
"anzen".to_owned(),
|
||||
"gameover".to_owned(),
|
||||
"gravity".to_owned(),
|
||||
"weed".to_owned(),
|
||||
"mdown2".to_owned(),
|
||||
"fireeye".to_owned(),
|
||||
"vivi".to_owned(),
|
||||
"mura".to_owned(),
|
||||
"fanfale1".to_owned(),
|
||||
"ginsuke".to_owned(),
|
||||
"cemetery".to_owned(),
|
||||
"plant".to_owned(),
|
||||
"kodou".to_owned(),
|
||||
"fanfale3".to_owned(),
|
||||
"fanfale2".to_owned(),
|
||||
"dr".to_owned(),
|
||||
"escape".to_owned(),
|
||||
"jenka".to_owned(),
|
||||
"maze".to_owned(),
|
||||
"access".to_owned(),
|
||||
"ironh".to_owned(),
|
||||
"grand".to_owned(),
|
||||
"curly".to_owned(),
|
||||
"oside".to_owned(),
|
||||
"requiem".to_owned(),
|
||||
"wanpak2".to_owned(),
|
||||
"quiet".to_owned(),
|
||||
"lastcave".to_owned(),
|
||||
"balcony".to_owned(),
|
||||
"lastbtl".to_owned(),
|
||||
"lastbt3".to_owned(),
|
||||
"ending".to_owned(),
|
||||
"zonbie".to_owned(),
|
||||
"bdown".to_owned(),
|
||||
"hell".to_owned(),
|
||||
"jenka2".to_owned(),
|
||||
"marine".to_owned(),
|
||||
"ballos".to_owned(),
|
||||
"toroko".to_owned(),
|
||||
"white".to_owned(),
|
||||
"kaze".to_owned(),
|
||||
],
|
||||
organya_paths: vec![
|
||||
"/org/".to_string(), // NXEngine
|
||||
"/base/Org/".to_string(), // CS+
|
||||
"/Resource/ORG/".to_string(), // CSE2E
|
||||
"/org/".to_owned(), // NXEngine
|
||||
"/base/Org/".to_owned(), // CS+
|
||||
"/Resource/ORG/".to_owned(), // CSE2E
|
||||
],
|
||||
}
|
||||
}
|
||||
|
@ -1496,15 +1495,15 @@ impl EngineConstants {
|
|||
|
||||
self.is_cs_plus = true;
|
||||
self.supports_og_textures = true;
|
||||
self.tex_sizes.insert(str!("Caret"), (320, 320));
|
||||
self.tex_sizes.insert(str!("MyChar"), (200, 384));
|
||||
self.tex_sizes.insert(str!("Npc/NpcRegu"), (320, 410));
|
||||
self.tex_sizes.insert("Caret".to_owned(), (320, 320));
|
||||
self.tex_sizes.insert("MyChar".to_owned(), (200, 384));
|
||||
self.tex_sizes.insert("Npc/NpcRegu".to_owned(), (320, 410));
|
||||
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 50 };
|
||||
self.font_path = str!("csfont.fnt");
|
||||
self.font_path = "csfont.fnt".to_owned();
|
||||
self.font_scale = 0.5;
|
||||
self.font_space_offset = 2.0;
|
||||
self.soundtracks.insert("Remastered".to_string(), "/base/Ogg11/".to_string());
|
||||
self.soundtracks.insert("New".to_string(), "/base/Ogg/".to_string());
|
||||
self.soundtracks.insert("Remastered".to_owned(), "/base/Ogg11/".to_owned());
|
||||
self.soundtracks.insert("New".to_owned(), "/base/Ogg/".to_owned());
|
||||
|
||||
let typewriter_sample = PixToneParameters {
|
||||
// fx2 (CS+)
|
||||
|
@ -1539,8 +1538,8 @@ impl EngineConstants {
|
|||
|
||||
self.is_switch = true;
|
||||
self.supports_og_textures = true;
|
||||
self.tex_sizes.insert(str!("bkMoon"), (427, 240));
|
||||
self.tex_sizes.insert(str!("bkFog"), (427, 240));
|
||||
self.tex_sizes.insert("bkMoon".to_owned(), (427, 240));
|
||||
self.tex_sizes.insert("bkFog".to_owned(), (427, 240));
|
||||
self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 62 };
|
||||
self.inventory_dim_color = Color::from_rgba(0, 0, 32, 150);
|
||||
self.textscript.encoding = TextScriptEncoding::UTF8;
|
||||
|
@ -1549,11 +1548,9 @@ impl EngineConstants {
|
|||
self.textscript.text_shadow = true;
|
||||
self.textscript.text_speed_normal = 1;
|
||||
self.textscript.text_speed_fast = 0;
|
||||
self.soundtracks.insert("Famitracks".to_string(), "/base/ogg17/".to_string());
|
||||
self.soundtracks.insert("Ridiculon".to_string(), "/base/ogg_ridic/".to_string());
|
||||
self.soundtracks.insert("Famitracks".to_owned(), "/base/ogg17/".to_owned());
|
||||
self.soundtracks.insert("Ridiculon".to_owned(), "/base/ogg_ridic/".to_owned());
|
||||
}
|
||||
|
||||
pub fn apply_constant_json_files(&mut self) {
|
||||
|
||||
}
|
||||
pub fn apply_constant_json_files(&mut self) {}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg_attr(feature = "scripting", macro_use)]
|
||||
#[cfg(feature = "scripting")]
|
||||
extern crate lua_ffi;
|
||||
extern crate strum;
|
||||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
|
@ -55,7 +52,6 @@ mod player;
|
|||
mod profile;
|
||||
mod rng;
|
||||
mod scene;
|
||||
#[cfg(feature = "scripting")]
|
||||
mod scripting;
|
||||
mod settings;
|
||||
#[cfg(feature = "backend-gfx")]
|
||||
|
@ -63,7 +59,6 @@ mod shaders;
|
|||
mod shared_game_state;
|
||||
mod sound;
|
||||
mod stage;
|
||||
mod text_script;
|
||||
mod texture_set;
|
||||
mod weapon;
|
||||
|
||||
|
@ -281,7 +276,7 @@ pub fn init(options: LaunchOptions) -> GameResult {
|
|||
|
||||
let game = UnsafeCell::new(Game::new(&mut context)?);
|
||||
let state_ref = unsafe { &mut *((&mut *game.get()).state.get()) };
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
{
|
||||
state_ref.lua.update_refs(unsafe { (&*game.get()).state.get() }, &mut context as *mut Context);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::framework::context::Context;
|
|||
use crate::framework::error::GameResult;
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::text_script::TextScriptExecutionState;
|
||||
use crate::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
|
@ -121,7 +121,7 @@ impl LiveDebugger {
|
|||
self.flags_visible = !self.flags_visible;
|
||||
}
|
||||
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
{
|
||||
ui.same_line(0.0);
|
||||
if ui.button(im_str!("Reload Scripts"), [0.0, 0.0]) {
|
||||
|
|
|
@ -5,16 +5,6 @@ pub use core::fmt;
|
|||
#[doc(hidden)]
|
||||
pub use core::mem::size_of;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! str {
|
||||
() => {
|
||||
String::new()
|
||||
};
|
||||
($x:expr) => {
|
||||
ToString::to_string(&$x)
|
||||
};
|
||||
}
|
||||
|
||||
// extended version of https://github.com/dzamlo/rust-bitfield
|
||||
|
||||
#[macro_export(local_inner_macros)]
|
||||
|
|
15
src/map.rs
15
src/map.rs
|
@ -13,7 +13,6 @@ use crate::framework::error::{GameError, GameResult};
|
|||
use crate::framework::filesystem;
|
||||
use crate::shared_game_state::TileSize;
|
||||
use crate::stage::{PxPackScroll, PxPackStageData, StageData};
|
||||
use crate::str;
|
||||
|
||||
static SUPPORTED_PXM_VERSIONS: [u8; 1] = [0x10];
|
||||
static SUPPORTED_PXE_VERSIONS: [u8; 2] = [0, 0x10];
|
||||
|
@ -43,7 +42,7 @@ impl Map {
|
|||
map_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"PXM" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
let version = map_data.read_u8()?;
|
||||
|
@ -80,7 +79,7 @@ impl Map {
|
|||
// based on https://github.com/tilderain/pxEdit/blob/kero/pxMap.py
|
||||
|
||||
if &magic != b"PXPACK121127a**\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
fn read_string<R: io::Read>(map_data: &mut R) -> GameResult<String> {
|
||||
|
@ -153,7 +152,7 @@ impl Map {
|
|||
map_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"pxMAP01\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
let width_fg = map_data.read_u16::<LE>()?;
|
||||
|
@ -170,7 +169,7 @@ impl Map {
|
|||
map_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"pxMAP01\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
let width_mg = map_data.read_u16::<LE>()?;
|
||||
|
@ -188,7 +187,7 @@ impl Map {
|
|||
map_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"pxMAP01\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
let width_bg = map_data.read_u16::<LE>()?;
|
||||
|
@ -214,7 +213,7 @@ impl Map {
|
|||
attrib_data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"pxMAP01\0" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
attrib_data.read_u16::<LE>()?;
|
||||
|
@ -475,7 +474,7 @@ impl NPCData {
|
|||
data.read_exact(&mut magic)?;
|
||||
|
||||
if &magic != b"PXE" {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
let version = data.read_u8()?;
|
||||
|
|
|
@ -20,7 +20,6 @@ use crate::player::Player;
|
|||
use crate::rng::Xoroshiro32PlusPlus;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::Stage;
|
||||
use crate::str;
|
||||
use crate::weapon::bullet::BulletManager;
|
||||
|
||||
pub mod ai;
|
||||
|
@ -199,7 +198,7 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu
|
|||
) -> GameResult {
|
||||
#[allow(unused_assignments)]
|
||||
let mut npc_hook_ran = false;
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
{
|
||||
npc_hook_ran = state.lua.try_run_npc_hook(self.id, self.npc_type);
|
||||
}
|
||||
|
@ -655,9 +654,9 @@ impl NPCTable {
|
|||
pub fn new() -> NPCTable {
|
||||
NPCTable {
|
||||
entries: Vec::new(),
|
||||
tileset_name: str!("Stage/Prt0"),
|
||||
tex_npc1_name: str!("Npc/Npc0"),
|
||||
tex_npc2_name: str!("Npc/Npc0"),
|
||||
tileset_name: "Stage/Prt0".to_owned(),
|
||||
tex_npc1_name: "Npc/Npc0".to_owned(),
|
||||
tex_npc2_name: "Npc/Npc0".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
use crate::player::Player;
|
||||
|
||||
pub struct RemotePlayerList {
|
||||
|
||||
}
|
||||
pub struct RemotePlayerList {}
|
||||
|
||||
impl RemotePlayerList {
|
||||
pub fn new() -> RemotePlayerList {
|
||||
RemotePlayerList {
|
||||
|
||||
}
|
||||
RemotePlayerList {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ use crate::framework::error::GameResult;
|
|||
use crate::player::ControlMode;
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::str;
|
||||
use crate::weapon::{WeaponLevel, WeaponType};
|
||||
|
||||
pub struct WeaponData {
|
||||
|
@ -294,7 +293,7 @@ impl GameProfile {
|
|||
pub fn load_from_save<R: io::Read>(mut data: R) -> GameResult<GameProfile> {
|
||||
// Do041220
|
||||
if data.read_u64::<BE>()? != 0x446f303431323230 {
|
||||
return Err(ResourceLoadError(str!("Invalid magic")));
|
||||
return Err(ResourceLoadError("Invalid magic".to_owned()));
|
||||
}
|
||||
|
||||
let current_map = data.read_u32::<LE>()?;
|
||||
|
@ -354,7 +353,7 @@ impl GameProfile {
|
|||
data.read_exact(&mut map_flags)?;
|
||||
|
||||
if data.read_u32::<BE>()? != 0x464c4147 {
|
||||
return Err(ResourceLoadError(str!("Invalid FLAG signature")));
|
||||
return Err(ResourceLoadError("Invalid FLAG signature".to_owned()));
|
||||
}
|
||||
|
||||
let mut flags = [0u8; 1000];
|
||||
|
|
|
@ -32,7 +32,7 @@ use crate::scene::title_scene::TitleScene;
|
|||
use crate::scene::Scene;
|
||||
use crate::shared_game_state::{SharedGameState, TileSize};
|
||||
use crate::stage::{BackgroundType, Stage};
|
||||
use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
|
||||
use crate::scripting::tsc::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState, TextScriptLine, TextScriptVM};
|
||||
use crate::texture_set::SpriteBatch;
|
||||
use crate::weapon::bullet::BulletManager;
|
||||
use crate::weapon::{Weapon, WeaponType};
|
||||
|
@ -1776,7 +1776,7 @@ impl Scene for GameScene {
|
|||
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, &state.constants, ctx)?);
|
||||
state.textscript_vm.suspend = false;
|
||||
state.tile_size = self.stage.map.tile_size;
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
state.lua.set_game_scene(self as *mut _);
|
||||
|
||||
self.player1.controller = state.settings.create_player1_controller();
|
||||
|
@ -1948,7 +1948,7 @@ impl Scene for GameScene {
|
|||
|
||||
self.flash.tick(state, ())?;
|
||||
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
state.lua.scene_tick();
|
||||
|
||||
if state.control_flags.tick_world() {
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::scene::no_data_scene::NoDataScene;
|
|||
use crate::scene::Scene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::stage::StageData;
|
||||
use crate::text_script::TextScript;
|
||||
use crate::scripting::tsc::text_script::TextScript;
|
||||
|
||||
pub struct LoadingScene {
|
||||
tick: usize,
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use std::io::Read;
|
||||
|
||||
use lua_ffi::c_str;
|
||||
use lua_ffi::ffi::luaL_Reg;
|
||||
use lua_ffi::lua_method;
|
||||
use lua_ffi::{c_int, LuaObject, State};
|
||||
|
||||
use crate::common::{Direction, Rect};
|
||||
use crate::framework::filesystem;
|
||||
use crate::rng::RNG;
|
||||
use crate::scripting::{check_status, LuaScriptingState, DRS_RUNTIME_GLOBAL};
|
||||
use crate::scene::game_scene::LightingMode;
|
||||
use crate::scripting::lua::{check_status, LuaScriptingState, DRS_RUNTIME_GLOBAL};
|
||||
|
||||
pub struct Doukutsu {
|
||||
pub ptr: *mut LuaScriptingState,
|
232
src/scripting/lua/mod.rs
Normal file
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::lua_method;
|
||||
use lua_ffi::{c_int, LuaObject, State};
|
||||
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::scripting::{LuaScriptingState, DRS_RUNTIME_GLOBAL};
|
||||
use crate::scripting::lua::{LuaScriptingState, DRS_RUNTIME_GLOBAL};
|
||||
|
||||
pub struct LuaGameScene {
|
||||
valid_reference: bool,
|
|
@ -1,235 +1,3 @@
|
|||
use std::io::Read;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::{GameResult, GameError};
|
||||
|
||||
|
||||
use lua_ffi::{c_int, State, ThreadStatus};
|
||||
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::scripting::doukutsu::Doukutsu;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::framework::filesystem::File;
|
||||
use crate::framework::filesystem;
|
||||
use lua_ffi::types::LuaValue;
|
||||
use crate::common::Rect;
|
||||
use lua_ffi::ffi::lua_State;
|
||||
|
||||
mod doukutsu;
|
||||
mod scene;
|
||||
|
||||
pub struct LuaScriptingState {
|
||||
state: Option<State>,
|
||||
state_ptr: *mut SharedGameState,
|
||||
ctx_ptr: *mut Context,
|
||||
game_scene: *mut GameScene,
|
||||
}
|
||||
|
||||
pub(in crate::scripting) static DRS_API_GLOBAL: &str = "__doukutsu_rs";
|
||||
pub(in crate::scripting) static DRS_RUNTIME_GLOBAL: &str = "__doukutsu_rs_runtime_dont_touch";
|
||||
|
||||
static BOOT_SCRIPT: &str = include_str!("boot.lua");
|
||||
|
||||
pub(in crate::scripting) fn check_status(status: ThreadStatus, state: &mut State) -> GameResult {
|
||||
match status {
|
||||
ThreadStatus::Ok | ThreadStatus::Yield => { return Ok(()); }
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let error = state.to_str(-1).unwrap_or("???");
|
||||
match status {
|
||||
ThreadStatus::RuntimeError => Err(GameError::EventLoopError(format!("Lua Runtime Error: {}", error))),
|
||||
ThreadStatus::SyntaxError => Err(GameError::EventLoopError(format!("Lua Syntax Error: {}", error))),
|
||||
ThreadStatus::MemoryError => Err(GameError::EventLoopError(format!("Lua Memory Error: {}", error))),
|
||||
ThreadStatus::MsgHandlerError => Err(GameError::EventLoopError(format!("Lua Message Handler Error: {}", error))),
|
||||
ThreadStatus::FileError => Err(GameError::EventLoopError(format!("Lua File Error: {}", error))),
|
||||
ThreadStatus::Unknown => Err(GameError::EventLoopError(format!("Unknown Error: {}", error))),
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn print(state: &mut State) -> c_int {
|
||||
if let Some(msg) = state.to_str(1) {
|
||||
log::info!("[Lua] {}", msg);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
impl LuaScriptingState {
|
||||
pub fn new() -> LuaScriptingState {
|
||||
LuaScriptingState {
|
||||
state: None,
|
||||
state_ptr: null_mut(),
|
||||
ctx_ptr: null_mut(),
|
||||
game_scene: null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_refs(&mut self, state: *mut SharedGameState, ctx: *mut Context) {
|
||||
self.state_ptr = state;
|
||||
self.ctx_ptr = ctx;
|
||||
}
|
||||
|
||||
pub fn set_game_scene(&mut self, game_scene: *mut GameScene) {
|
||||
self.game_scene = game_scene;
|
||||
}
|
||||
|
||||
fn load_script(mut state: &mut State, path: &str, mut script: File) -> bool {
|
||||
let mut buf = Vec::new();
|
||||
let res = script.read_to_end(&mut buf);
|
||||
|
||||
if let Err(err) = res {
|
||||
log::warn!("Error reading script {}: {}", path, err);
|
||||
return false;
|
||||
}
|
||||
|
||||
let name = format!("@{}", path);
|
||||
let res = state.load_buffer(&buf, &name);
|
||||
let res = check_status(res, &mut state);
|
||||
if let Err(err) = res {
|
||||
log::warn!("Error loading script {}: {}", path, err);
|
||||
return false;
|
||||
}
|
||||
|
||||
state.get_global(DRS_RUNTIME_GLOBAL);
|
||||
state.get_field(-1, "_initializeScript");
|
||||
state.push_value(-3);
|
||||
|
||||
let res = state.pcall(1, 0, 0);
|
||||
if let Err((_, err)) = res {
|
||||
log::warn!("Error evaluating script {}: {}", path, err);
|
||||
return false;
|
||||
}
|
||||
|
||||
log::info!("Successfully loaded Lua script: {}", path);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn reload_scripts(&mut self, ctx: &mut Context) -> GameResult {
|
||||
let mut state = State::new();
|
||||
state.open_libs();
|
||||
|
||||
state.push(lua_fn!(print));
|
||||
state.set_global("print");
|
||||
|
||||
state.push(Doukutsu { ptr: self as *mut LuaScriptingState });
|
||||
state.set_global(DRS_API_GLOBAL);
|
||||
|
||||
log::info!("Initializing Lua scripting engine...");
|
||||
let res = state.do_string(BOOT_SCRIPT);
|
||||
check_status(res, &mut state)?;
|
||||
|
||||
if filesystem::exists(ctx, "/drs-scripts/") {
|
||||
let mut script_count = 0;
|
||||
let files = filesystem::read_dir(ctx, "/drs-scripts/")?
|
||||
.filter(|f| f.to_string_lossy().to_lowercase().ends_with(".lua"));
|
||||
|
||||
for file in files {
|
||||
let path = file.clone();
|
||||
|
||||
match filesystem::open(ctx, file) {
|
||||
Ok(script) => {
|
||||
if LuaScriptingState::load_script(&mut state, path.to_string_lossy().as_ref(), script) {
|
||||
script_count += 1;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("Error opening script {:?}: {}", path, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if script_count > 0 {
|
||||
log::info!("{} Lua scripts have been loaded.", script_count);
|
||||
}
|
||||
}
|
||||
|
||||
let modcs_path = "/Scripts/main.lua";
|
||||
if filesystem::exists(ctx, modcs_path) {
|
||||
log::info!("Loading ModCS main script...");
|
||||
|
||||
match filesystem::open(ctx, modcs_path) {
|
||||
Ok(script) => {
|
||||
if !LuaScriptingState::load_script(&mut state, modcs_path, script) {
|
||||
log::warn!("Error loading ModCS main script.");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("Error opening script {:?}: {}", modcs_path, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.state = Some(state);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaValue for Rect<u16> {
|
||||
fn push_val(self, l: *mut lua_State) {
|
||||
unsafe {
|
||||
lua_ffi::ffi::lua_newtable(l);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 1);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 2);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 3);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaValue for Rect<i16> {
|
||||
fn push_val(self, l: *mut lua_State) {
|
||||
unsafe {
|
||||
lua_ffi::ffi::lua_newtable(l);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 1);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 2);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 3);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaValue for Rect<i32> {
|
||||
fn push_val(self, l: *mut lua_State) {
|
||||
unsafe {
|
||||
lua_ffi::ffi::lua_newtable(l);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.left as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 1);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.top as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 2);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.right as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 3);
|
||||
lua_ffi::ffi::lua_pushinteger(l, self.bottom as isize);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaValue for Rect<f32> {
|
||||
fn push_val(self, l: *mut lua_State) {
|
||||
unsafe {
|
||||
lua_ffi::ffi::lua_newtable(l);
|
||||
lua_ffi::ffi::lua_pushnumber(l, self.left as f64);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 1);
|
||||
lua_ffi::ffi::lua_pushnumber(l, self.top as f64);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 2);
|
||||
lua_ffi::ffi::lua_pushnumber(l, self.right as f64);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 3);
|
||||
lua_ffi::ffi::lua_pushnumber(l, self.bottom as f64);
|
||||
lua_ffi::ffi::lua_rawseti(l, -2, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
pub mod lua;
|
||||
pub mod tsc;
|
||||
|
|
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::title_scene::TitleScene;
|
||||
use crate::scene::Scene;
|
||||
#[cfg(feature = "scripting")]
|
||||
use crate::scripting::LuaScriptingState;
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
use crate::scripting::lua::LuaScriptingState;
|
||||
use crate::settings::Settings;
|
||||
use crate::sound::SoundManager;
|
||||
use crate::stage::StageData;
|
||||
use crate::str;
|
||||
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
|
||||
use crate::texture_set::TextureSet;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -143,7 +142,7 @@ pub struct SharedGameState {
|
|||
pub constants: EngineConstants,
|
||||
pub font: BMFontRenderer,
|
||||
pub texture_set: TextureSet,
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
pub lua: LuaScriptingState,
|
||||
pub sound_manager: SoundManager,
|
||||
pub settings: Settings,
|
||||
|
@ -223,7 +222,7 @@ impl SharedGameState {
|
|||
teleporter_slots: Vec::with_capacity(8),
|
||||
carets: Vec::with_capacity(32),
|
||||
touch_controls: TouchControls::new(),
|
||||
base_path: str!(base_path),
|
||||
base_path: base_path.to_owned(),
|
||||
npc_table: NPCTable::new(),
|
||||
npc_super_pos: (0, 0),
|
||||
npc_curly_target: (0, 0),
|
||||
|
@ -243,7 +242,7 @@ impl SharedGameState {
|
|||
constants,
|
||||
font,
|
||||
texture_set,
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
lua: LuaScriptingState::new(),
|
||||
sound_manager,
|
||||
settings,
|
||||
|
@ -290,7 +289,7 @@ impl SharedGameState {
|
|||
|
||||
pub fn start_new_game(&mut self, ctx: &mut Context) -> GameResult {
|
||||
self.reset();
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
self.lua.reload_scripts(ctx)?;
|
||||
|
||||
let mut next_scene = GameScene::new(self, ctx, self.constants.game.new_game_stage as usize)?;
|
||||
|
@ -309,7 +308,7 @@ impl SharedGameState {
|
|||
}
|
||||
|
||||
pub fn start_intro(&mut self, ctx: &mut Context) -> GameResult {
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
self.lua.reload_scripts(ctx)?;
|
||||
|
||||
let start_stage_id = self.constants.game.intro_stage as usize;
|
||||
|
@ -356,7 +355,7 @@ impl SharedGameState {
|
|||
|
||||
profile.apply(self, &mut next_scene, ctx);
|
||||
|
||||
#[cfg(feature = "scripting")]
|
||||
#[cfg(feature = "scripting-lua")]
|
||||
self.lua.reload_scripts(ctx)?;
|
||||
|
||||
self.next_scene = Some(Box::new(next_scene));
|
||||
|
|
|
@ -24,7 +24,6 @@ use crate::sound::org_playback::{OrgPlaybackEngine, SavedOrganyaPlaybackState};
|
|||
use crate::sound::organya::Song;
|
||||
use crate::sound::pixtone::{PixToneParameters, PixTonePlayback};
|
||||
use crate::sound::wave_bank::SoundBank;
|
||||
use crate::str;
|
||||
|
||||
mod fir;
|
||||
#[cfg(feature = "ogg-playback")]
|
||||
|
@ -73,7 +72,7 @@ impl SoundManager {
|
|||
|
||||
let host = cpal::default_host();
|
||||
let device =
|
||||
host.default_output_device().ok_or_else(|| AudioError(str!("Error initializing audio device.")))?;
|
||||
host.default_output_device().ok_or_else(|| AudioError("Error initializing audio device.".to_owned()))?;
|
||||
let config = device.default_output_config()?;
|
||||
|
||||
let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/organya-wavetable-doukutsu.bin")?)?;
|
||||
|
@ -284,7 +283,7 @@ impl SoundManager {
|
|||
}
|
||||
|
||||
if speed <= 0.0 {
|
||||
return Err(InvalidValue(str!("Speed must be bigger than 0.0!")));
|
||||
return Err(InvalidValue("Speed must be bigger than 0.0!".to_owned()));
|
||||
}
|
||||
|
||||
self.tx.send(PlaybackMessage::SetSpeed(speed))?;
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::framework::error::GameError::ResourceLoadError;
|
|||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::map::{Map, NPCData};
|
||||
use crate::text_script::TextScript;
|
||||
use crate::scripting::tsc::text_script::TextScript;
|
||||
use crate::common::Color;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -15,7 +15,6 @@ use crate::framework::filesystem;
|
|||
use crate::framework::graphics::{create_texture, FilterMode};
|
||||
use crate::settings::Settings;
|
||||
use crate::shared_game_state::Season;
|
||||
use crate::str;
|
||||
|
||||
pub static mut I_MAG: f32 = 1.0;
|
||||
pub static mut G_MAG: f32 = 1.0;
|
||||
|
@ -412,7 +411,7 @@ impl TextureSet {
|
|||
|
||||
if !self.tex_map.contains_key(name) {
|
||||
let batch = self.load_texture(ctx, constants, name)?;
|
||||
self.tex_map.insert(str!(name), batch);
|
||||
self.tex_map.insert(name.to_owned(), batch);
|
||||
}
|
||||
|
||||
Ok(self.tex_map.get_mut(name).unwrap())
|
||||
|
|
Loading…
Reference in a new issue