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:
parent
4af9552685
commit
44d4bd8ea3
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
() => {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
55
src/main.rs
55
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<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)?;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)?));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue