diff --git a/Cargo.toml b/Cargo.toml index 744b99e..fde96e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ panic = 'abort' [dependencies] approx = "0.3" bitflags = "1" +bitvec = "0.17.4" byteorder = "1.3" directories = "2" gfx = "0.18" @@ -28,10 +29,12 @@ image = {version = "0.22", default-features = false, features = ["png_codec", "p itertools = "0.9.0" lazy_static = "1.4.0" log = "0.4" +lru = "0.6.0" lyon = "0.13" maplit = "1.0.2" mint = "0.5" nalgebra = {version = "0.18", features = ["mint"] } +num-derive = "0.3.2" num-traits = "0.2.12" owning_ref = "0.4.1" paste = "1.0.0" diff --git a/src/common.rs b/src/common.rs index 6f4d52e..7ba73a2 100644 --- a/src/common.rs +++ b/src/common.rs @@ -4,8 +4,11 @@ pub use core::convert::Into; pub use core::fmt; #[doc(hidden)] pub use core::mem::size_of; +use std::io::{Cursor, Error}; +use byteorder::ReadBytesExt; use num_traits::Num; +use std::cell::RefCell; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { @@ -91,6 +94,26 @@ impl Rect { } } +#[derive(Debug)] +pub struct CursorIterator<'a> { + cursor: RefCell>>, +} + +impl<'a> CursorIterator<'a> { + pub fn new(cursor: RefCell>>) -> CursorIterator<'a> { + CursorIterator { cursor } + } +} + +impl<'a> Iterator for CursorIterator<'a> { + type Item = u8; + + #[inline] + fn next(&mut self) -> Option { + self.cursor.borrow_mut().read_u8().ok() + } +} + #[macro_export] macro_rules! str { () => { diff --git a/src/engine_constants.rs b/src/engine_constants.rs index c8131f3..ba6e157 100644 --- a/src/engine_constants.rs +++ b/src/engine_constants.rs @@ -85,6 +85,19 @@ impl Clone for WorldConsts { } } +#[derive(Debug)] +pub struct TextScriptConsts { + pub encoding: TextScriptEncoding, +} + +impl Clone for TextScriptConsts { + fn clone(&self) -> Self { + Self { + encoding: self.encoding, + } + } +} + #[derive(Debug)] pub struct EngineConstants { pub is_cs_plus: bool, @@ -93,7 +106,7 @@ pub struct EngineConstants { pub caret: CaretConsts, pub world: WorldConsts, pub tex_sizes: HashMap, - pub tsc_encoding: TextScriptEncoding, + pub textscript: TextScriptConsts, } impl Clone for EngineConstants { @@ -105,7 +118,7 @@ impl Clone for EngineConstants { caret: self.caret.clone(), world: self.world.clone(), tex_sizes: self.tex_sizes.clone(), - tsc_encoding: self.tsc_encoding, + textscript: self.textscript.clone(), } } } @@ -349,7 +362,9 @@ impl EngineConstants { str!("TextBox") => (244, 144), str!("Title") => (320, 48), }, - tsc_encoding: TextScriptEncoding::UTF8, + textscript: TextScriptConsts { + encoding: TextScriptEncoding::UTF8, + } } } diff --git a/src/live_debugger.rs b/src/live_debugger.rs index ca70729..badc425 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use imgui::{Condition, im_str, ImStr, ImString, Window}; use itertools::Itertools; @@ -6,25 +8,41 @@ use crate::scene::game_scene::GameScene; use crate::SharedGameState; pub struct LiveDebugger { - selected_item: i32, map_selector_visible: bool, + events_visible: bool, hacks_visible: bool, + last_stage_id: usize, stages: Vec, + selected_stage: i32, + events: Vec, + event_ids: Vec, + selected_event: i32, error: Option, } impl LiveDebugger { pub fn new() -> Self { Self { - selected_item: -1, map_selector_visible: false, + events_visible: false, hacks_visible: false, - stages: vec![], + last_stage_id: usize::MAX, + stages: Vec::new(), + selected_stage: -1, + events: Vec::new(), + event_ids: Vec::new(), + selected_event: -1, error: None, } } pub fn run_ingame(&mut self, game_scene: &mut GameScene, state: &mut SharedGameState, ctx: &mut Context, ui: &mut imgui::Ui) -> GameResult { + if self.last_stage_id != game_scene.stage_id { + self.last_stage_id = game_scene.stage_id; + self.events.clear(); + self.selected_event = -1; + } + Window::new(im_str!("Debugger")) .position([5.0, 5.0], Condition::FirstUseEver) .size([300.0, 120.0], Condition::FirstUseEver) @@ -49,6 +67,12 @@ impl LiveDebugger { self.map_selector_visible = true; } + ui.same_line(0.0); + if ui.button(im_str!("Events"), [0.0, 0.0]) { + self.events_visible = true; + } + + ui.same_line(0.0); if ui.button(im_str!("Hacks"), [0.0, 0.0]) { self.hacks_visible = true; } @@ -73,7 +97,7 @@ impl LiveDebugger { if self.map_selector_visible { Window::new(im_str!("Map selector")) .resizable(false) - .position([5.0, 35.0], Condition::FirstUseEver) + .position([80.0, 80.0], Condition::FirstUseEver) .size([240.0, 280.0], Condition::FirstUseEver) .build(ui, || { if self.stages.is_empty() { @@ -81,7 +105,7 @@ impl LiveDebugger { self.stages.push(ImString::new(s.name.to_owned())); } - self.selected_item = match state.stages.iter().find_position(|s| s.name == game_scene.stage.data.name) { + self.selected_stage = match state.stages.iter().find_position(|s| s.name == game_scene.stage.data.name) { Some((pos, _)) => { pos as i32 } _ => { -1 } }; @@ -89,10 +113,10 @@ impl LiveDebugger { let stages: Vec<&ImStr> = self.stages.iter().map(|e| e.as_ref()).collect(); ui.push_item_width(-1.0); - ui.list_box(im_str!(""), &mut self.selected_item, &stages, 10); + ui.list_box(im_str!(""), &mut self.selected_stage, &stages, 10); if ui.button(im_str!("Load"), [0.0, 0.0]) { - match GameScene::new(state, ctx, self.selected_item as usize) { + match GameScene::new(state, ctx, self.selected_stage as usize) { Ok(mut scene) => { scene.player.x = (scene.stage.map.width / 2 * 16 * 0x200) as isize; scene.player.y = (scene.stage.map.height / 2 * 16 * 0x200) as isize; @@ -107,6 +131,43 @@ impl LiveDebugger { }); } + if self.events_visible { + Window::new(im_str!("Events")) + .resizable(false) + .position([80.0, 80.0], Condition::FirstUseEver) + .size([250.0, 300.0], Condition::FirstUseEver) + .build(ui, || { + if self.events.is_empty() { + self.event_ids.clear(); + + let vm = &state.textscript_vm; + for event in vm.global_script.get_event_ids() { + self.events.push(ImString::new(format!("Global: #{:04}", event))); + self.event_ids.push(event); + } + + for event in vm.scene_script.get_event_ids() { + self.events.push(ImString::new(format!("Scene: #{:04}", event))); + self.event_ids.push(event); + } + } + let events: Vec<&ImStr> = self.events.iter().map(|e| e.as_ref()).collect(); + + ui.text(format!("Execution state: {:?}", state.textscript_vm.state)); + + ui.push_item_width(-1.0); + ui.list_box(im_str!(""), &mut self.selected_event, &events, 10); + + if ui.button(im_str!("Execute"), [0.0, 0.0]) { + assert_eq!(self.event_ids.len(), self.events.len()); + + if let Some(&event_num) = self.event_ids.get(self.selected_event as usize) { + state.textscript_vm.start_script(event_num); + } + } + }); + } + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index a216176..865cb56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,14 +31,15 @@ use crate::ggez::graphics::DrawParam; use crate::ggez::input::keyboard; use crate::ggez::mint::ColumnMatrix4; use crate::ggez::nalgebra::Vector2; - use crate::rng::RNG; use crate::scene::loading_scene::LoadingScene; use crate::scene::Scene; use crate::sound::SoundManager; use crate::stage::StageData; +use crate::text_script::TextScriptVM; use crate::texture_set::TextureSet; use crate::ui::UI; +use bitvec::vec::BitVec; mod caret; mod common; @@ -75,7 +76,7 @@ bitfield! { } bitfield! { - pub struct GameFlags(u32); + pub struct ControlFlags(u32); impl Debug; pub flag_x01, set_flag_x01: 0; pub control_enabled, set_control_enabled: 1; @@ -91,7 +92,8 @@ struct Game { } pub struct SharedGameState { - pub flags: GameFlags, + pub control_flags: ControlFlags, + pub game_flags: BitVec, pub game_rng: RNG, pub effect_rng: RNG, pub carets: Vec, @@ -106,6 +108,7 @@ pub struct SharedGameState { pub canvas_size: (f32, f32), pub screen_size: (f32, f32), pub next_scene: Option>, + pub textscript_vm: TextScriptVM, key_old: u16, } @@ -156,7 +159,8 @@ impl Game { ui: UI::new(ctx)?, def_matrix: DrawParam::new().to_matrix(), state: SharedGameState { - flags: GameFlags(0), + control_flags: ControlFlags(0), + game_flags: bitvec::bitvec![0; 8000], game_rng: RNG::new(0), effect_rng: RNG::new(Instant::now().elapsed().as_nanos() as i32), carets: Vec::with_capacity(32), @@ -171,6 +175,7 @@ impl Game { screen_size, canvas_size, next_scene: None, + textscript_vm: TextScriptVM::new(), key_old: 0, }, }; @@ -267,30 +272,32 @@ pub fn main() -> GameResult { ctx.process_event(&event); game.ui.handle_events(ctx, &event); - if let Event::WindowEvent { event, .. } = event { match event { - WindowEvent::CloseRequested => event::quit(ctx), - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: el_state, - virtual_keycode: Some(keycode), - modifiers, + if let Event::WindowEvent { event, .. } = event { + match event { + WindowEvent::CloseRequested => event::quit(ctx), + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: el_state, + virtual_keycode: Some(keycode), + modifiers, + .. + }, .. - }, - .. - } => { - match el_state { - ElementState::Pressed => { - let repeat = keyboard::is_key_repeated(ctx); - game.key_down_event(ctx, keycode, modifiers.into(), repeat); - } - ElementState::Released => { - game.key_up_event(ctx, keycode, modifiers.into()); + } => { + match el_state { + ElementState::Pressed => { + let repeat = keyboard::is_key_repeated(ctx); + game.key_down_event(ctx, keycode, modifiers.into(), repeat); + } + ElementState::Released => { + game.key_up_event(ctx, keycode, modifiers.into()); + } } } + _ => {} } - _ => {} - } } + } }); game.update(ctx)?; diff --git a/src/player.rs b/src/player.rs index bcc4b66..ebc1fc3 100644 --- a/src/player.rs +++ b/src/player.rs @@ -161,7 +161,7 @@ impl Player { state.create_caret(self.x, self.y - self.hit.top as isize, CaretType::LittleParticles, Direction::Left); } - if !state.flags.control_enabled() { + if !state.control_flags.control_enabled() { self.booster_switch = 0; } @@ -177,8 +177,8 @@ impl Player { self.booster_fuel = 0; } - if state.flags.control_enabled() { - if state.key_trigger.only_down() && state.key_state.only_down() && !self.cond.cond_x01() && !state.flags.flag_x04() { + if state.control_flags.control_enabled() { + if state.key_trigger.only_down() && state.key_state.only_down() && !self.cond.cond_x01() && !state.control_flags.flag_x04() { self.cond.set_cond_x01(true); self.question = true; } else { @@ -217,7 +217,7 @@ impl Player { } } } else { // air movement - if state.flags.control_enabled() { + if state.control_flags.control_enabled() { if state.key_trigger.jump() && self.booster_fuel != 0 { if self.equip.has_booster_0_8() { self.booster_switch = 1; @@ -281,7 +281,7 @@ impl Player { } // jumping - if state.flags.control_enabled() { + if state.control_flags.control_enabled() { self.up = state.key_state.up(); self.down = state.key_state.down() && !self.flags.flag_x08(); @@ -292,7 +292,7 @@ impl Player { } // stop interacting when moved - if state.flags.control_enabled() && (state.key_state.left() || state.key_state.right() || state.key_state.up() || state.key_state.jump() || state.key_state.fire()) { + if state.control_flags.control_enabled() && (state.key_state.left() || state.key_state.right() || state.key_state.up() || state.key_state.jump() || state.key_state.fire()) { self.cond.set_cond_x01(false); } @@ -368,13 +368,13 @@ impl Player { if self.flags.flag_x02() { self.vel_y = 0x200; // 1.0fix9 } - } else if self.vel_y < 0 && state.flags.control_enabled() && state.key_state.jump() { + } else if self.vel_y < 0 && state.control_flags.control_enabled() && state.key_state.jump() { self.vel_y += physics.gravity_air; } else { self.vel_y += physics.gravity_ground; } - if !state.flags.control_enabled() || !state.key_trigger.jump() { + if !state.control_flags.control_enabled() || !state.key_trigger.jump() { if self.flags.flag_x10() && self.vel_x < 0 { self.vel_y = -self.vel_x; } @@ -423,12 +423,12 @@ impl Player { } } - if state.flags.control_enabled() && state.key_state.up() { + if state.control_flags.control_enabled() && state.key_state.up() { self.index_y -= 0x200; // 1.0fix9 if self.index_y < -0x8000 { // -64.0fix9 self.index_y = -0x8000; } - } else if state.flags.control_enabled() && state.key_state.down() { + } else if state.control_flags.control_enabled() && state.key_state.down() { self.index_y += 0x200; // 1.0fix9 if self.index_y > 0x8000 { // -64.0fix9 self.index_y = 0x8000; @@ -467,7 +467,7 @@ impl Player { if self.flags.flag_x08() { if self.cond.cond_x01() { self.anim_num = 11; - } else if state.flags.control_enabled() && state.key_state.up() && (state.key_state.left() || state.key_state.right()) { + } else if state.control_flags.control_enabled() && state.key_state.up() && (state.key_state.left() || state.key_state.right()) { self.cond.set_cond_x04(true); self.anim_wait += 1; @@ -483,7 +483,7 @@ impl Player { if self.anim_num > 9 || self.anim_num < 6 { self.anim_num = 6; } - } else if state.flags.control_enabled() && (state.key_state.left() || state.key_state.right()) { + } else if state.control_flags.control_enabled() && (state.key_state.left() || state.key_state.right()) { self.cond.set_cond_x04(true); self.anim_wait += 1; @@ -499,7 +499,7 @@ impl Player { if self.anim_num > 4 || self.anim_num < 1 { self.anim_num = 1; } - } else if state.flags.control_enabled() && state.key_state.up() { + } else if state.control_flags.control_enabled() && state.key_state.up() { if self.cond.cond_x04() { // PlaySoundObject(24, SOUND_MODE_PLAY); todo } @@ -573,7 +573,7 @@ impl GameEntity for Player { match self.unit { 0 => { - if state.flags.flag_x04() && state.flags.control_enabled() { + if state.control_flags.flag_x04() && state.control_flags.control_enabled() { // AirProcess(); // todo } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index a8ef41b..e84e0c8 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -12,12 +12,14 @@ use crate::SharedGameState; use crate::stage::{BackgroundType, Stage}; use crate::str; use crate::ui::{UI, Components}; +use crate::text_script::{TextScript, TextScriptVM}; pub struct GameScene { pub tick: usize, pub stage: Stage, pub frame: Frame, pub player: Player, + pub stage_id: usize, tex_background_name: String, tex_caret_name: String, tex_hud_name: String, @@ -62,6 +64,7 @@ impl GameScene { y: 0, wait: 16, }, + stage_id: id, tex_background_name, tex_caret_name, tex_hud_name, @@ -276,18 +279,21 @@ impl GameScene { impl Scene for GameScene { fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + state.textscript_vm.set_scene_script(self.stage.text_script.clone()); + self.stage.text_script = TextScript::new(); + //self.player.x = 700 * 0x200; //self.player.y = 1000 * 0x200; self.player.equip.set_booster_2_0(true); - state.flags.set_flag_x01(true); - state.flags.set_control_enabled(true); + state.control_flags.set_flag_x01(true); + state.control_flags.set_control_enabled(true); Ok(()) } fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { state.update_key_trigger(); - if state.flags.flag_x01() { + if state.control_flags.flag_x01() { self.player.tick(state, ctx)?; self.player.flags.0 = 0; @@ -298,7 +304,7 @@ impl Scene for GameScene { self.frame.update(state, &self.player, &self.stage); } - if state.flags.control_enabled() { + if state.control_flags.control_enabled() { // update health bar if self.life_bar < self.player.life as usize { self.life_bar = self.player.life as usize; @@ -314,6 +320,7 @@ impl Scene for GameScene { } } + TextScriptVM::run(state, self); self.tick = self.tick.wrapping_add(1); Ok(()) } diff --git a/src/scene/loading_scene.rs b/src/scene/loading_scene.rs index 22303c8..5987690 100644 --- a/src/scene/loading_scene.rs +++ b/src/scene/loading_scene.rs @@ -1,9 +1,9 @@ -use crate::ggez::{Context, GameResult}; - +use crate::ggez::{Context, filesystem, GameResult}; use crate::scene::game_scene::GameScene; use crate::scene::Scene; use crate::SharedGameState; use crate::stage::StageData; +use crate::text_script::TextScript; pub struct LoadingScene { tick: usize, @@ -23,6 +23,8 @@ impl Scene for LoadingScene { if self.tick == 1 { let stages = StageData::load_stage_table(ctx, &state.base_path)?; state.stages = stages; + let script = TextScript::load_from(filesystem::open(ctx, [&state.base_path, "/Head.tsc"].join(""))?)?; + state.textscript_vm.set_global_script(script); state.next_scene = Some(Box::new(GameScene::new(state, ctx, 0)?)); } diff --git a/src/text_script.rs b/src/text_script.rs index e28cbf0..5449220 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -1,23 +1,35 @@ +use std::borrow::BorrowMut; +use std::cell::RefCell; use std::collections::HashMap; use std::io; +use std::io::{Cursor, Read, Seek, SeekFrom}; use std::iter::Peekable; -use std::slice::Iter; use std::str::FromStr; +use byteorder::ReadBytesExt; use itertools::Itertools; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use crate::{SharedGameState, str}; +use crate::common::CursorIterator; use crate::ggez::GameError::ParseError; use crate::ggez::GameResult; -use crate::str; +use crate::scene::game_scene::GameScene; /// Engine's text script VM operation codes. -#[derive(EnumString, Debug)] +#[derive(EnumString, Debug, FromPrimitive, PartialEq)] +#[repr(i32)] pub enum OpCode { // ---- Internal opcodes (used by bytecode, no TSC representation) /// internal: no operation - _NOP, + _NOP = 0, /// internal: unimplemented _UNI, + /// internal: string marker + _STR, + /// internal: implicit END marker + _END, // ---- Official opcodes ---- /// , + line_2: Vec, +} + +impl Default for TextScriptVM { + fn default() -> Self { + TextScriptVM::new() + } +} + +fn read_cur_varint(cursor: &mut Cursor<&Vec>) -> GameResult { + let mut result = 0u32; + + for o in 0..5 { + let n = cursor.read_u8()?; + result |= (n as u32 & 0x7f) << (o * 7); + + if n & 0x80 == 0 { + break; + } + } + + Ok(((result << 31) ^ (result >> 1)) as i32) +} + +/// Decodes UTF-8 character in a less strict way. +/// http://simonsapin.github.io/wtf-8/#decoding-wtf-8 +fn read_cur_wtf8(cursor: &mut Cursor<&Vec>, max_bytes: usize) -> (u8, char) { + let mut result = 0u32; + let mut consumed = 0u8; + + if max_bytes == 0 { + return (0, '\u{fffd}'); + } + + match cursor.read_u8() { + Ok(byte @ 0x00..=0x7f) => { + consumed = 1; + result = byte as u32; + } + Ok(byte @ 0xc2..=0xdf) if max_bytes >= 2 => { + let byte2 = { if let Ok(n) = cursor.read_u8() { n } else { return (1, '\u{fffd}'); } }; + + consumed = 2; + result = (byte as u32 & 0x1f) << 6 | (byte2 as u32 & 0x3f); + } + Ok(byte @ 0xe0..=0xef) if max_bytes >= 3 => { + let byte2 = { if let Ok(n) = cursor.read_u8() { n } else { return (1, '\u{fffd}'); } }; + let byte3 = { if let Ok(n) = cursor.read_u8() { n } else { return (2, '\u{fffd}'); } }; + + consumed = 3; + result = (byte as u32 & 0x0f) << 12 | (byte2 as u32 & 0x3f) << 6 | (byte3 as u32 & 0x3f); + } + Ok(byte @ 0xf0..=0xf4) if max_bytes >= 4 => { + let byte2 = { if let Ok(n) = cursor.read_u8() { n } else { return (1, '\u{fffd}'); } }; + let byte3 = { if let Ok(n) = cursor.read_u8() { n } else { return (2, '\u{fffd}'); } }; + let byte4 = { if let Ok(n) = cursor.read_u8() { n } else { return (3, '\u{fffd}'); } }; + + consumed = 4; + result = (byte as u32 & 0x07) << 18 | (byte2 as u32 & 0x3f) << 12 | (byte3 as u32 & 0x3f) << 6 | (byte4 as u32 & 0x3f); + } + _ => { return (1, '\u{fffd}'); } + } + + if let Ok(byte) = cursor.read_u8() {} else { + return (consumed, '\u{fffd}'); + } + + (consumed, std::char::from_u32(result).unwrap_or('\u{fffd}')) +} + +impl TextScriptVM { + pub fn new() -> Self { + Self { + global_script: TextScript::new(), + scene_script: TextScript::new(), + state: TextScriptExecutionState::Ended, + msg_timer: 0, + face: 0, + line_1: Vec::with_capacity(24), + line_2: Vec::with_capacity(24), + } + } + + pub fn set_global_script(&mut self, script: TextScript) { + self.global_script = script; + self.reset(); + } + + pub fn set_scene_script(&mut self, script: TextScript) { + self.scene_script = script; + self.reset(); + } + + pub fn find_script(&self, event_num: u16) -> Option<&Vec> { + if let Some(tsc) = self.scene_script.event_map.get(&event_num) { + return Some(tsc); + } else if let Some(tsc) = self.global_script.event_map.get(&event_num) { + return Some(tsc); + } + + None + } + + pub fn reset(&mut self) { + self.state = TextScriptExecutionState::Ended; + self.face = 0; + self.line_1.clear(); + self.line_2.clear(); + } + + pub fn start_script(&mut self, event_num: u16) { + self.reset(); + self.state = TextScriptExecutionState::Running(event_num, 0); + log::info!("Started script: #{:04}", event_num); + } + + pub fn run(state: &mut SharedGameState, game_scene: &mut GameScene) -> GameResult { + loop { + match state.textscript_vm.state { + TextScriptExecutionState::Ended => { + break; + } + TextScriptExecutionState::Running(event, ip) => { + state.textscript_vm.state = TextScriptVM::execute(event, ip, state, game_scene)?; + println!("new vm state: {:?}", state.textscript_vm.state); + } + TextScriptExecutionState::Msg(event, ip, remaining) => { + if let Some(bytecode) = state.textscript_vm.find_script(event) {} else { + state.textscript_vm.reset(); + } + } + TextScriptExecutionState::WaitTicks(event, ip, ticks) => { + if ticks == 0 { + state.textscript_vm.state = TextScriptExecutionState::Running(event, ip); + } else { + state.textscript_vm.state = TextScriptExecutionState::WaitTicks(event, ip, ticks - 1); + break; + } + } + TextScriptExecutionState::WaitInput(event, ip) => { + if state.key_trigger.jump() || state.key_trigger.fire() { + state.textscript_vm.state = TextScriptExecutionState::Running(event, ip); + } + break; + } + } + } + + Ok(()) + } + + pub fn execute(event: u16, ip: u32, state: &mut SharedGameState, game_scene: &mut GameScene) -> GameResult { + let mut exec_state = state.textscript_vm.state; + + if let Some(bytecode) = state.textscript_vm.find_script(event) { + let mut cursor = Cursor::new(bytecode); + cursor.seek(SeekFrom::Start(ip as u64))?; + + let op_maybe: Option = FromPrimitive::from_i32(read_cur_varint(&mut cursor)?); + + if let Some(op) = op_maybe { + match op { + OpCode::_NOP => { + println!("opcode: {:?}", op); + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + OpCode::_UNI => {} + OpCode::_STR => { + // simply skip the text if we aren't in message mode. + let len = read_cur_varint(&mut cursor)? as u32; + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32 + len); + } + OpCode::_END | OpCode::END => { + exec_state = TextScriptExecutionState::Ended; + } + + OpCode::NOD => { + exec_state = TextScriptExecutionState::WaitInput(event, cursor.position() as u32); + } + OpCode::FLp | OpCode::FLm => { + let flag_num = read_cur_varint(&mut cursor)? as usize; + state.game_flags.set(flag_num, op == OpCode::FLp); + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + OpCode::FLJ => { + let flag_num = read_cur_varint(&mut cursor)? as usize; + let event_num = read_cur_varint(&mut cursor)? as u16; + if let Some(true) = state.game_flags.get(flag_num) { + exec_state = TextScriptExecutionState::Running(event_num, 0); + } + } + OpCode::EVE => { + let event_num = read_cur_varint(&mut cursor)? as u16; + exec_state = TextScriptExecutionState::Running(event_num, 0); + } + OpCode::MM0 => { + game_scene.player.vel_x = 0; + } + OpCode::CMP => { + let pos_x = read_cur_varint(&mut cursor)? as usize; + let pos_y = read_cur_varint(&mut cursor)? as usize; + let tile_type = read_cur_varint(&mut cursor)? as u8; + + if let Some(ptr) = game_scene.stage.map.tiles.get_mut(pos_y * game_scene.stage.map.width + pos_x) { + *ptr = tile_type; + } + + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + OpCode::MLp => { + let life = read_cur_varint(&mut cursor)? as usize; + game_scene.player.max_life += life; + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + + // unimplemented opcodes + // Zero operands + OpCode::AEp | OpCode::CAT | OpCode::CIL | OpCode::CLO | OpCode::CLR | OpCode::CPS | + OpCode::CRE | OpCode::CSS | OpCode::ESC | OpCode::FLA | OpCode::FMU | + OpCode::FRE | OpCode::HMC | OpCode::INI | OpCode::KEY | OpCode::LDP | OpCode::MLP | + OpCode::MNA | OpCode::MS2 | OpCode::MS3 | OpCode::MSG | + OpCode::PRI | OpCode::RMU | OpCode::SAT | OpCode::SLP | OpCode::SMC | OpCode::SPS | + OpCode::STC | OpCode::SVP | OpCode::TUR | OpCode::WAS | OpCode::ZAM => { + println!("unimplemented opcode: {:?}", op); + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + // 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::MPp | OpCode::SKm | OpCode::SKp | OpCode::EQp | OpCode::EQm | OpCode::MLp | + OpCode::ITp | OpCode::ITm | OpCode::AMm | OpCode::UNJ | OpCode::MPJ | OpCode::YNJ | + OpCode::XX1 | OpCode::SIL | OpCode::LIp | OpCode::SOU | OpCode::CMU | + OpCode::SSS | OpCode::ACH => { + let par_a = read_cur_varint(&mut cursor)?; + println!("unimplemented opcode: {:?} {}", op, par_a); + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + // Two operand codes + OpCode::FON | OpCode::MOV | OpCode::AMp | OpCode::NCJ | OpCode::ECJ | + OpCode::ITJ | OpCode::SKJ | OpCode::AMJ | OpCode::SMP | OpCode::PSp => { + let par_a = read_cur_varint(&mut cursor)?; + let par_b = read_cur_varint(&mut cursor)?; + println!("unimplemented opcode: {:?} {} {}", op, par_a, par_b); + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + // Three operand codes + OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM => { + let par_a = read_cur_varint(&mut cursor)?; + let par_b = read_cur_varint(&mut cursor)?; + let par_c = read_cur_varint(&mut cursor)?; + println!("unimplemented opcode: {:?} {} {} {}", op, par_a, par_b, par_c); + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + // 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)?; + println!("unimplemented opcode: {:?} {} {} {} {}", op, par_a, par_b, par_c, par_d); + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + } + } + } else { + return Ok(TextScriptExecutionState::Ended); + } + + Ok(exec_state) + } } pub struct TextScript { event_map: HashMap>, } +impl Clone for TextScript { + fn clone(&self) -> Self { + Self { + event_map: self.event_map.clone(), + } + } +} + +impl Default for TextScript { + fn default() -> Self { + TextScript::new() + } +} + impl TextScript { + pub fn new() -> TextScript { + Self { + event_map: HashMap::new(), + } + } + /// Loads, decrypts and compiles a text script from specified stream. pub fn load_from(mut data: R) -> GameResult { let mut buf = Vec::new(); @@ -194,13 +504,18 @@ impl TextScript { TextScript::compile(&buf) } + pub fn get_event_ids(&self) -> Vec { + self.event_map.keys().copied().sorted().collect_vec() + } + /// Compiles a decrypted text script data into internal bytecode. pub fn compile(data: &[u8]) -> GameResult { - println!("data: {}", String::from_utf8(data.to_vec())?); + let code = unsafe { std::str::from_utf8_unchecked(data) }; + println!("data: {}", code); let mut event_map = HashMap::new(); - let mut iter = data.iter().peekable(); - while let Some(&&chr) = iter.peek() { + let mut iter = data.iter().copied().peekable(); + while let Some(&chr) = iter.peek() { match chr { b'#' => { iter.next(); @@ -230,31 +545,29 @@ impl TextScript { }) } - fn compile_event(iter: &mut Peekable>) -> GameResult> { + fn compile_event>(iter: &mut Peekable) -> GameResult> { let mut bytecode = Vec::new(); - let mut char_buf = Vec::with_capacity(16); - while let Some(&&chr) = iter.peek() { + while let Some(&chr) = iter.peek() { match chr { b'#' => { if !char_buf.is_empty() { - TextScript::put_varint(char_buf.len() as i32, &mut bytecode); - bytecode.append(&mut char_buf); + TextScript::put_string(&mut char_buf, &mut bytecode); } // some events end without { if !char_buf.is_empty() { - TextScript::put_varint(char_buf.len() as i32, &mut bytecode); - bytecode.append(&mut char_buf); + TextScript::put_string(&mut char_buf, &mut bytecode); } iter.next(); - let n = iter.next_tuple::<(&u8, &u8, &u8)>() - .map(|t| [*t.0, *t.1, *t.2]) + let n = iter.next_tuple::<(u8, u8, u8)>() + .map(|t| [t.0, t.1, t.2]) .ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; let code = unsafe { std::str::from_utf8_unchecked(&n) }; @@ -272,6 +585,12 @@ impl TextScript { Ok(bytecode) } + fn put_string(buffer: &mut Vec, out: &mut Vec) { + TextScript::put_varint(OpCode::_STR as i32, out); + TextScript::put_varint(buffer.len() as i32, out); + out.append(buffer); + } + fn put_varint(val: i32, out: &mut Vec) { let mut x = ((val as u32) >> 31) ^ ((val as u32) << 1); @@ -283,11 +602,11 @@ impl TextScript { out.push(x as u8); } - fn read_varint(iter: &mut Peekable>) -> GameResult { + fn read_varint>(iter: &mut I) -> GameResult { let mut result = 0u32; for o in 0..5 { - let &n = iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; + let n = iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; result |= (n as u32 & 0x7f) << (o * 7); if n & 0x80 == 0 { @@ -298,8 +617,8 @@ impl TextScript { Ok(((result << 31) ^ (result >> 1)) as i32) } - fn compile_code(code: &str, iter: &mut Peekable>, out: &mut Vec) -> GameResult { - let instr = OpCode::from_str(code).map_err(|e| ParseError(format!("Unknown opcode: {}", code)))?; + fn compile_code>(code: &str, iter: &mut Peekable, out: &mut Vec) -> GameResult { + let instr = OpCode::from_str(code).map_err(|_| ParseError(format!("Unknown opcode: {}", code)))?; match instr { // Zero operand codes @@ -372,7 +691,7 @@ impl TextScript { Ok(()) } - fn expect_newline(iter: &mut Peekable>) -> GameResult { + fn expect_newline>(iter: &mut Peekable) -> GameResult { if let Some(b'\r') = iter.peek() { iter.next(); } @@ -380,14 +699,14 @@ impl TextScript { TextScript::expect_char(b'\n', iter) } - fn expect_char(expect: u8, iter: &mut Peekable>) -> GameResult { - let mut res = iter.next(); + fn expect_char>(expect: u8, iter: &mut I) -> GameResult { + let res = iter.next(); match res { - Some(&n) if n == expect => { + Some(n) if n == expect => { Ok(()) } - Some(&n) => { + Some(n) => { Err(ParseError(format!("Expected {}, found {}", expect as char, n as char))) } None => { @@ -396,8 +715,8 @@ impl TextScript { } } - fn skip_until(expect: u8, iter: &mut Peekable>) -> GameResult { - while let Some(&chr) = iter.next() { + fn skip_until>(expect: u8, iter: &mut I) -> GameResult { + for chr in iter { if chr == expect { return Ok(()); } @@ -408,12 +727,12 @@ impl TextScript { /// Reads a 4 digit TSC formatted number from iterator. /// Intentionally does no '0'..'9' range checking, since it was often exploited by modders. - fn read_number(iter: &mut Peekable>) -> GameResult { + fn read_number>(iter: &mut Peekable) -> GameResult { Some(0) - .and_then(|result| iter.next().map(|&v| result + 1000 * (v - b'0') as i32)) - .and_then(|result| iter.next().map(|&v| result + 100 * (v - b'0') as i32)) - .and_then(|result| iter.next().map(|&v| result + 10 * (v - b'0') as i32)) - .and_then(|result| iter.next().map(|&v| result + (v - b'0') as i32)) + .and_then(|result| iter.next().map(|v| result + 1000 * (v - b'0') as i32)) + .and_then(|result| iter.next().map(|v| result + 100 * (v - b'0') as i32)) + .and_then(|result| iter.next().map(|v| result + 10 * (v - b'0') as i32)) + .and_then(|result| iter.next().map(|v| result + (v - b'0') as i32)) .ok_or_else(|| ParseError(str!("Script unexpectedly ended."))) } @@ -428,7 +747,7 @@ fn test_varint() { for &n in [1_i32, 23, 456, 7890, 12345, -1, -23, -456].iter() { let mut out = Vec::new(); TextScript::put_varint(n, &mut out); - let result = TextScript::read_varint(&mut out.iter().peekable()).unwrap(); + let result = TextScript::read_varint(&mut out.iter().copied()).unwrap(); assert_eq!(result, n); } } diff --git a/src/texture_set.rs b/src/texture_set.rs index ae3716a..c0334ae 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -130,8 +130,8 @@ impl TextureSet { let image = self.load_image(ctx, &path)?; let size = image.dimensions(); - assert_ne!(size.w, 0.0, "size.w == 0"); - assert_ne!(size.h, 0.0, "size.h == 0"); + assert_ne!(size.w as isize, 0, "size.w == 0"); + assert_ne!(size.h as isize, 0, "size.h == 0"); let dim = (size.w as usize, size.h as usize); let orig_dimensions = constants.tex_sizes.get(name).unwrap_or_else(|| &dim);