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