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::{self, Poise, PoiseState, inventory::item::armor::Friction},
15 resources::Time,
16 uid,
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 uids = client.state().ecs().read_storage::<uid::Uid>();
335 let client_pos = positions.get(client.entity());
336
337 Window::new("ECS Entities")
338 .open(&mut windows.ecs_entities)
339 .default_width(500.0)
340 .default_height(500.0)
341 .show(ctx, |ui| {
342 ui.label(format!("Entity count: {}", ecs.entities().join().count()));
343 ui.add(
344 Slider::new(&mut max_entity_distance, 1.0..=100000.0)
345 .logarithmic(true)
346 .clamping(egui::SliderClamping::Always)
347 .text("Max entity distance"),
348 );
349
350 ui.add(
351 Slider::new(&mut selected_entity_cylinder_height, 0.1..=100.0)
352 .logarithmic(true)
353 .clamping(egui::SliderClamping::Always)
354 .text("Cylinder height"),
355 );
356
357 let scroll_area = ScrollArea::vertical().max_height(800.0);
358 scroll_area.show(ui, |ui| {
359 Grid::new("entities_grid")
360 .spacing([40.0, 4.0])
361 .max_col_width(300.0)
362 .striped(true)
363 .show(ui, |ui| {
364 ui.label("-");
365 ui.label("ID");
366 ui.label("Pos");
367 ui.label("Vel");
368 ui.label("Name");
369 ui.label("Body");
370 ui.label("Poise");
371 ui.label("Character State");
372 ui.label("Character Activity");
373 ui.end_row();
374 for (
375 entity,
376 body,
377 stats,
378 pos,
379 _ori,
380 vel,
381 poise,
382 character_state,
383 character_activity,
384 ) in (
385 &ecs.entities(),
386 ecs.read_storage::<Body>().maybe(),
387 ecs.read_storage::<comp::Stats>().maybe(),
388 ecs.read_storage::<comp::Pos>().maybe(),
389 ecs.read_storage::<comp::Ori>().maybe(),
390 ecs.read_storage::<comp::Vel>().maybe(),
391 ecs.read_storage::<Poise>().maybe(),
392 ecs.read_storage::<comp::CharacterState>().maybe(),
393 ecs.read_storage::<comp::CharacterActivity>().maybe(),
394 )
395 .join()
396 .filter(
397 |(_, _, _, pos, _, _, _, _, _)| {
398 client_pos.is_none_or(|client_pos| {
399 pos.map_or(0.0, |pos| {
400 pos.0.distance_squared(client_pos.0)
401 }) < max_entity_distance
402 })
403 },
404 )
405 {
406 if ui.button("View").clicked() {
407 previous_selected_entity =
408 mem::take(&mut egui_state.selected_entity_info);
409
410 if pos.is_some() {
411 egui_actions.actions.push(EguiAction::DebugShape(
412 EguiDebugShapeAction::AddCylinder {
413 radius: 1.0,
414 height: egui_state.selected_entity_cylinder_height,
415 },
416 ));
417 }
418 egui_state.selected_entity_info =
419 Some(SelectedEntityInfo::new(entity.id()));
420 }
421
422 ui.label(format!("{}", entity.id()));
423
424 if let Some(pos) = pos {
425 ui.horizontal(|ui| {
426 ui.label(format!(
427 "{:.0},{:.0},{:.0}",
428 pos.0.x, pos.0.y, pos.0.z
429 ));
430
431 if let Some(uid) = uids.get(entity)
432 && ui.button("GoTo").clicked()
433 {
434 egui_actions.actions.push(EguiAction::ChatCommand {
436 cmd: ServerChatCommand::Tp,
437 args: vec![format!("uid@{uid}")],
438 })
439 }
440 });
441 } else {
442 ui.label("-");
443 }
444
445 if let Some(vel) = vel {
446 ui.label(format!("{:.1}u/s", vel.0.magnitude()));
447 } else {
448 ui.label("-");
449 }
450 if let Some(stats) = stats {
451 ui.label(format!("{:?}", stats.name));
452 } else {
453 ui.label("-");
454 }
455 if let Some(body) = body {
456 ui.label(body_species(body));
457 } else {
458 ui.label("-");
459 }
460
461 if let Some(poise) = poise {
462 poise_state_label(ui, poise);
463 } else {
464 ui.label("-");
465 }
466
467 if let Some(character_state) = character_state {
468 ui.label(character_state.to_string());
469 } else {
470 ui.label("-");
471 }
472
473 if let Some(character_activity) = character_activity {
474 ui.label(format!("{:?}", character_activity));
475 } else {
476 ui.label("-");
477 }
478
479 ui.end_row();
480 }
481 });
482 let margin = ui.visuals().clip_rect_margin;
483
484 let current_scroll = ui.clip_rect().top() - ui.min_rect().top() + margin;
485 let max_scroll =
486 ui.min_rect().height() - ui.clip_rect().height() + 2.0 * margin;
487 (current_scroll, max_scroll)
488 });
489 });
490 if let Some(selected_entity_info) = &mut egui_state.selected_entity_info {
491 let selected_entity = ecs.entities().entity(selected_entity_info.entity_id);
492 if !selected_entity.r#gen().is_alive() {
493 previous_selected_entity = mem::take(&mut egui_state.selected_entity_info);
494 } else {
495 selected_entity_window(ctx, ecs, selected_entity_info, &mut egui_actions);
496 }
497 }
498 }
499
500 draw_admin_commands_window(
501 ctx,
502 &mut egui_state.admin_command_state,
503 &mut windows.admin_commands,
504 &mut egui_actions,
505 );
506
507 draw_experimental_shaders_window(
508 ctx,
509 &mut windows.experimental_shaders,
510 &mut egui_actions,
511 &experimental_shaders,
512 );
513
514 if let Some(previous) = previous_selected_entity
515 && let Some(debug_shape_id) = previous.debug_shape_id
516 {
517 egui_actions
518 .actions
519 .push(EguiAction::DebugShape(EguiDebugShapeAction::RemoveShape(
520 debug_shape_id,
521 )));
522 };
523
524 if let Some(selected_entity) = &egui_state.selected_entity_info
525 && let Some(debug_shape_id) = selected_entity.debug_shape_id
526 && (egui_state.selected_entity_cylinder_height - selected_entity_cylinder_height).abs()
527 > f32::EPSILON
528 {
529 egui_actions
530 .actions
531 .push(EguiAction::DebugShape(EguiDebugShapeAction::RemoveShape(
532 debug_shape_id,
533 )));
534 egui_actions
535 .actions
536 .push(EguiAction::DebugShape(EguiDebugShapeAction::AddCylinder {
537 radius: 1.0,
538 height: selected_entity_cylinder_height,
539 }));
540 };
541 if debug_vectors_enabled_mut != egui_state.debug_vectors_enabled {
542 egui_actions
543 .actions
544 .push(EguiAction::SetShowDebugVector(debug_vectors_enabled_mut));
545 egui_state.debug_vectors_enabled = debug_vectors_enabled_mut;
546 }
547
548 egui_state.max_entity_distance = max_entity_distance;
549 egui_state.selected_entity_cylinder_height = selected_entity_cylinder_height;
550 egui_state.windows = windows;
551 egui_actions
552}
553
554fn selected_entity_window(
555 ctx: &Context,
556 ecs: &World,
557 selected_entity_info: &mut SelectedEntityInfo,
558 egui_actions: &mut EguiActions,
559) {
560 let entity_id = selected_entity_info.entity_id;
561 for (
562 _entity,
563 body,
564 stats,
565 pos,
566 _ori,
567 vel,
568 poise,
569 buffs,
570 auras,
571 character_state,
572 character_activity,
573 physics_state,
574 alignment,
575 scale,
576 (mass, density, health, energy),
577 ) in (
578 &ecs.entities(),
579 ecs.read_storage::<Body>().maybe(),
580 ecs.read_storage::<comp::Stats>().maybe(),
581 ecs.read_storage::<comp::Pos>().maybe(),
582 ecs.read_storage::<comp::Ori>().maybe(),
583 ecs.read_storage::<comp::Vel>().maybe(),
584 ecs.read_storage::<Poise>().maybe(),
585 ecs.read_storage::<comp::Buffs>().maybe(),
586 ecs.read_storage::<comp::Auras>().maybe(),
587 ecs.read_storage::<comp::CharacterState>().maybe(),
588 ecs.read_storage::<comp::CharacterActivity>().maybe(),
589 ecs.read_storage::<comp::PhysicsState>().maybe(),
590 ecs.read_storage::<comp::Alignment>().maybe(),
591 ecs.read_storage::<comp::Scale>().maybe(),
592 (
593 ecs.read_storage::<comp::Mass>().maybe(),
594 ecs.read_storage::<comp::Density>().maybe(),
595 ecs.read_storage::<comp::Health>().maybe(),
596 ecs.read_storage::<comp::Energy>().maybe(),
597 ),
598 )
599 .join()
600 .filter(|(e, _, _, _, _, _, _, _, _, _, _, _, _, _, (_, _, _, _))| e.id() == entity_id)
601 {
602 let time = ecs.read_resource::<Time>();
603 if let Some(pos) = pos
604 && let Some(shape_id) = selected_entity_info.debug_shape_id
605 {
606 egui_actions.actions.push(EguiAction::DebugShape(
607 EguiDebugShapeAction::SetPosAndColor {
608 id: shape_id,
609 color: [1.0, 1.0, 0.0, 0.5],
610 pos: [pos.0.x, pos.0.y, pos.0.z + 2.0, 0.0],
611 },
612 ));
613 };
614
615 Window::new("Selected Entity")
616 .default_width(300.0)
617 .default_height(200.0)
618 .show(ctx, |ui| {
619 ui.vertical(|ui| {
620 CollapsingHeader::new("General").default_open(true).show(ui, |ui| {
621 Grid::new("selected_entity_general_grid")
622 .spacing([40.0, 4.0])
623 .striped(true)
624 .show(ui, |ui| {
625 two_col_row(ui, "Health", health.map_or("-".to_owned(), |x| format!("{:.1}/{:.1}", x.current(), x.maximum())));
626 two_col_row(ui, "Energy", energy.map_or("-".to_owned(), |x| format!("{:.1}/{:.1}", x.current(), x.maximum())));
627 two_col_row(ui, "Position", pos.map_or("-".to_owned(), |x| format!("({:.1},{:.1},{:.1})", x.0.x, x.0.y, x.0.z)));
628 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())));
629 two_col_row(ui, "Alignment", alignment.map_or("-".to_owned(), |x| format!("{:?}", x)));
630 two_col_row(ui, "Scale", scale.map_or("-".to_owned(), |x| format!("{:?}", x)));
631 two_col_row(ui, "Mass", mass.map_or("-".to_owned(), |x| format!("{:.1}", x.0)));
632 two_col_row(ui, "Density", density.map_or("-".to_owned(), |x| format!("{:.1}", x.0)));
633 });
634
635 });
636 if let Some(stats) = stats {
637 CollapsingHeader::new("Stats").default_open(true).show(ui, |ui| {
638 Grid::new("selected_entity_stats_grid")
639 .spacing([40.0, 4.0])
640 .striped(true)
641 .show(ui, |ui| {
642 two_col_row(ui, "Name", format!("{:?}", stats.name));
643 two_col_row(ui, "Damage Reduction", format!("{:.1}", stats.damage_reduction.modifier()));
644 two_col_row(ui, "Multiplicative Max Health Modifier", format!("{:.1}", stats.max_health_modifiers.mult_mod));
645 two_col_row(ui, "Move Speed Modifier", format!("{:.1}", stats.move_speed_modifier));
646 });
647
648 });
649 }
650 if let Some(body) = body {
651 CollapsingHeader::new("Body").default_open(false).show(ui, |ui| {
652 Grid::new("selected_entity_body_grid")
653 .spacing([40.0, 4.0])
654 .striped(true)
655 .show(ui, |ui| {
656 two_col_row(ui, "Type", body.to_string());
657 two_col_row(ui, "Species", body_species(body));
658 });
659
660 });
661 }
662 if let Some(pos) = pos {
663 CollapsingHeader::new("Pos").default_open(false).show(ui, |ui| {
664 Grid::new("selected_entity_pos_grid")
665 .spacing([40.0, 4.0])
666 .max_col_width(100.0)
667 .striped(true)
668 .show(ui, |ui| {
669 two_col_row(ui, "x", format!("{:.1}", pos.0.x));
670 two_col_row(ui, "y", format!("{:.1}", pos.0.y));
671 two_col_row(ui, "z", format!("{:.1}", pos.0.z));
672 });
673
674 });
675 }
676 if let Some(poise) = poise {
677 CollapsingHeader::new("Poise").default_open(false).show(ui, |ui| {
678 Grid::new("selected_entity_poise_grid")
679 .spacing([40.0, 4.0])
680 .max_col_width(100.0)
681 .striped(true)
682 .show(ui, |ui| { #[rustfmt::skip] {
690 ui.label("State");
691 poise_state_label(ui, poise);
692 ui.end_row();
693 two_col_row(ui, "Current", format!("{:.1}/{:.1}", poise.current(), poise.maximum()));
694 two_col_row(ui, "Base Max", format!("{:.1}", poise.base_max()));
695 }});
696 });
697 }
698
699 if let Some(buffs) = buffs {
700 CollapsingHeader::new("Buffs").default_open(false).show(ui, |ui| {
701 Grid::new("selected_entity_buffs_grid")
702 .spacing([40.0, 4.0])
703 .max_col_width(100.0)
704 .striped(true)
705 .show(ui, |ui| {
706 ui.label("Kind");
707 ui.label("Time");
708 ui.label("Source");
709 ui.end_row();
710 buffs.buffs.iter().for_each(|(_, v)| {
711 ui.label(format!("{:?}", v.kind));
712 ui.label(
713 v.end_time.map_or("-".to_string(), |end| {
714 format!("{:?}", end.0 - time.0)
715 }),
716 );
717 ui.label(format!("{:?}", v.source));
718 ui.end_row();
719 });
720 });
721 });
722 }
723
724 if let Some(auras) = auras {
725 CollapsingHeader::new("Auras").default_open(false).show(ui, |ui| {
726 Grid::new("selected_entity_auras_grid")
727 .spacing([40.0, 4.0])
728 .striped(true)
729 .show(ui, |ui| {
730 ui.label("Kind");
731 ui.label("Radius");
732 ui.label("Duration");
733 ui.label("Target");
734 ui.end_row();
735 auras.auras.iter().for_each(|(_, v)| {
736 ui.label(match v.aura_kind {
737 Buff { kind, .. } => format!("Buff - {:?}", kind),
738 FriendlyFire => "Friendly Fire".to_string(),
739 ForcePvP => "ForcedPvP".to_string(),
740 });
741 ui.label(format!("{:1}", v.radius));
742 ui.label(v.end_time.map_or("-".to_owned(), |x| format!("{:1}s", x.0 - time.0)));
743 ui.label(format!("{:?}", v.target));
744 ui.end_row();
745 });
746 });
747 });
748 }
749
750 if let Some(character_state) = character_state {
751 if selected_entity_info
752 .character_state_history
753 .first()
754 .unwrap_or(&"-".to_owned())
755 != &character_state.to_string()
756 {
757 selected_entity_info
758 .character_state_history
759 .insert(0, character_state.to_string());
760 if selected_entity_info.character_state_history.len() > 50 {
761 selected_entity_info.character_state_history.pop();
762 }
763 }
764
765 CollapsingHeader::new("Character State").default_open(false).show(ui, |ui| {
766 draw_char_state_group(ui, selected_entity_info, character_state);
767 });
768 }
769
770 if let Some(character_activity) = character_activity {
771 CollapsingHeader::new("CharacterActivity").default_open(false).show(ui, |ui| {
772 Grid::new("selected_entity_character_activity_grid")
773 .spacing([40.0, 4.0])
774 .max_col_width(100.0)
775 .striped(true)
776 .show(ui, |ui| {
777 two_col_row(ui, "look_dir", format!("{:.3?}", character_activity.look_dir));
778 });
779 });
780 }
781
782 if let Some(physics_state) = physics_state {
783 CollapsingHeader::new("Physics State").default_open(false).show(ui, |ui| {
784 Grid::new("selected_entity_physics_state_grid")
785 .spacing([40.0, 4.0])
786 .striped(true)
787 .show(ui, |ui| {
788 two_col_row(ui, "On Ground", physics_state.on_ground.map_or("None".to_owned(), |x| format!("{:?}", x)));
789 two_col_row(ui, "On Ceiling", (if physics_state.on_ceiling { "True" } else { "False " }).to_string());
790 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 )));
791 two_col_row(ui, "Touching Entities", physics_state.touch_entities.len().to_string());
792 two_col_row(ui, "In Fluid", match physics_state.in_fluid {
793 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()),
794 Some(Fluid::Liquid { depth, kind, .. }) => format!("{:?} (Depth: {:.1})", kind, depth),
795 _ => "None".to_owned() });
796 });
797 two_col_row(ui, "Footwear", match physics_state.footwear{ Friction::Ski => "Ski", Friction::Skate => "Skate", Friction::Normal=>"Normal",}.to_string());
798 });
799 }
800 });
801 });
802 }
803}
804
805fn body_species(body: &Body) -> String {
806 match body {
807 Body::Humanoid(body) => format!("{:?}", body.species),
808 Body::QuadrupedSmall(body) => format!("{:?}", body.species),
809 Body::QuadrupedMedium(body) => format!("{:?}", body.species),
810 Body::BirdMedium(body) => format!("{:?}", body.species),
811 Body::FishMedium(body) => format!("{:?}", body.species),
812 Body::Dragon(body) => format!("{:?}", body.species),
813 Body::BirdLarge(body) => format!("{:?}", body.species),
814 Body::FishSmall(body) => format!("{:?}", body.species),
815 Body::BipedLarge(body) => format!("{:?}", body.species),
816 Body::BipedSmall(body) => format!("{:?}", body.species),
817 Body::Object(body) => format!("{:?}", body),
818 Body::Item(body) => format!("{:?}", body),
819 Body::Golem(body) => format!("{:?}", body.species),
820 Body::Theropod(body) => format!("{:?}", body.species),
821 Body::QuadrupedLow(body) => format!("{:?}", body.species),
822 Body::Arthropod(body) => format!("{:?}", body.species),
823 Body::Ship(body) => format!("{:?}", body),
824 Body::Crustacean(body) => format!("{:?}", body.species),
825 Body::Plugin(body) => format!("{:?}", body),
826 }
827}
828
829fn poise_state_label(ui: &mut Ui, poise: &Poise) {
830 match poise.poise_state() {
831 PoiseState::Normal => {
832 ui.label("Normal");
833 },
834 PoiseState::Interrupted => {
835 ui.colored_label(Color32::YELLOW, "Interrupted");
836 },
837 PoiseState::Stunned => {
838 ui.colored_label(Color32::RED, "Stunned");
839 },
840 PoiseState::Dazed => {
841 ui.colored_label(Color32::RED, "Dazed");
842 },
843 PoiseState::KnockedDown => {
844 ui.colored_label(Color32::BLUE, "Knocked Down");
845 },
846 };
847}