1use crate::scene::{Body, Object, Shape};
22use nightshade::ecs::world::CORE;
23use nightshade::prelude::{Entity, KeyCode, MouseButton, TextAlignment, Vec3, World, vec3};
24use serde::{Deserialize, Serialize};
25
26#[derive(Serialize, Deserialize, Clone, Copy, Debug, enum2schema::Schema)]
31pub enum Ref {
32 Entity(#[schema(with = entity_schema)] Entity),
33 Result(u32),
34 Existing(u32),
39}
40
41#[derive(Serialize, Deserialize, Clone, Debug, enum2schema::Schema)]
45pub enum CommandReply {
46 None,
47 Entity(#[schema(with = entity_schema)] Entity),
48 Bool(bool),
49 Float(f32),
50 Int(i64),
51 Text(String),
52 Vector([f32; 3]),
53 Entities(#[schema(with = entities_schema)] Vec<Entity>),
54 Strings(Vec<String>),
55 Bytes(Vec<u8>),
56 Json(#[schema(with = any_schema)] enum2schema::serde_json::Value),
61 Error(String),
62}
63
64#[derive(Serialize, Clone, Debug)]
69pub struct FieldSpec {
70 pub name: &'static str,
71 pub type_name: &'static str,
72 pub role: &'static str,
73}
74
75#[derive(Serialize, Clone, Debug)]
79pub struct CommandSpec {
80 pub name: &'static str,
81 pub fields: Vec<FieldSpec>,
82 pub reply: &'static str,
83 pub description: &'static str,
84}
85
86pub fn command_manifest_json() -> String {
89 enum2schema::serde_json::to_string(&command_manifest()).unwrap_or_default()
90}
91
92fn entity_schema() -> enum2schema::serde_json::Value {
93 enum2schema::serde_json::json!({
94 "type": "object",
95 "properties": {
96 "id": { "type": "integer" },
97 "generation": { "type": "integer" }
98 },
99 "required": ["id", "generation"]
100 })
101}
102
103fn entities_schema() -> enum2schema::serde_json::Value {
104 enum2schema::serde_json::json!({ "type": "array", "items": entity_schema() })
105}
106
107fn any_schema() -> enum2schema::serde_json::Value {
108 enum2schema::serde_json::json!({})
109}
110
111pub fn command_schema() -> enum2schema::serde_json::Value {
115 <Command as enum2schema::Schema>::schema()
116}
117
118pub fn command_reply_schema() -> enum2schema::serde_json::Value {
120 <CommandReply as enum2schema::Schema>::schema()
121}
122
123pub fn submit_command(world: &mut World, command: &Command) -> CommandReply {
125 dispatch(world, command, &[])
126}
127
128pub fn submit_commands(world: &mut World, commands: &[Command]) -> Vec<CommandReply> {
132 let mut produced: Vec<Option<Entity>> = Vec::with_capacity(commands.len());
133 let mut replies = Vec::with_capacity(commands.len());
134 for command in commands {
135 let reply = dispatch(world, command, &produced);
136 produced.push(match &reply {
137 CommandReply::Entity(entity) => Some(*entity),
138 _ => None,
139 });
140 replies.push(reply);
141 }
142 replies
143}
144
145fn resolve(world: &World, reference: Ref, produced: &[Option<Entity>]) -> Option<Entity> {
146 match reference {
147 Ref::Entity(entity) => Some(entity),
148 Ref::Result(index) => produced.get(index as usize).copied().flatten(),
149 Ref::Existing(id) => world.ecs.worlds[CORE]
150 .entity_locations
151 .get(id)
152 .filter(|location| location.allocated)
153 .map(|location| Entity {
154 id,
155 generation: location.generation,
156 }),
157 }
158}
159
160fn array_to_vec3(values: [f32; 3]) -> Vec3 {
161 vec3(values[0], values[1], values[2])
162}
163
164fn array_to_vec2(values: [f32; 2]) -> nightshade::prelude::Vec2 {
165 nightshade::prelude::vec2(values[0], values[1])
166}
167
168fn spawn_object_command(
173 world: &mut World,
174 shape: Shape,
175 position: Vec3,
176 scale: Vec3,
177 color: [f32; 4],
178 body: Body,
179) -> Entity {
180 crate::scene::spawn_object(
181 world,
182 Object {
183 shape,
184 position,
185 scale,
186 color,
187 body,
188 },
189 )
190}
191
192#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, enum2schema::Schema)]
196pub struct InstanceWire {
197 pub position: [f32; 3],
198 pub rotation: [f32; 4],
199 pub scale: [f32; 3],
200}
201
202fn instance_from_wire(wire: &InstanceWire) -> nightshade::prelude::InstanceTransform {
203 use nightshade::prelude::nalgebra_glm::Quat;
204 nightshade::prelude::InstanceTransform::new(
205 array_to_vec3(wire.position),
206 Quat::new(
207 wire.rotation[3],
208 wire.rotation[0],
209 wire.rotation[1],
210 wire.rotation[2],
211 ),
212 array_to_vec3(wire.scale),
213 )
214}
215
216fn register_material_command(
217 world: &mut World,
218 name: &str,
219 base_color: [f32; 4],
220 metallic: f32,
221 roughness: f32,
222 emissive: [f32; 3],
223) -> String {
224 crate::materials::register_material(
225 world,
226 name,
227 nightshade::render::material::Material {
228 base_color,
229 metallic,
230 roughness,
231 emissive_factor: emissive,
232 ..Default::default()
233 },
234 )
235}
236
237fn spawn_objects_command(
238 world: &mut World,
239 shape: Shape,
240 scale: Vec3,
241 color: [f32; 4],
242 body: Body,
243 positions: &[Vec3],
244) -> Vec<Entity> {
245 crate::scene::spawn_objects(
246 world,
247 Object {
248 shape,
249 position: vec3(0.0, 0.0, 0.0),
250 scale,
251 color,
252 body,
253 },
254 positions,
255 )
256}
257
258fn spawn_custom_mesh_command(
264 world: &mut World,
265 name: &str,
266 positions: &[f32],
267 normals: &[f32],
268 uvs: &[f32],
269 indices: &[usize],
270 position: Vec3,
271) -> Entity {
272 let count = positions.len() / 3;
273 let vertices: Vec<([f32; 3], [f32; 3], [f32; 2])> = (0..count)
274 .map(|index| {
275 let point = index * 3;
276 let coord = index * 2;
277 (
278 [positions[point], positions[point + 1], positions[point + 2]],
279 [
280 normals.get(point).copied().unwrap_or(0.0),
281 normals.get(point + 1).copied().unwrap_or(1.0),
282 normals.get(point + 2).copied().unwrap_or(0.0),
283 ],
284 [
285 uvs.get(coord).copied().unwrap_or(0.0),
286 uvs.get(coord + 1).copied().unwrap_or(0.0),
287 ],
288 )
289 })
290 .collect();
291 let indices: Vec<u32> = indices.iter().map(|value| *value as u32).collect();
292 crate::mesh::spawn_custom_mesh(world, name, &vertices, &indices, position)
293}
294
295#[cfg(feature = "navmesh")]
296#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, enum2schema::Schema)]
297pub struct RecastConfigWire {
298 pub agent_radius: f32,
299 pub agent_height: f32,
300 pub cell_size_fraction: f32,
301 pub cell_height_fraction: f32,
302 pub walkable_climb: f32,
303 pub walkable_slope_angle: f32,
304 pub min_region_size: u32,
305 pub merge_region_size: u32,
306 pub max_simplification_error: f32,
307 pub edge_max_len_factor: u32,
308 pub max_vertices_per_polygon: u32,
309 pub detail_sample_dist: f32,
310 pub detail_sample_max_error: f32,
311}
312
313#[cfg(feature = "navmesh")]
314fn bake_navmesh_with_command(world: &mut World, config: RecastConfigWire) {
315 crate::navigation::bake_navmesh_with(
316 world,
317 &nightshade::prelude::RecastNavMeshConfig {
318 agent_radius: config.agent_radius,
319 agent_height: config.agent_height,
320 cell_size_fraction: config.cell_size_fraction,
321 cell_height_fraction: config.cell_height_fraction,
322 walkable_climb: config.walkable_climb,
323 walkable_slope_angle: config.walkable_slope_angle,
324 min_region_size: config.min_region_size as u16,
325 merge_region_size: config.merge_region_size as u16,
326 max_simplification_error: config.max_simplification_error,
327 edge_max_len_factor: config.edge_max_len_factor as u16,
328 max_vertices_per_polygon: config.max_vertices_per_polygon as u16,
329 detail_sample_dist: config.detail_sample_dist,
330 detail_sample_max_error: config.detail_sample_max_error,
331 },
332 );
333}
334
335#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, enum2schema::Schema)]
340pub struct EmitterWire {
341 pub position: [f32; 3],
342 pub color: [f32; 3],
343 pub spawn_rate: f32,
344 pub burst_count: u32,
345 pub lifetime: [f32; 2],
346 pub size: [f32; 2],
347 pub gravity: [f32; 3],
348 pub drag: f32,
349 pub one_shot: bool,
350}
351
352fn emitter_from_wire(wire: &EmitterWire) -> nightshade::prelude::ParticleEmitter {
353 let mut emitter = nightshade::prelude::ParticleEmitter::firework_explosion(
354 array_to_vec3(wire.position),
355 array_to_vec3(wire.color),
356 wire.burst_count,
357 );
358 emitter.spawn_rate = wire.spawn_rate;
359 emitter.particle_lifetime_min = wire.lifetime[0];
360 emitter.particle_lifetime_max = wire.lifetime[1];
361 emitter.size_start = wire.size[0];
362 emitter.size_end = wire.size[1];
363 emitter.gravity = array_to_vec3(wire.gravity);
364 emitter.drag = wire.drag;
365 emitter.one_shot = wire.one_shot;
366 emitter.enabled = true;
367 emitter
368}
369
370fn spawn_particle_emitter_command(world: &mut World, emitter: EmitterWire) -> Entity {
371 crate::effects::spawn_particle_emitter(world, emitter_from_wire(&emitter))
372}
373
374fn set_emitter_command(world: &mut World, emitter_entity: Entity, emitter: EmitterWire) {
375 crate::effects::set_emitter(world, emitter_entity, emitter_from_wire(&emitter));
376}
377
378fn update_material_command(
379 world: &mut World,
380 name: &str,
381 base_color: [f32; 4],
382 metallic: f32,
383 roughness: f32,
384 emissive: [f32; 3],
385) {
386 crate::materials::update_material(
387 world,
388 name,
389 nightshade::render::material::Material {
390 base_color,
391 metallic,
392 roughness,
393 emissive_factor: emissive,
394 ..Default::default()
395 },
396 );
397}
398
399fn set_material_variant_command(world: &mut World, variant: &str) -> usize {
400 let variant = if variant.is_empty() {
401 None
402 } else {
403 Some(variant)
404 };
405 crate::materials::set_material_variant(world, variant)
406}
407
408fn set_fog_command(world: &mut World, enabled: bool, color: [f32; 3], start: f32, end: f32) {
409 let fog = if enabled {
410 Some(nightshade::render::config::Fog {
411 color,
412 start,
413 end,
414 mode: nightshade::render::config::FogMode::Linear,
415 })
416 } else {
417 None
418 };
419 crate::environment::set_fog(world, fog);
420}
421
422fn set_fog_mode_command(world: &mut World, mode: u32) {
423 let current = world
424 .res::<nightshade::render::config::RenderSettings>()
425 .fog;
426 if let Some(mut fog) = current {
427 fog.mode = nightshade::render::config::FogMode::from_u32(mode);
428 crate::environment::set_fog(world, Some(fog));
429 }
430}
431
432fn set_depth_of_field_command(
433 world: &mut World,
434 enabled: bool,
435 focus_distance: f32,
436 focus_range: f32,
437 max_blur_radius: f32,
438 bokeh_threshold: f32,
439) {
440 crate::environment::set_depth_of_field(
441 world,
442 nightshade::render::config::DepthOfField {
443 enabled,
444 focus_distance,
445 focus_range,
446 max_blur_radius,
447 bokeh_threshold,
448 ..Default::default()
449 },
450 );
451}
452
453fn panel_data_grid_command(
454 world: &mut World,
455 panel: Entity,
456 headers: &[&str],
457 widths: &[f32],
458 pool_size: usize,
459) -> Entity {
460 let columns: Vec<(&str, f32)> = headers
461 .iter()
462 .zip(widths.iter())
463 .map(|(header, width)| (*header, *width))
464 .collect();
465 crate::ui::panel_data_grid(world, panel, &columns, pool_size)
466}
467
468fn panel_selectable_command(
469 world: &mut World,
470 panel: Entity,
471 text: &str,
472 group: u32,
473 grouped: bool,
474) -> Entity {
475 crate::ui::panel_selectable(world, panel, text, grouped.then_some(group))
476}
477
478fn panel_splitter_command(
479 world: &mut World,
480 panel: Entity,
481 horizontal: bool,
482 ratio: f32,
483) -> Entity {
484 let direction = if horizontal {
485 nightshade::prelude::SplitDirection::Horizontal
486 } else {
487 nightshade::prelude::SplitDirection::Vertical
488 };
489 crate::ui::panel_splitter(world, panel, direction, ratio)
490}
491
492fn screenshot_command(world: &mut World, path: &str) {
493 crate::environment::screenshot(world, std::path::PathBuf::from(path));
494}
495
496fn easing_from_name(name: &str) -> nightshade::prelude::EasingFunction {
497 use nightshade::prelude::EasingFunction::*;
498 match name.to_ascii_lowercase().as_str() {
499 "quadin" => QuadIn,
500 "quadout" => QuadOut,
501 "quadinout" => QuadInOut,
502 "cubicin" => CubicIn,
503 "cubicout" => CubicOut,
504 "cubicinout" => CubicInOut,
505 "quartin" => QuartIn,
506 "quartout" => QuartOut,
507 "quartinout" => QuartInOut,
508 "quintin" => QuintIn,
509 "quintout" => QuintOut,
510 "quintinout" => QuintInOut,
511 "sinein" => SineIn,
512 "sineout" => SineOut,
513 "sineinout" => SineInOut,
514 "expoin" => ExpoIn,
515 "expoout" => ExpoOut,
516 "expoinout" => ExpoInOut,
517 _ => Linear,
518 }
519}
520
521fn animate_position_command(
522 world: &mut World,
523 entity: Entity,
524 to: Vec3,
525 seconds: f32,
526 easing: &str,
527) {
528 crate::animate::animate_position(world, entity, to, seconds, easing_from_name(easing));
529}
530
531fn animate_scale_command(world: &mut World, entity: Entity, to: Vec3, seconds: f32, easing: &str) {
532 crate::animate::animate_scale(world, entity, to, seconds, easing_from_name(easing));
533}
534
535fn animate_color_command(
536 world: &mut World,
537 entity: Entity,
538 to: [f32; 4],
539 seconds: f32,
540 easing: &str,
541) {
542 crate::animate::animate_color(world, entity, to, seconds, easing_from_name(easing));
543}
544
545fn set_shading_mode_command(world: &mut World, mode: &str) {
546 use nightshade::prelude::ShadingMode;
547 let mode = match mode.to_ascii_lowercase().as_str() {
548 "wireframe" => ShadingMode::Wireframe,
549 "flat" => ShadingMode::Flat,
550 "rendered" => ShadingMode::Rendered,
551 _ => ShadingMode::Solid,
552 };
553 crate::camera::set_shading_mode(world, mode);
554}
555
556#[cfg(feature = "physics")]
557#[derive(Serialize)]
558struct RaycastResultWire {
559 entity_id: u32,
560 distance: f32,
561 point: [f32; 3],
562 normal: [f32; 3],
563}
564
565#[cfg(feature = "physics")]
566fn raycast_command(
567 world: &mut World,
568 origin: Vec3,
569 direction: Vec3,
570 max_distance: f32,
571) -> Option<RaycastResultWire> {
572 crate::physics::raycast(world, origin, direction, max_distance).map(|hit| RaycastResultWire {
573 entity_id: hit.entity.id,
574 distance: hit.distance,
575 point: [hit.point.x, hit.point.y, hit.point.z],
576 normal: [hit.normal.x, hit.normal.y, hit.normal.z],
577 })
578}
579
580#[cfg(feature = "physics")]
581fn attach_fixed_command(world: &mut World, parent: Entity, child: Entity) -> bool {
582 crate::physics::attach_fixed(world, parent, child).is_some()
583}
584
585#[cfg(feature = "physics")]
586fn attach_hinge_command(world: &mut World, parent: Entity, child: Entity, axis: &str) -> bool {
587 use nightshade::plugins::physics::joints::JointAxisDirection;
588 let axis = match axis.to_ascii_lowercase().as_str() {
589 "y" => JointAxisDirection::Y,
590 "z" => JointAxisDirection::Z,
591 _ => JointAxisDirection::X,
592 };
593 crate::physics::attach_hinge(world, parent, child, axis).is_some()
594}
595
596#[cfg(feature = "physics")]
597fn attach_spring_command(
598 world: &mut World,
599 parent: Entity,
600 child: Entity,
601 rest_length: f32,
602 stiffness: f32,
603 damping: f32,
604) -> bool {
605 crate::physics::attach_spring(world, parent, child, rest_length, stiffness, damping).is_some()
606}
607
608#[cfg(feature = "physics")]
609fn attach_rope_command(
610 world: &mut World,
611 parent: Entity,
612 child: Entity,
613 max_distance: f32,
614) -> bool {
615 crate::physics::attach_rope(world, parent, child, max_distance).is_some()
616}
617
618#[cfg(feature = "picking")]
619#[derive(Serialize)]
620struct SurfacePickWire {
621 world_position: [f32; 3],
622 world_normal: [f32; 3],
623 depth: f32,
624 entity_id: Option<u32>,
625}
626
627#[cfg(feature = "picking")]
628fn take_surface_pick_command(world: &mut World) -> Option<SurfacePickWire> {
629 crate::picking::take_surface_pick(world).map(|result| SurfacePickWire {
630 world_position: [
631 result.world_position.x,
632 result.world_position.y,
633 result.world_position.z,
634 ],
635 world_normal: [
636 result.world_normal.x,
637 result.world_normal.y,
638 result.world_normal.z,
639 ],
640 depth: result.depth,
641 entity_id: result.entity_id,
642 })
643}
644
645fn save_scene_command(world: &mut World, name: &str) -> Vec<u8> {
646 crate::serialize::save_scene(world, name).unwrap_or_default()
647}
648
649fn load_scene_command(world: &mut World, bytes: &[u8]) -> Vec<Entity> {
650 crate::serialize::load_scene(world, bytes).unwrap_or_default()
651}
652
653fn key_from_name(name: &str) -> Option<KeyCode> {
657 let lower = name.to_ascii_lowercase();
658 Some(match lower.as_str() {
659 "a" => KeyCode::KeyA,
660 "b" => KeyCode::KeyB,
661 "c" => KeyCode::KeyC,
662 "d" => KeyCode::KeyD,
663 "e" => KeyCode::KeyE,
664 "f" => KeyCode::KeyF,
665 "g" => KeyCode::KeyG,
666 "h" => KeyCode::KeyH,
667 "i" => KeyCode::KeyI,
668 "j" => KeyCode::KeyJ,
669 "k" => KeyCode::KeyK,
670 "l" => KeyCode::KeyL,
671 "m" => KeyCode::KeyM,
672 "n" => KeyCode::KeyN,
673 "o" => KeyCode::KeyO,
674 "p" => KeyCode::KeyP,
675 "q" => KeyCode::KeyQ,
676 "r" => KeyCode::KeyR,
677 "s" => KeyCode::KeyS,
678 "t" => KeyCode::KeyT,
679 "u" => KeyCode::KeyU,
680 "v" => KeyCode::KeyV,
681 "w" => KeyCode::KeyW,
682 "x" => KeyCode::KeyX,
683 "y" => KeyCode::KeyY,
684 "z" => KeyCode::KeyZ,
685 "0" => KeyCode::Digit0,
686 "1" => KeyCode::Digit1,
687 "2" => KeyCode::Digit2,
688 "3" => KeyCode::Digit3,
689 "4" => KeyCode::Digit4,
690 "5" => KeyCode::Digit5,
691 "6" => KeyCode::Digit6,
692 "7" => KeyCode::Digit7,
693 "8" => KeyCode::Digit8,
694 "9" => KeyCode::Digit9,
695 "space" => KeyCode::Space,
696 "enter" | "return" => KeyCode::Enter,
697 "escape" | "esc" => KeyCode::Escape,
698 "tab" => KeyCode::Tab,
699 "backspace" => KeyCode::Backspace,
700 "delete" => KeyCode::Delete,
701 "left" => KeyCode::ArrowLeft,
702 "right" => KeyCode::ArrowRight,
703 "up" => KeyCode::ArrowUp,
704 "down" => KeyCode::ArrowDown,
705 "shift" | "lshift" => KeyCode::ShiftLeft,
706 "rshift" => KeyCode::ShiftRight,
707 "ctrl" | "control" | "lctrl" => KeyCode::ControlLeft,
708 "rctrl" => KeyCode::ControlRight,
709 "alt" | "lalt" => KeyCode::AltLeft,
710 "ralt" => KeyCode::AltRight,
711 _ => return None,
712 })
713}
714
715fn mouse_button_from_index(index: u8) -> MouseButton {
717 match index {
718 1 => MouseButton::Middle,
719 2 => MouseButton::Right,
720 _ => MouseButton::Left,
721 }
722}
723
724fn key_down_command(world: &World, key: &str) -> bool {
725 key_from_name(key)
726 .map(|key| crate::input::key_down(world, key))
727 .unwrap_or(false)
728}
729
730fn key_pressed_command(world: &World, key: &str) -> bool {
731 key_from_name(key)
732 .map(|key| crate::input::key_pressed(world, key))
733 .unwrap_or(false)
734}
735
736fn mouse_down_command(world: &World, button: u8) -> bool {
737 crate::input::mouse_down(world, mouse_button_from_index(button))
738}
739
740fn mouse_clicked_command(world: &World, button: u8) -> bool {
741 crate::input::mouse_clicked(world, mouse_button_from_index(button))
742}
743
744#[cfg(feature = "gamepad")]
745fn gamepad_button_down_command(world: &mut World, button: &str) -> bool {
746 crate::input::gamepad_button_from_name(button)
747 .map(|button| crate::input::gamepad_button_down(world, button))
748 .unwrap_or(false)
749}
750
751#[cfg(feature = "gamepad")]
752fn gamepad_button_pressed_command(world: &World, button: &str) -> bool {
753 crate::input::gamepad_button_from_name(button)
754 .map(|button| crate::input::gamepad_button_pressed(world, button))
755 .unwrap_or(false)
756}
757
758#[cfg(feature = "gamepad")]
759fn gamepad_axis_command(world: &mut World, axis: &str) -> f32 {
760 crate::input::gamepad_axis_from_name(axis)
761 .map(|axis| crate::input::gamepad_axis(world, axis))
762 .unwrap_or(0.0)
763}
764
765#[cfg(feature = "gamepad")]
766fn gamepad_left_stick_command(world: &mut World) -> nightshade::prelude::Vec3 {
767 let stick = crate::input::gamepad_left_stick(world);
768 nightshade::prelude::Vec3::new(stick.x, stick.y, 0.0)
769}
770
771#[cfg(feature = "gamepad")]
772fn gamepad_right_stick_command(world: &mut World) -> nightshade::prelude::Vec3 {
773 let stick = crate::input::gamepad_right_stick(world);
774 nightshade::prelude::Vec3::new(stick.x, stick.y, 0.0)
775}
776
777fn mouse_delta_command(world: &World) -> nightshade::prelude::Vec3 {
778 let delta = crate::input::mouse_delta(world);
779 nightshade::prelude::Vec3::new(delta.x, delta.y, 0.0)
780}
781
782fn touch_count_command(world: &World) -> i64 {
783 world
784 .res::<nightshade::ecs::input::resources::Input>()
785 .touch
786 .touches
787 .len() as i64
788}
789
790fn touches_command(world: &World) -> enum2schema::serde_json::Value {
791 let points: Vec<enum2schema::serde_json::Value> = world
792 .res::<nightshade::ecs::input::resources::Input>()
793 .touch
794 .touches
795 .values()
796 .map(|point| {
797 enum2schema::serde_json::json!({
798 "id": point.id,
799 "x": point.position.x,
800 "y": point.position.y,
801 })
802 })
803 .collect();
804 enum2schema::serde_json::Value::Array(points)
805}
806
807macro_rules! bind_argument {
808 ($field:ident, entity, $produced:ident, $world:ident) => {
809 let $field = match resolve($world, *$field, $produced) {
810 Some(entity) => entity,
811 None => {
812 return CommandReply::Error(
813 concat!(stringify!($field), ": unresolved entity reference").to_string(),
814 );
815 }
816 };
817 };
818 ($field:ident, opt_entity, $produced:ident, $world:ident) => {
819 let $field = match $field {
820 Some(reference) => match resolve($world, *reference, $produced) {
821 Some(entity) => Some(entity),
822 None => {
823 return CommandReply::Error(
824 concat!(stringify!($field), ": unresolved entity reference").to_string(),
825 );
826 }
827 },
828 None => None,
829 };
830 };
831 ($field:ident, vec3, $produced:ident, $world:ident) => {
832 let $field = array_to_vec3(*$field);
833 };
834 ($field:ident, vec2, $produced:ident, $world:ident) => {
835 let $field = array_to_vec2(*$field);
836 };
837 ($field:ident, copy, $produced:ident, $world:ident) => {
838 let $field = *$field;
839 };
840 ($field:ident, owned, $produced:ident, $world:ident) => {
841 let $field = $field.clone();
842 };
843 ($field:ident, text, $produced:ident, $world:ident) => {
844 let $field = $field.as_str();
845 };
846 ($field:ident, bytes, $produced:ident, $world:ident) => {
847 let $field = $field.as_slice();
848 };
849 ($field:ident, strs, $produced:ident, $world:ident) => {
850 let $field: Vec<&str> = $field.iter().map(|value| value.as_str()).collect();
851 let $field = $field.as_slice();
852 };
853 ($field:ident, vec3_list, $produced:ident, $world:ident) => {
854 let $field: Vec<Vec3> = $field.iter().map(|value| array_to_vec3(*value)).collect();
855 let $field = $field.as_slice();
856 };
857 ($field:ident, floats, $produced:ident, $world:ident) => {
858 let $field = $field.as_slice();
859 };
860 ($field:ident, indices, $produced:ident, $world:ident) => {
861 let $field: Vec<usize> = $field.iter().map(|value| *value as usize).collect();
862 let $field = $field.as_slice();
863 };
864 ($field:ident, opt_vec3, $produced:ident, $world:ident) => {
865 let $field = (*$field).map(array_to_vec3);
866 };
867 ($field:ident, transforms, $produced:ident, $world:ident) => {
868 let $field: Vec<nightshade::prelude::InstanceTransform> =
869 $field.iter().map(instance_from_wire).collect();
870 };
871 ($field:ident, refs, $produced:ident, $world:ident) => {
872 let mut resolved = Vec::with_capacity($field.len());
873 for reference in $field.iter() {
874 match resolve($world, *reference, $produced) {
875 Some(entity) => resolved.push(entity),
876 None => {
877 return CommandReply::Error(
878 concat!(stringify!($field), ": unresolved entity reference").to_string(),
879 );
880 }
881 }
882 }
883 let $field = resolved.as_slice();
884 };
885}
886
887macro_rules! wrap_reply {
888 (none, $call:expr) => {{
889 $call;
890 CommandReply::None
891 }};
892 (entity, $call:expr) => {
893 CommandReply::Entity($call)
894 };
895 (opt_entity, $call:expr) => {
896 match $call {
897 Some(entity) => CommandReply::Entity(entity),
898 None => CommandReply::None,
899 }
900 };
901 (bool, $call:expr) => {
902 CommandReply::Bool($call)
903 };
904 (float, $call:expr) => {
905 CommandReply::Float($call)
906 };
907 (vector, $call:expr) => {{
908 let value = $call;
909 CommandReply::Vector([value.x, value.y, value.z])
910 }};
911 (opt_vector, $call:expr) => {
912 match $call {
913 Some(value) => CommandReply::Vector([value.x, value.y, value.z]),
914 None => CommandReply::None,
915 }
916 };
917 (entities, $call:expr) => {
918 CommandReply::Entities($call)
919 };
920 (strings, $call:expr) => {
921 CommandReply::Strings($call)
922 };
923 (int, $call:expr) => {
924 CommandReply::Int($call as i64)
925 };
926 (text, $call:expr) => {
927 CommandReply::Text($call)
928 };
929 (bytes, $call:expr) => {
930 CommandReply::Bytes($call)
931 };
932 (json, $call:expr) => {
933 CommandReply::Json(
934 enum2schema::serde_json::to_value($call)
935 .unwrap_or(enum2schema::serde_json::Value::Null),
936 )
937 };
938}
939
940#[cfg(feature = "scripting")]
947pub fn command_method_name(variant: &str) -> String {
948 let characters: Vec<char> = variant.chars().collect();
949 let mut name = String::new();
950 for index in 0..characters.len() {
951 let character = characters[index];
952 if character.is_uppercase() {
953 let previous_lower = index > 0
954 && (characters[index - 1].is_lowercase() || characters[index - 1].is_ascii_digit());
955 let previous_upper = index > 0 && characters[index - 1].is_uppercase();
956 let next_lower = index + 1 < characters.len() && characters[index + 1].is_lowercase();
957 if index != 0 && (previous_lower || (previous_upper && next_lower)) {
958 name.push('_');
959 }
960 name.extend(character.to_lowercase());
961 } else if character.is_ascii_digit() {
962 if index > 0 && characters[index - 1].is_alphabetic() {
963 name.push('_');
964 }
965 name.push(character);
966 } else {
967 name.push(character);
968 }
969 }
970 name
971}
972
973#[cfg(feature = "scripting")]
977fn command_method_map(name: &str, pairs: Vec<(&'static str, rhai::Dynamic)>) -> rhai::Dynamic {
978 let mut fields = rhai::Map::new();
979 for (key, value) in pairs {
980 fields.insert(key.into(), value);
981 }
982 let mut outer = rhai::Map::new();
983 outer.insert(name.into(), rhai::Dynamic::from_map(fields));
984 rhai::Dynamic::from_map(outer)
985}
986
987fn command_description(variant: &str) -> &'static str {
992 match variant {
993 "SpawnCube" => "Spawn a cube at the given position",
994 "SpawnSphere" => "Spawn a sphere at the given position",
995 "SpawnCylinder" => "Spawn a cylinder at the given position",
996 "SpawnCone" => "Spawn a cone at the given position",
997 "SpawnPlane" => "Spawn a plane at the given position",
998 "SpawnTorus" => "Spawn a torus at the given position",
999 "SpawnFloor" => "Spawn a flat ground plane reaching the half extent in each direction",
1000 "SpawnGroup" => "Spawn an invisible group at a position for building hierarchies",
1001 "SpawnModel" => "Spawn a glb model with its textures, materials, skins, and animations",
1002 "SpawnCustomMesh" => "Spawn an entity rendering a mesh built from raw vertex data",
1003 "RegisterPrefab" => "Capture an entity subtree as a named reusable prefab",
1004 "SpawnPrefabNamed" => "Stamp a copy of a named prefab at a position",
1005 "SpawnObject" => "Spawn an object with mesh, color, and optional physics body in one call",
1006 "SetColor" => "Set the entity's base color as linear RGBA",
1007 "SetMetallicRoughness" => "Set the entity's metallic and roughness factors",
1008 "SetEmissive" => "Make the entity glow with the given color and strength",
1009 "SetUnlit" => "Disable lighting on the entity so its color renders as is",
1010 "SetTexture" => "Set the entity's base color texture by name",
1011 "SetTextureTiling" => {
1012 "Tile the entity's base color texture the given number of times per axis"
1013 }
1014 "SetNormalTexture" => "Set the entity's normal map by texture name",
1015 "SetMetallicRoughnessTexture" => {
1016 "Set the entity's metallic and roughness map by texture name"
1017 }
1018 "SetEmissiveTexture" => "Set the entity's emissive map by texture name",
1019 "SetOcclusionTexture" => "Set the entity's ambient occlusion map by texture name",
1020 "SetPosition" => "Set the entity's position in its parent's space",
1021 "SetScale" => "Set the entity's scale",
1022 "SetRotation" => "Replace the entity's rotation with the given angle around an axis",
1023 "Rotate" => "Rotate the entity around an axis on top of its current rotation",
1024 "Position" => "Get the entity's position in world space",
1025 "SetParent" => "Parent a child to a parent or unparent it, keeping its world position",
1026 "SetVisible" => "Show or hide the entity without despawning it",
1027 "Despawn" => "Despawn the entity and its descendants",
1028 "Tag" => "Tag the entity with a label",
1029 "Untag" => "Remove a label from the entity",
1030 "HasTag" => "Check whether the entity carries the label",
1031 "QueryTagged" => "Get every entity carrying the label",
1032 "PointLight" => "Add a point light at the given position",
1033 "SpotLight" => "Add a shadow casting spot light aimed at a target",
1034 "SetSun" => "Adjust the default sun's color and intensity",
1035 "SetBackground" => "Set the scene background",
1036 "ShowGrid" => "Show or hide the reference grid",
1037 "SetAmbient" => "Set the ambient light color as linear RGBA",
1038 "SetClearColor" => "Set the background clear color as linear RGBA",
1039 "SetBloom" => "Toggle bloom",
1040 "SetBloomIntensity" => "Set the bloom strength",
1041 "SetSsao" => "Toggle screen-space ambient occlusion",
1042 "SetSsr" => "Toggle screen-space reflections on glossy surfaces",
1043 "SetSsgi" => "Toggle screen-space global illumination",
1044 "SetTaa" => "Toggle temporal antialiasing",
1045 "SetExposure" => "Set the manual exposure multiplier",
1046 "SetColorGrading" => "Set saturation, contrast, and brightness color grading",
1047 "SetTimeOfDay" => "Set the hour of the day from 0 to 24",
1048 "SetTimeScale" => "Set the global time scale: 0.5 for slow motion, 2.0 for fast forward",
1049 "TimeScale" => "Get the current global time scale",
1050 "Pause" => "Pause game time so scaled delta time reports zero",
1051 "Unpause" => "Resume game time after a pause",
1052 "SetPaused" => "Set whether game time is paused",
1053 "IsPaused" => "Whether game time is currently paused",
1054 "EmitFire" => "Emit a continuous fire at the given position",
1055 "EmitSmoke" => "Emit a continuous smoke column at the given position",
1056 "EmitBurst" => "Emit a one-shot burst of colored particles at the given position",
1057 "DrawCube" => "Draw a cube with the given size and color for one frame",
1058 "DrawSphere" => "Draw a sphere with the given radius and color for one frame",
1059 "DrawCylinder" => "Draw an upright cylinder with the given size and color for one frame",
1060 "DrawCone" => "Draw an upright cone with the given size and color for one frame",
1061 "DrawTorus" => "Draw a flat torus with the given size and color for one frame",
1062 "DrawLine" => "Draw a line from start to end for one frame",
1063 "DrawText3d" => "Draw billboard text at a 3d position for one frame",
1064 "SpawnLabel" => "Spawn 3d text at a position that always faces the camera",
1065 "SpawnText" => "Spawn screen text at the given anchor",
1066 "SetText" => "Replace the content of a text entity",
1067 "SetTextColor" => "Set a text entity's color as linear RGBA",
1068 "SetTextSize" => "Set a text entity's font size",
1069 "SpawnPanel" => {
1070 "Spawn an empty panel anchored to a window corner or center, sized in pixels"
1071 }
1072 "PanelLabel" => "Add a line of text to a panel",
1073 "PanelButton" => "Add a themed button to a panel",
1074 "ButtonClicked" => "Check whether the button was clicked this frame",
1075 "ButtonHovered" => "Check whether the pointer is over the button",
1076 "DespawnPanel" => "Remove a panel and everything in it",
1077 "PanelRow" => "Add a horizontal row to a panel",
1078 "PanelGrid" => "Add a fixed-column grid to a panel",
1079 "PanelScroll" => "Add a scrollable region to a panel and return its content container",
1080 "SetScrollOffset" => "Scroll a panel-scroll region to a pixel offset from the top",
1081 "SetFocusOrder" => "Set a widget's keyboard focus order",
1082 "FocusWidget" => "Give keyboard focus to a widget immediately",
1083 "SpawnPanelAt" => {
1084 "Spawn a panel at any of the nine screen positions with a pixel offset and size"
1085 }
1086 "PanelText" => "Add a text label to a parent in a pixel rectangle with alignment",
1087 "PanelBox" => "Add a solid colored rectangle to a parent at a pixel offset and size",
1088 "PanelButtonAt" => "Add an interactive button to a parent at a pixel offset and size",
1089 "SetPanelRect" => "Reposition and resize a UI node within its parent, in pixels",
1090 "SetPanelColor" => "Set a UI node's background color as linear RGBA",
1091 "SetPanelText" => "Replace a panel text label's content",
1092 "SetPanelTextColor" => "Recolor a panel text label",
1093 "SetPanelSelected" => "Toggle a button's selected highlight with an accent tint",
1094 "SetPanelVisible" => "Show or hide a UI node and its children",
1095 "PlayAnimation" => "Start playing the model's animation clip at the given index",
1096 "PlayAnimationNamed" => "Play the animation clip with the given name",
1097 "SetAnimationLooping" => "Set whether the entity's current animation repeats",
1098 "SetAnimationSpeed" => "Set the playback speed of the entity's animation",
1099 "BlendToAnimation" => "Cross-fade from the current clip to another over the given seconds",
1100 "PauseAnimation" => "Pause the entity's animation at the current frame",
1101 "ResumeAnimation" => "Resume a paused animation from where it left off",
1102 "StopAnimation" => "Stop the entity's animation and reset it to the first frame",
1103 "AnimationClips" => "Get the names of the entity's animation clips in index order",
1104 "AddAnimationEvent" => "Add a named marker at a time on the clip at the given index",
1105 "AddAnimationEventNamed" => "Add a named marker at a time on the clip with the given name",
1106 "SetAnimationLayerWeight" => "Set the blend weight of an animation layer",
1107 "ClearAnimationLayers" => "Remove every animation layer, leaving only the base animation",
1108 "AimAt" => "Point a bone's forward axis at a world target, recomputed each frame",
1109 "OrbitCamera" => "Add an orbit camera focused on a point at the given radius",
1110 "FlyCamera" => "Add a free-flying camera at the given position",
1111 "FixedCamera" => "Add a stationary camera at an eye looking at a target",
1112 "LookAt" => "Repoint the active camera to look at a target from an eye",
1113 "SetOrbitFocus" => "Move the orbit camera's focus point",
1114 "SetOrbitView" => "Set the orbit camera's focus, distance, yaw, and pitch at once",
1115 "SetOrbitZoom" => "Enable or disable scroll-wheel zoom on the orbit camera",
1116 "SetOrbitModifier" => {
1117 "Set the modifier key the orbit camera requires before drag orbits, or clear it"
1118 }
1119 "SetFieldOfView" => "Set the active perspective camera's vertical field of view in degrees",
1120 "SetOrthographic" => "Switch the active camera to an orthographic projection",
1121 "SetPerspective" => "Switch the active camera to a perspective projection",
1122 "CameraPosition" => "Get the active camera's world position",
1123 "CameraForward" => "Get the active camera's forward direction as a unit vector",
1124 "FirstPerson" => {
1125 "Add a walking first-person player with mouse look, WASD, sprint, and jump"
1126 }
1127 "DeltaTime" => "Get the seconds the previous frame took",
1128 "ElapsedSeconds" => "Get the seconds since the app started",
1129 "KeyDown" => "Check whether a key is held down",
1130 "KeyPressed" => "Check whether a key went down this frame",
1131 "MouseDown" => "Check whether a mouse button is held down",
1132 "MouseClicked" => "Check whether a mouse button went down this frame",
1133 "Wasd" => "Get the WASD movement direction on the ground plane",
1134 "PointerOverUi" => "Check whether the pointer is over a UI element this frame",
1135 "MouseScroll" => "Get the scroll wheel delta this frame",
1136 "MouseDelta" => "Get the pointer movement this frame as a vector",
1137 "TouchCount" => "Get the number of active touch points",
1138 "Touches" => "Get the active touch points as [{ id, x, y }]",
1139 "GamepadButtonDown" => "Check whether a named gamepad button is held down",
1140 "GamepadButtonPressed" => "Check whether a named gamepad button went down this frame",
1141 "GamepadAxis" => "Get a named gamepad axis value, about -1 to 1",
1142 "GamepadLeftStick" => "Get the gamepad left stick as a vector",
1143 "GamepadRightStick" => "Get the gamepad right stick as a vector",
1144 "GamepadConnected" => "Check whether a gamepad is connected",
1145 "Push" => "Apply an instant impulse to a dynamic entity",
1146 "SetVelocity" => "Set a dynamic body's linear velocity directly",
1147 "ApplyForce" => "Apply a continuous force to a dynamic entity for this step",
1148 "ApplyTorque" => "Apply a continuous torque to a dynamic entity for this step",
1149 "SetAngularVelocity" => "Set a dynamic body's angular velocity directly",
1150 "Velocity" => "Get the entity's current linear velocity if it has a body",
1151 "AngularVelocity" => "Get the entity's current angular velocity if it has a body",
1152 "MakeSensor" => "Turn the entity's collider into an overlap-reporting sensor",
1153 "OverlapSphere" => "Get every entity whose collider overlaps a sphere",
1154 "SetCollisionGroups" => "Set the entity collider's membership and filter collision masks",
1155 "SetFriction" => "Set an entity collider's friction",
1156 "SetRestitution" => "Set an entity collider's restitution or bounciness",
1157 "SetLinearDamping" => "Set a dynamic body's linear damping",
1158 "SetAngularDamping" => "Set a dynamic body's angular damping",
1159 "SetMass" => "Set a dynamic body's mass in kilograms",
1160 "SetGravityScale" => "Set a body's per-body gravity multiplier",
1161 "BakeNavmesh" => "Bake a navmesh over the current static geometry",
1162 "SpawnWalker" => "Spawn a navmesh agent that walks to ordered destinations",
1163 "WalkTo" => "Order an agent to walk to a destination along the navmesh",
1164 "SetWalkSpeed" => "Set an agent's walk speed in units per second",
1165 "StopWalking" => "Stop an agent where it stands and clear its path",
1166 "ClickedEntity" => "Get the entity clicked this frame, if any",
1167 "EntityUnderCursor" => "Get the entity currently under the cursor, if any",
1168 "CursorOnGround" => "Get where the cursor ray meets the ground plane, if it does",
1169 "SpawnWorldPanel" => "Spawn a flat panel in the 3d world facing the camera",
1170 "WorldPanelButton" => "Add a button to a world panel at local coordinates",
1171 "WorldPanelLabel" => "Add a text label to a world panel at local coordinates",
1172 "WorldButtonClicked" => "Check whether a world-panel button was clicked this frame",
1173 "PauseSound" => "Pause a playing sound, keeping its position",
1174 "ResumeSound" => "Resume a paused sound from where it stopped",
1175 "FadeVolume" => "Fade a playing sound to a target volume over the given seconds",
1176 "Crossfade" => "Crossfade between two sounds over the given seconds",
1177 "SetBusVolume" => "Set an audio bus's volume in decibels, fading over seconds",
1178 "DuckVoice" => "Duck the music and ambient buses under the voice bus",
1179 "DirectionalLight" => {
1180 "Add a directional light shining along a direction, like a second sun"
1181 }
1182 "AreaLight" => "Add a rectangular area light, a glowing panel facing a target",
1183 "SetLightShadows" => "Turn shadow casting on or off for a light entity",
1184 "EmitSparks" => "Emit a continuous fountain of bright sparks at the given position",
1185 "EmitFirework" => "Launch a firework shell that arcs and bursts on its own",
1186 "EmitParticles" => "Spawn a configurable continuous emitter at the given position",
1187 "SetAlphaBlend" => "Turn alpha blending on or off for the entity",
1188 "SetAlphaCutoff" => "Switch the entity to alpha cutout, discarding texels below the cutoff",
1189 "SetDoubleSided" => "Render both faces of the entity's triangles",
1190 "SetIor" => "Set the index of refraction for the entity's surface",
1191 "SetTransmission" => "Set how much light passes through the entity",
1192 "SetClearcoat" => "Add a clearcoat layer over the entity with its own factor and roughness",
1193 "SetAnisotropy" => "Set anisotropic reflection stretching highlights along a rotation",
1194 "SetUvTransform" => "Transform the entity's base color texture coordinates",
1195 "SetSheen" => "Add a soft retroreflective sheen tint to the entity",
1196 "SetIridescence" => "Add a thin-film iridescence to the entity",
1197 "SetSpecular" => "Set the entity's specular reflectance factor and tint",
1198 "SetNormalScale" => "Scale the strength of the entity's normal map",
1199 "SetOcclusionStrength" => {
1200 "Scale how strongly the entity's ambient occlusion map darkens it"
1201 }
1202 "SetEmissiveStrength" => "Set the entity's emissive strength on its own",
1203 "SetThickness" => "Set the volume thickness of a transmissive entity",
1204 "SetTextOutline" => "Set a 3d text or label entity's outline width and color",
1205 "SetMorphWeight" => "Set one morph target's weight on an entity by index",
1206 "SetWindowTitle" => "Set the OS window title",
1207 "LockCursor" => "Lock the cursor to the window for mouse-look, or release it",
1208 "RequestExit" => "Ask the app to exit at the end of the frame",
1209 "SetRenderLayer" => "Put an entity on a render layer cameras can selectively show",
1210 "SetCameraLayers" => "Set which render layers a camera sees as a bitmask",
1211 "ThirdPersonCamera" => "Add a third-person camera trailing a target at a distance",
1212 "SpawnCloth" => "Spawn a cloth grid hanging from a position, pinned along its top edge",
1213 "ResetCloth" => "Reset a cloth back to its spawned shape, clearing all motion",
1214 "SetWind" => "Set a global wind force on all cloth",
1215 "PauseCutscene" => "Pause the running cutscene timeline",
1216 "ResumeCutscene" => "Resume a paused cutscene",
1217 "StopCutscene" => "Stop the cutscene and clear it",
1218 "SeekCutscene" => "Jump the cutscene to the given time along its timeline",
1219 "SetCutsceneCamera" => "Set the camera the cutscene drives",
1220 "BindCutsceneActor" => "Bind a named cutscene track to an entity it animates",
1221 "SpawnCylinderBody" => "Spawn a dynamic cylinder physics body at the given position",
1222 "SpawnCapsuleBody" => "Spawn a dynamic capsule physics body at the given position",
1223 "SetControllerSpeed" => "Set a character controller's move speed in units per second",
1224 "SetControllerJump" => "Set a character controller's jump impulse",
1225 "IsGrounded" => "Check whether a character controller is grounded this frame",
1226 "EnableTerrain" => "Turn on procedural terrain seeded by the given value",
1227 "DisableTerrain" => "Turn off procedural terrain",
1228 "SetTerrainHeightRange" => "Set the terrain's minimum and maximum height in world units",
1229 "SetTerrainSnowHeight" => "Set the elevation above which terrain turns to snow",
1230 "EnableGrass" => "Turn on procedural grass over the terrain",
1231 "DisableGrass" => "Turn off procedural grass",
1232 "LoadTexture" => "Register a texture from encoded png or jpeg bytes",
1233 "LoadTextureLinear" => {
1234 "Register a linear-space texture for normal, metallic-roughness, or occlusion data"
1235 }
1236 "RegisterTexture" => "Register a texture from raw RGBA8 pixels",
1237 "ListMaterials" => "Get every registered material name",
1238 "Material" => "Get a registered material's properties by name",
1239 "RegisterMaterial" => "Register a named material in the shared registry",
1240 "UpdateMaterial" => "Replace the material registered under a name and reupload it",
1241 "SetMaterialVariant" => "Activate a material variant by name across the scene",
1242 "SpawnObjects" => "Spawn one object at each position, all sharing one material",
1243 "SpawnInstanced" => {
1244 "Spawn one entity rendering many copies of a shape in a single draw call"
1245 }
1246 "SpawnInstancedWithMaterial" => {
1247 "Spawn an instanced mesh drawing every transform with a named material"
1248 }
1249 "SetInstances" => {
1250 "Replace an instanced batch's transforms and reupload its instance buffer"
1251 }
1252 "SpawnClothSheet" => "Spawn a simulated cloth sheet hanging from a position, pinned on top",
1253 "BlendToAnimationNamed" => "Cross-fade to a named clip over the given seconds",
1254 "AddAnimationLayer" => "Play an extra clip on top of the base animation at a weight",
1255 "NameEntity" => "Register an entity under a name so it can be looked up",
1256 "Name" => "Get the entity's name, or a stable fallback when it has none",
1257 "SetEntityName" => "Rename the entity",
1258 "Children" => "Get the entity's direct children in id order",
1259 "Descendants" => "Get the entity's whole subtree below it",
1260 "Roots" => "Get every parentless transform entity in id order",
1261 "SceneTree" => "Get the whole transform hierarchy flattened depth-first",
1262 "MaterialOf" => "Get the entity's resolved material as json",
1263 "Color" => "Get the entity's base color as linear RGBA",
1264 "MetallicRoughness" => "Get the entity's metallic and roughness factors",
1265 "Emissive" => "Get the entity's emissive color and strength",
1266 "Unlit" => "Check whether the entity renders unlit",
1267 "Texture" => "Get the entity's base color texture name, if any",
1268 "DescribeEntity" => "Get a summary of the entity as json",
1269 "SetTextAlignment" => "Set the horizontal alignment of a 3d text or label entity",
1270 "SetMorphWeights" => "Set every morph weight at once in target order",
1271 "MorphWeight" => "Get one morph target's weight by index",
1272 "MorphTargetCount" => "Get the number of morph targets the entity has",
1273 "SetFog" => "Enable distance fog between a start and end, or disable it",
1274 "SetFogMode" => {
1275 "Set the fog density falloff: 1 linear, 2 exponential, 3 exponential squared"
1276 }
1277 "SetDepthOfField" => "Set depth of field focus and blur parameters",
1278 "Screenshot" => "Save a screenshot of the next rendered frame to a path as png",
1279 "SetShadingMode" => "Set the active camera's shading mode",
1280 "AnimatePosition" => "Glide the entity to a target position over the given seconds",
1281 "AnimateScale" => "Grow or shrink the entity to a target scale over the given seconds",
1282 "AnimateColor" => "Fade the entity's base color to a target over the given seconds",
1283 "ShakeCamera" => "Shake the camera for a duration at the given strength",
1284 "ReachTo" => "Bend a three-joint chain so the tip reaches a world target",
1285 "Bounds" => "Get an entity's world-space bounding box as json",
1286 "BoundsOf" => "Get the combined world-space bounding box of several entities",
1287 "FrameEntities" => "Frame the given entities in view, moving the camera to fit them",
1288 "SpawnParticleEmitter" => "Spawn a fully configured particle emitter",
1289 "SetEmitter" => "Replace a spawned emitter's configuration",
1290 "SpawnDecal" => "Project a texture onto the surface at a position facing a normal",
1291 "SaveScene" => "Capture the world to a self-contained compressed binary scene",
1292 "LoadScene" => "Spawn a saved scene into the world and return its root entities",
1293 "WindowSize" => "Get the window's inner size in physical pixels",
1294 "CursorLocked" => "Check whether the cursor is currently locked",
1295 "FramesPerSecond" => "Get the current frames per second",
1296 "FrameCount" => "Get the number of frames rendered since startup",
1297 "UptimeMilliseconds" => "Get the milliseconds since the app started",
1298 "BakeNavmeshWith" => "Bake the navmesh with an explicit Recast configuration",
1299 "Raycast" => "Cast a ray against all physics colliders and return the closest hit",
1300 "AttachFixed" => "Weld two bodies together rigidly",
1301 "AttachHinge" => "Hinge two bodies around an axis",
1302 "AttachSpring" => "Connect two bodies with a spring",
1303 "AttachRope" => "Tether two bodies with a maximum separation",
1304 "ControllerVelocity" => "Get a character controller's current velocity",
1305 "MoveCharacter" => "Move a character controller this frame with input and a jump flag",
1306 "RequestSurfacePick" => "Request a precise GPU surface pick at a screen position",
1307 "TakeSurfacePick" => "Take the result of a requested surface pick, if ready",
1308 "WorldButtonHovered" => "Check whether the pointer is over a world-panel button this frame",
1309 "LoadSound" => "Register a sound from encoded audio file bytes",
1310 "PlaySound" => "Play a loaded sound once and return its voice entity",
1311 "PlaySoundLooping" => "Play a loaded sound on a loop and return its voice entity",
1312 "PlaySoundAt" => "Play a loaded sound once at a world position, attenuating with distance",
1313 "SetVolume" => "Set a playing sound's volume",
1314 "StopSound" => "Stop a sound and free its voice",
1315 "SetPitch" => "Set a sound's pitch and speed multiplier",
1316 "SetSpatialDistance" => "Set the distance range over which a spatial sound fades",
1317 "PanelCheckbox" => "Add a labeled checkbox to a panel",
1318 "CheckboxValue" => "Get the checkbox's current on or off value",
1319 "PanelSlider" => "Add a slider from min to max starting at an initial value to a panel",
1320 "SliderValue" => "Get the slider's current value",
1321 "SetSliderValue" => "Set the slider's value",
1322 "PanelTextInput" => "Add a single-line text input with placeholder text to a panel",
1323 "TextInputChanged" => "Get the input's new contents if it changed this frame",
1324 "PanelDropdown" => "Add a dropdown of options with an initial selection to a panel",
1325 "DropdownSelected" => "Get the newly chosen index if the dropdown changed this frame",
1326 "PanelProgressBar" => "Add a progress bar filled to an initial value to a panel",
1327 "SetProgress" => "Set a progress bar's fill from 0 to 1",
1328 "PanelToggle" => "Add an on or off toggle to a panel",
1329 "ToggleValue" => "Get the toggle's current on or off value",
1330 "PanelRadio" => "Add a radio button to a panel as one option of a group",
1331 "RadioSelected" => "Get the selected option index of a radio group, if any",
1332 "PanelRangeSlider" => "Add a dual-handle range slider to a panel",
1333 "SetRange" => "Set both handles of a range slider",
1334 "PanelTabs" => "Add a tab bar of labels to a panel with an initial selection",
1335 "SetTab" => "Select a tab bar's active tab by index",
1336 "PanelCollapsing" => "Add a collapsing section to a panel and return its content container",
1337 "PanelColorPicker" => "Add a color picker to a panel starting at a linear RGBA color",
1338 "ColorValue" => "Get the color picker's current color as linear RGBA",
1339 "PanelTextArea" => "Add a multi-line text area with placeholder text to a panel",
1340 "PanelTextAreaWithValue" => {
1341 "Add a multi-line text area pre-filled with an initial value to a panel"
1342 }
1343 "SetTextArea" => "Replace a text area's contents",
1344 "PanelMultiSelect" => "Add a multi-select chip list of options to a panel",
1345 "SetMultiSelect" => "Set a multi-select's chosen option indices",
1346 "PanelDatePicker" => "Add a date picker starting at a given date to a panel",
1347 "SetDate" => "Set a date picker's value",
1348 "PanelMenu" => "Add a dropdown menu listing items to a panel",
1349 "PanelColorPickerHsv" => {
1350 "Add an HSV color picker starting at a linear RGBA color to a panel"
1351 }
1352 "PanelSplitter" => "Add a draggable splitter dividing a panel into two resizable panes",
1353 "PanelBreadcrumb" => "Add a breadcrumb trail of segments to a panel",
1354 "PanelVirtualList" => "Add a virtual list that recycles a pool of rows to a panel",
1355 "PanelTable" => "Add a simple table with column headers and widths to a panel",
1356 "PanelDataGrid" => "Add a sortable, selectable data grid to a panel",
1357 "SetDataGridRows" => "Set a data grid's total row count",
1358 "SetDataGridCell" => "Set the text of a single data grid cell",
1359 "DataGridSelectionChanged" => {
1360 "Check whether the data grid's row selection changed this frame"
1361 }
1362 "PanelCommandPalette" => "Add a searchable command palette overlay to a panel",
1363 "PanelPropertyGrid" => "Add a two-column label and value property grid to a panel",
1364 "PanelPropertyRow" => "Add a labeled row to a property grid and return its value cell",
1365 "PanelTreeView" => "Add a tree view to a panel",
1366 "TreeContent" => "Get the root container of a tree view for adding top-level nodes",
1367 "TreeNode" => "Add a node at a depth under a container in a tree view",
1368 "TreeNodeChildren" => "Get the container a node's children go into for nesting",
1369 "SetTreeNodeExpanded" => "Expand or collapse a tree node",
1370 "TreeViewSelected" => "Get the entities of the currently selected tree nodes",
1371 "PanelDragValue" => "Add a numeric drag value field that scrubs as you drag to a panel",
1372 "DragValue" => "Get the drag value's current value",
1373 "PanelSelectable" => "Add a selectable label to a panel",
1374 "PanelModal" => "Add a centered modal dialog to a panel and return its content container",
1375 "PanelSpinner" => "Add an animated loading spinner to a panel",
1376 "PanelSeparator" => "Add a thin horizontal divider line to a panel",
1377 "PanelHeading" => "Add a larger heading-styled line of text to a panel",
1378 "SaveSceneToFile" => "Serialize the scene and write it to a file path (native only)",
1379 "LoadSceneFromFile" => {
1380 "Read a scene file from a path and spawn it, replying its root entities (native only)"
1381 }
1382 _ => "",
1383 }
1384}
1385
1386macro_rules! commands {
1387 (
1388 $(
1389 $(#[$meta:meta])*
1390 $variant:ident { $( $field:ident : $field_type:ty [$role:ident] ),* $(,)? }
1391 => $func:path , $reply:ident ;
1392 )*
1393 ) => {
1394 #[derive(Serialize, Deserialize, Clone, Debug, enum2schema::Schema)]
1398 pub enum Command {
1399 $(
1400 $(#[$meta])*
1401 $variant { $( $field : $field_type ),* },
1402 )*
1403 }
1404
1405 impl Command {
1406 pub fn name(&self) -> &'static str {
1409 match self {
1410 $(
1411 $(#[$meta])*
1412 Command::$variant { .. } => stringify!($variant),
1413 )*
1414 }
1415 }
1416 }
1417
1418 fn dispatch(
1419 world: &mut World,
1420 command: &Command,
1421 produced: &[Option<Entity>],
1422 ) -> CommandReply {
1423 match command {
1424 $(
1425 $(#[$meta])*
1426 Command::$variant { $( $field ),* } => {
1427 $( bind_argument!($field, $role, produced, world); )*
1428 wrap_reply!($reply, $func(world $(, $field)*))
1429 }
1430 )*
1431 }
1432 }
1433
1434 pub fn command_manifest() -> Vec<CommandSpec> {
1439 let mut specs = Vec::new();
1440 $(
1441 $(#[$meta])*
1442 specs.extend([CommandSpec {
1443 name: stringify!($variant),
1444 fields: vec![
1445 $( FieldSpec {
1446 name: stringify!($field),
1447 type_name: stringify!($field_type),
1448 role: stringify!($role),
1449 } ),*
1450 ],
1451 reply: stringify!($reply),
1452 description: command_description(stringify!($variant)),
1453 }]);
1454 )*
1455 specs
1456 }
1457
1458 #[cfg(feature = "scripting")]
1465 pub fn register_command_methods(engine: &mut rhai::Engine) {
1466 $(
1467 $(#[$meta])*
1468 engine.register_fn(
1469 command_method_name(stringify!($variant)),
1470 |commands: &mut rhai::Array $(, $field: rhai::Dynamic )*| {
1471 commands.push(command_method_map(
1472 stringify!($variant),
1473 vec![ $( (stringify!($field), $field) ),* ],
1474 ));
1475 },
1476 );
1477 )*
1478 }
1479 };
1480}
1481
1482commands! {
1483 SpawnCube { position: [f32; 3] [vec3] } => crate::scene::spawn_cube, entity;
1484 SpawnSphere { position: [f32; 3] [vec3] } => crate::scene::spawn_sphere, entity;
1485 SpawnCylinder { position: [f32; 3] [vec3] } => crate::scene::spawn_cylinder, entity;
1486 SpawnCone { position: [f32; 3] [vec3] } => crate::scene::spawn_cone, entity;
1487 SpawnPlane { position: [f32; 3] [vec3] } => crate::scene::spawn_plane, entity;
1488 SpawnTorus { position: [f32; 3] [vec3] } => crate::scene::spawn_torus, entity;
1489 SpawnFloor { half_extent: f32 [copy] } => crate::scene::spawn_floor, entity;
1490 SpawnGroup { position: [f32; 3] [vec3] } => crate::scene::spawn_group, entity;
1491 SpawnModel { glb: Vec<u8> [bytes], position: [f32; 3] [vec3] } => crate::scene::spawn_model, entity;
1492 SpawnCustomMesh { name: String [text], positions: Vec<f32> [floats], normals: Vec<f32> [floats], uvs: Vec<f32> [floats], indices: Vec<u32> [indices], position: [f32; 3] [vec3] } => spawn_custom_mesh_command, entity;
1493 RegisterPrefab { name: String [text], root: Ref [entity] } => crate::prefab::register_prefab_command, bool;
1494 SpawnPrefabNamed { name: String [text], position: [f32; 3] [vec3] } => crate::prefab::spawn_prefab_named_command, entities;
1495 SpawnObject { shape: Shape [copy], position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy], body: Body [copy] } => spawn_object_command, entity;
1496
1497 SetColor { entity: Ref [entity], color: [f32; 4] [copy] } => crate::appearance::set_color, none;
1498 SetMetallicRoughness { entity: Ref [entity], metallic: f32 [copy], roughness: f32 [copy] } => crate::appearance::set_metallic_roughness, none;
1499 SetEmissive { entity: Ref [entity], color: [f32; 3] [copy], strength: f32 [copy] } => crate::appearance::set_emissive, none;
1500 SetUnlit { entity: Ref [entity], unlit: bool [copy] } => crate::appearance::set_unlit, none;
1501 SetTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_texture, none;
1502 SetTextureTiling { entity: Ref [entity], repeats: f32 [copy] } => crate::appearance::set_texture_tiling, none;
1503 SetNormalTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_normal_texture, none;
1504 SetMetallicRoughnessTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_metallic_roughness_texture, none;
1505 SetEmissiveTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_emissive_texture, none;
1506 SetOcclusionTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_occlusion_texture, none;
1507
1508 SetPosition { entity: Ref [entity], position: [f32; 3] [vec3] } => crate::placement::set_position, none;
1509 SetScale { entity: Ref [entity], scale: [f32; 3] [vec3] } => crate::placement::set_scale, none;
1510 SetRotation { entity: Ref [entity], axis: [f32; 3] [vec3], radians: f32 [copy] } => crate::placement::set_rotation, none;
1511 Rotate { entity: Ref [entity], axis: [f32; 3] [vec3], radians: f32 [copy] } => crate::placement::rotate, none;
1512 Position { entity: Ref [entity] } => crate::placement::position, vector;
1513
1514 SetParent { child: Ref [entity], parent: Option<Ref> [opt_entity] } => crate::scene::set_parent, none;
1515 SetVisible { entity: Ref [entity], visible: bool [copy] } => crate::scene::set_visible, none;
1516 Despawn { entity: Ref [entity] } => crate::scene::despawn, none;
1517
1518 Tag { entity: Ref [entity], label: String [text] } => crate::groups::tag, none;
1519 Untag { entity: Ref [entity], label: String [text] } => crate::groups::untag, none;
1520 HasTag { entity: Ref [entity], label: String [text] } => crate::groups::has_tag, bool;
1521 QueryTagged { label: String [text] } => crate::groups::tagged, entities;
1522
1523 PointLight { position: [f32; 3] [vec3], color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::point_light, entity;
1524 SpotLight { position: [f32; 3] [vec3], target: [f32; 3] [vec3], color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::spot_light, entity;
1525 SetSun { color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::set_sun, none;
1526
1527 SetBackground { background: crate::environment::Background [owned] } => crate::environment::set_background, none;
1528 ShowGrid { enabled: bool [copy] } => crate::environment::show_grid, none;
1529 SetAmbient { color: [f32; 4] [copy] } => crate::environment::set_ambient, none;
1530 SetClearColor { color: [f32; 4] [copy] } => crate::environment::set_clear_color, none;
1531 SetBloom { enabled: bool [copy] } => crate::environment::set_bloom, none;
1532 SetBloomIntensity { intensity: f32 [copy] } => crate::environment::set_bloom_intensity, none;
1533 SetSsao { enabled: bool [copy] } => crate::environment::set_ssao, none;
1534 SetSsr { enabled: bool [copy] } => crate::environment::set_ssr, none;
1535 SetSsgi { enabled: bool [copy] } => crate::environment::set_ssgi, none;
1536 SetTaa { enabled: bool [copy] } => crate::environment::set_taa, none;
1537 SetExposure { exposure: f32 [copy] } => crate::environment::set_exposure, none;
1538 SetColorGrading { saturation: f32 [copy], contrast: f32 [copy], brightness: f32 [copy] } => crate::environment::set_color_grading, none;
1539 SetTimeOfDay { hour: f32 [copy] } => crate::environment::set_time_of_day, none;
1540
1541 SetTimeScale { scale: f32 [copy] } => crate::clock::set_time_scale, none;
1542 TimeScale { } => crate::clock::time_scale, float;
1543 Pause { } => crate::clock::pause, none;
1544 Unpause { } => crate::clock::unpause, none;
1545 SetPaused { paused: bool [copy] } => crate::clock::set_paused, none;
1546 IsPaused { } => crate::clock::is_paused, bool;
1547
1548 EmitFire { position: [f32; 3] [vec3] } => crate::effects::emit_fire, entity;
1549 EmitSmoke { position: [f32; 3] [vec3] } => crate::effects::emit_smoke, entity;
1550 EmitBurst { position: [f32; 3] [vec3], color: [f32; 4] [copy], count: u32 [copy] } => crate::effects::emit_burst, entity;
1551
1552 DrawCube { position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_cube, none;
1553 DrawSphere { position: [f32; 3] [vec3], radius: f32 [copy], color: [f32; 4] [copy] } => crate::draw::draw_sphere, none;
1554 DrawCylinder { position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_cylinder, none;
1555 DrawCone { position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_cone, none;
1556 DrawTorus { position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_torus, none;
1557 DrawLine { start: [f32; 3] [vec3], end: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_line, none;
1558 DrawText3d { text: String [text], position: [f32; 3] [vec3] } => crate::draw::draw_text_3d, none;
1559
1560 SpawnLabel { text: String [text], position: [f32; 3] [vec3] } => crate::text::spawn_label, entity;
1561 SpawnText { text: String [text], anchor: crate::text::ScreenAnchor [copy] } => crate::text::spawn_text, entity;
1562 SetText { entity: Ref [entity], text: String [text] } => crate::text::set_text, none;
1563 SetTextColor { entity: Ref [entity], color: [f32; 4] [copy] } => crate::text::set_text_color, none;
1564 SetTextSize { entity: Ref [entity], size: f32 [copy] } => crate::text::set_text_size, none;
1565
1566 SpawnPanel { anchor: crate::text::ScreenAnchor [copy], width: f32 [copy], height: f32 [copy] } => crate::ui::spawn_panel, entity;
1567 PanelLabel { panel: Ref [entity], text: String [text] } => crate::ui::panel_label, entity;
1568 PanelButton { panel: Ref [entity], text: String [text] } => crate::ui::panel_button, entity;
1569 ButtonClicked { button: Ref [entity] } => crate::ui::button_clicked, bool;
1570 ButtonHovered { button: Ref [entity] } => crate::ui::button_hovered, bool;
1571 DespawnPanel { panel: Ref [entity] } => crate::ui::despawn_panel, none;
1572 PanelRow { panel: Ref [entity], height: f32 [copy] } => crate::ui::panel_row, entity;
1573 PanelGrid { panel: Ref [entity], columns: usize [copy], row_height: f32 [copy], height: f32 [copy] } => crate::ui::panel_grid, entity;
1574 PanelScroll { panel: Ref [entity], height: f32 [copy] } => crate::ui::panel_scroll, entity;
1575 SetScrollOffset { scroll_area: Ref [entity], offset: f32 [copy] } => crate::ui::set_scroll_offset, none;
1576 SetFocusOrder { entity: Ref [entity], order: i32 [copy] } => crate::ui::set_focus_order, none;
1577 FocusWidget { entity: Ref [entity] } => crate::ui::focus_widget, none;
1578 SpawnPanelAt { anchor: crate::text::ScreenAnchor [copy], offset: [f32; 2] [vec2], size: [f32; 2] [vec2], color: [f32; 4] [copy] } => crate::ui::spawn_panel_at, entity;
1579 PanelText { parent: Ref [entity], text: String [text], rect: [f32; 4] [copy], font_size: f32 [copy], color: [f32; 4] [copy], align: TextAlignment [copy] } => crate::ui::panel_text, entity;
1580 PanelBox { parent: Ref [entity], offset: [f32; 2] [vec2], size: [f32; 2] [vec2], color: [f32; 4] [copy] } => crate::ui::panel_box, entity;
1581 PanelButtonAt { parent: Ref [entity], label: String [text], offset: [f32; 2] [vec2], size: [f32; 2] [vec2] } => crate::ui::panel_button_at, entity;
1582 SetPanelRect { node: Ref [entity], offset: [f32; 2] [vec2], size: [f32; 2] [vec2] } => crate::ui::set_panel_rect, none;
1583 SetPanelColor { node: Ref [entity], color: [f32; 4] [copy] } => crate::ui::set_panel_color, none;
1584 SetPanelText { label: Ref [entity], text: String [text] } => crate::ui::set_panel_text, none;
1585 SetPanelTextColor { label: Ref [entity], color: [f32; 4] [copy] } => crate::ui::set_panel_text_color, none;
1586 SetPanelSelected { button: Ref [entity], selected: bool [copy], accent: [f32; 4] [copy] } => crate::ui::set_panel_selected, none;
1587 SetPanelVisible { node: Ref [entity], visible: bool [copy] } => crate::ui::set_panel_visible, none;
1588
1589 PlayAnimation { entity: Ref [entity], clip: usize [copy] } => crate::scene::play_animation, none;
1590 PlayAnimationNamed { entity: Ref [entity], name: String [text] } => crate::scene::play_animation_named, bool;
1591 SetAnimationLooping { entity: Ref [entity], looping: bool [copy] } => crate::scene::set_animation_looping, none;
1592 SetAnimationSpeed { entity: Ref [entity], speed: f32 [copy] } => crate::scene::set_animation_speed, none;
1593 BlendToAnimation { entity: Ref [entity], clip: usize [copy], seconds: f32 [copy] } => crate::scene::blend_to_animation, none;
1594 PauseAnimation { entity: Ref [entity] } => crate::scene::pause_animation, none;
1595 ResumeAnimation { entity: Ref [entity] } => crate::scene::resume_animation, none;
1596 StopAnimation { entity: Ref [entity] } => crate::scene::stop_animation, none;
1597 AnimationClips { entity: Ref [entity] } => crate::scene::animation_clips, strings;
1598 AddAnimationEvent { entity: Ref [entity], clip_index: usize [copy], time: f32 [copy], name: String [text] } => crate::scene::add_animation_event, bool;
1599 AddAnimationEventNamed { entity: Ref [entity], clip_name: String [text], time: f32 [copy], name: String [text] } => crate::scene::add_animation_event_named, bool;
1600 SetAnimationLayerWeight { entity: Ref [entity], layer_index: usize [copy], weight: f32 [copy] } => crate::scene::set_animation_layer_weight, none;
1601 ClearAnimationLayers { entity: Ref [entity] } => crate::scene::clear_animation_layers, none;
1602 AimAt { bone: Ref [entity], target: [f32; 3] [vec3], forward: [f32; 3] [vec3] } => crate::animate::aim_at, none;
1603
1604 OrbitCamera { focus: [f32; 3] [vec3], radius: f32 [copy] } => crate::camera::orbit_camera, entity;
1605 FlyCamera { position: [f32; 3] [vec3] } => crate::camera::fly_camera, entity;
1606 FixedCamera { eye: [f32; 3] [vec3], target: [f32; 3] [vec3] } => crate::camera::fixed_camera, entity;
1607 LookAt { eye: [f32; 3] [vec3], target: [f32; 3] [vec3] } => crate::camera::look_at, none;
1608 SetOrbitFocus { focus: [f32; 3] [vec3] } => crate::camera::set_orbit_focus, none;
1609 SetOrbitView { focus: [f32; 3] [vec3], radius: f32 [copy], yaw: f32 [copy], pitch: f32 [copy] } => crate::camera::set_orbit_view, none;
1610 SetOrbitZoom { enabled: bool [copy] } => crate::camera::set_orbit_zoom, none;
1611 SetOrbitModifier { modifier: String [text] } => crate::camera::set_orbit_modifier, none;
1612 SetFieldOfView { degrees: f32 [copy] } => crate::camera::set_field_of_view, none;
1613 SetOrthographic { half_height: f32 [copy] } => crate::camera::set_orthographic, none;
1614 SetPerspective { degrees: f32 [copy] } => crate::camera::set_perspective, none;
1615 CameraPosition {} => crate::camera::camera_position, vector;
1616 CameraForward {} => crate::camera::camera_forward, vector;
1617 #[cfg(feature = "physics")]
1618 FirstPerson { position: [f32; 3] [vec3] } => crate::camera::first_person, entity;
1619
1620 DeltaTime {} => crate::input::delta_time, float;
1621 ElapsedSeconds {} => crate::input::elapsed_seconds, float;
1622 KeyDown { key: String [text] } => key_down_command, bool;
1623 KeyPressed { key: String [text] } => key_pressed_command, bool;
1624 MouseDown { button: u8 [copy] } => mouse_down_command, bool;
1625 MouseClicked { button: u8 [copy] } => mouse_clicked_command, bool;
1626 Wasd {} => crate::input::wasd, vector;
1627 PointerOverUi {} => crate::input::pointer_over_ui, bool;
1628 MouseScroll {} => crate::input::mouse_scroll, float;
1629 MouseDelta {} => mouse_delta_command, vector;
1630 TouchCount {} => touch_count_command, int;
1631 Touches {} => touches_command, json;
1632 #[cfg(feature = "gamepad")]
1633 GamepadButtonDown { button: String [text] } => gamepad_button_down_command, bool;
1634 #[cfg(feature = "gamepad")]
1635 GamepadButtonPressed { button: String [text] } => gamepad_button_pressed_command, bool;
1636 #[cfg(feature = "gamepad")]
1637 GamepadAxis { axis: String [text] } => gamepad_axis_command, float;
1638 #[cfg(feature = "gamepad")]
1639 GamepadLeftStick {} => gamepad_left_stick_command, vector;
1640 #[cfg(feature = "gamepad")]
1641 GamepadRightStick {} => gamepad_right_stick_command, vector;
1642 #[cfg(feature = "gamepad")]
1643 GamepadConnected {} => crate::input::gamepad_connected, bool;
1644
1645 #[cfg(feature = "physics")]
1646 Push { entity: Ref [entity], impulse: [f32; 3] [vec3] } => crate::physics::push, none;
1647 #[cfg(feature = "physics")]
1648 SetVelocity { entity: Ref [entity], velocity: [f32; 3] [vec3] } => crate::physics::set_velocity, none;
1649 #[cfg(feature = "physics")]
1650 ApplyForce { entity: Ref [entity], force: [f32; 3] [vec3] } => crate::physics::apply_force, none;
1651 #[cfg(feature = "physics")]
1652 ApplyTorque { entity: Ref [entity], torque: [f32; 3] [vec3] } => crate::physics::apply_torque, none;
1653 #[cfg(feature = "physics")]
1654 SetAngularVelocity { entity: Ref [entity], velocity: [f32; 3] [vec3] } => crate::physics::set_angular_velocity, none;
1655 #[cfg(feature = "physics")]
1656 Velocity { entity: Ref [entity] } => crate::physics::velocity, opt_vector;
1657 #[cfg(feature = "physics")]
1658 AngularVelocity { entity: Ref [entity] } => crate::physics::angular_velocity, opt_vector;
1659 #[cfg(feature = "physics")]
1660 MakeSensor { entity: Ref [entity] } => crate::physics::make_sensor, none;
1661 #[cfg(feature = "physics")]
1662 OverlapSphere { center: [f32; 3] [vec3], radius: f32 [copy] } => crate::physics::overlap_sphere, entities;
1663 #[cfg(feature = "physics")]
1664 SetCollisionGroups { entity: Ref [entity], membership: u32 [copy], filter: u32 [copy] } => crate::physics::set_collision_groups, none;
1665 #[cfg(feature = "physics")]
1666 SetFriction { entity: Ref [entity], friction: f32 [copy] } => crate::physics::set_friction, none;
1667 #[cfg(feature = "physics")]
1668 SetRestitution { entity: Ref [entity], restitution: f32 [copy] } => crate::physics::set_restitution, none;
1669 #[cfg(feature = "physics")]
1670 SetLinearDamping { entity: Ref [entity], damping: f32 [copy] } => crate::physics::set_linear_damping, none;
1671 #[cfg(feature = "physics")]
1672 SetAngularDamping { entity: Ref [entity], damping: f32 [copy] } => crate::physics::set_angular_damping, none;
1673 #[cfg(feature = "physics")]
1674 SetMass { entity: Ref [entity], mass: f32 [copy] } => crate::physics::set_mass, none;
1675 #[cfg(feature = "physics")]
1676 SetGravityScale { entity: Ref [entity], scale: f32 [copy] } => crate::physics::set_gravity_scale, none;
1677
1678 #[cfg(feature = "navmesh")]
1679 BakeNavmesh {} => crate::navigation::bake_navmesh, none;
1680 #[cfg(feature = "navmesh")]
1681 SpawnWalker { position: [f32; 3] [vec3] } => crate::navigation::spawn_walker, entity;
1682 #[cfg(feature = "navmesh")]
1683 WalkTo { agent: Ref [entity], destination: [f32; 3] [vec3] } => crate::navigation::walk_to, none;
1684 #[cfg(feature = "navmesh")]
1685 SetWalkSpeed { agent: Ref [entity], speed: f32 [copy] } => crate::navigation::set_walk_speed, none;
1686 #[cfg(feature = "navmesh")]
1687 StopWalking { agent: Ref [entity] } => crate::navigation::stop_walking, none;
1688
1689 #[cfg(feature = "picking")]
1690 ClickedEntity {} => crate::picking::clicked_entity, opt_entity;
1691 #[cfg(feature = "picking")]
1692 EntityUnderCursor {} => crate::picking::entity_under_cursor, opt_entity;
1693 #[cfg(feature = "picking")]
1694 CursorOnGround {} => crate::picking::cursor_on_ground, opt_vector;
1695 #[cfg(feature = "picking")]
1696 SpawnWorldPanel { position: [f32; 3] [vec3], width: f32 [copy], height: f32 [copy], color: [f32; 4] [copy] } => crate::world_ui::spawn_world_panel, entity;
1697 #[cfg(feature = "picking")]
1698 WorldPanelButton { panel: Ref [entity], x: f32 [copy], y: f32 [copy], width: f32 [copy], height: f32 [copy], color: [f32; 4] [copy] } => crate::world_ui::world_panel_button, entity;
1699 #[cfg(feature = "picking")]
1700 WorldPanelLabel { panel: Ref [entity], text: String [text], x: f32 [copy], y: f32 [copy] } => crate::world_ui::world_panel_label, entity;
1701 #[cfg(feature = "picking")]
1702 WorldButtonClicked { button: Ref [entity] } => crate::world_ui::world_button_clicked, bool;
1703
1704 #[cfg(feature = "audio")]
1705 PauseSound { entity: Ref [entity] } => crate::audio::pause_sound, none;
1706 #[cfg(feature = "audio")]
1707 ResumeSound { entity: Ref [entity] } => crate::audio::resume_sound, none;
1708 #[cfg(feature = "audio")]
1709 FadeVolume { entity: Ref [entity], volume: f32 [copy], seconds: f32 [copy] } => crate::audio::fade_volume, none;
1710 #[cfg(feature = "audio")]
1711 Crossfade { fade_out: Ref [entity], fade_in: Ref [entity], volume: f32 [copy], seconds: f32 [copy] } => crate::audio::crossfade, none;
1712 #[cfg(feature = "audio")]
1713 SetBusVolume { bus: nightshade::prelude::AudioBus [copy], decibels: f32 [copy], fade_seconds: f32 [copy] } => crate::audio::set_bus_volume, none;
1714 #[cfg(feature = "audio")]
1715 DuckVoice { amount: f32 [copy], fade_seconds: f32 [copy] } => crate::audio::duck_voice, none;
1716
1717 DirectionalLight { direction: [f32; 3] [vec3], color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::directional_light, entity;
1718 AreaLight { position: [f32; 3] [vec3], target: [f32; 3] [vec3], width: f32 [copy], height: f32 [copy], color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::area_light, entity;
1719 SetLightShadows { light: Ref [entity], enabled: bool [copy] } => crate::lighting::set_light_shadows, none;
1720
1721 EmitSparks { position: [f32; 3] [vec3] } => crate::effects::emit_sparks, entity;
1722 EmitFirework { position: [f32; 3] [vec3], velocity: [f32; 3] [vec3] } => crate::effects::emit_firework, entity;
1723 EmitParticles { position: [f32; 3] [vec3], rate: f32 [copy], lifetime: f32 [copy], size: f32 [copy], gravity: [f32; 3] [vec3] } => crate::effects::emit_particles, entity;
1724
1725 SetAlphaBlend { entity: Ref [entity], enabled: bool [copy] } => crate::appearance::set_alpha_blend, none;
1726 SetAlphaCutoff { entity: Ref [entity], cutoff: f32 [copy] } => crate::appearance::set_alpha_cutoff, none;
1727 SetDoubleSided { entity: Ref [entity], double_sided: bool [copy] } => crate::appearance::set_double_sided, none;
1728 SetIor { entity: Ref [entity], ior: f32 [copy] } => crate::appearance::set_ior, none;
1729 SetTransmission { entity: Ref [entity], factor: f32 [copy] } => crate::appearance::set_transmission, none;
1730 SetClearcoat { entity: Ref [entity], factor: f32 [copy], roughness: f32 [copy] } => crate::appearance::set_clearcoat, none;
1731 SetAnisotropy { entity: Ref [entity], strength: f32 [copy], rotation: f32 [copy] } => crate::appearance::set_anisotropy, none;
1732 SetUvTransform { entity: Ref [entity], offset: [f32; 2] [copy], scale: [f32; 2] [copy], rotation: f32 [copy] } => crate::appearance::set_uv_transform, none;
1733 SetSheen { entity: Ref [entity], color: [f32; 3] [copy], roughness: f32 [copy] } => crate::appearance::set_sheen, none;
1734 SetIridescence { entity: Ref [entity], factor: f32 [copy], ior: f32 [copy] } => crate::appearance::set_iridescence, none;
1735 SetSpecular { entity: Ref [entity], factor: f32 [copy], color: [f32; 3] [copy] } => crate::appearance::set_specular, none;
1736 SetNormalScale { entity: Ref [entity], scale: f32 [copy] } => crate::appearance::set_normal_scale, none;
1737 SetOcclusionStrength { entity: Ref [entity], strength: f32 [copy] } => crate::appearance::set_occlusion_strength, none;
1738 SetEmissiveStrength { entity: Ref [entity], strength: f32 [copy] } => crate::appearance::set_emissive_strength, none;
1739 SetThickness { entity: Ref [entity], thickness: f32 [copy] } => crate::appearance::set_thickness, none;
1740
1741 SetTextOutline { entity: Ref [entity], width: f32 [copy], color: [f32; 4] [copy] } => crate::text::set_text_outline, none;
1742
1743 SetMorphWeight { entity: Ref [entity], index: u32 [copy], weight: f32 [copy] } => crate::morph::set_morph_weight, none;
1744
1745 SetWindowTitle { title: String [text] } => crate::window::set_window_title, none;
1746 LockCursor { locked: bool [copy] } => crate::window::lock_cursor, none;
1747 RequestExit {} => crate::window::request_exit, none;
1748
1749 SetRenderLayer { entity: Ref [entity], layer: u32 [copy] } => crate::render::set_render_layer, none;
1750 SetCameraLayers { camera: Ref [entity], mask: u32 [copy] } => crate::render::set_camera_layers, none;
1751
1752 ThirdPersonCamera { target: Ref [entity], distance: f32 [copy] } => crate::camera::third_person_camera, entity;
1753
1754 SpawnCloth { position: [f32; 3] [vec3], width: f32 [copy], height: f32 [copy], columns: u32 [copy], rows: u32 [copy] } => crate::cloth::spawn_cloth, entity;
1755 ResetCloth { entity: Ref [entity] } => crate::cloth::reset_cloth, none;
1756 SetWind { direction: [f32; 3] [vec3], strength: f32 [copy] } => crate::cloth::set_wind, none;
1757
1758 PauseCutscene {} => crate::cutscene::pause_cutscene, none;
1759 ResumeCutscene {} => crate::cutscene::resume_cutscene, none;
1760 StopCutscene {} => crate::cutscene::stop_cutscene, none;
1761 SeekCutscene { seconds: f32 [copy] } => crate::cutscene::seek_cutscene, none;
1762 SetCutsceneCamera { camera: Ref [entity] } => crate::cutscene::set_cutscene_camera, none;
1763 BindCutsceneActor { name: String [text], entity: Ref [entity] } => crate::cutscene::bind_cutscene_actor, none;
1764
1765 #[cfg(feature = "physics")]
1766 SpawnCylinderBody { position: [f32; 3] [vec3], half_height: f32 [copy], radius: f32 [copy], mass: f32 [copy], color: [f32; 4] [copy] } => crate::physics::spawn_cylinder_body, entity;
1767 #[cfg(feature = "physics")]
1768 SpawnCapsuleBody { position: [f32; 3] [vec3], half_height: f32 [copy], radius: f32 [copy], mass: f32 [copy], color: [f32; 4] [copy] } => crate::scene::spawn_capsule_body, entity;
1769 #[cfg(feature = "physics")]
1770 SetControllerSpeed { entity: Ref [entity], speed: f32 [copy] } => crate::character::set_controller_speed, none;
1771 #[cfg(feature = "physics")]
1772 SetControllerJump { entity: Ref [entity], impulse: f32 [copy] } => crate::character::set_controller_jump, none;
1773 #[cfg(feature = "physics")]
1774 IsGrounded { entity: Ref [entity] } => crate::character::is_grounded, bool;
1775
1776 #[cfg(feature = "terrain")]
1777 EnableTerrain { seed: u32 [copy] } => crate::terrain::enable_terrain, none;
1778 #[cfg(feature = "terrain")]
1779 DisableTerrain {} => crate::terrain::disable_terrain, none;
1780 #[cfg(feature = "terrain")]
1781 SetTerrainHeightRange { min: f32 [copy], max: f32 [copy] } => crate::terrain::set_terrain_height_range, none;
1782 #[cfg(feature = "terrain")]
1783 SetTerrainSnowHeight { height: f32 [copy] } => crate::terrain::set_terrain_snow_height, none;
1784 #[cfg(feature = "grass")]
1785 EnableGrass {} => crate::terrain::enable_grass, none;
1786 #[cfg(feature = "grass")]
1787 DisableGrass {} => crate::terrain::disable_grass, none;
1788
1789 LoadTexture { name: String [text], image_bytes: Vec<u8> [bytes] } => crate::appearance::load_texture, none;
1790 LoadTextureLinear { name: String [text], image_bytes: Vec<u8> [bytes] } => crate::appearance::load_texture_linear, none;
1791 RegisterTexture { name: String [text], width: u32 [copy], height: u32 [copy], rgba: Vec<u8> [bytes] } => crate::appearance::register_texture, none;
1792
1793 ListMaterials {} => crate::materials::list_materials, json;
1794 Material { name: String [text] } => crate::materials::material, json;
1795 RegisterMaterial { name: String [text], base_color: [f32; 4] [copy], metallic: f32 [copy], roughness: f32 [copy], emissive: [f32; 3] [copy] } => register_material_command, text;
1796 UpdateMaterial { name: String [text], base_color: [f32; 4] [copy], metallic: f32 [copy], roughness: f32 [copy], emissive: [f32; 3] [copy] } => update_material_command, none;
1797 SetMaterialVariant { variant: String [text] } => set_material_variant_command, int;
1798
1799 SpawnObjects { shape: Shape [copy], scale: [f32; 3] [vec3], color: [f32; 4] [copy], body: Body [copy], positions: Vec<[f32; 3]> [vec3_list] } => spawn_objects_command, entities;
1800 SpawnInstanced { shape: Shape [copy], transforms: Vec<InstanceWire> [transforms], color: [f32; 4] [copy] } => crate::scene::spawn_instanced, entity;
1801 SpawnInstancedWithMaterial { shape: Shape [copy], transforms: Vec<InstanceWire> [transforms], material: String [text] } => crate::scene::spawn_instanced_with_material, entity;
1802 SetInstances { batch: Ref [entity], transforms: Vec<InstanceWire> [transforms] } => crate::scene::set_instances, none;
1803 SpawnClothSheet { position: [f32; 3] [vec3], width: f32 [copy], height: f32 [copy] } => crate::scene::spawn_cloth_sheet, entity;
1804 BlendToAnimationNamed { entity: Ref [entity], name: String [text], seconds: f32 [copy] } => crate::scene::blend_to_animation_named, bool;
1805 AddAnimationLayer { entity: Ref [entity], clip_index: usize [copy], weight: f32 [copy] } => crate::scene::add_animation_layer, none;
1806 NameEntity { name: String [text], entity: Ref [entity] } => crate::scene::name_entity, none;
1807
1808 Name { entity: Ref [entity] } => crate::hierarchy::name, text;
1809 SetEntityName { entity: Ref [entity], name: String [text] } => crate::hierarchy::set_name, none;
1810 Children { entity: Ref [entity] } => crate::hierarchy::children, entities;
1811 Descendants { entity: Ref [entity] } => crate::hierarchy::descendants, entities;
1812 Roots {} => crate::hierarchy::roots, entities;
1813 SceneTree {} => crate::hierarchy::scene_tree, json;
1814
1815 MaterialOf { entity: Ref [entity] } => crate::inspect::material_of, json;
1816 Color { entity: Ref [entity] } => crate::inspect::color, json;
1817 MetallicRoughness { entity: Ref [entity] } => crate::inspect::metallic_roughness, json;
1818 Emissive { entity: Ref [entity] } => crate::inspect::emissive, json;
1819 Unlit { entity: Ref [entity] } => crate::inspect::unlit, json;
1820 Texture { entity: Ref [entity] } => crate::inspect::texture, json;
1821 DescribeEntity { entity: Ref [entity] } => crate::inspect::describe_entity, json;
1822
1823 SetTextAlignment { entity: Ref [entity], alignment: TextAlignment [copy] } => crate::text::set_text_alignment, none;
1824
1825 SetMorphWeights { entity: Ref [entity], weights: Vec<f32> [floats] } => crate::morph::set_morph_weights, none;
1826 MorphWeight { entity: Ref [entity], index: u32 [copy] } => crate::morph::morph_weight, float;
1827 MorphTargetCount { entity: Ref [entity] } => crate::morph::morph_target_count, int;
1828
1829 SetFog { enabled: bool [copy], color: [f32; 3] [copy], start: f32 [copy], end: f32 [copy] } => set_fog_command, none;
1830 SetFogMode { mode: u32 [copy] } => set_fog_mode_command, none;
1831 SetDepthOfField { enabled: bool [copy], focus_distance: f32 [copy], focus_range: f32 [copy], max_blur_radius: f32 [copy], bokeh_threshold: f32 [copy] } => set_depth_of_field_command, none;
1832 Screenshot { path: String [text] } => screenshot_command, none;
1833
1834 SetShadingMode { mode: String [text] } => set_shading_mode_command, none;
1835
1836 AnimatePosition { entity: Ref [entity], to: [f32; 3] [vec3], seconds: f32 [copy], easing: String [text] } => animate_position_command, none;
1837 AnimateScale { entity: Ref [entity], to: [f32; 3] [vec3], seconds: f32 [copy], easing: String [text] } => animate_scale_command, none;
1838 AnimateColor { entity: Ref [entity], to: [f32; 4] [copy], seconds: f32 [copy], easing: String [text] } => animate_color_command, none;
1839 ShakeCamera { strength: f32 [copy], seconds: f32 [copy] } => crate::animate::shake_camera, none;
1840 ReachTo { root: Ref [entity], mid: Ref [entity], tip: Ref [entity], target: [f32; 3] [vec3], pole: Option<[f32; 3]> [opt_vec3] } => crate::animate::reach_to, none;
1841
1842 Bounds { entity: Ref [entity] } => crate::bounds::bounds, json;
1843 BoundsOf { entities: Vec<Ref> [refs] } => crate::bounds::bounds_of, json;
1844 FrameEntities { entities: Vec<Ref> [refs] } => crate::bounds::frame_entities, none;
1845
1846 SpawnParticleEmitter { emitter: EmitterWire [owned] } => spawn_particle_emitter_command, entity;
1847 SetEmitter { emitter_entity: Ref [entity], emitter: EmitterWire [owned] } => set_emitter_command, none;
1848
1849 SpawnDecal { texture: String [text], position: [f32; 3] [vec3], normal: [f32; 3] [vec3], size: f32 [copy] } => crate::decals::spawn_decal, entity;
1850
1851 SaveScene { name: String [text] } => save_scene_command, bytes;
1852 LoadScene { bytes: Vec<u8> [bytes] } => load_scene_command, entities;
1853
1854 WindowSize {} => crate::window::window_size, json;
1855 CursorLocked {} => crate::window::cursor_locked, bool;
1856 FramesPerSecond {} => crate::window::frames_per_second, float;
1857 FrameCount {} => crate::window::frame_count, int;
1858 UptimeMilliseconds {} => crate::window::uptime_milliseconds, int;
1859
1860 #[cfg(feature = "navmesh")]
1861 BakeNavmeshWith { config: RecastConfigWire [owned] } => bake_navmesh_with_command, none;
1862
1863 #[cfg(feature = "physics")]
1864 Raycast { origin: [f32; 3] [vec3], direction: [f32; 3] [vec3], max_distance: f32 [copy] } => raycast_command, json;
1865 #[cfg(feature = "physics")]
1866 AttachFixed { parent: Ref [entity], child: Ref [entity] } => attach_fixed_command, bool;
1867 #[cfg(feature = "physics")]
1868 AttachHinge { parent: Ref [entity], child: Ref [entity], axis: String [text] } => attach_hinge_command, bool;
1869 #[cfg(feature = "physics")]
1870 AttachSpring { parent: Ref [entity], child: Ref [entity], rest_length: f32 [copy], stiffness: f32 [copy], damping: f32 [copy] } => attach_spring_command, bool;
1871 #[cfg(feature = "physics")]
1872 AttachRope { parent: Ref [entity], child: Ref [entity], max_distance: f32 [copy] } => attach_rope_command, bool;
1873 #[cfg(feature = "physics")]
1874 ControllerVelocity { entity: Ref [entity] } => crate::character::controller_velocity, vector;
1875 #[cfg(feature = "physics")]
1876 MoveCharacter { entity: Ref [entity], movement: [f32; 2] [vec2], jump: bool [copy] } => crate::character::move_character, none;
1877
1878 #[cfg(feature = "picking")]
1879 RequestSurfacePick { screen_pos: [f32; 2] [vec2] } => crate::picking::request_surface_pick, none;
1880 #[cfg(feature = "picking")]
1881 TakeSurfacePick {} => take_surface_pick_command, json;
1882 #[cfg(feature = "picking")]
1883 WorldButtonHovered { button: Ref [entity] } => crate::world_ui::world_button_hovered, bool;
1884
1885 #[cfg(feature = "audio")]
1886 LoadSound { name: String [text], bytes: Vec<u8> [bytes] } => crate::audio::load_sound, none;
1887 #[cfg(feature = "audio")]
1888 PlaySound { name: String [text] } => crate::audio::play_sound, entity;
1889 #[cfg(feature = "audio")]
1890 PlaySoundLooping { name: String [text] } => crate::audio::play_sound_looping, entity;
1891 #[cfg(feature = "audio")]
1892 PlaySoundAt { name: String [text], position: [f32; 3] [vec3] } => crate::audio::play_sound_at, entity;
1893 #[cfg(feature = "audio")]
1894 SetVolume { entity: Ref [entity], volume: f32 [copy] } => crate::audio::set_volume, none;
1895 #[cfg(feature = "audio")]
1896 StopSound { entity: Ref [entity] } => crate::audio::stop_sound, none;
1897 #[cfg(feature = "audio")]
1898 SetPitch { entity: Ref [entity], rate: f32 [copy] } => crate::audio::set_pitch, none;
1899 #[cfg(feature = "audio")]
1900 SetSpatialDistance { entity: Ref [entity], min: f32 [copy], max: f32 [copy] } => crate::audio::set_spatial_distance, none;
1901
1902 PanelCheckbox { panel: Ref [entity], label: String [text], initial: bool [copy] } => crate::ui::panel_checkbox, entity;
1903 CheckboxValue { checkbox: Ref [entity] } => crate::ui::checkbox_value, bool;
1904 PanelSlider { panel: Ref [entity], min: f32 [copy], max: f32 [copy], initial: f32 [copy] } => crate::ui::panel_slider, entity;
1905 SliderValue { slider: Ref [entity] } => crate::ui::slider_value, float;
1906 SetSliderValue { slider: Ref [entity], value: f32 [copy] } => crate::ui::set_slider_value, none;
1907 PanelTextInput { panel: Ref [entity], placeholder: String [text] } => crate::ui::panel_text_input, entity;
1908 TextInputChanged { input: Ref [entity] } => crate::ui::text_input_changed, json;
1909 PanelDropdown { panel: Ref [entity], options: Vec<String> [strs], initial: usize [copy] } => crate::ui::panel_dropdown, entity;
1910 DropdownSelected { dropdown: Ref [entity] } => crate::ui::dropdown_selected, json;
1911 PanelProgressBar { panel: Ref [entity], initial: f32 [copy] } => crate::ui::panel_progress_bar, entity;
1912 SetProgress { bar: Ref [entity], value: f32 [copy] } => crate::ui::set_progress, none;
1913 PanelToggle { panel: Ref [entity], initial: bool [copy] } => crate::ui::panel_toggle, entity;
1914 ToggleValue { toggle: Ref [entity] } => crate::ui::toggle_value, bool;
1915 PanelRadio { panel: Ref [entity], label: String [text], group_id: u32 [copy], option_index: usize [copy] } => crate::ui::panel_radio, entity;
1916 RadioSelected { group_id: u32 [copy] } => crate::ui::radio_selected, json;
1917 PanelRangeSlider { panel: Ref [entity], min: f32 [copy], max: f32 [copy], low: f32 [copy], high: f32 [copy] } => crate::ui::panel_range_slider, entity;
1918 SetRange { slider: Ref [entity], low: f32 [copy], high: f32 [copy] } => crate::ui::set_range, none;
1919 PanelTabs { panel: Ref [entity], labels: Vec<String> [strs], initial: usize [copy] } => crate::ui::panel_tabs, entity;
1920 SetTab { tabs: Ref [entity], index: usize [copy] } => crate::ui::set_tab, none;
1921 PanelCollapsing { panel: Ref [entity], label: String [text], open: bool [copy] } => crate::ui::panel_collapsing, entity;
1922 PanelColorPicker { panel: Ref [entity], initial: [f32; 4] [copy] } => crate::ui::panel_color_picker, entity;
1923 ColorValue { picker: Ref [entity] } => crate::ui::color_value, json;
1924 PanelTextArea { panel: Ref [entity], placeholder: String [text], rows: usize [copy] } => crate::ui::panel_text_area, entity;
1925 PanelTextAreaWithValue { panel: Ref [entity], placeholder: String [text], rows: usize [copy], initial: String [text] } => crate::ui::panel_text_area_with_value, entity;
1926 SetTextArea { area: Ref [entity], text: String [text] } => crate::ui::set_text_area, none;
1927 PanelMultiSelect { panel: Ref [entity], options: Vec<String> [strs] } => crate::ui::panel_multi_select, entity;
1928 SetMultiSelect { widget: Ref [entity], indices: Vec<u32> [indices] } => crate::ui::set_multi_select, none;
1929 PanelDatePicker { panel: Ref [entity], year: i32 [copy], month: u32 [copy], day: u32 [copy] } => crate::ui::panel_date_picker, entity;
1930 SetDate { picker: Ref [entity], year: i32 [copy], month: u32 [copy], day: u32 [copy] } => crate::ui::set_date, none;
1931 PanelMenu { panel: Ref [entity], label: String [text], items: Vec<String> [strs] } => crate::ui::panel_menu, entity;
1932 PanelColorPickerHsv { panel: Ref [entity], initial: [f32; 4] [copy] } => crate::ui::panel_color_picker_hsv, entity;
1933 PanelSplitter { panel: Ref [entity], horizontal: bool [copy], ratio: f32 [copy] } => panel_splitter_command, entity;
1934 PanelBreadcrumb { panel: Ref [entity], segments: Vec<String> [strs] } => crate::ui::panel_breadcrumb, entity;
1935 PanelVirtualList { panel: Ref [entity], item_height: f32 [copy], pool_size: usize [copy] } => crate::ui::panel_virtual_list, entity;
1936 PanelTable { panel: Ref [entity], headers: Vec<String> [strs], widths: Vec<f32> [floats] } => crate::ui::panel_table, entity;
1937 PanelDataGrid { panel: Ref [entity], headers: Vec<String> [strs], widths: Vec<f32> [floats], pool_size: usize [copy] } => panel_data_grid_command, entity;
1938 SetDataGridRows { grid: Ref [entity], count: usize [copy] } => crate::ui::set_data_grid_rows, none;
1939 SetDataGridCell { grid: Ref [entity], row: usize [copy], column: usize [copy], text: String [text] } => crate::ui::set_data_grid_cell, none;
1940 DataGridSelectionChanged { grid: Ref [entity] } => crate::ui::data_grid_selection_changed, bool;
1941 PanelCommandPalette { panel: Ref [entity], pool_size: usize [copy] } => crate::ui::panel_command_palette, entity;
1942 PanelPropertyGrid { panel: Ref [entity], label_width: f32 [copy] } => crate::ui::panel_property_grid, entity;
1943 PanelPropertyRow { grid: Ref [entity], label: String [text] } => crate::ui::panel_property_row, entity;
1944 PanelTreeView { panel: Ref [entity], multi_select: bool [copy] } => crate::ui::panel_tree_view, entity;
1945 TreeContent { tree_view: Ref [entity] } => crate::ui::tree_content, entity;
1946 TreeNode { tree_view: Ref [entity], parent_container: Ref [entity], label: String [text], depth: usize [copy], user_data: u64 [copy] } => crate::ui::tree_node, entity;
1947 TreeNodeChildren { node: Ref [entity] } => crate::ui::tree_node_children, entity;
1948 SetTreeNodeExpanded { node: Ref [entity], expanded: bool [copy] } => crate::ui::set_tree_node_expanded, none;
1949 TreeViewSelected { tree_view: Ref [entity] } => crate::ui::tree_view_selected, entities;
1950 PanelDragValue { panel: Ref [entity], min: f32 [copy], max: f32 [copy], initial: f32 [copy] } => crate::ui::panel_drag_value, entity;
1951 DragValue { widget: Ref [entity] } => crate::ui::drag_value, float;
1952 PanelSelectable { panel: Ref [entity], text: String [text], group: u32 [copy], grouped: bool [copy] } => panel_selectable_command, entity;
1953 PanelModal { panel: Ref [entity], title: String [text], width: f32 [copy], height: f32 [copy] } => crate::ui::panel_modal, entity;
1954 PanelSpinner { panel: Ref [entity] } => crate::ui::panel_spinner, entity;
1955 PanelSeparator { panel: Ref [entity] } => crate::ui::panel_separator, entity;
1956 PanelHeading { panel: Ref [entity], text: String [text] } => crate::ui::panel_heading, entity;
1957
1958 SaveSceneToFile { path: String [text] } => crate::filesystem::save_scene_to_path, bool;
1959 LoadSceneFromFile { path: String [text] } => crate::filesystem::load_scene_from_path, entities;
1960}
1961
1962#[cfg(test)]
1963mod tests {
1964 use super::*;
1965
1966 #[test]
1967 fn command_schema_covers_the_surface() {
1968 let schema = command_schema();
1969 assert!(schema.get("oneOf").is_some());
1970 let text = enum2schema::serde_json::to_string(&schema).unwrap();
1971 for variant in [
1972 "SpawnCube",
1973 "SpawnObject",
1974 "SetColor",
1975 "Rotate",
1976 "QueryTagged",
1977 ] {
1978 assert!(text.contains(variant), "schema missing {variant}");
1979 }
1980 }
1981
1982 #[test]
1983 fn reply_schema_is_generated() {
1984 assert!(command_reply_schema().get("oneOf").is_some());
1985 }
1986
1987 #[test]
1988 fn manifest_covers_the_surface() {
1989 let manifest = command_manifest();
1990 assert!(!manifest.is_empty());
1991 let names: Vec<&str> = manifest.iter().map(|spec| spec.name).collect();
1992 for variant in [
1993 "SpawnCube",
1994 "SpawnObject",
1995 "SetColor",
1996 "Rotate",
1997 "QueryTagged",
1998 ] {
1999 assert!(names.contains(&variant), "manifest missing {variant}");
2000 }
2001 let replies = [
2002 "none",
2003 "entity",
2004 "opt_entity",
2005 "bool",
2006 "float",
2007 "int",
2008 "text",
2009 "vector",
2010 "opt_vector",
2011 "entities",
2012 "strings",
2013 "bytes",
2014 "json",
2015 ];
2016 for spec in &manifest {
2017 assert!(
2018 replies.contains(&spec.reply),
2019 "unknown reply {}",
2020 spec.reply
2021 );
2022 }
2023 }
2024
2025 #[test]
2026 fn every_command_has_a_description() {
2027 for spec in command_manifest() {
2028 assert!(
2029 !spec.description.is_empty(),
2030 "command {} has no description; add one to command_description",
2031 spec.name
2032 );
2033 }
2034 }
2035
2036 #[test]
2037 fn entity_reference_serializes_as_its_schema_claims() {
2038 let entity = Entity {
2039 id: 3,
2040 generation: 1,
2041 };
2042 let value = enum2schema::serde_json::to_value(Ref::Entity(entity)).unwrap();
2043 let inner = value
2044 .get("Entity")
2045 .and_then(|tagged| tagged.as_object())
2046 .expect("Ref::Entity serializes as an externally tagged object");
2047 assert!(inner.contains_key("id"));
2048 assert!(inner.contains_key("generation"));
2049 assert_eq!(inner.len(), 2);
2050 }
2051}