debugger ui improvements
This commit is contained in:
parent
d2a3a7669f
commit
f5c813aaab
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
struct DifficultyModifier {
|
||||
|
||||
}
|
|
@ -36,6 +36,7 @@ mod bullet;
|
|||
mod caret;
|
||||
mod common;
|
||||
mod components;
|
||||
mod difficulty_modifier;
|
||||
mod encoding;
|
||||
mod engine_constants;
|
||||
mod entity;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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."))),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue