add in-game debug command line
This commit is contained in:
parent
b1b3b131e2
commit
4ed7ba66b8
|
@ -8,6 +8,7 @@ use std::rc::Rc;
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use imgui::internal::RawWrapper;
|
||||
use imgui::sys::{ImGuiKey_Backspace, ImGuiKey_Delete, ImGuiKey_Enter};
|
||||
use imgui::{ConfigFlags, DrawCmd, DrawData, DrawIdx, DrawVert, Key, MouseCursor, TextureId, Ui};
|
||||
use sdl2::controller::GameController;
|
||||
use sdl2::event::{Event, WindowEvent};
|
||||
|
@ -424,7 +425,11 @@ impl BackendEventLoop for SDL2EventLoop {
|
|||
|
||||
#[cfg(feature = "render-opengl")]
|
||||
if *self.opengl_available.borrow() {
|
||||
let imgui = init_imgui()?;
|
||||
let mut imgui = init_imgui()?;
|
||||
let mut key_map = &mut imgui.io_mut().key_map;
|
||||
key_map[ImGuiKey_Backspace as usize] = Scancode::Backspace as u32;
|
||||
key_map[ImGuiKey_Delete as usize] = Scancode::Delete as u32;
|
||||
key_map[ImGuiKey_Enter as usize] = Scancode::Return as u32;
|
||||
|
||||
let refs = self.refs.clone();
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
//! Error types and conversion functions.
|
||||
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::sync::{Arc, PoisonError};
|
||||
use std::sync::mpsc::SendError;
|
||||
use std::sync::{Arc, PoisonError};
|
||||
|
||||
/// An enum containing all kinds of game framework errors.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -43,6 +42,8 @@ pub enum GameError {
|
|||
ParseError(String),
|
||||
/// Something went wrong while converting a value.
|
||||
InvalidValue(String),
|
||||
/// Something went wrong while executing a debug command line command.
|
||||
CommandLineError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for GameError {
|
||||
|
@ -50,11 +51,9 @@ impl fmt::Display for GameError {
|
|||
match *self {
|
||||
GameError::ConfigError(ref s) => write!(f, "Config error: {}", s),
|
||||
GameError::ResourceLoadError(ref s) => write!(f, "Error loading resource: {}", s),
|
||||
GameError::ResourceNotFound(ref s, ref paths) => write!(
|
||||
f,
|
||||
"Resource not found: {}, searched in paths {:?}",
|
||||
s, paths
|
||||
),
|
||||
GameError::ResourceNotFound(ref s, ref paths) => {
|
||||
write!(f, "Resource not found: {}, searched in paths {:?}", s, paths)
|
||||
}
|
||||
GameError::WindowError(ref e) => write!(f, "Window creation error: {}", e),
|
||||
_ => write!(f, "GameError {:?}", self),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,293 @@
|
|||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::framework::error::{GameError::CommandLineError, GameResult};
|
||||
use crate::scene::game_scene::GameScene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
use crate::weapon::WeaponType;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CommandLineCommand {
|
||||
AddItem(u16),
|
||||
RemoveItem(u16),
|
||||
AddWeapon(u16, u16),
|
||||
RemoveWeapon(u16),
|
||||
AddWeaponAmmo(u16),
|
||||
SetWeaponMaxAmmo(u16),
|
||||
RefillAmmo,
|
||||
RefillHP,
|
||||
AddXP(u16),
|
||||
RemoveXP(u16),
|
||||
SetMaxHP(u16),
|
||||
}
|
||||
|
||||
impl CommandLineCommand {
|
||||
pub fn from_components(components: Vec<&str>) -> Option<CommandLineCommand> {
|
||||
if components.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let command = components[0];
|
||||
|
||||
match command.replacen("/", "", 1).as_str() {
|
||||
"add_item" => {
|
||||
if components.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item_id = components[1].parse::<u16>();
|
||||
if item_id.is_ok() {
|
||||
return Some(CommandLineCommand::AddItem(item_id.unwrap()));
|
||||
}
|
||||
}
|
||||
"remove_item" => {
|
||||
if components.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item_id = components[1].parse::<u16>();
|
||||
if item_id.is_ok() {
|
||||
return Some(CommandLineCommand::RemoveItem(item_id.unwrap()));
|
||||
}
|
||||
}
|
||||
"add_weapon" => {
|
||||
if components.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let weapon_id = components[1].parse::<u16>();
|
||||
let ammo_count = components[2].parse::<u16>();
|
||||
|
||||
if weapon_id.is_ok() && ammo_count.is_ok() {
|
||||
return Some(CommandLineCommand::AddWeapon(weapon_id.unwrap(), ammo_count.unwrap()));
|
||||
}
|
||||
}
|
||||
"remove_weapon" => {
|
||||
if components.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let weapon_id = components[1].parse::<u16>();
|
||||
if weapon_id.is_ok() {
|
||||
return Some(CommandLineCommand::RemoveWeapon(weapon_id.unwrap()));
|
||||
}
|
||||
}
|
||||
"add_weapon_ammo" => {
|
||||
if components.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ammo_count = components[1].parse::<u16>();
|
||||
if ammo_count.is_ok() {
|
||||
return Some(CommandLineCommand::AddWeaponAmmo(ammo_count.unwrap()));
|
||||
}
|
||||
}
|
||||
"set_weapon_max_ammo" => {
|
||||
if components.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let max_ammo_count = components[1].parse::<u16>();
|
||||
if max_ammo_count.is_ok() {
|
||||
return Some(CommandLineCommand::SetWeaponMaxAmmo(max_ammo_count.unwrap()));
|
||||
}
|
||||
}
|
||||
"refill_ammo" => {
|
||||
return Some(CommandLineCommand::RefillAmmo);
|
||||
}
|
||||
"refill_hp" => {
|
||||
return Some(CommandLineCommand::RefillHP);
|
||||
}
|
||||
"add_xp" => {
|
||||
if components.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let xp_count = components[1].parse::<u16>();
|
||||
if xp_count.is_ok() {
|
||||
return Some(CommandLineCommand::AddXP(xp_count.unwrap()));
|
||||
}
|
||||
}
|
||||
"remove_xp" => {
|
||||
if components.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let xp_count = components[1].parse::<u16>();
|
||||
if xp_count.is_ok() {
|
||||
return Some(CommandLineCommand::RemoveXP(xp_count.unwrap()));
|
||||
}
|
||||
}
|
||||
"set_max_hp" => {
|
||||
if components.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let hp_count = components[1].parse::<u16>();
|
||||
if hp_count.is_ok() {
|
||||
return Some(CommandLineCommand::SetMaxHP(hp_count.unwrap()));
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, game_scene: &mut GameScene, state: &mut SharedGameState) -> GameResult {
|
||||
match *self {
|
||||
CommandLineCommand::AddItem(item_id) => {
|
||||
game_scene.inventory_player1.add_item(item_id);
|
||||
}
|
||||
CommandLineCommand::RemoveItem(item_id) => {
|
||||
if !game_scene.inventory_player1.has_item(item_id) {
|
||||
return Err(CommandLineError(format!("Player does not have item {}", item_id)));
|
||||
}
|
||||
|
||||
game_scene.inventory_player1.remove_item(item_id);
|
||||
}
|
||||
CommandLineCommand::AddWeapon(weapon_id, ammo_count) => {
|
||||
let weapon_type: Option<WeaponType> = FromPrimitive::from_u16(weapon_id);
|
||||
match weapon_type {
|
||||
Some(weapon_type) => game_scene.inventory_player1.add_weapon(weapon_type, ammo_count),
|
||||
None => return Err(CommandLineError(format!("Invalid weapon id {}", weapon_id))),
|
||||
}
|
||||
}
|
||||
CommandLineCommand::RemoveWeapon(weapon_id) => {
|
||||
let weapon_type: Option<WeaponType> = FromPrimitive::from_u16(weapon_id);
|
||||
match weapon_type {
|
||||
Some(weapon_type) => {
|
||||
if !game_scene.inventory_player1.has_weapon(weapon_type) {
|
||||
return Err(CommandLineError(format!("Player does not have weapon {:?}", weapon_type)));
|
||||
}
|
||||
|
||||
game_scene.inventory_player1.remove_weapon(weapon_type);
|
||||
}
|
||||
None => return Err(CommandLineError(format!("Invalid weapon id {}", weapon_id))),
|
||||
};
|
||||
}
|
||||
CommandLineCommand::AddWeaponAmmo(ammo_count) => {
|
||||
let weapon = game_scene.inventory_player1.get_current_weapon_mut();
|
||||
match weapon {
|
||||
Some(weapon) => weapon.ammo += ammo_count,
|
||||
None => return Err(CommandLineError(format!("Player does not have an active weapon"))),
|
||||
}
|
||||
}
|
||||
CommandLineCommand::SetWeaponMaxAmmo(max_ammo) => {
|
||||
let weapon = game_scene.inventory_player1.get_current_weapon_mut();
|
||||
match weapon {
|
||||
Some(weapon) => weapon.max_ammo = max_ammo,
|
||||
None => return Err(CommandLineError(format!("Player does not have an active weapon"))),
|
||||
}
|
||||
}
|
||||
CommandLineCommand::RefillAmmo => {
|
||||
game_scene.inventory_player1.refill_all_ammo();
|
||||
}
|
||||
CommandLineCommand::RefillHP => {
|
||||
game_scene.player1.life = game_scene.player1.max_life;
|
||||
}
|
||||
CommandLineCommand::AddXP(xp_count) => {
|
||||
game_scene.inventory_player1.add_xp(xp_count, &mut game_scene.player1, state);
|
||||
}
|
||||
CommandLineCommand::RemoveXP(xp_count) => {
|
||||
game_scene.inventory_player1.take_xp(xp_count, state);
|
||||
}
|
||||
CommandLineCommand::SetMaxHP(hp_count) => {
|
||||
game_scene.player1.max_life = hp_count;
|
||||
game_scene.player1.life = hp_count;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn to_command(&self) -> String {
|
||||
match self {
|
||||
CommandLineCommand::AddItem(item_id) => format!("/add_item {}", item_id),
|
||||
CommandLineCommand::RemoveItem(item_id) => format!("/remove_item {}", item_id),
|
||||
CommandLineCommand::AddWeapon(weapon_id, ammo_count) => format!("/add_weapon {} {}", weapon_id, ammo_count),
|
||||
CommandLineCommand::RemoveWeapon(weapon_id) => format!("/remove_weapon {}", weapon_id),
|
||||
CommandLineCommand::AddWeaponAmmo(ammo_count) => format!("/add_weapon_ammo {}", ammo_count),
|
||||
CommandLineCommand::SetWeaponMaxAmmo(max_ammo_count) => format!("/set_weapon_max_ammo {}", max_ammo_count),
|
||||
CommandLineCommand::RefillAmmo => "/refill_ammo".to_string(),
|
||||
CommandLineCommand::RefillHP => "/refill_hp".to_string(),
|
||||
CommandLineCommand::AddXP(xp_count) => format!("/add_xp {}", xp_count),
|
||||
CommandLineCommand::RemoveXP(xp_count) => format!("/remove_xp {}", xp_count),
|
||||
CommandLineCommand::SetMaxHP(hp_count) => format!("/set_max_hp {}", hp_count),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn feedback_string(&self) -> String {
|
||||
match self {
|
||||
CommandLineCommand::AddItem(item_id) => format!("Added item with ID {}.", item_id),
|
||||
CommandLineCommand::RemoveItem(item_id) => format!("Removed item with ID {}.", item_id),
|
||||
CommandLineCommand::AddWeapon(weapon_id, ammo_count) => {
|
||||
format!("Added weapon with ID {} and {} ammo.", weapon_id, ammo_count)
|
||||
}
|
||||
CommandLineCommand::RemoveWeapon(weapon_id) => format!("Removed weapon with ID {}.", weapon_id),
|
||||
CommandLineCommand::AddWeaponAmmo(ammo_count) => format!("Added {} ammo to current weapon.", ammo_count),
|
||||
CommandLineCommand::SetWeaponMaxAmmo(max_ammo_count) => {
|
||||
format!("Set max ammo of current weapon to {}.", max_ammo_count)
|
||||
}
|
||||
CommandLineCommand::RefillAmmo => "Refilled ammo of all weapons.".to_string(),
|
||||
CommandLineCommand::RefillHP => "Refilled HP of player.".to_string(),
|
||||
CommandLineCommand::AddXP(xp_count) => format!("Added {} XP to current weapon.", xp_count),
|
||||
CommandLineCommand::RemoveXP(xp_count) => format!("Removed {} XP from current weapon.", xp_count),
|
||||
CommandLineCommand::SetMaxHP(hp_count) => format!("Set max HP of player to {}.", hp_count),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandLineParser {
|
||||
command_history: Vec<CommandLineCommand>,
|
||||
cursor: usize,
|
||||
pub last_feedback: String,
|
||||
pub last_feedback_color: [f32; 4],
|
||||
pub buffer: String,
|
||||
}
|
||||
|
||||
impl CommandLineParser {
|
||||
pub fn new() -> CommandLineParser {
|
||||
CommandLineParser {
|
||||
command_history: Vec::new(),
|
||||
last_feedback: "Awaiting command.".to_string(),
|
||||
last_feedback_color: [1.0, 1.0, 1.0, 1.0],
|
||||
cursor: 0,
|
||||
buffer: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, command: String) -> Option<CommandLineCommand> {
|
||||
let components = command.split_whitespace().collect::<Vec<&str>>();
|
||||
let command = CommandLineCommand::from_components(components);
|
||||
|
||||
match command {
|
||||
Some(command) => {
|
||||
self.command_history.push(command);
|
||||
self.cursor = self.command_history.len() - 1;
|
||||
|
||||
Some(command)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn traverse(&mut self, delta: i16) -> Option<&CommandLineCommand> {
|
||||
if self.command_history.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let command = self.command_history.get(self.cursor);
|
||||
|
||||
if delta == -1 && self.cursor == 0 {
|
||||
self.cursor = self.command_history.len() - 1;
|
||||
} else if delta == 1 && self.cursor == self.command_history.len() - 1 {
|
||||
self.cursor = 0;
|
||||
} else {
|
||||
self.cursor = (self.cursor as i16 + delta) as usize;
|
||||
}
|
||||
|
||||
command
|
||||
}
|
||||
}
|
|
@ -7,6 +7,10 @@ use crate::scene::game_scene::GameScene;
|
|||
use crate::scripting::tsc::text_script::TextScriptExecutionState;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
use self::command_line::CommandLineParser;
|
||||
|
||||
pub mod command_line;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum ScriptType {
|
||||
|
@ -22,6 +26,7 @@ pub struct LiveDebugger {
|
|||
flags_visible: bool,
|
||||
npc_inspector_visible: bool,
|
||||
hotkey_list_visible: bool,
|
||||
command_line_parser: CommandLineParser,
|
||||
last_stage_id: usize,
|
||||
stages: Vec<ImString>,
|
||||
selected_stage: i32,
|
||||
|
@ -40,6 +45,7 @@ impl LiveDebugger {
|
|||
flags_visible: false,
|
||||
npc_inspector_visible: false,
|
||||
hotkey_list_visible: false,
|
||||
command_line_parser: CommandLineParser::new(),
|
||||
last_stage_id: usize::MAX,
|
||||
stages: Vec::new(),
|
||||
selected_stage: -1,
|
||||
|
@ -64,6 +70,60 @@ impl LiveDebugger {
|
|||
self.selected_event = -1;
|
||||
}
|
||||
|
||||
if state.command_line {
|
||||
let width = state.screen_size.0;
|
||||
let height = 85.0;
|
||||
let x = 0.0 as f32;
|
||||
let y = state.screen_size.1 - height;
|
||||
|
||||
Window::new("Command Line")
|
||||
.position([x, y], Condition::FirstUseEver)
|
||||
.size([width, height], Condition::FirstUseEver)
|
||||
.resizable(false)
|
||||
.collapsible(false)
|
||||
.movable(false)
|
||||
.build(ui, || {
|
||||
ui.text("Command:");
|
||||
ui.same_line();
|
||||
|
||||
ui.input_text("", &mut self.command_line_parser.buffer).build();
|
||||
|
||||
if ui.is_item_active() {
|
||||
state.control_flags.set_tick_world(false);
|
||||
} else {
|
||||
state.control_flags.set_tick_world(true);
|
||||
}
|
||||
|
||||
ui.same_line();
|
||||
if ui.is_key_released(imgui::Key::Enter) || ui.button("Execute") {
|
||||
log::info!("Executing command: {}", self.command_line_parser.buffer);
|
||||
match self.command_line_parser.push(self.command_line_parser.buffer.clone()) {
|
||||
Some(mut command) => match command.execute(game_scene, state) {
|
||||
Ok(()) => {
|
||||
self.command_line_parser.last_feedback = command.feedback_string();
|
||||
self.command_line_parser.last_feedback_color = [0.0, 1.0, 0.0, 1.0];
|
||||
state.sound_manager.play_sfx(5);
|
||||
}
|
||||
Err(e) => {
|
||||
self.command_line_parser.last_feedback = e.to_string();
|
||||
self.command_line_parser.last_feedback_color = [1.0, 0.0, 0.0, 1.0];
|
||||
state.sound_manager.play_sfx(12);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
self.command_line_parser.last_feedback = "Invalid command".to_string();
|
||||
self.command_line_parser.last_feedback_color = [1.0, 0.0, 0.0, 1.0];
|
||||
state.sound_manager.play_sfx(12);
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.text_colored(
|
||||
self.command_line_parser.last_feedback_color,
|
||||
self.command_line_parser.last_feedback.clone(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if !state.debugger {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -72,7 +132,7 @@ impl LiveDebugger {
|
|||
.resizable(false)
|
||||
.collapsed(true, Condition::FirstUseEver)
|
||||
.position([5.0, 5.0], Condition::FirstUseEver)
|
||||
.size([400.0, 235.0], Condition::FirstUseEver)
|
||||
.size([400.0, 265.0], Condition::FirstUseEver)
|
||||
.build(ui, || {
|
||||
ui.text(format!(
|
||||
"Player position: ({:.1},{:.1}), velocity: ({:.1},{:.1})",
|
||||
|
@ -164,6 +224,10 @@ impl LiveDebugger {
|
|||
self.hotkey_list_visible = !self.hotkey_list_visible;
|
||||
}
|
||||
|
||||
if ui.button("Command Line") {
|
||||
state.command_line = !state.command_line;
|
||||
}
|
||||
|
||||
ui.checkbox("noclip", &mut state.settings.noclip);
|
||||
ui.same_line();
|
||||
ui.checkbox("more rust", &mut state.more_rust);
|
||||
|
@ -405,7 +469,7 @@ impl LiveDebugger {
|
|||
if self.hotkey_list_visible {
|
||||
Window::new("Hotkeys")
|
||||
.position([400.0, 5.0], Condition::FirstUseEver)
|
||||
.size([300.0, 280.0], Condition::FirstUseEver)
|
||||
.size([300.0, 300.0], Condition::FirstUseEver)
|
||||
.resizable(false)
|
||||
.build(ui, || {
|
||||
let key = vec![
|
||||
|
@ -420,6 +484,7 @@ impl LiveDebugger {
|
|||
"F10 > Debug Overlay",
|
||||
"F11 > Toggle FPS Counter",
|
||||
"F12 > Toggle Debugger",
|
||||
"` > Toggle Command Line",
|
||||
"Ctrl + F3 > Reload Sound Manager",
|
||||
"Ctrl + S > Quick Save",
|
||||
];
|
||||
|
@ -433,6 +498,7 @@ impl LiveDebugger {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut remove = -1;
|
||||
for (idx, (_, title, contents)) in self.text_windows.iter().enumerate() {
|
||||
let mut opened = true;
|
|
@ -2358,6 +2358,7 @@ impl Scene for GameScene {
|
|||
ScanCode::F10 => state.settings.debug_outlines = !state.settings.debug_outlines,
|
||||
ScanCode::F11 => state.settings.fps_counter = !state.settings.fps_counter,
|
||||
ScanCode::F12 => state.debugger = !state.debugger,
|
||||
ScanCode::Grave => state.command_line = !state.command_line,
|
||||
_ => {}
|
||||
};
|
||||
|
||||
|
|
|
@ -314,6 +314,7 @@ pub struct SharedGameState {
|
|||
pub stages: Vec<StageData>,
|
||||
pub frame_time: f64,
|
||||
pub debugger: bool,
|
||||
pub command_line: bool,
|
||||
pub scale: f32,
|
||||
pub canvas_size: (f32, f32),
|
||||
pub screen_size: (f32, f32),
|
||||
|
@ -459,6 +460,7 @@ impl SharedGameState {
|
|||
stages: Vec::with_capacity(96),
|
||||
frame_time: 0.0,
|
||||
debugger: false,
|
||||
command_line: false,
|
||||
scale: 2.0,
|
||||
screen_size: (640.0, 480.0),
|
||||
canvas_size: (320.0, 240.0),
|
||||
|
|
Loading…
Reference in New Issue