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