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