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