1use nightshade::ecs::animation::components::AnimationPlayer;
13use nightshade::ecs::audio::components::AudioSource;
14use nightshade::ecs::camera::components::Camera;
15use nightshade::ecs::decal::components::Decal;
16use nightshade::ecs::material::components::Material;
17use nightshade::ecs::morph::components::MorphWeights;
18use nightshade::ecs::particles::components::ParticleEmitter;
19use nightshade::ecs::prefab::components::PrefabSource;
20use nightshade::ecs::primitives::{CameraCullingMask, CullingMask, RenderLayer};
21use nightshade::ecs::script::Script;
22use nightshade::ecs::text::components::{Text, TextProperties};
23use nightshade::ecs::transform::components::IgnoreParentScale;
24use nightshade::ecs::vfx::components::{Beam, LightningBolt, Trail, VfxAnimator};
25use nightshade::ecs::water::components::Water;
26use nightshade::prelude::*;
27use serde::{Deserialize, Serialize};
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, enum2schema::Schema)]
32#[schema(string_enum)]
33pub enum ComponentKind {
34 ParticleEmitter,
35 Decal,
36 Water,
37 AudioSource,
38 RigidBody,
39 Collider,
40 CharacterController,
41 NavmeshAgent,
42 Camera,
43 Text,
44 AnimationPlayer,
45 Visibility,
46 CastsShadow,
47 Light,
48 RenderLayer,
49 CullingMask,
50 CameraCullingMask,
51 IgnoreParentScale,
52 AudioListener,
53 CollisionListener,
54 PhysicsInterpolation,
55 MorphWeights,
56 MaterialVariants,
57 PanOrbitCamera,
58 ThirdPersonCamera,
59 CameraEnvironment,
60 CameraPostProcess,
61 ConstrainedAspect,
62 ViewportUpdateMode,
63 Script,
64 Beam,
65 LightningBolt,
66 Trail,
67 VfxAnimator,
68}
69
70pub const ALL_COMPONENT_KINDS: [ComponentKind; 34] = [
72 ComponentKind::ParticleEmitter,
73 ComponentKind::Decal,
74 ComponentKind::Water,
75 ComponentKind::AudioSource,
76 ComponentKind::RigidBody,
77 ComponentKind::Collider,
78 ComponentKind::CharacterController,
79 ComponentKind::NavmeshAgent,
80 ComponentKind::Camera,
81 ComponentKind::Text,
82 ComponentKind::AnimationPlayer,
83 ComponentKind::Visibility,
84 ComponentKind::CastsShadow,
85 ComponentKind::Light,
86 ComponentKind::RenderLayer,
87 ComponentKind::CullingMask,
88 ComponentKind::CameraCullingMask,
89 ComponentKind::IgnoreParentScale,
90 ComponentKind::AudioListener,
91 ComponentKind::CollisionListener,
92 ComponentKind::PhysicsInterpolation,
93 ComponentKind::MorphWeights,
94 ComponentKind::MaterialVariants,
95 ComponentKind::PanOrbitCamera,
96 ComponentKind::ThirdPersonCamera,
97 ComponentKind::CameraEnvironment,
98 ComponentKind::CameraPostProcess,
99 ComponentKind::ConstrainedAspect,
100 ComponentKind::ViewportUpdateMode,
101 ComponentKind::Script,
102 ComponentKind::Beam,
103 ComponentKind::LightningBolt,
104 ComponentKind::Trail,
105 ComponentKind::VfxAnimator,
106];
107
108pub fn kind_mask(kind: ComponentKind) -> u64 {
111 use nightshade::ecs::world as masks;
112 match kind {
113 ComponentKind::ParticleEmitter => masks::PARTICLE_EMITTER,
114 ComponentKind::Decal => masks::DECAL,
115 ComponentKind::Water => masks::WATER,
116 ComponentKind::AudioSource => masks::AUDIO_SOURCE,
117 ComponentKind::RigidBody => masks::RIGID_BODY,
118 ComponentKind::Collider => masks::COLLIDER,
119 ComponentKind::CharacterController => masks::CHARACTER_CONTROLLER,
120 ComponentKind::NavmeshAgent => masks::NAVMESH_AGENT,
121 ComponentKind::Camera => masks::CAMERA,
122 ComponentKind::Text => masks::TEXT,
123 ComponentKind::AnimationPlayer => masks::ANIMATION_PLAYER,
124 ComponentKind::Visibility => masks::VISIBILITY,
125 ComponentKind::CastsShadow => masks::CASTS_SHADOW,
126 ComponentKind::Light => masks::LIGHT,
127 ComponentKind::RenderLayer => masks::RENDER_LAYER,
128 ComponentKind::CullingMask => masks::CULLING_MASK,
129 ComponentKind::CameraCullingMask => masks::CAMERA_CULLING_MASK,
130 ComponentKind::IgnoreParentScale => masks::IGNORE_PARENT_SCALE,
131 ComponentKind::AudioListener => masks::AUDIO_LISTENER,
132 ComponentKind::CollisionListener => masks::COLLISION_LISTENER,
133 ComponentKind::PhysicsInterpolation => masks::PHYSICS_INTERPOLATION,
134 ComponentKind::MorphWeights => masks::MORPH_WEIGHTS,
135 ComponentKind::MaterialVariants => masks::MATERIAL_VARIANTS,
136 ComponentKind::PanOrbitCamera => masks::PAN_ORBIT_CAMERA,
137 ComponentKind::ThirdPersonCamera => masks::THIRD_PERSON_CAMERA,
138 ComponentKind::CameraEnvironment => masks::CAMERA_ENVIRONMENT,
139 ComponentKind::CameraPostProcess => masks::CAMERA_POST_PROCESS,
140 ComponentKind::ConstrainedAspect => masks::CONSTRAINED_ASPECT,
141 ComponentKind::ViewportUpdateMode => masks::VIEWPORT_UPDATE_MODE,
142 ComponentKind::Script => masks::SCRIPT,
143 ComponentKind::Beam => masks::BEAM,
144 ComponentKind::LightningBolt => masks::LIGHTNING_BOLT,
145 ComponentKind::Trail => masks::TRAIL,
146 ComponentKind::VfxAnimator => masks::VFX_ANIMATOR,
147 }
148}
149
150pub fn has_component(world: &World, entity: Entity, kind: ComponentKind) -> bool {
152 world.core.entity_has_components(entity, kind_mask(kind))
153}
154
155pub fn remove_component(world: &mut World, entity: Entity, kind: ComponentKind) {
157 world.core.remove_components(entity, kind_mask(kind));
158}
159
160pub fn present_components(world: &World, entity: Entity) -> Vec<ComponentKind> {
162 ALL_COMPONENT_KINDS
163 .into_iter()
164 .filter(|kind| has_component(world, entity, *kind))
165 .collect()
166}
167
168pub fn addable_components(world: &World, entity: Entity) -> Vec<ComponentKind> {
170 ALL_COMPONENT_KINDS
171 .into_iter()
172 .filter(|kind| !has_component(world, entity, *kind))
173 .collect()
174}
175
176pub fn add_component_default(world: &mut World, entity: Entity, kind: ComponentKind) {
180 use nightshade::ecs::world as masks;
181 match kind {
182 ComponentKind::ParticleEmitter => {
183 world.core.add_components(entity, masks::PARTICLE_EMITTER);
184 world
185 .core
186 .set_particle_emitter(entity, ParticleEmitter::default());
187 }
188 ComponentKind::Decal => {
189 world.core.add_components(entity, masks::DECAL);
190 world.core.set_decal(entity, Decal::default());
191 }
192 ComponentKind::Water => {
193 world.core.add_components(entity, masks::WATER);
194 world.core.set_water(entity, Water::default());
195 }
196 ComponentKind::AudioSource => {
197 world.core.add_components(entity, masks::AUDIO_SOURCE);
198 world.core.set_audio_source(entity, AudioSource::default());
199 }
200 ComponentKind::RigidBody => {
201 world.core.add_components(entity, masks::RIGID_BODY);
202 world
203 .core
204 .set_rigid_body(entity, RigidBodyComponent::new_dynamic());
205 }
206 ComponentKind::Collider => {
207 world.core.add_components(entity, masks::COLLIDER);
208 world
209 .core
210 .set_collider(entity, ColliderComponent::new_cuboid(0.5, 0.5, 0.5));
211 }
212 ComponentKind::CharacterController => {
213 world
214 .core
215 .add_components(entity, masks::CHARACTER_CONTROLLER);
216 world.core.set_character_controller(
217 entity,
218 CharacterControllerComponent::new_capsule(0.9, 0.3),
219 );
220 }
221 ComponentKind::NavmeshAgent => {
222 world.core.add_components(entity, masks::NAVMESH_AGENT);
223 world.core.set_navmesh_agent(entity, NavMeshAgent::new());
224 }
225 ComponentKind::Camera => {
226 world.core.add_components(entity, masks::CAMERA);
227 world.core.set_camera(entity, Camera::default());
228 }
229 ComponentKind::Text => {
230 let text_index = world.resources.text.cache.add_text("Text");
231 world.core.add_components(entity, masks::TEXT);
232 world.core.set_text(entity, Text::new(text_index));
233 }
234 ComponentKind::AnimationPlayer => {
235 world.core.add_components(entity, masks::ANIMATION_PLAYER);
236 world
237 .core
238 .set_animation_player(entity, AnimationPlayer::default());
239 }
240 ComponentKind::Visibility => {
241 world.core.add_components(entity, masks::VISIBILITY);
242 world
243 .core
244 .set_visibility(entity, Visibility { visible: true });
245 }
246 ComponentKind::CastsShadow => {
247 world.core.add_components(entity, masks::CASTS_SHADOW);
248 world.core.set_casts_shadow(entity, CastsShadow);
249 }
250 ComponentKind::Light => {
251 world.core.add_components(entity, masks::LIGHT);
252 world.core.set_light(entity, Light::default());
253 }
254 ComponentKind::RenderLayer => {
255 world.core.add_components(entity, masks::RENDER_LAYER);
256 world.core.set_render_layer(entity, RenderLayer::default());
257 }
258 ComponentKind::CullingMask => {
259 world.core.add_components(entity, masks::CULLING_MASK);
260 world.core.set_culling_mask(entity, CullingMask::default());
261 }
262 ComponentKind::CameraCullingMask => {
263 world
264 .core
265 .add_components(entity, masks::CAMERA_CULLING_MASK);
266 world
267 .core
268 .set_camera_culling_mask(entity, CameraCullingMask::default());
269 }
270 ComponentKind::IgnoreParentScale => {
271 world
272 .core
273 .add_components(entity, masks::IGNORE_PARENT_SCALE);
274 world
275 .core
276 .set_ignore_parent_scale(entity, IgnoreParentScale);
277 }
278 ComponentKind::AudioListener => {
279 world.core.add_components(entity, masks::AUDIO_LISTENER);
280 world
281 .core
282 .set_audio_listener(entity, nightshade::ecs::audio::components::AudioListener);
283 }
284 ComponentKind::CollisionListener => {
285 world.core.add_components(entity, masks::COLLISION_LISTENER);
286 world.core.set_collision_listener(
287 entity,
288 nightshade::ecs::physics::components::CollisionListener,
289 );
290 }
291 ComponentKind::PhysicsInterpolation => {
292 world
293 .core
294 .add_components(entity, masks::PHYSICS_INTERPOLATION);
295 world.core.set_physics_interpolation(
296 entity,
297 nightshade::ecs::physics::components::PhysicsInterpolation::default(),
298 );
299 }
300 ComponentKind::MorphWeights => {
301 world.core.add_components(entity, masks::MORPH_WEIGHTS);
302 world
303 .core
304 .set_morph_weights(entity, MorphWeights::default());
305 }
306 ComponentKind::MaterialVariants => {
307 world.core.add_components(entity, masks::MATERIAL_VARIANTS);
308 world.core.set_material_variants(
309 entity,
310 nightshade::ecs::material::components::MaterialVariants::default(),
311 );
312 }
313 ComponentKind::PanOrbitCamera => {
314 world.core.add_components(entity, masks::PAN_ORBIT_CAMERA);
315 world.core.set_pan_orbit_camera(
316 entity,
317 nightshade::ecs::camera::components::PanOrbitCamera::default(),
318 );
319 }
320 ComponentKind::ThirdPersonCamera => {
321 world
322 .core
323 .add_components(entity, masks::THIRD_PERSON_CAMERA);
324 world.core.set_third_person_camera(
325 entity,
326 nightshade::ecs::camera::components::ThirdPersonCamera::default(),
327 );
328 }
329 ComponentKind::CameraEnvironment => {
330 world.core.add_components(entity, masks::CAMERA_ENVIRONMENT);
331 world.core.set_camera_environment(
332 entity,
333 nightshade::ecs::camera::components::CameraEnvironment::default(),
334 );
335 }
336 ComponentKind::CameraPostProcess => {
337 world
338 .core
339 .add_components(entity, masks::CAMERA_POST_PROCESS);
340 world.core.set_camera_post_process(
341 entity,
342 nightshade::ecs::camera::components::CameraPostProcess::default(),
343 );
344 }
345 ComponentKind::ConstrainedAspect => {
346 world.core.add_components(entity, masks::CONSTRAINED_ASPECT);
347 world.core.set_constrained_aspect(
348 entity,
349 nightshade::ecs::camera::components::ConstrainedAspect::default(),
350 );
351 }
352 ComponentKind::ViewportUpdateMode => {
353 world
354 .core
355 .add_components(entity, masks::VIEWPORT_UPDATE_MODE);
356 world.core.set_viewport_update_mode(
357 entity,
358 nightshade::ecs::camera::components::ViewportUpdateMode::default(),
359 );
360 }
361 ComponentKind::Script => {
362 world.core.add_components(entity, masks::SCRIPT);
363 world.core.set_script(entity, Script::default());
364 }
365 ComponentKind::Beam => {
366 world.core.add_components(entity, masks::BEAM);
367 world.core.set_beam(entity, Beam::default());
368 }
369 ComponentKind::LightningBolt => {
370 world.core.add_components(entity, masks::LIGHTNING_BOLT);
371 world
372 .core
373 .set_lightning_bolt(entity, LightningBolt::default());
374 }
375 ComponentKind::Trail => {
376 world.core.add_components(entity, masks::TRAIL);
377 world.core.set_trail(entity, Trail::default());
378 }
379 ComponentKind::VfxAnimator => {
380 world.core.add_components(entity, masks::VFX_ANIMATOR);
381 world.core.set_vfx_animator(entity, VfxAnimator::default());
382 }
383 }
384}
385
386#[derive(Clone, Serialize, Deserialize, enum2schema::Schema)]
390pub enum ComponentPatch {
391 Light(Light),
392 Visibility(bool),
393 CastsShadow(bool),
394 Camera(Camera),
395 RigidBody(RigidBodyComponent),
396 Collider(ColliderComponent),
397 CharacterController(CharacterControllerComponent),
398 NavmeshAgent(NavMeshAgent),
399 ParticleEmitter(Box<ParticleEmitter>),
400 Decal(Decal),
401 Water(Water),
402 AudioSource(AudioSource),
403 RenderLayer(RenderLayer),
404 CullingMask(CullingMask),
405 CameraCullingMask(CameraCullingMask),
406 IgnoreParentScale(bool),
407 MorphWeights(Vec<f32>),
408 Text {
409 content: String,
410 properties: TextProperties,
411 },
412 Script(String),
413 Beam(Box<Beam>),
414 LightningBolt(Box<LightningBolt>),
415 Trail(Box<Trail>),
416 VfxAnimator(Box<VfxAnimator>),
417}
418
419impl ComponentPatch {
420 pub fn kind(&self) -> ComponentKind {
422 match self {
423 Self::Light(_) => ComponentKind::Light,
424 Self::Visibility(_) => ComponentKind::Visibility,
425 Self::CastsShadow(_) => ComponentKind::CastsShadow,
426 Self::Camera(_) => ComponentKind::Camera,
427 Self::RigidBody(_) => ComponentKind::RigidBody,
428 Self::Collider(_) => ComponentKind::Collider,
429 Self::CharacterController(_) => ComponentKind::CharacterController,
430 Self::NavmeshAgent(_) => ComponentKind::NavmeshAgent,
431 Self::ParticleEmitter(_) => ComponentKind::ParticleEmitter,
432 Self::Decal(_) => ComponentKind::Decal,
433 Self::Water(_) => ComponentKind::Water,
434 Self::AudioSource(_) => ComponentKind::AudioSource,
435 Self::RenderLayer(_) => ComponentKind::RenderLayer,
436 Self::CullingMask(_) => ComponentKind::CullingMask,
437 Self::CameraCullingMask(_) => ComponentKind::CameraCullingMask,
438 Self::IgnoreParentScale(_) => ComponentKind::IgnoreParentScale,
439 Self::MorphWeights(_) => ComponentKind::MorphWeights,
440 Self::Text { .. } => ComponentKind::Text,
441 Self::Script(_) => ComponentKind::Script,
442 Self::Beam(_) => ComponentKind::Beam,
443 Self::LightningBolt(_) => ComponentKind::LightningBolt,
444 Self::Trail(_) => ComponentKind::Trail,
445 Self::VfxAnimator(_) => ComponentKind::VfxAnimator,
446 }
447 }
448}
449
450pub fn apply_patch(world: &mut World, entity: Entity, patch: ComponentPatch) {
454 use nightshade::ecs::world as masks;
455 match patch {
456 ComponentPatch::Light(light) => {
457 if let Some(slot) = world.core.get_light_mut(entity) {
458 *slot = light;
459 }
460 }
461 ComponentPatch::Visibility(visible) => {
462 world.core.add_components(entity, masks::VISIBILITY);
463 world.core.set_visibility(entity, Visibility { visible });
464 }
465 ComponentPatch::CastsShadow(value) => {
466 if value {
467 world.core.add_components(entity, masks::CASTS_SHADOW);
468 world.core.set_casts_shadow(entity, CastsShadow);
469 } else {
470 world.core.remove_components(entity, masks::CASTS_SHADOW);
471 }
472 }
473 ComponentPatch::Camera(camera) => {
474 if let Some(slot) = world.core.get_camera_mut(entity) {
475 *slot = camera;
476 }
477 }
478 ComponentPatch::RigidBody(mut body) => {
479 if let Some(slot) = world.core.get_rigid_body_mut(entity) {
480 body.handle = slot.handle;
481 *slot = body;
482 }
483 }
484 ComponentPatch::Collider(mut collider) => {
485 if let Some(slot) = world.core.get_collider_mut(entity) {
486 collider.handle = slot.handle;
487 *slot = collider;
488 }
489 }
490 ComponentPatch::CharacterController(controller) => {
491 if let Some(slot) = world.core.get_character_controller_mut(entity) {
492 *slot = controller;
493 }
494 }
495 ComponentPatch::NavmeshAgent(agent) => {
496 if let Some(slot) = world.core.get_navmesh_agent_mut(entity) {
497 *slot = agent;
498 }
499 }
500 ComponentPatch::ParticleEmitter(emitter) => {
501 if let Some(slot) = world.core.get_particle_emitter_mut(entity) {
502 *slot = *emitter;
503 }
504 }
505 ComponentPatch::Decal(decal) => {
506 if let Some(slot) = world.core.get_decal_mut(entity) {
507 *slot = decal;
508 }
509 }
510 ComponentPatch::Water(water) => {
511 if let Some(slot) = world.core.get_water_mut(entity) {
512 *slot = water;
513 }
514 }
515 ComponentPatch::AudioSource(source) => {
516 if let Some(slot) = world.core.get_audio_source_mut(entity) {
517 *slot = source;
518 }
519 }
520 ComponentPatch::RenderLayer(layer) => {
521 if let Some(slot) = world.core.get_render_layer_mut(entity) {
522 *slot = layer;
523 }
524 }
525 ComponentPatch::CullingMask(mask) => {
526 if let Some(slot) = world.core.get_culling_mask_mut(entity) {
527 *slot = mask;
528 }
529 }
530 ComponentPatch::CameraCullingMask(mask) => {
531 if let Some(slot) = world.core.get_camera_culling_mask_mut(entity) {
532 *slot = mask;
533 }
534 }
535 ComponentPatch::IgnoreParentScale(present) => {
536 if present {
537 world
538 .core
539 .add_components(entity, masks::IGNORE_PARENT_SCALE);
540 world
541 .core
542 .set_ignore_parent_scale(entity, IgnoreParentScale);
543 } else {
544 world
545 .core
546 .remove_components(entity, masks::IGNORE_PARENT_SCALE);
547 }
548 }
549 ComponentPatch::MorphWeights(weights) => {
550 if let Some(slot) = world.core.get_morph_weights_mut(entity) {
551 for (index, weight) in weights.into_iter().enumerate() {
552 slot.set_weight(index, weight);
553 }
554 }
555 }
556 ComponentPatch::Text {
557 content,
558 properties,
559 } => {
560 let text_index = world.core.get_text(entity).map(|text| text.text_index);
561 if let Some(text_index) = text_index {
562 world.resources.text.cache.set_text(text_index, &content);
563 if let Some(text) = world.core.get_text_mut(entity) {
564 text.properties = properties;
565 text.dirty = true;
566 }
567 }
568 }
569 ComponentPatch::Script(source) => {
570 world.core.add_components(entity, masks::SCRIPT);
571 world.core.set_script(entity, Script::from_source(source));
572 }
573 ComponentPatch::Beam(beam) => {
574 if let Some(slot) = world.core.get_beam_mut(entity) {
575 *slot = *beam;
576 }
577 }
578 ComponentPatch::LightningBolt(bolt) => {
579 if let Some(slot) = world.core.get_lightning_bolt_mut(entity) {
580 *slot = *bolt;
581 }
582 }
583 ComponentPatch::Trail(trail) => {
584 if let Some(slot) = world.core.get_trail_mut(entity) {
585 *slot = *trail;
586 }
587 }
588 ComponentPatch::VfxAnimator(animator) => {
589 if let Some(slot) = world.core.get_vfx_animator_mut(entity) {
590 *slot = *animator;
591 }
592 }
593 }
594}
595
596#[derive(Clone, Copy, PartialEq, Eq, Hash)]
599pub enum SnapshotKind {
600 Light,
601 Visibility,
602 CastsShadow,
603 Name,
604 ParticleEmitter,
605 Decal,
606 Water,
607 AudioSource,
608 RigidBody,
609 Collider,
610 CharacterController,
611 NavmeshAgent,
612 Camera,
613 AnimationPlayer,
614 Text,
615 RenderLayer,
616 CullingMask,
617 CameraCullingMask,
618 IgnoreParentScale,
619 PrefabSource,
620 Beam,
621 LightningBolt,
622 Trail,
623 VfxAnimator,
624}
625
626pub fn snapshot_kind_for(kind: ComponentKind) -> Option<SnapshotKind> {
630 Some(match kind {
631 ComponentKind::ParticleEmitter => SnapshotKind::ParticleEmitter,
632 ComponentKind::Decal => SnapshotKind::Decal,
633 ComponentKind::Water => SnapshotKind::Water,
634 ComponentKind::AudioSource => SnapshotKind::AudioSource,
635 ComponentKind::RigidBody => SnapshotKind::RigidBody,
636 ComponentKind::Collider => SnapshotKind::Collider,
637 ComponentKind::CharacterController => SnapshotKind::CharacterController,
638 ComponentKind::NavmeshAgent => SnapshotKind::NavmeshAgent,
639 ComponentKind::Camera => SnapshotKind::Camera,
640 ComponentKind::Text => SnapshotKind::Text,
641 ComponentKind::AnimationPlayer => SnapshotKind::AnimationPlayer,
642 ComponentKind::Visibility => SnapshotKind::Visibility,
643 ComponentKind::CastsShadow => SnapshotKind::CastsShadow,
644 ComponentKind::Light => SnapshotKind::Light,
645 ComponentKind::RenderLayer => SnapshotKind::RenderLayer,
646 ComponentKind::CullingMask => SnapshotKind::CullingMask,
647 ComponentKind::CameraCullingMask => SnapshotKind::CameraCullingMask,
648 ComponentKind::IgnoreParentScale => SnapshotKind::IgnoreParentScale,
649 ComponentKind::Beam => SnapshotKind::Beam,
650 ComponentKind::LightningBolt => SnapshotKind::LightningBolt,
651 ComponentKind::Trail => SnapshotKind::Trail,
652 ComponentKind::VfxAnimator => SnapshotKind::VfxAnimator,
653 _ => return None,
654 })
655}
656
657#[derive(Clone)]
661pub enum ComponentSnapshot {
662 Light(Light),
663 Visibility(bool),
664 CastsShadow(bool),
665 Name(String),
666 ParticleEmitter(Option<Box<ParticleEmitter>>),
667 Decal(Option<Box<Decal>>),
668 Water(Option<Box<Water>>),
669 AudioSource(Option<Box<AudioSource>>),
670 RigidBody(Option<Box<RigidBodyComponent>>),
671 Collider(Option<Box<ColliderComponent>>),
672 CharacterController(Option<Box<CharacterControllerComponent>>),
673 NavmeshAgent(Option<Box<NavMeshAgent>>),
674 Camera(Option<Camera>),
675 AnimationPlayer(Option<Box<AnimationPlayer>>),
676 Material {
677 name: String,
678 material: Option<Box<Material>>,
679 },
680 Text {
681 component: Option<Box<Text>>,
682 content: Option<String>,
683 },
684 RenderLayer(Option<RenderLayer>),
685 CullingMask(Option<CullingMask>),
686 CameraCullingMask(Option<CameraCullingMask>),
687 IgnoreParentScale(bool),
688 PrefabSource(Option<Box<PrefabSource>>),
689 Beam(Option<Box<Beam>>),
690 LightningBolt(Option<Box<LightningBolt>>),
691 Trail(Option<Box<Trail>>),
692 VfxAnimator(Option<Box<VfxAnimator>>),
693}
694
695impl PartialEq for ComponentSnapshot {
696 fn eq(&self, other: &Self) -> bool {
697 match (self, other) {
698 (Self::Light(a), Self::Light(b)) => {
699 a.light_type == b.light_type
700 && a.color == b.color
701 && a.intensity == b.intensity
702 && a.range == b.range
703 && a.inner_cone_angle == b.inner_cone_angle
704 && a.outer_cone_angle == b.outer_cone_angle
705 && a.cast_shadows == b.cast_shadows
706 && a.shadow_bias == b.shadow_bias
707 }
708 (Self::Visibility(a), Self::Visibility(b)) => a == b,
709 (Self::CastsShadow(a), Self::CastsShadow(b)) => a == b,
710 (Self::Name(a), Self::Name(b)) => a == b,
711 (Self::ParticleEmitter(a), Self::ParticleEmitter(b)) => {
712 snapshot_bytes(a) == snapshot_bytes(b)
713 }
714 (Self::Decal(a), Self::Decal(b)) => a == b,
715 (Self::Water(a), Self::Water(b)) => a == b,
716 (Self::AudioSource(a), Self::AudioSource(b)) => snapshot_bytes(a) == snapshot_bytes(b),
717 (Self::RigidBody(a), Self::RigidBody(b)) => snapshot_bytes(a) == snapshot_bytes(b),
718 (Self::Collider(a), Self::Collider(b)) => snapshot_bytes(a) == snapshot_bytes(b),
719 (Self::CharacterController(a), Self::CharacterController(b)) => {
720 snapshot_bytes(a) == snapshot_bytes(b)
721 }
722 (Self::NavmeshAgent(a), Self::NavmeshAgent(b)) => {
723 snapshot_bytes(a) == snapshot_bytes(b)
724 }
725 (Self::Camera(a), Self::Camera(b)) => a == b,
726 (Self::AnimationPlayer(a), Self::AnimationPlayer(b)) => a == b,
727 (
728 Self::Material {
729 name: a_name,
730 material: a_mat,
731 },
732 Self::Material {
733 name: b_name,
734 material: b_mat,
735 },
736 ) => a_name == b_name && a_mat == b_mat,
737 (
738 Self::Text {
739 component: a_component,
740 content: a_content,
741 },
742 Self::Text {
743 component: b_component,
744 content: b_content,
745 },
746 ) => {
747 snapshot_bytes(a_component) == snapshot_bytes(b_component) && a_content == b_content
748 }
749 (Self::RenderLayer(a), Self::RenderLayer(b)) => a == b,
750 (Self::CullingMask(a), Self::CullingMask(b)) => a == b,
751 (Self::CameraCullingMask(a), Self::CameraCullingMask(b)) => a == b,
752 (Self::IgnoreParentScale(a), Self::IgnoreParentScale(b)) => a == b,
753 (Self::PrefabSource(a), Self::PrefabSource(b)) => {
754 snapshot_bytes(a) == snapshot_bytes(b)
755 }
756 (Self::Beam(a), Self::Beam(b)) => snapshot_bytes(a) == snapshot_bytes(b),
757 (Self::LightningBolt(a), Self::LightningBolt(b)) => {
758 snapshot_bytes(a) == snapshot_bytes(b)
759 }
760 (Self::Trail(a), Self::Trail(b)) => snapshot_bytes(a) == snapshot_bytes(b),
761 (Self::VfxAnimator(a), Self::VfxAnimator(b)) => snapshot_bytes(a) == snapshot_bytes(b),
762 _ => false,
763 }
764 }
765}
766
767fn snapshot_bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
768 bincode::serialize(value).unwrap_or_default()
769}
770
771pub fn snapshot_component(
774 world: &World,
775 entity: Entity,
776 kind: SnapshotKind,
777) -> Option<ComponentSnapshot> {
778 match kind {
779 SnapshotKind::Light => world
780 .core
781 .get_light(entity)
782 .cloned()
783 .map(ComponentSnapshot::Light),
784 SnapshotKind::Visibility => world
785 .core
786 .get_visibility(entity)
787 .map(|visibility| ComponentSnapshot::Visibility(visibility.visible)),
788 SnapshotKind::CastsShadow => Some(ComponentSnapshot::CastsShadow(
789 world.core.entity_has_casts_shadow(entity),
790 )),
791 SnapshotKind::Name => Some(ComponentSnapshot::Name(
792 world
793 .core
794 .get_name(entity)
795 .map(|name| name.0.clone())
796 .unwrap_or_default(),
797 )),
798 SnapshotKind::ParticleEmitter => Some(ComponentSnapshot::ParticleEmitter(
799 world
800 .core
801 .get_particle_emitter(entity)
802 .cloned()
803 .map(Box::new),
804 )),
805 SnapshotKind::Decal => Some(ComponentSnapshot::Decal(
806 world.core.get_decal(entity).cloned().map(Box::new),
807 )),
808 SnapshotKind::Water => Some(ComponentSnapshot::Water(
809 world.core.get_water(entity).cloned().map(Box::new),
810 )),
811 SnapshotKind::AudioSource => Some(ComponentSnapshot::AudioSource(
812 world.core.get_audio_source(entity).cloned().map(Box::new),
813 )),
814 SnapshotKind::RigidBody => Some(ComponentSnapshot::RigidBody(
815 world.core.get_rigid_body(entity).cloned().map(Box::new),
816 )),
817 SnapshotKind::Collider => Some(ComponentSnapshot::Collider(
818 world.core.get_collider(entity).cloned().map(Box::new),
819 )),
820 SnapshotKind::CharacterController => Some(ComponentSnapshot::CharacterController(
821 world
822 .core
823 .get_character_controller(entity)
824 .cloned()
825 .map(Box::new),
826 )),
827 SnapshotKind::NavmeshAgent => Some(ComponentSnapshot::NavmeshAgent(
828 world.core.get_navmesh_agent(entity).cloned().map(Box::new),
829 )),
830 SnapshotKind::Camera => Some(ComponentSnapshot::Camera(
831 world.core.get_camera(entity).copied(),
832 )),
833 SnapshotKind::AnimationPlayer => Some(ComponentSnapshot::AnimationPlayer(
834 world
835 .core
836 .get_animation_player(entity)
837 .cloned()
838 .map(Box::new),
839 )),
840 SnapshotKind::Text => {
841 let component = world.core.get_text(entity).cloned().map(Box::new);
842 let content = component
843 .as_ref()
844 .and_then(|text| world.resources.text.cache.get_text(text.text_index))
845 .map(str::to_string);
846 Some(ComponentSnapshot::Text { component, content })
847 }
848 SnapshotKind::RenderLayer => Some(ComponentSnapshot::RenderLayer(
849 world.core.get_render_layer(entity).copied(),
850 )),
851 SnapshotKind::CullingMask => Some(ComponentSnapshot::CullingMask(
852 world.core.get_culling_mask(entity).copied(),
853 )),
854 SnapshotKind::CameraCullingMask => Some(ComponentSnapshot::CameraCullingMask(
855 world.core.get_camera_culling_mask(entity).copied(),
856 )),
857 SnapshotKind::IgnoreParentScale => Some(ComponentSnapshot::IgnoreParentScale(
858 world.core.entity_has_ignore_parent_scale(entity),
859 )),
860 SnapshotKind::PrefabSource => Some(ComponentSnapshot::PrefabSource(
861 world.core.get_prefab_source(entity).cloned().map(Box::new),
862 )),
863 SnapshotKind::Beam => Some(ComponentSnapshot::Beam(
864 world.core.get_beam(entity).cloned().map(Box::new),
865 )),
866 SnapshotKind::LightningBolt => Some(ComponentSnapshot::LightningBolt(
867 world.core.get_lightning_bolt(entity).cloned().map(Box::new),
868 )),
869 SnapshotKind::Trail => Some(ComponentSnapshot::Trail(
870 world.core.get_trail(entity).cloned().map(Box::new),
871 )),
872 SnapshotKind::VfxAnimator => Some(ComponentSnapshot::VfxAnimator(
873 world.core.get_vfx_animator(entity).cloned().map(Box::new),
874 )),
875 }
876}
877
878pub fn restore_component(snapshot: &ComponentSnapshot, world: &mut World, entity: Entity) {
881 use nightshade::ecs::world as masks;
882 match snapshot {
883 ComponentSnapshot::Light(light) => {
884 if let Some(target) = world.core.get_light_mut(entity) {
885 *target = light.clone();
886 }
887 }
888 ComponentSnapshot::Visibility(visible) => {
889 world.core.add_components(entity, masks::VISIBILITY);
890 world
891 .core
892 .set_visibility(entity, Visibility { visible: *visible });
893 }
894 ComponentSnapshot::CastsShadow(value) => {
895 if *value {
896 world.core.add_components(entity, masks::CASTS_SHADOW);
897 world.core.set_casts_shadow(entity, CastsShadow);
898 } else {
899 world.core.remove_components(entity, masks::CASTS_SHADOW);
900 }
901 }
902 ComponentSnapshot::Name(text) => {
903 world.core.set_name(entity, Name(text.clone()));
904 }
905 ComponentSnapshot::ParticleEmitter(value) => {
906 restore_optional(
907 world,
908 entity,
909 value.as_deref().cloned(),
910 masks::PARTICLE_EMITTER,
911 |world, entity, value| world.core.set_particle_emitter(entity, value),
912 );
913 }
914 ComponentSnapshot::Decal(value) => {
915 restore_optional(
916 world,
917 entity,
918 value.as_deref().cloned(),
919 masks::DECAL,
920 |world, entity, value| world.core.set_decal(entity, value),
921 );
922 }
923 ComponentSnapshot::Water(value) => {
924 restore_optional(
925 world,
926 entity,
927 value.as_deref().cloned(),
928 masks::WATER,
929 |world, entity, value| world.core.set_water(entity, value),
930 );
931 }
932 ComponentSnapshot::AudioSource(value) => {
933 restore_optional(
934 world,
935 entity,
936 value.as_deref().cloned(),
937 masks::AUDIO_SOURCE,
938 |world, entity, value| world.core.set_audio_source(entity, value),
939 );
940 }
941 ComponentSnapshot::RigidBody(value) => {
942 restore_optional(
943 world,
944 entity,
945 value.as_deref().cloned(),
946 masks::RIGID_BODY,
947 |world, entity, value| world.core.set_rigid_body(entity, value),
948 );
949 }
950 ComponentSnapshot::Collider(value) => {
951 restore_optional(
952 world,
953 entity,
954 value.as_deref().cloned(),
955 masks::COLLIDER,
956 |world, entity, value| world.core.set_collider(entity, value),
957 );
958 }
959 ComponentSnapshot::CharacterController(value) => {
960 restore_optional(
961 world,
962 entity,
963 value.as_deref().cloned(),
964 masks::CHARACTER_CONTROLLER,
965 |world, entity, value| world.core.set_character_controller(entity, value),
966 );
967 }
968 ComponentSnapshot::NavmeshAgent(value) => {
969 restore_optional(
970 world,
971 entity,
972 value.as_deref().cloned(),
973 masks::NAVMESH_AGENT,
974 |world, entity, value| world.core.set_navmesh_agent(entity, value),
975 );
976 }
977 ComponentSnapshot::Camera(value) => {
978 restore_optional(
979 world,
980 entity,
981 *value,
982 masks::CAMERA,
983 |world, entity, value| world.core.set_camera(entity, value),
984 );
985 }
986 ComponentSnapshot::AnimationPlayer(value) => {
987 restore_optional(
988 world,
989 entity,
990 value.as_deref().cloned(),
991 masks::ANIMATION_PLAYER,
992 |world, entity, value| world.core.set_animation_player(entity, value),
993 );
994 }
995 ComponentSnapshot::Material { name, material } => {
996 if let Some(material) = material.as_deref() {
997 queue_ecs_command(
998 world,
999 nightshade::ecs::world::commands::EcsCommand::ReloadMaterial {
1000 name: name.clone(),
1001 material: Box::new(material.clone()),
1002 },
1003 );
1004 }
1005 }
1006 ComponentSnapshot::Text { component, content } => {
1007 restore_optional(
1008 world,
1009 entity,
1010 component.as_deref().cloned(),
1011 masks::TEXT,
1012 |world, entity, value| world.core.set_text(entity, value),
1013 );
1014 if let (Some(component), Some(content)) = (component.as_deref(), content.as_deref()) {
1015 world
1016 .resources
1017 .text
1018 .cache
1019 .set_text(component.text_index, content);
1020 }
1021 }
1022 ComponentSnapshot::RenderLayer(value) => {
1023 restore_optional(
1024 world,
1025 entity,
1026 *value,
1027 masks::RENDER_LAYER,
1028 |world, entity, value| world.core.set_render_layer(entity, value),
1029 );
1030 }
1031 ComponentSnapshot::CullingMask(value) => {
1032 restore_optional(
1033 world,
1034 entity,
1035 *value,
1036 masks::CULLING_MASK,
1037 |world, entity, value| world.core.set_culling_mask(entity, value),
1038 );
1039 }
1040 ComponentSnapshot::CameraCullingMask(value) => {
1041 restore_optional(
1042 world,
1043 entity,
1044 *value,
1045 masks::CAMERA_CULLING_MASK,
1046 |world, entity, value| world.core.set_camera_culling_mask(entity, value),
1047 );
1048 }
1049 ComponentSnapshot::IgnoreParentScale(present) => {
1050 if *present {
1051 world
1052 .core
1053 .add_components(entity, masks::IGNORE_PARENT_SCALE);
1054 world
1055 .core
1056 .set_ignore_parent_scale(entity, IgnoreParentScale);
1057 } else {
1058 world
1059 .core
1060 .remove_components(entity, masks::IGNORE_PARENT_SCALE);
1061 }
1062 }
1063 ComponentSnapshot::PrefabSource(value) => {
1064 restore_optional(
1065 world,
1066 entity,
1067 value.as_deref().cloned(),
1068 masks::PREFAB_SOURCE,
1069 |world, entity, value| world.core.set_prefab_source(entity, value),
1070 );
1071 }
1072 ComponentSnapshot::Beam(value) => {
1073 restore_optional(
1074 world,
1075 entity,
1076 value.as_deref().cloned(),
1077 masks::BEAM,
1078 |world, entity, value| world.core.set_beam(entity, value),
1079 );
1080 }
1081 ComponentSnapshot::LightningBolt(value) => {
1082 restore_optional(
1083 world,
1084 entity,
1085 value.as_deref().cloned(),
1086 masks::LIGHTNING_BOLT,
1087 |world, entity, value| world.core.set_lightning_bolt(entity, value),
1088 );
1089 }
1090 ComponentSnapshot::Trail(value) => {
1091 restore_optional(
1092 world,
1093 entity,
1094 value.as_deref().cloned(),
1095 masks::TRAIL,
1096 |world, entity, value| world.core.set_trail(entity, value),
1097 );
1098 }
1099 ComponentSnapshot::VfxAnimator(value) => {
1100 restore_optional(
1101 world,
1102 entity,
1103 value.as_deref().cloned(),
1104 masks::VFX_ANIMATOR,
1105 |world, entity, value| world.core.set_vfx_animator(entity, value),
1106 );
1107 }
1108 }
1109}
1110
1111fn restore_optional<T, F>(world: &mut World, entity: Entity, value: Option<T>, mask: u64, set: F)
1114where
1115 F: FnOnce(&mut World, Entity, T),
1116{
1117 if let Some(value) = value {
1118 world.core.add_components(entity, mask);
1119 set(world, entity, value);
1120 } else {
1121 world.core.remove_components(entity, mask);
1122 }
1123}