diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 614d86bb72..69398bf91f 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -255,9 +255,11 @@ lazy_static! { }) }; - static ref KITS: Vec<String> = { + pub static ref KITS: Vec<String> = { if let Ok(kits) = KitManifest::load(KIT_MANIFEST_PATH) { - kits.read().0.keys().cloned().collect() + let mut kits = kits.read().0.keys().cloned().collect::<Vec<String>>(); + kits.sort(); + kits } else { Vec::new() } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 3554d12ac9..a214a4baf5 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -472,7 +472,16 @@ fn handle_give_item( if let Ok(item) = Item::new_from_asset(&item_name.replace('/', ".").replace("\\", ".")) { let mut item: Item = item; let mut res = Ok(()); - if let Ok(()) = item.set_amount(give_amount.min(2000)) { + + const MAX_GIVE_AMOUNT: u32 = 2000; + // Cap give_amount for non-stackable items + let give_amount = if item.is_stackable() { + give_amount + } else { + give_amount.min(MAX_GIVE_AMOUNT) + }; + + if let Ok(()) = item.set_amount(give_amount) { server .state .ecs() diff --git a/voxygen/egui/Cargo.toml b/voxygen/egui/Cargo.toml index 6c59194d8c..29b246f246 100644 --- a/voxygen/egui/Cargo.toml +++ b/voxygen/egui/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" version = "0.9.0" [features] -use-dyn-lib = ["lazy_static", "voxygen-dynlib"] +use-dyn-lib = ["voxygen-dynlib"] be-dyn-lib = [] [dependencies] @@ -13,8 +13,7 @@ client = {package = "veloren-client", path = "../../client"} common = {package = "veloren-common", path = "../../common"} egui = "0.12" egui_winit_platform = "0.8" +lazy_static = "1.4.0" voxygen-dynlib = {package = "veloren-voxygen-dynlib", path = "../dynlib", optional = true} -# Hot Reloading -lazy_static = {version = "1.4.0", optional = true} diff --git a/voxygen/egui/src/admin.rs b/voxygen/egui/src/admin.rs new file mode 100644 index 0000000000..73b365321b --- /dev/null +++ b/voxygen/egui/src/admin.rs @@ -0,0 +1,94 @@ +use crate::{AdminCommandState, EguiAction, EguiActions, EguiWindows}; +use common::cmd::ChatCommand; +use egui::{CollapsingHeader, CtxRef, Resize, Slider, Ui, Vec2, Window}; +use lazy_static::lazy_static; + +lazy_static! { + static ref ITEM_SPECS: Vec<String> = { + let mut item_specs = common::cmd::ITEM_SPECS + .iter() + .map(|item_desc| item_desc.replace("common.items.", "")) + .collect::<Vec<String>>(); + item_specs.sort(); + item_specs + }; +} + +pub fn draw_admin_commands_window( + ctx: &CtxRef, + state: &mut AdminCommandState, + windows: &mut EguiWindows, + egui_actions: &mut EguiActions, +) { + Window::new("Admin Commands") + .open(&mut windows.admin_commands) + .default_width(400.0) + .default_height(600.0) + .show(ctx, |ui| { + ui.spacing_mut().item_spacing = Vec2::new(10.0, 10.0); + ui.vertical(|ui| { + CollapsingHeader::new("Give Items") + .default_open(true) + .show(ui, |ui| { + draw_give_items(ui, state, egui_actions); + }); + CollapsingHeader::new("Kits") + .default_open(false) + .show(ui, |ui| { + draw_kits(ui, state, egui_actions); + }); + }); + }); +} + +fn draw_kits(ui: &mut Ui, state: &mut AdminCommandState, egui_actions: &mut EguiActions) { + ui.vertical(|ui| { + if ui.button("Give Kit").clicked() { + egui_actions.actions.push(EguiAction::ChatCommand { + cmd: ChatCommand::Kit, + args: vec![common::cmd::KITS[state.kits_selected_idx].clone()], + }); + }; + crate::widgets::filterable_list(ui, &common::cmd::KITS, "", &mut state.kits_selected_idx) + }); +} + +fn draw_give_items(ui: &mut Ui, state: &mut AdminCommandState, egui_actions: &mut EguiActions) { + ui.spacing_mut().window_padding = Vec2::new(10.0, 10.0); + Resize::default() + .default_size([400.0, 200.0]) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.add( + Slider::new(&mut state.give_item_qty, 1..=100000) + .logarithmic(true) + .clamp_to_range(true) + .text("Qty"), + ); + if ui.button("Give Items").clicked() { + egui_actions.actions.push(EguiAction::ChatCommand { + cmd: ChatCommand::GiveItem, + args: vec![ + format!( + "common.items.{}", + ITEM_SPECS[state.give_item_selected_idx].clone() + ), + format!("{}", state.give_item_qty), + ], + }); + }; + }); + ui.horizontal(|ui| { + ui.label("Filter:"); + + ui.text_edit_singleline(&mut state.give_item_search_text); + }); + + crate::widgets::filterable_list( + ui, + &ITEM_SPECS, + &state.give_item_search_text, + &mut state.give_item_selected_idx, + ); + }); +} diff --git a/voxygen/egui/src/character_states.rs b/voxygen/egui/src/character_states.rs index 39a8660119..d27a8975cc 100644 --- a/voxygen/egui/src/character_states.rs +++ b/voxygen/egui/src/character_states.rs @@ -1,4 +1,4 @@ -use crate::{two_col_row, SelectedEntityInfo}; +use crate::{widgets::two_col_row, SelectedEntityInfo}; use common::{ comp::CharacterState, states::{charged_melee, combo_melee, dash_melee, leap_melee}, diff --git a/voxygen/egui/src/lib.rs b/voxygen/egui/src/lib.rs index 7acf8a3f0b..74636be28e 100644 --- a/voxygen/egui/src/lib.rs +++ b/voxygen/egui/src/lib.rs @@ -3,7 +3,9 @@ #[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))] compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once"); +mod admin; mod character_states; +mod widgets; use client::{Client, Join, World, WorldExt}; use common::{ @@ -14,17 +16,17 @@ use core::mem; use egui::{ plot::{Plot, Value}, widgets::plot::Curve, - CollapsingHeader, Color32, Grid, Label, Pos2, ScrollArea, Slider, Ui, Window, + CollapsingHeader, Color32, Grid, Pos2, ScrollArea, Slider, Ui, Window, }; -fn two_col_row(ui: &mut Ui, label: impl Into<Label>, content: impl Into<Label>) { - ui.label(label); - ui.label(content); - ui.end_row(); -} - -use crate::character_states::draw_char_state_group; -use common::comp::{aura::AuraKind::Buff, Body, Fluid}; +use crate::{ + admin::draw_admin_commands_window, character_states::draw_char_state_group, + widgets::two_col_row, +}; +use common::{ + cmd::ChatCommand, + comp::{aura::AuraKind::Buff, Body, Fluid}, +}; use egui_winit_platform::Platform; use std::time::Duration; #[cfg(feature = "use-dyn-lib")] @@ -58,6 +60,24 @@ impl SelectedEntityInfo { } } +pub struct AdminCommandState { + give_item_qty: u32, + give_item_selected_idx: usize, + give_item_search_text: String, + kits_selected_idx: usize, +} + +impl AdminCommandState { + fn new() -> Self { + Self { + give_item_qty: 1, + give_item_selected_idx: 0, + give_item_search_text: String::new(), + kits_selected_idx: 0, + } + } +} + pub struct EguiDebugInfo { pub frame_time: Duration, pub ping_ms: f64, @@ -65,13 +85,16 @@ pub struct EguiDebugInfo { pub struct EguiInnerState { selected_entity_info: Option<SelectedEntityInfo>, + admin_command_state: AdminCommandState, max_entity_distance: f32, selected_entity_cylinder_height: f32, frame_times: Vec<f32>, + windows: EguiWindows, } #[derive(Clone, Default)] pub struct EguiWindows { + admin_commands: bool, egui_inspection: bool, egui_settings: bool, egui_memory: bool, @@ -82,15 +105,17 @@ pub struct EguiWindows { impl Default for EguiInnerState { fn default() -> Self { Self { + admin_command_state: AdminCommandState::new(), selected_entity_info: None, max_entity_distance: 100000.0, selected_entity_cylinder_height: 10.0, frame_times: Vec::new(), + windows: EguiWindows::default(), } } } -pub enum DebugShapeAction { +pub enum EguiDebugShapeAction { AddCylinder { radius: f32, height: f32, @@ -103,9 +128,14 @@ pub enum DebugShapeAction { }, } +pub enum EguiAction { + ChatCommand { cmd: ChatCommand, args: Vec<String> }, + DebugShape(EguiDebugShapeAction), +} + #[derive(Default)] pub struct EguiActions { - pub actions: Vec<DebugShapeAction>, + pub actions: Vec<EguiAction>, } #[cfg(feature = "use-dyn-lib")] @@ -114,7 +144,6 @@ pub fn init() { lazy_static::initialize(&LIB); } pub fn maintain( platform: &mut Platform, egui_state: &mut EguiInnerState, - egui_windows: &mut EguiWindows, client: &Client, debug_info: Option<EguiDebugInfo>, added_cylinder_shape_id: Option<u64>, @@ -124,7 +153,6 @@ pub fn maintain( maintain_egui_inner( platform, egui_state, - egui_windows, client, debug_info, added_cylinder_shape_id, @@ -141,7 +169,6 @@ pub fn maintain( fn( &mut Platform, &mut EguiInnerState, - &mut EguiWindows, &Client, Option<EguiDebugInfo>, Option<u64>, @@ -160,7 +187,6 @@ pub fn maintain( maintain_fn( platform, egui_state, - egui_windows, client, debug_info, added_cylinder_shape_id, @@ -172,7 +198,6 @@ pub fn maintain( pub fn maintain_egui_inner( platform: &mut Platform, egui_state: &mut EguiInnerState, - egui_windows: &mut EguiWindows, client: &Client, debug_info: Option<EguiDebugInfo>, added_cylinder_shape_id: Option<u64>, @@ -184,6 +209,7 @@ pub fn maintain_egui_inner( let mut previous_selected_entity: Option<SelectedEntityInfo> = None; let mut max_entity_distance = egui_state.max_entity_distance; let mut selected_entity_cylinder_height = egui_state.selected_entity_cylinder_height; + let mut windows = egui_state.windows.clone(); // If a debug cylinder was added in the last frame, store it against the // selected entity @@ -216,8 +242,9 @@ pub fn maintain_egui_inner( }); ui.group(|ui| { ui.vertical(|ui| { - ui.checkbox(&mut egui_windows.ecs_entities, "ECS Entities"); - ui.checkbox(&mut egui_windows.frame_time, "Frame Time"); + ui.checkbox(&mut windows.admin_commands, "Admin Commands"); + ui.checkbox(&mut windows.ecs_entities, "ECS Entities"); + ui.checkbox(&mut windows.frame_time, "Frame Time"); }); }); @@ -225,36 +252,36 @@ pub fn maintain_egui_inner( ui.vertical(|ui| { ui.label("Show EGUI Windows"); ui.horizontal(|ui| { - ui.checkbox(&mut egui_windows.egui_inspection, "🔍 Inspection"); - ui.checkbox(&mut egui_windows.egui_settings, "🔧 Settings"); - ui.checkbox(&mut egui_windows.egui_memory, "📝 Memory"); + ui.checkbox(&mut windows.egui_inspection, "🔍 Inspection"); + ui.checkbox(&mut windows.egui_settings, "🔧 Settings"); + ui.checkbox(&mut windows.egui_memory, "📝 Memory"); }) }) }); }); Window::new("🔧 Settings") - .open(&mut egui_windows.egui_settings) + .open(&mut windows.egui_settings) .scroll(true) .show(ctx, |ui| { ctx.settings_ui(ui); }); Window::new("🔍 Inspection") - .open(&mut egui_windows.egui_inspection) + .open(&mut windows.egui_inspection) .scroll(true) .show(ctx, |ui| { ctx.inspection_ui(ui); }); Window::new("📝 Memory") - .open(&mut egui_windows.egui_memory) + .open(&mut windows.egui_memory) .resizable(false) .show(ctx, |ui| { ctx.memory_ui(ui); }); Window::new("Frame Time") - .open(&mut egui_windows.frame_time) + .open(&mut windows.frame_time) .default_width(200.0) .default_height(200.0) .show(ctx, |ui| { @@ -268,14 +295,14 @@ pub fn maintain_egui_inner( ui.add(plot); }); - if egui_windows.ecs_entities { + if windows.ecs_entities { let ecs = client.state().ecs(); let positions = client.state().ecs().read_storage::<comp::Pos>(); let client_pos = positions.get(client.entity()); egui::Window::new("ECS Entities") - .open(&mut egui_windows.ecs_entities) + .open(&mut windows.ecs_entities) .default_width(500.0) .default_height(500.0) .show(ctx, |ui| { @@ -333,10 +360,12 @@ pub fn maintain_egui_inner( mem::take(&mut egui_state.selected_entity_info); if pos.is_some() { - egui_actions.actions.push(DebugShapeAction::AddCylinder { - radius: 1.0, - height: egui_state.selected_entity_cylinder_height, - }); + egui_actions.actions.push(EguiAction::DebugShape( + EguiDebugShapeAction::AddCylinder { + radius: 1.0, + height: egui_state.selected_entity_cylinder_height, + }, + )); } egui_state.selected_entity_info = Some(SelectedEntityInfo::new(entity.id())); @@ -403,11 +432,20 @@ pub fn maintain_egui_inner( } } + draw_admin_commands_window( + ctx, + &mut egui_state.admin_command_state, + &mut windows, + &mut egui_actions, + ); + if let Some(previous) = previous_selected_entity { if let Some(debug_shape_id) = previous.debug_shape_id { egui_actions .actions - .push(DebugShapeAction::RemoveShape(debug_shape_id)); + .push(EguiAction::DebugShape(EguiDebugShapeAction::RemoveShape( + debug_shape_id, + ))); } }; @@ -416,19 +454,22 @@ pub fn maintain_egui_inner( if (egui_state.selected_entity_cylinder_height - selected_entity_cylinder_height).abs() > f32::EPSILON { - egui_actions - .actions - .push(DebugShapeAction::RemoveShape(debug_shape_id)); - egui_actions.actions.push(DebugShapeAction::AddCylinder { - radius: 1.0, - height: selected_entity_cylinder_height, - }); + egui_actions.actions.push(EguiAction::DebugShape( + EguiDebugShapeAction::RemoveShape(debug_shape_id), + )); + egui_actions.actions.push(EguiAction::DebugShape( + EguiDebugShapeAction::AddCylinder { + radius: 1.0, + height: selected_entity_cylinder_height, + }, + )); } } }; egui_state.max_entity_distance = max_entity_distance; egui_state.selected_entity_cylinder_height = selected_entity_cylinder_height; + egui_state.windows = windows; egui_actions } @@ -481,11 +522,13 @@ fn selected_entity_window( { if let Some(pos) = pos { if let Some(shape_id) = selected_entity_info.debug_shape_id { - egui_actions.actions.push(DebugShapeAction::SetPosAndColor { - id: shape_id, - color: [1.0, 1.0, 0.0, 0.5], - pos: [pos.0.x, pos.0.y, pos.0.z + 2.0, 0.0], - }); + egui_actions.actions.push(EguiAction::DebugShape( + EguiDebugShapeAction::SetPosAndColor { + id: shape_id, + color: [1.0, 1.0, 0.0, 0.5], + pos: [pos.0.x, pos.0.y, pos.0.z + 2.0, 0.0], + }, + )); } }; diff --git a/voxygen/egui/src/widgets.rs b/voxygen/egui/src/widgets.rs new file mode 100644 index 0000000000..8cde109367 --- /dev/null +++ b/voxygen/egui/src/widgets.rs @@ -0,0 +1,34 @@ +use egui::{Label, ScrollArea, Ui, Vec2}; + +pub(crate) fn filterable_list( + ui: &mut Ui, + list_items: &[String], + search_text: &str, + selected_index: &mut usize, +) { + let scroll_area = ScrollArea::auto_sized(); + scroll_area.show(ui, |ui| { + ui.spacing_mut().item_spacing = Vec2::new(0.0, 2.0); + let search_text = search_text.to_lowercase(); + for (i, list_item) in list_items.iter().enumerate().filter_map(|(i, list_item)| { + if search_text.is_empty() || list_item.to_lowercase().contains(&search_text) { + Some((i, list_item)) + } else { + None + } + }) { + if ui + .selectable_label(i == *selected_index, list_item) + .clicked() + { + *selected_index = i; + }; + } + }); +} + +pub(crate) fn two_col_row(ui: &mut Ui, label: impl Into<Label>, content: impl Into<Label>) { + ui.label(label); + ui.label(content); + ui.end_row(); +} diff --git a/voxygen/src/run.rs b/voxygen/src/run.rs index 2a3627820e..3549e48128 100644 --- a/voxygen/src/run.rs +++ b/voxygen/src/run.rs @@ -32,7 +32,13 @@ pub fn run(mut global_state: GlobalState, event_loop: EventLoop) { *control_flow = winit::event_loop::ControlFlow::Poll; #[cfg(feature = "egui-ui")] - global_state.egui_state.platform.handle_event(&event); + { + global_state.egui_state.platform.handle_event(&event); + if global_state.egui_state.platform.captures_event(&event) { + return; + } + } + // Get events for the ui. if let Some(event) = ui::Event::try_from(&event, global_state.window.window()) { global_state.window.send_event(Event::Ui(event)); diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 6e39ff8504..3907d616c9 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1091,7 +1091,7 @@ impl PlayState for SessionState { #[cfg(feature = "egui-ui")] if global_state.settings.interface.egui_enabled() { global_state.egui_state.maintain( - &self.client.borrow(), + &mut self.client.borrow_mut(), &mut self.scene, debug_info.map(|debug_info| EguiDebugInfo { frame_time: debug_info.frame_time, diff --git a/voxygen/src/ui/egui/mod.rs b/voxygen/src/ui/egui/mod.rs index 47dab5aa33..e98db48f5b 100644 --- a/voxygen/src/ui/egui/mod.rs +++ b/voxygen/src/ui/egui/mod.rs @@ -5,12 +5,11 @@ use crate::{ use client::Client; use egui::FontDefinitions; use egui_winit_platform::{Platform, PlatformDescriptor}; -use voxygen_egui::{DebugShapeAction, EguiDebugInfo, EguiInnerState, EguiWindows}; +use voxygen_egui::{EguiAction, EguiDebugInfo, EguiDebugShapeAction, EguiInnerState}; pub struct EguiState { pub platform: Platform, egui_inner_state: EguiInnerState, - egui_windows: EguiWindows, new_debug_shape_id: Option<u64>, } @@ -27,43 +26,48 @@ impl EguiState { Self { platform, egui_inner_state: EguiInnerState::default(), - egui_windows: EguiWindows::default(), new_debug_shape_id: None, } } pub fn maintain( &mut self, - client: &Client, + client: &mut Client, scene: &mut Scene, debug_info: Option<EguiDebugInfo>, ) { let egui_actions = voxygen_egui::maintain( &mut self.platform, &mut self.egui_inner_state, - &mut self.egui_windows, client, debug_info, self.new_debug_shape_id.take(), ); - egui_actions.actions.iter().for_each(|action| match action { - DebugShapeAction::AddCylinder { height, radius } => { - let shape_id = scene.debug.add_shape(DebugShape::Cylinder { - height: *height, - radius: *radius, - }); - self.new_debug_shape_id = Some(shape_id.0); - }, - DebugShapeAction::RemoveShape(debug_shape_id) => { - scene.debug.remove_shape(DebugShapeId(*debug_shape_id)); - }, - DebugShapeAction::SetPosAndColor { id, pos, color } => { - let identity_ori = [0.0, 0.0, 0.0, 1.0]; - scene - .debug - .set_context(DebugShapeId(*id), *pos, *color, identity_ori); - }, - }) + egui_actions + .actions + .into_iter() + .for_each(|action| match action { + EguiAction::ChatCommand { cmd, args } => { + client.send_command(cmd.keyword().into(), args); + }, + EguiAction::DebugShape(debug_shape_action) => match debug_shape_action { + EguiDebugShapeAction::AddCylinder { height, radius } => { + let shape_id = scene + .debug + .add_shape(DebugShape::Cylinder { height, radius }); + self.new_debug_shape_id = Some(shape_id.0); + }, + EguiDebugShapeAction::RemoveShape(debug_shape_id) => { + scene.debug.remove_shape(DebugShapeId(debug_shape_id)); + }, + EguiDebugShapeAction::SetPosAndColor { id, pos, color } => { + let identity_ori = [0.0, 0.0, 0.0, 1.0]; + scene + .debug + .set_context(DebugShapeId(id), pos, color, identity_ori); + }, + }, + }) } }