1#![feature(stmt_expr_attributes)]
2
3#[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))]
4compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once");
5
6mod admin;
7mod character_states;
8mod experimental_shaders;
9mod widgets;
10
11use client::{Client, Join, LendJoin, World, WorldExt};
12use common::{
13 cmd::ServerChatCommand,
14 comp,
15 comp::{Poise, PoiseState, inventory::item::armor::Friction},
16 resources::Time,
17};
18use core::mem;
19use egui::{CollapsingHeader, Color32, Context, Grid, Pos2, ScrollArea, Slider, Ui, Window};
20use egui_plot::{Line, Plot, PlotPoints};
21
22use crate::{
23 admin::draw_admin_commands_window, character_states::draw_char_state_group,
24 experimental_shaders::draw_experimental_shaders_window, widgets::two_col_row,
25};
26use common::comp::{
27 Body, Fluid,
28 aura::AuraKind::{Buff, ForcePvP, FriendlyFire},
29};
30use egui_winit::State as WinitState;
31use std::time::Duration;
32use winit::window::Window as WinitWindow;
33#[cfg(feature = "use-dyn-lib")]
34use {
35 common_dynlib::LoadedLib, lazy_static::lazy_static, std::ffi::CStr, std::sync::Arc,
36 std::sync::Mutex,
37};
38
39#[cfg(feature = "use-dyn-lib")]
40lazy_static! {
41 static ref LIB: Arc<Mutex<Option<LoadedLib>>> =
42 common_dynlib::init("veloren-voxygen-egui", "egui", &[]);
43}
44
45#[cfg(feature = "use-dyn-lib")]
46const MAINTAIN_EGUI_FN: &[u8] = b"maintain_egui_inner\0";
47
48pub struct SelectedEntityInfo {
49 entity_id: u32,
50 debug_shape_id: Option<u64>,
51 character_state_history: Vec<String>,
52}
53
54impl SelectedEntityInfo {
55 fn new(entity_id: u32) -> Self {
56 Self {
57 entity_id,
58 debug_shape_id: None,
59 character_state_history: Vec::new(),
60 }
61 }
62}
63
64pub struct AdminCommandState {
65 give_item_qty: u32,
66 give_item_selected_idx: usize,
67 give_item_search_text: String,
68 kits_selected_idx: usize,
69 spawn_entity_qty: u32,
70 spawn_entity_selected_idx: usize,
71 spawn_entity_search_text: String,
72}
73
74impl AdminCommandState {
75 fn new() -> Self {
76 Self {
77 give_item_qty: 1,
78 give_item_selected_idx: 0,
79 give_item_search_text: String::new(),
80 kits_selected_idx: 0,
81 spawn_entity_qty: 1,
82 spawn_entity_selected_idx: 0,
83 spawn_entity_search_text: String::new(),
84 }
85 }
86}
87
88pub struct EguiDebugInfo {
89 pub frame_time: Duration,
90 pub ping_ms: f64,
91}
92
93pub struct EguiInnerState {
94 selected_entity_info: Option<SelectedEntityInfo>,
95 admin_command_state: AdminCommandState,
96 max_entity_distance: f32,
97 selected_entity_cylinder_height: f32,
98 frame_times: Vec<f32>,
99 windows: EguiWindows,
100 debug_vectors_enabled: bool,
101}
102
103#[derive(Clone, Default)]
104pub struct EguiWindows {
105 admin_commands: bool,
106 egui_inspection: bool,
107 egui_settings: bool,
108 egui_memory: bool,
109 frame_time: bool,
110 ecs_entities: bool,
111 experimental_shaders: bool,
112}
113
114impl Default for EguiInnerState {
115 fn default() -> Self {
116 Self {
117 admin_command_state: AdminCommandState::new(),
118 selected_entity_info: None,
119 max_entity_distance: 100000.0,
120 selected_entity_cylinder_height: 10.0,
121 frame_times: Vec::new(),
122 windows: EguiWindows::default(),
123 debug_vectors_enabled: false,
124 }
125 }
126}
127
128pub enum EguiDebugShapeAction {
129 AddCylinder {
130 radius: f32,
131 height: f32,
132 },
133 RemoveShape(u64),
134 SetPosAndColor {
135 id: u64,
136 pos: [f32; 4],
137 color: [f32; 4],
138 },
139}
140
141pub enum EguiAction {
142 ChatCommand {
143 cmd: ServerChatCommand,
144 args: Vec<String>,
145 },
146 DebugShape(EguiDebugShapeAction),
147 SetExperimentalShader(String, bool),
148 SetShowDebugVector(bool),
149}
150
151#[derive(Default)]
152pub struct EguiActions {
153 pub actions: Vec<EguiAction>,
154}
155
156#[cfg(feature = "use-dyn-lib")]
157pub fn init() { lazy_static::initialize(&LIB); }
158
159pub fn maintain(
160 winit_state: &mut WinitState,
161 egui_state: &mut EguiInnerState,
162 client: &Client,
163 winit_window: &WinitWindow,
164 debug_info: Option<EguiDebugInfo>,
165 added_cylinder_shape_id: Option<u64>,
166 experimental_shaders: Vec<(String, bool)>,
167) -> EguiActions {
168 #[cfg(not(feature = "use-dyn-lib"))]
169 {
170 maintain_egui_inner(
171 winit_state,
172 egui_state,
173 client,
174 winit_window,
175 debug_info,
176 added_cylinder_shape_id,
177 experimental_shaders,
178 )
179 }
180
181 #[cfg(feature = "use-dyn-lib")]
182 {
183 let lock = LIB.lock().unwrap();
184 let lib = &lock.as_ref().unwrap().lib;
185
186 let maintain_fn: common_dynlib::Symbol<
187 fn(
188 &mut WinitState,
189 &mut EguiInnerState,
190 &Client,
191 &WinitWindow,
192 Option<EguiDebugInfo>,
193 Option<u64>,
194 Vec<(String, bool)>,
195 ) -> EguiActions,
196 > = unsafe { lib.get(MAINTAIN_EGUI_FN) }.unwrap_or_else(|e| {
197 panic!(
198 "Trying to use: {} but had error: {:?}",
199 CStr::from_bytes_with_nul(MAINTAIN_EGUI_FN)
200 .map(CStr::to_str)
201 .unwrap()
202 .unwrap(),
203 e
204 )
205 });
206
207 maintain_fn(
208 winit_state,
209 egui_state,
210 client,
211 winit_window,
212 debug_info,
213 added_cylinder_shape_id,
214 experimental_shaders,
215 )
216 }
217}
218
219#[cfg_attr(feature = "be-dyn-lib", unsafe(export_name = "maintain_egui_inner"))]
220pub fn maintain_egui_inner(
221 winit_state: &mut WinitState,
222 egui_state: &mut EguiInnerState,
223 client: &Client,
224 winit_window: &WinitWindow,
225 debug_info: Option<EguiDebugInfo>,
226 added_cylinder_shape_id: Option<u64>,
227 experimental_shaders: Vec<(String, bool)>,
228) -> EguiActions {
229 let raw_input = winit_state.take_egui_input(winit_window);
230 let ctx = winit_state.egui_ctx();
231 ctx.begin_pass(raw_input);
232
233 let mut egui_actions = EguiActions::default();
234 let mut previous_selected_entity: Option<SelectedEntityInfo> = None;
235 let mut max_entity_distance = egui_state.max_entity_distance;
236 let mut selected_entity_cylinder_height = egui_state.selected_entity_cylinder_height;
237 let mut windows = egui_state.windows.clone();
238 let mut debug_vectors_enabled_mut = egui_state.debug_vectors_enabled;
239
240 if let Some(shape_id) = added_cylinder_shape_id
243 && let Some(selected_entity) = &mut egui_state.selected_entity_info
244 {
245 selected_entity.debug_shape_id = Some(shape_id);
246 }
247
248 if let Some(debug_info) = debug_info.as_ref() {
249 egui_state
250 .frame_times
251 .push(debug_info.frame_time.as_nanos() as f32);
252 if egui_state.frame_times.len() > 250 {
253 egui_state.frame_times.remove(0);
254 }
255 };
256
257 let start_pos = Pos2 { x: 300.0, y: 0.0 };
258 Window::new("Debug Control")
259 .default_pos(start_pos)
260 .default_width(200.0)
261 .default_height(200.0)
262 .show(ctx, |ui| {
263 ui.horizontal(|ui| {
264 ui.label(format!(
265 "Ping: {:.1}ms",
266 debug_info.as_ref().map_or(0.0, |x| x.ping_ms)
267 ));
268 });
269 ui.group(|ui| {
270 ui.vertical(|ui| {
271 ui.checkbox(&mut windows.admin_commands, "Admin Commands");
272 ui.checkbox(&mut windows.ecs_entities, "ECS Entities");
273 ui.checkbox(&mut windows.frame_time, "Frame Time");
274 ui.checkbox(&mut windows.experimental_shaders, "Experimental Shaders");
275 ui.checkbox(&mut debug_vectors_enabled_mut, "Show Debug Vectors");
276 });
277 });
278
279 ui.group(|ui| {
280 ui.vertical(|ui| {
281 ui.label("Show EGUI Windows");
282 ui.horizontal(|ui| {
283 ui.checkbox(&mut windows.egui_inspection, "🔍 Inspection");
284 ui.checkbox(&mut windows.egui_settings, "🔧 Settings");
285 ui.checkbox(&mut windows.egui_memory, "📝 Memory");
286 })
287 })
288 });
289 });
290
291 Window::new("🔧 Settings")
292 .open(&mut windows.egui_settings)
293 .vscroll(true)
294 .show(ctx, |ui| {
295 ctx.settings_ui(ui);
296 });
297 Window::new("🔍 Inspection")
298 .open(&mut windows.egui_inspection)
299 .vscroll(true)
300 .show(ctx, |ui| {
301 ctx.inspection_ui(ui);
302 });
303
304 Window::new("📝 Memory")
305 .open(&mut windows.egui_memory)
306 .resizable(false)
307 .show(ctx, |ui| {
308 ctx.memory_ui(ui);
309 });
310
311 Window::new("Frame Time")
312 .open(&mut windows.frame_time)
313 .default_width(200.0)
314 .default_height(200.0)
315 .show(ctx, |ui| {
316 Plot::new("Frame Time").show(ui, |plot_ui| {
317 plot_ui.line(Line::new(
318 "Frame Time",
319 PlotPoints::from_iter(
320 egui_state
321 .frame_times
322 .iter()
323 .enumerate()
324 .map(|(i, x)| [i as f64, *x as f64]),
325 ),
326 ))
327 });
328 });
329
330 if windows.ecs_entities {
331 let ecs = client.state().ecs();
332
333 let positions = client.state().ecs().read_storage::<comp::Pos>();
334 let client_pos = positions.get(client.entity());
335
336 Window::new("ECS Entities")
337 .open(&mut windows.ecs_entities)
338 .default_width(500.0)
339 .default_height(500.0)
340 .show(ctx, |ui| {
341 ui.label(format!("Entity count: {}", &ecs.entities().join().count()));
342 ui.add(
343 Slider::new(&mut max_entity_distance, 1.0..=100000.0)
344 .logarithmic(true)
345 .clamping(egui::SliderClamping::Always)
346 .text("Max entity distance"),
347 );
348
349 ui.add(
350 Slider::new(&mut selected_entity_cylinder_height, 0.1..=100.0)
351 .logarithmic(true)
352 .clamping(egui::SliderClamping::Always)
353 .text("Cylinder height"),
354 );
355
356 let scroll_area = ScrollArea::vertical().max_height(800.0);
357 scroll_area.show(ui, |ui| {
358 Grid::new("entities_grid")
359 .spacing([40.0, 4.0])
360 .max_col_width(300.0)
361 .striped(true)
362 .show(ui, |ui| {
363 ui.label("-");
364 ui.label("ID");
365 ui.label("Pos");
366 ui.label("Vel");
367 ui.label("Name");
368 ui.label("Body");
369 ui.label("Poise");
370 ui.label("Character State");
371 ui.label("Character Activity");
372 ui.end_row();
373 for (
374 entity,
375 body,
376 stats,
377 pos,
378 _ori,
379 vel,
380 poise,
381 character_state,
382 character_activity,
383 ) in (
384 &ecs.entities(),
385 ecs.read_storage::<Body>().maybe(),
386 ecs.read_storage::<comp::Stats>().maybe(),
387 ecs.read_storage::<comp::Pos>().maybe(),
388 ecs.read_storage::<comp::Ori>().maybe(),
389 ecs.read_storage::<comp::Vel>().maybe(),
390 ecs.read_storage::<Poise>().maybe(),
391 ecs.read_storage::<comp::CharacterState>().maybe(),
392 ecs.read_storage::<comp::CharacterActivity>().maybe(),
393 )
394 .join()
395 .filter(
396 |(_, _, _, pos, _, _, _, _, _)| {
397 client_pos.is_none_or(|client_pos| {
398 pos.map_or(0.0, |pos| {
399 pos.0.distance_squared(client_pos.0)
400 }) < max_entity_distance
401 })
402 },
403 )
404 {
405 if ui.button("View").clicked() {
406 previous_selected_entity =
407 mem::take(&mut egui_state.selected_entity_info);
408
409 if pos.is_some() {
410 egui_actions.actions.push(EguiAction::DebugShape(
411 EguiDebugShapeAction::AddCylinder {
412 radius: 1.0,
413 height: egui_state.selected_entity_cylinder_height,
414 },
415 ));
416 }
417 egui_state.selected_entity_info =
418 Some(SelectedEntityInfo::new(entity.id()));
419 }
420
421 ui.label(format!("{}", entity.id()));
422
423 if let Some(pos) = pos {
424 ui.label(format!(
425 "{:.0},{:.0},{:.0}",
426 pos.0.x, pos.0.y, pos.0.z
427 ));
428 } else {
429 ui.label("-");
430 }
431
432 if let Some(vel) = vel {
433 ui.label(format!("{:.1}u/s", vel.0.magnitude()));
434 } else {
435 ui.label("-");
436 }
437 if let Some(stats) = stats {
438 ui.label(format!("{:?}", &stats.name));
439 } else {
440 ui.label("-");
441 }
442 if let Some(body) = body {
443 ui.label(body_species(body));
444 } else {
445 ui.label("-");
446 }
447
448 if let Some(poise) = poise {
449 poise_state_label(ui, poise);
450 } else {
451 ui.label("-");
452 }
453
454 if let Some(character_state) = character_state {
455 ui.label(character_state.to_string());
456 } else {
457 ui.label("-");
458 }
459
460 if let Some(character_activity) = character_activity {
461 ui.label(format!("{:?}", character_activity));
462 } else {
463 ui.label("-");
464 }
465
466 ui.end_row();
467 }
468 });
469 let margin = ui.visuals().clip_rect_margin;
470
471 let current_scroll = ui.clip_rect().top() - ui.min_rect().top() + margin;
472 let max_scroll =
473 ui.min_rect().height() - ui.clip_rect().height() + 2.0 * margin;
474 (current_scroll, max_scroll)
475 });
476 });
477 if let Some(selected_entity_info) = &mut egui_state.selected_entity_info {
478 let selected_entity = ecs.entities().entity(selected_entity_info.entity_id);
479 if !selected_entity.r#gen().is_alive() {
480 previous_selected_entity = mem::take(&mut egui_state.selected_entity_info);
481 } else {
482 selected_entity_window(ctx, ecs, selected_entity_info, &mut egui_actions);
483 }
484 }
485 }
486
487 draw_admin_commands_window(
488 ctx,
489 &mut egui_state.admin_command_state,
490 &mut windows.admin_commands,
491 &mut egui_actions,
492 );
493
494 draw_experimental_shaders_window(
495 ctx,
496 &mut windows.experimental_shaders,
497 &mut egui_actions,
498 &experimental_shaders,
499 );
500
501 if let Some(previous) = previous_selected_entity
502 && let Some(debug_shape_id) = previous.debug_shape_id
503 {
504 egui_actions
505 .actions
506 .push(EguiAction::DebugShape(EguiDebugShapeAction::RemoveShape(
507 debug_shape_id,
508 )));
509 };
510
511 if let Some(selected_entity) = &egui_state.selected_entity_info
512 && let Some(debug_shape_id) = selected_entity.debug_shape_id
513 && (egui_state.selected_entity_cylinder_height - selected_entity_cylinder_height).abs()
514 > f32::EPSILON
515 {
516 egui_actions
517 .actions
518 .push(EguiAction::DebugShape(EguiDebugShapeAction::RemoveShape(
519 debug_shape_id,
520 )));
521 egui_actions
522 .actions
523 .push(EguiAction::DebugShape(EguiDebugShapeAction::AddCylinder {
524 radius: 1.0,
525 height: selected_entity_cylinder_height,
526 }));
527 };
528 if debug_vectors_enabled_mut != egui_state.debug_vectors_enabled {
529 egui_actions
530 .actions
531 .push(EguiAction::SetShowDebugVector(debug_vectors_enabled_mut));
532 egui_state.debug_vectors_enabled = debug_vectors_enabled_mut;
533 }
534
535 egui_state.max_entity_distance = max_entity_distance;
536 egui_state.selected_entity_cylinder_height = selected_entity_cylinder_height;
537 egui_state.windows = windows;
538 egui_actions
539}
540
541fn selected_entity_window(
542 ctx: &Context,
543 ecs: &World,
544 selected_entity_info: &mut SelectedEntityInfo,
545 egui_actions: &mut EguiActions,
546) {
547 let entity_id = selected_entity_info.entity_id;
548 for (
549 _entity,
550 body,
551 stats,
552 pos,
553 _ori,
554 vel,
555 poise,
556 buffs,
557 auras,
558 character_state,
559 character_activity,
560 physics_state,
561 alignment,
562 scale,
563 (mass, density, health, energy),
564 ) in (
565 &ecs.entities(),
566 ecs.read_storage::<Body>().maybe(),
567 ecs.read_storage::<comp::Stats>().maybe(),
568 ecs.read_storage::<comp::Pos>().maybe(),
569 ecs.read_storage::<comp::Ori>().maybe(),
570 ecs.read_storage::<comp::Vel>().maybe(),
571 ecs.read_storage::<Poise>().maybe(),
572 ecs.read_storage::<comp::Buffs>().maybe(),
573 ecs.read_storage::<comp::Auras>().maybe(),
574 ecs.read_storage::<comp::CharacterState>().maybe(),
575 ecs.read_storage::<comp::CharacterActivity>().maybe(),
576 ecs.read_storage::<comp::PhysicsState>().maybe(),
577 ecs.read_storage::<comp::Alignment>().maybe(),
578 ecs.read_storage::<comp::Scale>().maybe(),
579 (
580 ecs.read_storage::<comp::Mass>().maybe(),
581 ecs.read_storage::<comp::Density>().maybe(),
582 ecs.read_storage::<comp::Health>().maybe(),
583 ecs.read_storage::<comp::Energy>().maybe(),
584 ),
585 )
586 .join()
587 .filter(|(e, _, _, _, _, _, _, _, _, _, _, _, _, _, (_, _, _, _))| e.id() == entity_id)
588 {
589 let time = ecs.read_resource::<Time>();
590 if let Some(pos) = pos
591 && let Some(shape_id) = selected_entity_info.debug_shape_id
592 {
593 egui_actions.actions.push(EguiAction::DebugShape(
594 EguiDebugShapeAction::SetPosAndColor {
595 id: shape_id,
596 color: [1.0, 1.0, 0.0, 0.5],
597 pos: [pos.0.x, pos.0.y, pos.0.z + 2.0, 0.0],
598 },
599 ));
600 };
601
602 Window::new("Selected Entity")
603 .default_width(300.0)
604 .default_height(200.0)
605 .show(ctx, |ui| {
606 ui.vertical(|ui| {
607 CollapsingHeader::new("General").default_open(true).show(ui, |ui| {
608 Grid::new("selected_entity_general_grid")
609 .spacing([40.0, 4.0])
610 .striped(true)
611 .show(ui, |ui| {
612 two_col_row(ui, "Health", health.map_or("-".to_owned(), |x| format!("{:.1}/{:.1}", x.current(), x.maximum())));
613 two_col_row(ui, "Energy", energy.map_or("-".to_owned(), |x| format!("{:.1}/{:.1}", x.current(), x.maximum())));
614 two_col_row(ui, "Position", pos.map_or("-".to_owned(), |x| format!("({:.1},{:.1},{:.1})", x.0.x, x.0.y, x.0.z)));
615 two_col_row(ui, "Velocity", vel.map_or("-".to_owned(), |x| format!("({:.1},{:.1},{:.1}) ({:.1} u/s)", x.0.x, x.0.y, x.0.z, x.0.magnitude())));
616 two_col_row(ui, "Alignment", alignment.map_or("-".to_owned(), |x| format!("{:?}", x)));
617 two_col_row(ui, "Scale", scale.map_or("-".to_owned(), |x| format!("{:?}", x)));
618 two_col_row(ui, "Mass", mass.map_or("-".to_owned(), |x| format!("{:.1}", x.0)));
619 two_col_row(ui, "Density", density.map_or("-".to_owned(), |x| format!("{:.1}", x.0)));
620 });
621
622 });
623 if let Some(stats) = stats {
624 CollapsingHeader::new("Stats").default_open(true).show(ui, |ui| {
625 Grid::new("selected_entity_stats_grid")
626 .spacing([40.0, 4.0])
627 .striped(true)
628 .show(ui, |ui| {
629 two_col_row(ui, "Name", format!("{:?}", stats.name));
630 two_col_row(ui, "Damage Reduction", format!("{:.1}", stats.damage_reduction.modifier()));
631 two_col_row(ui, "Multiplicative Max Health Modifier", format!("{:.1}", stats.max_health_modifiers.mult_mod));
632 two_col_row(ui, "Move Speed Modifier", format!("{:.1}", stats.move_speed_modifier));
633 });
634
635 });
636 }
637 if let Some(body) = body {
638 CollapsingHeader::new("Body").default_open(false).show(ui, |ui| {
639 Grid::new("selected_entity_body_grid")
640 .spacing([40.0, 4.0])
641 .striped(true)
642 .show(ui, |ui| {
643 two_col_row(ui, "Type", body.to_string());
644 two_col_row(ui, "Species", body_species(body));
645 });
646
647 });
648 }
649 if let Some(pos) = pos {
650 CollapsingHeader::new("Pos").default_open(false).show(ui, |ui| {
651 Grid::new("selected_entity_pos_grid")
652 .spacing([40.0, 4.0])
653 .max_col_width(100.0)
654 .striped(true)
655 .show(ui, |ui| {
656 two_col_row(ui, "x", format!("{:.1}", pos.0.x));
657 two_col_row(ui, "y", format!("{:.1}", pos.0.y));
658 two_col_row(ui, "z", format!("{:.1}", pos.0.z));
659 });
660
661 });
662 }
663 if let Some(poise) = poise {
664 CollapsingHeader::new("Poise").default_open(false).show(ui, |ui| {
665 Grid::new("selected_entity_poise_grid")
666 .spacing([40.0, 4.0])
667 .max_col_width(100.0)
668 .striped(true)
669 .show(ui, |ui| { #[rustfmt::skip] {
677 ui.label("State");
678 poise_state_label(ui, poise);
679 ui.end_row();
680 two_col_row(ui, "Current", format!("{:.1}/{:.1}", poise.current(), poise.maximum()));
681 two_col_row(ui, "Base Max", format!("{:.1}", poise.base_max()));
682 }});
683 });
684 }
685
686 if let Some(buffs) = buffs {
687 CollapsingHeader::new("Buffs").default_open(false).show(ui, |ui| {
688 Grid::new("selected_entity_buffs_grid")
689 .spacing([40.0, 4.0])
690 .max_col_width(100.0)
691 .striped(true)
692 .show(ui, |ui| {
693 ui.label("Kind");
694 ui.label("Time");
695 ui.label("Source");
696 ui.end_row();
697 buffs.buffs.iter().for_each(|(_, v)| {
698 ui.label(format!("{:?}", v.kind));
699 ui.label(
700 v.end_time.map_or("-".to_string(), |end| {
701 format!("{:?}", end.0 - time.0)
702 }),
703 );
704 ui.label(format!("{:?}", v.source));
705 ui.end_row();
706 });
707 });
708 });
709 }
710
711 if let Some(auras) = auras {
712 CollapsingHeader::new("Auras").default_open(false).show(ui, |ui| {
713 Grid::new("selected_entity_auras_grid")
714 .spacing([40.0, 4.0])
715 .striped(true)
716 .show(ui, |ui| {
717 ui.label("Kind");
718 ui.label("Radius");
719 ui.label("Duration");
720 ui.label("Target");
721 ui.end_row();
722 auras.auras.iter().for_each(|(_, v)| {
723 ui.label(match v.aura_kind {
724 Buff { kind, .. } => format!("Buff - {:?}", kind),
725 FriendlyFire => "Friendly Fire".to_string(),
726 ForcePvP => "ForcedPvP".to_string(),
727 });
728 ui.label(format!("{:1}", v.radius));
729 ui.label(v.end_time.map_or("-".to_owned(), |x| format!("{:1}s", x.0 - time.0)));
730 ui.label(format!("{:?}", v.target));
731 ui.end_row();
732 });
733 });
734 });
735 }
736
737 if let Some(character_state) = character_state {
738 if selected_entity_info
739 .character_state_history
740 .first()
741 .unwrap_or(&"-".to_owned())
742 != &character_state.to_string()
743 {
744 selected_entity_info
745 .character_state_history
746 .insert(0, character_state.to_string());
747 if selected_entity_info.character_state_history.len() > 50 {
748 selected_entity_info.character_state_history.pop();
749 }
750 }
751
752 CollapsingHeader::new("Character State").default_open(false).show(ui, |ui| {
753 draw_char_state_group(ui, selected_entity_info, character_state);
754 });
755 }
756
757 if let Some(character_activity) = character_activity {
758 CollapsingHeader::new("CharacterActivity").default_open(false).show(ui, |ui| {
759 Grid::new("selected_entity_character_activity_grid")
760 .spacing([40.0, 4.0])
761 .max_col_width(100.0)
762 .striped(true)
763 .show(ui, |ui| {
764 two_col_row(ui, "look_dir", format!("{:.3?}", character_activity.look_dir));
765 });
766 });
767 }
768
769 if let Some(physics_state) = physics_state {
770 CollapsingHeader::new("Physics State").default_open(false).show(ui, |ui| {
771 Grid::new("selected_entity_physics_state_grid")
772 .spacing([40.0, 4.0])
773 .striped(true)
774 .show(ui, |ui| {
775 two_col_row(ui, "On Ground", physics_state.on_ground.map_or("None".to_owned(), |x| format!("{:?}", x)));
776 two_col_row(ui, "On Ceiling", (if physics_state.on_ceiling { "True" } else { "False " }).to_string());
777 two_col_row(ui, "On Wall", physics_state.on_wall.map_or("-".to_owned(), |x| format!("{:.1},{:.1},{:.1}", x.x, x.y, x.z )));
778 two_col_row(ui, "Touching Entities", physics_state.touch_entities.len().to_string());
779 two_col_row(ui, "In Fluid", match physics_state.in_fluid {
780 Some(Fluid::Air { elevation, vel, .. }) => format!("Air (Elevation: {:.1}), vel: ({:.1},{:.1},{:.1}) ({:.1} u/s)", elevation, vel.0.x, vel.0.y, vel.0.z, vel.0.magnitude()),
781 Some(Fluid::Liquid { depth, kind, .. }) => format!("{:?} (Depth: {:.1})", kind, depth),
782 _ => "None".to_owned() });
783 });
784 two_col_row(ui, "Footwear", match physics_state.footwear{ Friction::Ski => "Ski", Friction::Skate => "Skate", Friction::Normal=>"Normal",}.to_string());
785 });
786 }
787 });
788 });
789 }
790}
791
792fn body_species(body: &Body) -> String {
793 match body {
794 Body::Humanoid(body) => format!("{:?}", body.species),
795 Body::QuadrupedSmall(body) => format!("{:?}", body.species),
796 Body::QuadrupedMedium(body) => format!("{:?}", body.species),
797 Body::BirdMedium(body) => format!("{:?}", body.species),
798 Body::FishMedium(body) => format!("{:?}", body.species),
799 Body::Dragon(body) => format!("{:?}", body.species),
800 Body::BirdLarge(body) => format!("{:?}", body.species),
801 Body::FishSmall(body) => format!("{:?}", body.species),
802 Body::BipedLarge(body) => format!("{:?}", body.species),
803 Body::BipedSmall(body) => format!("{:?}", body.species),
804 Body::Object(body) => format!("{:?}", body),
805 Body::Item(body) => format!("{:?}", body),
806 Body::Golem(body) => format!("{:?}", body.species),
807 Body::Theropod(body) => format!("{:?}", body.species),
808 Body::QuadrupedLow(body) => format!("{:?}", body.species),
809 Body::Arthropod(body) => format!("{:?}", body.species),
810 Body::Ship(body) => format!("{:?}", body),
811 Body::Crustacean(body) => format!("{:?}", body.species),
812 Body::Plugin(body) => format!("{:?}", body),
813 }
814}
815
816fn poise_state_label(ui: &mut Ui, poise: &Poise) {
817 match poise.poise_state() {
818 PoiseState::Normal => {
819 ui.label("Normal");
820 },
821 PoiseState::Interrupted => {
822 ui.colored_label(Color32::YELLOW, "Interrupted");
823 },
824 PoiseState::Stunned => {
825 ui.colored_label(Color32::RED, "Stunned");
826 },
827 PoiseState::Dazed => {
828 ui.colored_label(Color32::RED, "Dazed");
829 },
830 PoiseState::KnockedDown => {
831 ui.colored_label(Color32::BLUE, "Knocked Down");
832 },
833 };
834}