debugger ui improvements

This commit is contained in:
Alula 2020-11-25 00:08:27 +01:00
parent d2a3a7669f
commit f5c813aaab
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
9 changed files with 422 additions and 72 deletions

View File

@ -57,9 +57,9 @@ gfx_core = "0.9"
gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"}
ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "aad56b0d173ca9f4aeb28599075b5af49ab9214e"}
glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "android-support"}
imgui = {git = "https://github.com/JMS55/imgui-rs.git"}
imgui-gfx-renderer = {git = "https://github.com/JMS55/imgui-rs.git"}
imgui-winit-support = {git = "https://github.com/JMS55/imgui-rs.git", default-features = false, features = ["winit-23"]}
imgui = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "a990a538b66cb67dba3a072bf299b6a51c001447"}
imgui-gfx-renderer = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "a990a538b66cb67dba3a072bf299b6a51c001447"}
imgui-winit-support = {git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "a990a538b66cb67dba3a072bf299b6a51c001447"}
image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"]}
itertools = "0.9.0"
lazy_static = "1.4.0"

View File

@ -0,0 +1,3 @@
struct DifficultyModifier {
}

View File

@ -36,6 +36,7 @@ mod bullet;
mod caret;
mod common;
mod components;
mod difficulty_modifier;
mod encoding;
mod engine_constants;
mod entity;

View File

