2020-11-24 23:08:27 +00:00
|
|
|
use std::ops::Deref;
|
2020-08-19 11:21:40 +00:00
|
|
|
|
2020-11-04 23:25:18 +00:00
|
|
|
use ggez::{Context, GameResult};
|
2020-11-24 23:08:27 +00:00
|
|
|
use imgui::{CollapsingHeader, Condition, im_str, ImStr, ImString, Slider, Window, WindowFlags};
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
2020-08-19 13:11:34 +00:00
|
|
|
use crate::scene::game_scene::GameScene;
|
2020-09-20 15:27:31 +00:00
|
|
|
use crate::shared_game_state::SharedGameState;
|
2020-08-19 11:21:40 +00:00
|
|
|
|
2020-11-24 23:08:27 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
|
|
#[repr(u8)]
|
|
|
|
pub enum ScriptType {
|
|
|
|
Scene,
|
|
|
|
Global,
|
|
|
|
Inventory,
|
|
|
|
StageSelect,
|
|
|
|
}
|
|
|
|
|
2020-08-19 11:21:40 +00:00
|
|
|
pub struct LiveDebugger {
|
2020-08-20 18:31:47 +00:00
|
|
|
map_selector_visible: bool,
|
2020-08-27 02:43:21 +00:00
|
|
|
events_visible: bool,
|
2020-08-23 02:16:31 +00:00
|
|
|
hacks_visible: bool,
|
2020-09-10 23:40:45 +00:00
|
|
|
flags_visible: bool,
|
2020-08-27 02:43:21 +00:00
|
|
|
last_stage_id: usize,
|
2020-08-19 13:11:34 +00:00
|
|
|
stages: Vec<ImString>,
|
2020-08-27 02:43:21 +00:00
|
|
|
selected_stage: i32,
|
|
|
|
events: Vec<ImString>,
|
2020-11-24 23:08:27 +00:00
|
|
|
event_ids: Vec<(ScriptType, u16)>,
|
2020-08-27 02:43:21 +00:00
|
|
|
selected_event: i32,
|
2020-11-24 23:08:27 +00:00
|
|
|
text_windows: Vec<(u32, ImString, ImString)>,
|
2020-08-19 13:11:34 +00:00
|
|
|
error: Option<ImString>,
|
2020-08-19 11:21:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl LiveDebugger {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
2020-08-20 18:31:47 +00:00
|
|
|
map_selector_visible: false,
|
2020-08-27 02:43:21 +00:00
|
|
|
events_visible: false,
|
2020-08-23 02:16:31 +00:00
|
|
|
hacks_visible: false,
|
2020-09-10 23:40:45 +00:00
|
|
|
flags_visible: false,
|
2020-08-27 02:43:21 +00:00
|
|
|
last_stage_id: usize::MAX,
|
|
|
|
stages: Vec::new(),
|
|
|
|
selected_stage: -1,
|
|
|
|
events: Vec::new(),
|
|
|
|
event_ids: Vec::new(),
|
|
|
|
selected_event: -1,
|
2020-11-24 23:08:27 +00:00
|
|
|
text_windows: Vec::new(),
|
2020-08-19 13:11:34 +00:00
|
|
|
error: None,
|
2020-08-19 11:21:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 13:11:34 +00:00
|
|
|
pub fn run_ingame(&mut self, game_scene: &mut GameScene, state: &mut SharedGameState, ctx: &mut Context, ui: &mut imgui::Ui) -> GameResult {
|
2020-08-27 02:43:21 +00:00
|
|
|
if self.last_stage_id != game_scene.stage_id {
|
|
|
|
self.last_stage_id = game_scene.stage_id;
|
|
|
|
self.events.clear();
|
|
|
|
self.selected_event = -1;
|
|
|
|
}
|
|
|
|
|
2020-08-23 02:16:31 +00:00
|
|
|
Window::new(im_str!("Debugger"))
|
2020-11-24 23:08:27 +00:00
|
|
|
.resizable(false)
|
2020-09-29 00:43:55 +00:00
|
|
|
.collapsed(true, Condition::FirstUseEver)
|
2020-08-20 18:31:47 +00:00
|
|
|
.position([5.0, 5.0], Condition::FirstUseEver)
|
2020-11-24 23:08:27 +00:00
|
|
|
.size([380.0, 170.0], Condition::FirstUseEver)
|
2020-08-19 11:21:40 +00:00
|
|
|
.build(ui, || {
|
|
|
|
ui.text(format!(
|
2020-11-24 23:08:27 +00:00
|
|
|
"Player position: ({:.1},{:.1}), velocity: ({:.1},{:.1})",
|
2020-11-28 19:25:51 +00:00
|
|
|
game_scene.player1.x as f32 / 512.0,
|
|
|
|
game_scene.player1.y as f32 / 512.0,
|
|
|
|
game_scene.player1.vel_x as f32 / 512.0,
|
|
|
|
game_scene.player1.vel_y as f32 / 512.0,
|
2020-08-20 18:31:47 +00:00
|
|
|
));
|
|
|
|
|
2020-09-23 13:10:42 +00:00
|
|
|
ui.text(format!(
|
|
|
|
"NPC Count: {}/{}",
|
2020-11-04 23:25:18 +00:00
|
|
|
game_scene.npc_map.npcs.values().filter(|n| n.borrow().cond.alive()).count(),
|
2020-09-23 13:10:42 +00:00
|
|
|
game_scene.npc_map.npcs.len(),
|
|
|
|
));
|
|
|
|
|
2020-08-20 18:31:47 +00:00
|
|
|
ui.text(format!(
|
2020-11-28 19:25:51 +00:00
|
|
|
"Booster fuel: {}", game_scene.player1.booster_fuel
|
2020-08-19 11:21:40 +00:00
|
|
|
));
|
2020-08-20 18:31:47 +00:00
|
|
|
|
2020-11-24 23:08:27 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-08-20 18:31:47 +00:00
|
|
|
if ui.button(im_str!("Map Selector"), [0.0, 0.0]) {
|
2020-09-10 13:24:04 +00:00
|
|
|
self.map_selector_visible = !self.map_selector_visible;
|
2020-08-20 18:31:47 +00:00
|
|
|
}
|
2020-08-23 02:16:31 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
ui.same_line(0.0);
|
|
|
|
if ui.button(im_str!("Events"), [0.0, 0.0]) {
|
2020-09-10 13:24:04 +00:00
|
|
|
self.events_visible = !self.events_visible;
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ui.same_line(0.0);
|
2020-08-23 02:16:31 +00:00
|
|
|
if ui.button(im_str!("Hacks"), [0.0, 0.0]) {
|
2020-09-10 13:24:04 +00:00
|
|
|
self.hacks_visible = !self.hacks_visible;
|
2020-08-23 02:16:31 +00:00
|
|
|
}
|
2020-09-10 23:40:45 +00:00
|
|
|
|
|
|
|
ui.same_line(0.0);
|
|
|
|
if ui.button(im_str!("Flags"), [0.0, 0.0]) {
|
|
|
|
self.flags_visible = !self.flags_visible;
|
|
|
|
}
|
2020-08-20 18:31:47 +00:00
|
|
|
});
|
2020-08-19 13:11:34 +00:00
|
|
|
|
2020-08-20 18:31:47 +00:00
|
|
|
if self.map_selector_visible {
|
|
|
|
Window::new(im_str!("Map selector"))
|
|
|
|
.resizable(false)
|
2020-11-24 23:08:27 +00:00
|
|
|
.position([80.0, 80.0], Condition::Appearing)
|
|
|
|
.size([240.0, 280.0], Condition::Appearing)
|
2020-08-20 18:31:47 +00:00
|
|
|
.build(ui, || {
|
|
|
|
if self.stages.is_empty() {
|
|
|
|
for s in state.stages.iter() {
|
|
|
|
self.stages.push(ImString::new(s.name.to_owned()));
|
|
|
|
}
|
2020-08-19 13:11:34 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
self.selected_stage = match state.stages.iter().find_position(|s| s.name == game_scene.stage.data.name) {
|
2020-08-20 18:31:47 +00:00
|
|
|
Some((pos, _)) => { pos as i32 }
|
|
|
|
_ => { -1 }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
let stages: Vec<&ImStr> = self.stages.iter().map(|e| e.as_ref()).collect();
|
2020-08-19 13:11:34 +00:00
|
|
|
|
2020-08-20 18:31:47 +00:00
|
|
|
ui.push_item_width(-1.0);
|
2020-08-27 02:43:21 +00:00
|
|
|
ui.list_box(im_str!(""), &mut self.selected_stage, &stages, 10);
|
2020-08-19 13:11:34 +00:00
|
|
|
|
2020-08-20 18:31:47 +00:00
|
|
|
if ui.button(im_str!("Load"), [0.0, 0.0]) {
|
2020-08-27 02:43:21 +00:00
|
|
|
match GameScene::new(state, ctx, self.selected_stage as usize) {
|
2020-08-20 18:31:47 +00:00
|
|
|
Ok(mut scene) => {
|
2020-11-28 19:25:51 +00:00
|
|
|
scene.inventory_player1 = game_scene.inventory_player1.clone();
|
|
|
|
scene.player1 = game_scene.player1.clone();
|
|
|
|
scene.player1.x = (scene.stage.map.width / 2 * 16 * 0x200) as isize;
|
|
|
|
scene.player1.y = (scene.stage.map.height / 2 * 16 * 0x200) as isize;
|
2020-09-10 23:40:45 +00:00
|
|
|
|
2020-11-28 19:25:51 +00:00
|
|
|
if scene.player1.life == 0 {
|
|
|
|
scene.player1.life = scene.player1.max_life;
|
2020-09-10 23:40:45 +00:00
|
|
|
}
|
|
|
|
|
2020-08-20 18:31:47 +00:00
|
|
|
state.next_scene = Some(Box::new(scene));
|
|
|
|
}
|
|
|
|
Err(e) => {
|
2020-08-26 01:07:04 +00:00
|
|
|
log::error!("Error loading map: {:?}", e);
|
2020-08-20 18:31:47 +00:00
|
|
|
self.error = Some(ImString::new(e.to_string()));
|
|
|
|
}
|
2020-08-19 13:11:34 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-20 18:31:47 +00:00
|
|
|
});
|
|
|
|
}
|
2020-08-19 11:21:40 +00:00
|
|
|
|
2020-08-27 02:43:21 +00:00
|
|
|
if self.events_visible {
|
|
|
|
Window::new(im_str!("Events"))
|
|
|
|
.resizable(false)
|
2020-11-24 23:08:27 +00:00
|
|
|
.position([80.0, 80.0], Condition::Appearing)
|
|
|
|
.size([300.0, 300.0], Condition::Appearing)
|
2020-08-27 02:43:21 +00:00
|
|
|
.build(ui, || {
|
|
|
|
if self.events.is_empty() {
|
|
|
|
self.event_ids.clear();
|
|
|
|
|
|
|
|
let vm = &state.textscript_vm;
|
2020-11-24 23:08:27 +00:00
|
|
|
|
|
|
|
for event in vm.scripts.scene_script.get_event_ids() {
|
|
|
|
self.events.push(ImString::new(format!("Scene: #{:04}", event)));
|
|
|
|
self.event_ids.push((ScriptType::Scene, event));
|
|
|
|
}
|
|
|
|
|
2020-08-27 05:12:14 +00:00
|
|
|
for event in vm.scripts.global_script.get_event_ids() {
|
2020-08-27 02:43:21 +00:00
|
|
|
self.events.push(ImString::new(format!("Global: #{:04}", event)));
|
2020-11-24 23:08:27 +00:00
|
|
|
self.event_ids.push((ScriptType::Global, event));
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
|
2020-11-24 23:08:27 +00:00
|
|
|
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));
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let events: Vec<&ImStr> = self.events.iter().map(|e| e.as_ref()).collect();
|
|
|
|
|
2020-08-27 05:12:14 +00:00
|
|
|
ui.text_wrapped(&ImString::new(format!("Execution state: {:?}", state.textscript_vm.state)));
|
2020-08-27 02:43:21 +00:00
|
|
|
|
|
|
|
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());
|
|
|
|
|
2020-11-24 23:08:27 +00:00
|
|
|
if let Some((_, event_num)) = self.event_ids.get(self.selected_event as usize) {
|
2020-09-25 12:55:28 +00:00
|
|
|
state.control_flags.set_tick_world(true);
|
2020-09-21 23:53:46 +00:00
|
|
|
state.control_flags.set_interactions_disabled(true);
|
2020-11-24 23:08:27 +00:00
|
|
|
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)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-27 02:43:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-10 23:40:45 +00:00
|
|
|
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, || {
|
2020-11-24 23:08:27 +00:00
|
|
|
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);
|
2020-09-10 23:40:45 +00:00
|
|
|
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);
|
2020-11-24 23:08:27 +00:00
|
|
|
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) {
|
2020-11-28 19:25:51 +00:00
|
|
|
cond_flags(&ui, &mut game_scene.player1.cond);
|
2020-11-24 23:08:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if CollapsingHeader::new(im_str!("Player equipment")).default_open(false).build(&ui) {
|
2020-11-28 19:25:51 +00:00
|
|
|
ui.checkbox_flags(im_str!("Booster 0.8"), &mut game_scene.player1.equip.0, 1);
|
|
|
|
ui.checkbox_flags(im_str!("Map System"), &mut game_scene.player1.equip.0, 2);
|
|
|
|
ui.checkbox_flags(im_str!("Arms Barrier"), &mut game_scene.player1.equip.0, 4);
|
|
|
|
ui.checkbox_flags(im_str!("Turbocharge"), &mut game_scene.player1.equip.0, 8);
|
|
|
|
ui.checkbox_flags(im_str!("Air Tank"), &mut game_scene.player1.equip.0, 16);
|
|
|
|
ui.checkbox_flags(im_str!("Booster 2.0"), &mut game_scene.player1.equip.0, 32);
|
|
|
|
ui.checkbox_flags(im_str!("Mimiga Mask"), &mut game_scene.player1.equip.0, 64);
|
|
|
|
ui.checkbox_flags(im_str!("Whimsical Star"), &mut game_scene.player1.equip.0, 128);
|
|
|
|
ui.checkbox_flags(im_str!("Nikumaru Counter"), &mut game_scene.player1.equip.0, 256);
|
2020-11-24 23:08:27 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-09-10 23:40:45 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-08-19 11:21:40 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2020-11-24 23:08:27 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|