1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2024-11-22 05:33:02 +00:00

complete the TSC VM

This commit is contained in:
Alula 2020-08-27 04:43:21 +02:00
parent 4af9552685
commit 44d4bd8ea3
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
10 changed files with 532 additions and 95 deletions

View file

@ -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"

View file

@ -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<S: Num + Copy> Rect<S> {
}
}
#[derive(Debug)]
pub struct CursorIterator<'a> {
cursor: RefCell<Cursor<&'a Vec<u8>>>,
}
impl<'a> CursorIterator<'a> {
pub fn new(cursor: RefCell<Cursor<&'a Vec<u8>>>) -> CursorIterator<'a> {
CursorIterator { cursor }
}
}
impl<'a> Iterator for CursorIterator<'a> {
type Item = u8;
#[inline]
fn next(&mut self) -> Option<u8> {
self.cursor.borrow_mut().read_u8().ok()
}
}
#[macro_export]
macro_rules! str {
() => {

View file

@ -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<String, (usize, usize)>,
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,
}
}
}

View file

@ -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<ImString>,
selected_stage: i32,
events: Vec<ImString>,
event_ids: Vec<u16>,
selected_event: i32,
error: Option<ImString>,
}
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(())
}
}

View file

@ -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<Caret>,
@ -106,6 +108,7 @@ pub struct SharedGameState {
pub canvas_size: (f32, f32),
pub screen_size: (f32, f32),
pub next_scene: Option<Box<dyn Scene>>,
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)?;

View file

@ -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
}

View file

@ -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(())
}

View file

@ -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)?));
}

View file

@ -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 ----
/// <BOAxxxx, start boss animation
@ -155,22 +167,320 @@ pub enum TextScriptEncoding {
ShiftJIS,
}
enum TextScriptExecutionState {
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum TextScriptExecutionState {
Ended,
Running(u32, u32),
Running(u16, u32),
Msg(u16, u32, u32),
WaitTicks(u16, u32, u32),
WaitInput(u16, u32),
}
pub struct TextScriptManager {
global_script: TextScript,
scene_script: TextScript,
state: TextScriptExecutionState,
pub struct TextScriptVM {
pub global_script: TextScript,
pub scene_script: TextScript,
pub state: TextScriptExecutionState,
msg_timer: u8,
face: u16,
line_1: Vec<char>,
line_2: Vec<char>,
}
impl Default for TextScriptVM {
fn default() -> Self {
TextScriptVM::new()
}
}
fn read_cur_varint(cursor: &mut Cursor<&Vec<u8>>) -> GameResult<i32> {
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<u8>>, 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<u8>> {
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<TextScriptExecutionState> {
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<OpCode> = 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<u16, Vec<u8>>,
}
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<R: io::Read>(mut data: R) -> GameResult<TextScript> {
let mut buf = Vec::new();
@ -194,13 +504,18 @@ impl TextScript {
TextScript::compile(&buf)
}
pub fn get_event_ids(&self) -> Vec<u16> {
self.event_map.keys().copied().sorted().collect_vec()
}
/// Compiles a decrypted text script data into internal bytecode.
pub fn compile(data: &[u8]) -> GameResult<TextScript> {
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<Iter<u8>>) -> GameResult<Vec<u8>> {
fn compile_event<I: Iterator<Item=u8>>(iter: &mut Peekable<I>) -> GameResult<Vec<u8>> {
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 <END marker.
TextScript::put_varint(OpCode::_END as i32, &mut bytecode);
break;
}
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);
}
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<u8>, out: &mut Vec<u8>) {
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<u8>) {
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<Iter<u8>>) -> GameResult<i32> {
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(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<Iter<u8>>, out: &mut Vec<u8>) -> GameResult {
let instr = OpCode::from_str(code).map_err(|e| ParseError(format!("Unknown opcode: {}", code)))?;
fn compile_code<I: Iterator<Item=u8>>(code: &str, 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
@ -372,7 +691,7 @@ impl TextScript {
Ok(())
}
fn expect_newline(iter: &mut Peekable<Iter<u8>>) -> GameResult {
fn expect_newline<I: Iterator<Item=u8>>(iter: &mut Peekable<I>) -> 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<Iter<u8>>) -> GameResult {
let mut res = iter.next();
fn expect_char<I: Iterator<Item=u8>>(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<Iter<u8>>) -> GameResult {
while let Some(&chr) = iter.next() {
fn skip_until<I: Iterator<Item=u8>>(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<Iter<u8>>) -> GameResult<i32> {
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 - 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);
}
}

View file

@ -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);