@ -1,10 +1,21 @@
use imgui::{CollapsingHeader, Condition, im_str, ImStr, ImString, Window};
use itertools::Itertools;
use std::ops::Deref;
use ggez::{Context, GameResult};
use imgui::{CollapsingHeader, Condition, im_str, ImStr, ImString, Slider, Window, WindowFlags};
use itertools::Itertools;
use crate::scene::game_scene::GameScene;
use crate::shared_game_state::SharedGameState;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
pub enum ScriptType {
Scene,
Global,
Inventory,
StageSelect,
}
pub struct LiveDebugger {
map_selector_visible: bool,
events_visible: bool,
@ -14,8 +25,9 @@ pub struct LiveDebugger {
stages: Vec<ImString>,
selected_stage: i32,
events: Vec<ImString>,
event_ids: Vec<u16>,
event_ids: Vec<(ScriptType, u16)>,
selected_event: i32,
text_windows: Vec<(u32, ImString, ImString)>,
error: Option<ImString>,
}
@ -32,6 +44,7 @@ impl LiveDebugger {
events: Vec::new(),
event_ids: Vec::new(),
selected_event: -1,
text_windows: Vec::new(),
error: None,
}
}
@ -44,18 +57,15 @@ impl LiveDebugger {
}
Window::new(im_str!("Debugger"))
.resizable(false)
.collapsed(true, Condition::FirstUseEver)
.position([5.0, 5.0], Condition::FirstUseEver)
.size([300.0, 120.0], Condition::FirstUseEver)
.size([380.0, 170.0], Condition::FirstUseEver)
.build(ui, || {
ui.text(format!(
"Player position: ({:.1},{:.1})",
"Player position: ({:.1},{:.1}), velocity: ({:.1},{:.1})",
game_scene.player.x as f32 / 512.0,
game_scene.player.y as f32 / 512.0,
));
ui.text(format!(
"Player velocity: ({:.1},{:.1})",
game_scene.player.vel_x as f32 / 512.0,
game_scene.player.vel_y as f32 / 512.0,
));
@ -67,9 +77,24 @@ impl LiveDebugger {
));
ui.text(format!(
"Booster fuel: ({})", game_scene.player.booster_fuel
"Booster fuel: {}", game_scene.player.booster_fuel
));
ui.text(format!("Game speed ({:.1} TPS):", state.current_tps()));
let mut speed = state.settings.speed;
Slider::new(im_str!(""))
.range(0.1..=3.0)
.build(ui, &mut speed);
ui.same_line(0.0);
if ui.button(im_str!("Reset"), [0.0, 0.0]) {
speed = 1.0
}
if state.settings.speed != speed {
state.set_speed(speed);
}
if ui.button(im_str!("Map Selector"), [0.0, 0.0]) {
self.map_selector_visible = !self.map_selector_visible;
}
@ -90,27 +115,11 @@ impl LiveDebugger {
}
});
if self.error.is_some() {
Window::new(im_str!("Error!"))
.resizable(false)
.collapsible(false)
.position([((state.screen_size.0 - 300.0) / 2.0).floor(), ((state.screen_size.1 - 100.0) / 2.0).floor()], Condition::Appearing)
.size([300.0, 100.0], Condition::Appearing)
.build(ui, || {
ui.push_item_width(-1.0);
ui.text_wrapped(self.error.as_ref().unwrap());
if ui.button(im_str!("OK"), [0.0, 0.0]) {
self.error = None;
}
});
}
if self.map_selector_visible {
Window::new(im_str!("Map selector"))
.resizable(false)
.position([80.0, 80.0], Condition::FirstUseEver)
.size([240.0, 280.0], Condition::FirstUseEver)
.position([80.0, 80.0], Condition::Appearing)
.size([240.0, 280.0], Condition::Appearing)
.build(ui, || {
if self.stages.is_empty() {
for s in state.stages.iter() {
@ -153,21 +162,32 @@ impl LiveDebugger {
if self.events_visible {
Window::new(im_str!("Events"))
.resizable(false)
.position([80.0, 80.0], Condition::FirstUseEver)
.size([280.0, 300.0], Condition::FirstUseEver)
.position([80.0, 80.0], Condition::Appearing)
.size([300.0, 300.0], Condition::Appearing)
.build(ui, || {
if self.events.is_empty() {
self.event_ids.clear();
let vm = &state.textscript_vm;
for event in vm.scripts.global_script.get_event_ids() {
self.events.push(ImString::new(format!("Global: #{:04}", event)));
self.event_ids.push(event);
}
for event in vm.scripts.scene_script.get_event_ids() {
self.events.push(ImString::new(format!("Scene: #{:04}", event)));
self.event_ids.push(event);
self.event_ids.push((ScriptType::Scene, event));
}
for event in vm.scripts.global_script.get_event_ids() {
self.events.push(ImString::new(format!("Global: #{:04}", event)));
self.event_ids.push((ScriptType::Global, event));
}
for event in vm.scripts.inventory_script.get_event_ids() {
self.events.push(ImString::new(format!("Inventory: #{:04}", event)));
self.event_ids.push((ScriptType::Inventory, event));
}
for event in vm.scripts.stage_select_script.get_event_ids() {
self.events.push(ImString::new(format!("Stage Select: #{:04}", event)));
self.event_ids.push((ScriptType::StageSelect, event));
}
}
let events: Vec<&ImStr> = self.events.iter().map(|e| e.as_ref()).collect();
@ -180,26 +200,108 @@ impl LiveDebugger {
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) {
if let Some((_, event_num)) = self.event_ids.get(self.selected_event as usize) {
state.control_flags.set_tick_world(true);
state.control_flags.set_interactions_disabled(true);
state.textscript_vm.start_script(event_num);
state.textscript_vm.start_script(*event_num);
}
}
ui.same_line(0.0);
if ui.button(im_str!("Decompile"), [0.0, 0.0]) {
if let Some((stype, event_num)) = self.event_ids.get(self.selected_event as usize) {
let id = ((*stype as u32) << 16) | (*event_num as u32);
if !self.text_windows.iter().any(|(e, _, _)| *e == id) {
let script = match stype {
ScriptType::Scene => &state.textscript_vm.scripts.scene_script,
ScriptType::Global => &state.textscript_vm.scripts.global_script,
ScriptType::Inventory => &state.textscript_vm.scripts.inventory_script,
ScriptType::StageSelect => &state.textscript_vm.scripts.stage_select_script,
};
match script.decompile_event(*event_num) {
Ok(code) => {
self.text_windows.push((
id,
ImString::new(format!("Decompiled event: #{:04}", *event_num)),
ImString::new(code)
));
}
Err(e) => {
self.error = Some(ImString::new(format!("Error decompiling TextScript #{:04}: {}", *event_num, e)));
}
}
}
}
}
});
}
if self.flags_visible {
Window::new(im_str!("Flags"))
.position([80.0, 80.0], Condition::FirstUseEver)
.size([280.0, 300.0], Condition::FirstUseEver)
.build(ui, || {
if CollapsingHeader::new(im_str!("Control flags")).default_open(true).build(&ui)
{
ui.checkbox_flags(im_str!("Flag 0x01"), &mut state.control_flags.0, 1);
if CollapsingHeader::new(im_str!("Control flags")).default_open(false).build(&ui) {
ui.checkbox_flags(im_str!("Tick world"), &mut state.control_flags.0, 1);
ui.checkbox_flags(im_str!("Control enabled"), &mut state.control_flags.0, 2);
ui.checkbox_flags(im_str!("Interactions disabled"), &mut state.control_flags.0, 4);
ui.checkbox_flags(im_str!("Credits running"), &mut state.control_flags.0, 8);
ui.separator();
ui.checkbox_flags(im_str!("[Internal] Windy level"), &mut state.control_flags.0, 15);
}
if CollapsingHeader::new(im_str!("Player condition flags")).default_open(false).build(&ui) {
cond_flags(&ui, &mut game_scene.player.cond);
}
if CollapsingHeader::new(im_str!("Player equipment")).default_open(false).build(&ui) {
ui.checkbox_flags(im_str!("Booster 0.8"), &mut game_scene.player.equip.0, 1);
ui.checkbox_flags(im_str!("Map System"), &mut game_scene.player.equip.0, 2);
ui.checkbox_flags(im_str!("Arms Barrier"), &mut game_scene.player.equip.0, 4);
ui.checkbox_flags(im_str!("Turbocharge"), &mut game_scene.player.equip.0, 8);
ui.checkbox_flags(im_str!("Air Tank"), &mut game_scene.player.equip.0, 16);
ui.checkbox_flags(im_str!("Booster 2.0"), &mut game_scene.player.equip.0, 32);
ui.checkbox_flags(im_str!("Mimiga Mask"), &mut game_scene.player.equip.0, 64);
ui.checkbox_flags(im_str!("Whimsical Star"), &mut game_scene.player.equip.0, 128);
ui.checkbox_flags(im_str!("Nikumaru Counter"), &mut game_scene.player.equip.0, 256);
}
});
}
let mut remove = -1;
for (idx, (_, title, contents)) in self.text_windows.iter().enumerate() {
let mut opened = true;
Window::new(title)
.position([100.0, 100.0], Condition::FirstUseEver)
.size([400.0, 300.0], Condition::FirstUseEver)
.opened(&mut opened)
.build(ui, || {
ui.text_wrapped(contents);
});
if !opened {
remove = idx as i32;
}
}
if remove >= 0 {
self.text_windows.remove(remove as usize);
}
if self.error.is_some() {
Window::new(im_str!("Error!"))
.resizable(false)
.collapsible(false)
.position([((state.screen_size.0 - 300.0) / 2.0).floor(), ((state.screen_size.1 - 100.0) / 2.0).floor()], Condition::Appearing)
.size([300.0, 100.0], Condition::Appearing)
.build(ui, || {
ui.push_item_width(-1.0);
ui.text_wrapped(self.error.as_ref().unwrap());
if ui.button(im_str!("OK"), [0.0, 0.0]) {
self.error = None;
}
});
}
@ -207,3 +309,14 @@ impl LiveDebugger {
Ok(())
}
}
fn cond_flags(ui: &imgui::Ui, cond: &mut crate::common::Condition) {
ui.checkbox_flags(im_str!("Interacted"), &mut cond.0, 1);
ui.checkbox_flags(im_str!("Hidden"), &mut cond.0, 2);
ui.checkbox_flags(im_str!("Fallen"), &mut cond.0, 4);
ui.checkbox_flags(im_str!("Built-in NPC destroy handler"), &mut cond.0, 8);
ui.checkbox_flags(im_str!("Damage first boss NPC"), &mut cond.0, 16);
ui.checkbox_flags(im_str!("Increased acceleration"), &mut cond.0, 32);
ui.checkbox_flags(im_str!("Unknown (0x40)"), &mut cond.0, 64);
ui.checkbox_flags(im_str!("Alive"), &mut cond.0, 128);
}

76
src/npc/chaco.rs Normal file
View File

@ -0,0 +1,76 @@
use ggez::GameResult;
use crate::caret::CaretType;
use crate::common::Direction;
use crate::npc::NPC;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
impl NPC {
pub(crate) fn tick_n093_chaco(&mut self, state: &mut SharedGameState, player: &Player) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = 0;
self.anim_counter = 0;
}
if state.game_rng.range(0..120) == 10 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
if (self.x - player.x).abs() < 32 * 0x200
&& self.y - 32 * 0x200 < player.y
&& self.y + 16 * 0x200 > player.y {
self.direction = if self.x > player.x { Direction::Left } else { Direction::Right };
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 8 {
self.action_num = 1;
self.anim_num = 0;
}
}
3 | 4 => {
if self.action_num == 3 {
self.action_num = 4;
self.anim_num = 2;
self.anim_counter = 0;
}
self.anim_counter += 1;
if self.anim_counter > 4 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 5 {
self.anim_num = 2;
}
}
self.x += self.direction.vector_x() * 0x200;
}
10 => {
self.anim_num = 6;
self.action_counter += 1;
if self.action_counter > 200 {
self.action_counter = 0;
state.create_caret(self.x, self.y, CaretType::Zzz, Direction::Left);
}
}
_ => {}
}
let dir_offset = if self.direction == Direction::Left { 0 } else { 7 };
self.anim_rect = state.constants.npc.n093_chaco[self.anim_num as usize + dir_offset];
Ok(())
}
}

View File

@ -26,6 +26,7 @@ use crate::str;
pub mod balrog;
pub mod boss;
pub mod chaco;
pub mod characters;
pub mod egg_corridor;
pub mod first_cave;
@ -241,6 +242,8 @@ impl GameEntity<(&mut Player, &BTreeMap<u16, RefCell<NPC>>, &mut Stage)> for NPC
88 => self.tick_n088_igor_boss(state, player),
89 => self.tick_n089_igor_dead(state, player),
91 => self.tick_n091_mimiga_cage(state),
92 => self.tick_n092_sue_at_pc(state),
93 => self.tick_n093_chaco(state, player),
94 => self.tick_n094_kulala(state, player),
95 => self.tick_n095_jelly(state),
96 => self.tick_n096_fan_left(state, player),
@ -389,7 +392,6 @@ impl PhysicalEntity for NPC {
pub struct NPCMap {
ids: HashSet<u16>,
/// Do not iterate over this directly outside render pipeline.
pub npcs: BTreeMap<u16, RefCell<NPC>>,
/// NPCMap but for bosses and of static size.
pub boss_map: BossNPC,
@ -509,18 +511,6 @@ impl NPCMap {
}
pub fn garbage_collect(&mut self) {
// let dead_npcs = self.npcs.iter().(|(&id, npc_cell)| {
// if !npc_cell.borrow().cond.alive() {
// Some(id)
// } else {
// None
// }
// }).collect_vec();
//
// for npc_id in dead_npcs.iter() {
// self.npcs.remove(npc_id);
// }
for npc_cell in self.npcs.values_mut() {
let mut npc = npc_cell.borrow();

View File

@ -1,12 +1,13 @@
use std::cell::RefCell;
use std::collections::BTreeMap;
use crate::common::Direction;
use ggez::GameResult;
use num_traits::clamp;
use crate::common::Direction;
use crate::npc::{NPC, NPCMap};
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
use num_traits::clamp;
impl NPC {
pub fn tick_n042_sue(&mut self, state: &mut SharedGameState, player: &Player, map: &BTreeMap<u16, RefCell<NPC>>) -> GameResult {
@ -198,7 +199,7 @@ impl NPC {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 5{
if self.anim_num > 5 {
self.anim_num = 2;
}
}
@ -230,4 +231,61 @@ impl NPC {
Ok(())
}
pub(crate) fn tick_n092_sue_at_pc(&mut self, state: &mut SharedGameState) -> GameResult {
match self.action_num {
0 | 1 => {
if self.action_num == 0 {
self.action_num = 1;
self.action_counter = 0;
self.anim_counter = 0;
self.x -= 4 * 0x200;
self.y += 16 * 0x200;
}
self.anim_counter += 1;
if self.anim_counter > 2 {
self.anim_counter = 0;
self.anim_num += 1;
if self.anim_num > 1 {
self.anim_num = 0;
}
}
if state.game_rng.range(0..80) == 1 {
self.action_num = 2;
self.action_counter = 0;
self.anim_num = 1;
}
if state.game_rng.range(0..120) == 10 {
self.action_num = 3;
self.action_counter = 0;
self.anim_num = 2;
}
}
2 => {
self.action_counter += 1;
if self.action_counter > 40 {
self.action_num = 3;
self.action_counter = 0;
self.anim_num = 2;
}
}
3 => {
self.action_counter += 1;
if self.action_counter > 80 {
self.action_num = 1;
self.anim_num = 0;
}
}
_ => {}
}
self.anim_rect = state.constants.npc.n092_sue_at_pc[self.anim_num as usize];
Ok(())
}
}

View File

@ -7,6 +7,7 @@ use gfx::{self, *};
use ggez::{Context, filesystem, GameResult, graphics};
use ggez::filesystem::OpenOptions;
use ggez::graphics::{Canvas, Shader};
use num_traits::clamp;
use crate::bmfont_renderer::BMFontRenderer;
use crate::caret::{Caret, CaretType};
@ -47,6 +48,14 @@ impl TimingMode {
TimingMode::FrameSynchronized => { 0.0 }
}
}
pub fn get_tps(self) -> usize {
match self {
TimingMode::_50Hz => { 50 }
TimingMode::_60Hz => { 60 }
TimingMode::FrameSynchronized => { 0 }
}
}
}
@ -361,7 +370,7 @@ impl SharedGameState {
}
pub fn set_speed(&mut self, value: f64) {
self.settings.speed = value;
self.settings.speed = clamp(value, 0.1, 3.0);
self.frame_time = 0.0;
if let Err(err) = self.sound_manager.set_speed(value as f32) {
@ -369,6 +378,10 @@ impl SharedGameState {
}
}
pub fn current_tps(&self) -> f64 {
self.timing_mode.get_tps() as f64 * self.settings.speed
}
pub fn shutdown(&mut self) {
self.shutdown = true;
}

View File

@ -9,8 +9,8 @@ use std::ops::Not;
use std::str::FromStr;
use byteorder::ReadBytesExt;
use ggez::{Context, GameResult};
use ggez::GameError::ParseError;
use ggez::{Context, GameError, GameResult};
use ggez::GameError::{InvalidValue, ParseError};
use itertools::Itertools;
use num_derive::FromPrimitive;
use num_traits::{clamp, FromPrimitive};
@ -1604,6 +1604,9 @@ impl TextScript {
TextScript::compile_code(code.as_ref(), strict, iter, &mut bytecode)?;
}
b'\r' => {
iter.next();
}
_ => {
char_buf.push(chr);
@ -1745,19 +1748,112 @@ impl TextScript {
Ok(())
}
pub fn decompile_event(&self, id: u16) -> GameResult<String> {
if let Some(bytecode) = self.event_map.get(&id) {
let mut result = String::new();
let mut cursor = Cursor::new(bytecode);
while let Ok(op_num) = read_cur_varint(&mut cursor) {
let op_maybe: Option<OpCode> = FromPrimitive::from_i32(op_num);
if let Some(op) = op_maybe {
match op {
// Zero operand codes
OpCode::AEp | OpCode::CAT | OpCode::CIL | OpCode::CLO | OpCode::CLR | OpCode::CPS |
OpCode::CRE | OpCode::CSS | OpCode::END | OpCode::ESC | OpCode::FLA | OpCode::FMU |
OpCode::FRE | OpCode::HMC | OpCode::INI | OpCode::KEY | OpCode::LDP | OpCode::MLP |
OpCode::MM0 | OpCode::MNA | OpCode::MS2 | OpCode::MS3 | OpCode::MSG | OpCode::NOD |
OpCode::PRI | OpCode::RMU | OpCode::SAT | OpCode::SLP | OpCode::SMC | OpCode::SPS |
OpCode::STC | OpCode::SVP | OpCode::TUR | OpCode::WAS | OpCode::ZAM | OpCode::HM2 |
OpCode::POP | OpCode::KE2 | OpCode::FR2 => {
result.push_str(format!("{:?}()\n", op).as_str());
}
// One operand codes
OpCode::BOA | OpCode::BSL | OpCode::FOB | OpCode::FOM | OpCode::QUA | OpCode::UNI |
OpCode::MYB | OpCode::MYD | OpCode::FAI | OpCode::FAO | OpCode::WAI | OpCode::FAC |
OpCode::GIT | OpCode::NUM | OpCode::DNA | OpCode::DNP | OpCode::FLm | OpCode::FLp |
OpCode::MPp | OpCode::SKm | OpCode::SKp | OpCode::EQp | OpCode::EQm | OpCode::MLp |
OpCode::ITp | OpCode::ITm | OpCode::AMm | OpCode::UNJ | OpCode::MPJ | OpCode::YNJ |
OpCode::EVE | OpCode::XX1 | OpCode::SIL | OpCode::LIp | OpCode::SOU | OpCode::CMU |
OpCode::SSS | OpCode::ACH | OpCode::S2MV | OpCode::PSH => {
let par_a = read_cur_varint(&mut cursor)?;
result.push_str(format!("{:?}({})\n", op, par_a).as_str());
}
// Two operand codes
OpCode::FON | OpCode::MOV | OpCode::AMp | OpCode::NCJ | OpCode::ECJ | OpCode::FLJ |
OpCode::ITJ | OpCode::SKJ | OpCode::AMJ | OpCode::SMP | OpCode::PSp | OpCode::IpN |
OpCode::FFm => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
result.push_str(format!("{:?}({}, {})\n", op, par_a, par_b).as_str());
}
// Three operand codes
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP | OpCode::INJ => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
let par_c = read_cur_varint(&mut cursor)?;
result.push_str(format!("{:?}({}, {}, {})\n", op, par_a, par_b, par_c).as_str());
}
// Four operand codes
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
let par_c = read_cur_varint(&mut cursor)?;
let par_d = read_cur_varint(&mut cursor)?;
result.push_str(format!("{:?}({}, {}, {}, {})\n", op, par_a, par_b, par_c, par_d).as_str());
}
OpCode::_STR => {
let len = read_cur_varint(&mut cursor)?;
result.push_str(format!("%string(len = {}, value = \"", len).as_str());
for _ in 0..len {
let chr = std::char::from_u32(read_cur_varint(&mut cursor)? as u32).unwrap_or('?');
match chr {
'\n' => {
result.push_str("\\n");
}
'\r' => {
result.push_str("\\r");
}
'\t' => {
result.push_str("\\t");
}
'\u{0000}'..='\u{001f}' | '\u{0080}'..='\u{ffff}' => {
result.push_str(chr.escape_unicode().to_string().as_str());
}
_ => {
result.push(chr);
}
}
}
result.push_str("\")\n");
}
OpCode::_NOP => result.push_str("%no_op()\n"),
OpCode::_UNI => result.push_str("%unimplemented()\n"),
OpCode::_END => result.push_str("%end_marker()\n"),
}
} else {
break;
}
}
Ok(result)
} else {
Err(InvalidValue("Unknown script.".to_string()))
}
}
fn expect_char<I: Iterator<Item=u8>>(expect: u8, iter: &mut I) -> GameResult {
let res = iter.next();
match res {
Some(n) if n == expect => {
Ok(())
}
Some(n) => {
Err(ParseError(format!("Expected {}, found {}", expect as char, n as char)))
}
None => {
Err(ParseError(str!("Script unexpectedly ended.")))
}
Some(n) if n == expect => Ok(()),
Some(n) => Err(ParseError(format!("Expected {}, found {}", expect as char, n as char))),
None => Err(ParseError(str!("Script unexpectedly ended."))),
}
}