chunkedge_entity/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(
3    clippy::unseparated_literal_suffix,
4    clippy::manual_string_new,
5    clippy::needless_raw_strings
6)]
7
8pub mod active_status_effects;
9pub mod attributes;
10mod flags;
11pub mod hitbox;
12pub mod manager;
13pub mod query;
14pub mod tracked_data;
15
16use bevy_app::prelude::*;
17use bevy_ecs::prelude::*;
18use chunkedge_binary::{Decode, Encode, IdOr, TextComponent, VarInt};
19use chunkedge_math::{DVec3, Vec3};
20use chunkedge_server_common::{Despawned, UniqueId};
21use derive_more::{Deref, DerefMut};
22pub use manager::EntityManager;
23use paste::paste;
24use tracing::warn;
25use tracked_data::TrackedData;
26
27use crate::attributes::TrackedEntityAttributes;
28
29include!(concat!(env!("OUT_DIR"), "/entity.rs"));
30
31pub struct EntityPlugin;
32
33/// When new Minecraft entities are initialized and added to
34/// [`EntityManager`].
35///
36/// Systems that need Minecraft entities to be in a valid state should run
37/// _after_ this set.
38///
39/// This set lives in [`PostUpdate`].
40#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
41pub struct InitEntitiesSet;
42
43/// When tracked data is written to the entity's [`TrackedData`] component.
44/// Systems that modify tracked data should run _before_ this.
45///
46/// This set lives in [`PostUpdate`].
47#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
48pub struct UpdateTrackedDataSet;
49
50/// When derived entity state is copied into tracked components ahead of
51/// tracked-data serialization.
52///
53/// This set lives in [`PostUpdate`].
54#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
55pub struct UpdateDerivedEntityDataSet;
56
57/// When entities are updated and changes from the current tick are cleared.
58/// Systems that need to observe changes to entities (Such as the difference
59/// between [`Position`] and [`OldPosition`]) should run _before_ this set (and
60/// probably after [`InitEntitiesSet`]).
61///
62/// This set lives in [`PostUpdate`].
63#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
64pub struct ClearEntityChangesSet;
65
66impl Plugin for EntityPlugin {
67    fn build(&self, app: &mut App) {
68        app.insert_resource(EntityManager::new())
69            .configure_sets(
70                PostUpdate,
71                (
72                    InitEntitiesSet,
73                    UpdateDerivedEntityDataSet.after(InitEntitiesSet),
74                    UpdateTrackedDataSet.after(UpdateDerivedEntityDataSet),
75                    ClearEntityChangesSet
76                        .after(InitEntitiesSet)
77                        .after(UpdateDerivedEntityDataSet)
78                        .after(UpdateTrackedDataSet),
79                ),
80            )
81            .add_systems(
82                PostUpdate,
83                (remove_despawned_from_manager, init_entities)
84                    .chain()
85                    .in_set(InitEntitiesSet),
86            )
87            .add_systems(
88                PostUpdate,
89                (
90                    clear_status_changes,
91                    clear_animation_changes,
92                    clear_tracked_data_changes,
93                    clear_tracked_attributes_changes,
94                    update_old_position,
95                    update_old_layer_id,
96                )
97                    .in_set(ClearEntityChangesSet),
98            );
99
100        add_tracked_data_systems(app);
101    }
102}
103
104fn update_old_position(mut query: Query<(&Position, &mut OldPosition)>) {
105    for (pos, mut old_pos) in &mut query {
106        old_pos.0 = pos.0;
107    }
108}
109
110fn update_old_layer_id(mut query: Query<(&EntityLayerId, &mut OldEntityLayerId)>) {
111    for (loc, mut old_loc) in &mut query {
112        old_loc.0 = loc.0;
113    }
114}
115
116fn remove_despawned_from_manager(
117    entities: Query<&EntityId, (With<EntityKind>, With<Despawned>)>,
118    mut manager: ResMut<EntityManager>,
119) {
120    for id in &entities {
121        manager.id_to_entity.remove(&id.0);
122    }
123}
124
125fn init_entities(
126    mut entities: Query<
127        (Entity, &mut EntityId, &Position, &mut OldPosition),
128        (Added<EntityKind>, Without<Despawned>),
129    >,
130    mut manager: ResMut<EntityManager>,
131) {
132    for (entity, mut id, pos, mut old_pos) in &mut entities {
133        *old_pos = OldPosition::new(pos.0);
134
135        if *id == EntityId::default() {
136            *id = manager.next_id();
137        }
138
139        if let Some(conflict) = manager.id_to_entity.insert(id.0, entity) {
140            warn!(
141                "entity {entity:?} has conflicting entity ID of {} with entity {conflict:?}",
142                id.0
143            );
144        }
145    }
146}
147
148fn clear_status_changes(mut statuses: Query<&mut EntityStatuses, Changed<EntityStatuses>>) {
149    for mut statuses in &mut statuses {
150        statuses.0 = 0;
151    }
152}
153
154fn clear_animation_changes(
155    mut animations: Query<&mut EntityAnimations, Changed<EntityAnimations>>,
156) {
157    for mut animations in &mut animations {
158        animations.0 = 0;
159    }
160}
161
162fn clear_tracked_data_changes(mut tracked_data: Query<&mut TrackedData, Changed<TrackedData>>) {
163    for mut tracked_data in &mut tracked_data {
164        tracked_data.clear_update_values();
165    }
166}
167
168fn clear_tracked_attributes_changes(
169    mut attributes: Query<&mut TrackedEntityAttributes, Changed<TrackedEntityAttributes>>,
170) {
171    for mut attributes in &mut attributes {
172        attributes.clear();
173    }
174}
175
176/// Contains the entity layer an entity is on.
177#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref)]
178pub struct EntityLayerId(pub Entity);
179
180impl Default for EntityLayerId {
181    fn default() -> Self {
182        Self(Entity::PLACEHOLDER)
183    }
184}
185
186impl PartialEq<OldEntityLayerId> for EntityLayerId {
187    fn eq(&self, other: &OldEntityLayerId) -> bool {
188        self.0 == other.0
189    }
190}
191
192/// The value of [`EntityLayerId`] from the end of the previous tick.
193#[derive(Component, Copy, Clone, PartialEq, Eq, Debug, Deref)]
194pub struct OldEntityLayerId(Entity);
195
196impl OldEntityLayerId {
197    pub fn get(&self) -> Entity {
198        self.0
199    }
200}
201
202impl Default for OldEntityLayerId {
203    fn default() -> Self {
204        Self(Entity::PLACEHOLDER)
205    }
206}
207
208impl PartialEq<EntityLayerId> for OldEntityLayerId {
209    fn eq(&self, other: &EntityLayerId) -> bool {
210        self.0 == other.0
211    }
212}
213
214#[derive(Component, Copy, Clone, PartialEq, Default, Debug, Deref, DerefMut)]
215pub struct Position(pub DVec3);
216
217impl Position {
218    pub fn new<P: Into<DVec3>>(pos: P) -> Self {
219        Self(pos.into())
220    }
221
222    pub fn get(self) -> DVec3 {
223        self.0
224    }
225
226    pub fn set<P: Into<DVec3>>(&mut self, pos: P) {
227        self.0 = pos.into();
228    }
229}
230
231impl PartialEq<OldPosition> for Position {
232    fn eq(&self, other: &OldPosition) -> bool {
233        self.0 == other.0
234    }
235}
236
237/// The value of [`Position`] from the end of the previous tick.
238///
239/// **NOTE**: You should not modify this component after the entity is spawned.
240#[derive(Component, Clone, PartialEq, Default, Debug, Deref)]
241pub struct OldPosition(DVec3);
242
243impl OldPosition {
244    pub fn new<P: Into<DVec3>>(pos: P) -> Self {
245        Self(pos.into())
246    }
247
248    pub fn get(&self) -> DVec3 {
249        self.0
250    }
251}
252
253impl PartialEq<Position> for OldPosition {
254    fn eq(&self, other: &Position) -> bool {
255        self.0 == other.0
256    }
257}
258
259/// Describes the direction an entity is looking using pitch and yaw angles.
260#[derive(Component, Copy, Clone, PartialEq, Default, Debug)]
261pub struct Look {
262    /// The yaw angle in degrees, where:
263    /// - `-90` is looking east (towards positive x).
264    /// - `0` is looking south (towards positive z).
265    /// - `90` is looking west (towards negative x).
266    /// - `180` is looking north (towards negative z).
267    ///
268    /// Values -180 to 180 are also valid.
269    pub yaw: f32,
270    /// The pitch angle in degrees, where:
271    /// - `-90` is looking straight up.
272    /// - `0` is looking straight ahead.
273    /// - `90` is looking straight down.
274    pub pitch: f32,
275}
276
277impl Look {
278    pub const fn new(yaw: f32, pitch: f32) -> Self {
279        Self { yaw, pitch }
280    }
281
282    /// Gets a normalized direction vector from the yaw and pitch.
283    pub fn vec(self) -> Vec3 {
284        let (yaw_sin, yaw_cos) = (self.yaw + 90.0).to_radians().sin_cos();
285        let (pitch_sin, pitch_cos) = (-self.pitch).to_radians().sin_cos();
286
287        Vec3::new(yaw_cos * pitch_cos, pitch_sin, yaw_sin * pitch_cos)
288    }
289
290    /// Sets the yaw and pitch using a normalized direction vector.
291    pub fn set_vec(&mut self, dir: Vec3) {
292        debug_assert!(
293            dir.is_normalized(),
294            "the direction vector should be normalized"
295        );
296
297        // Preserve the current yaw if we're looking straight up or down.
298        if dir.x != 0.0 || dir.z != 0.0 {
299            self.yaw = f32::atan2(dir.z, dir.x).to_degrees() - 90.0;
300        }
301
302        self.pitch = -(dir.y).asin().to_degrees();
303    }
304}
305
306#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug, Deref, DerefMut)]
307pub struct OnGround(pub bool);
308
309/// A Minecraft entity's ID according to the protocol.
310///
311/// IDs should be _unique_ for the duration of the server and  _constant_ for
312/// the lifetime of the entity. IDs of -1 (the default) will be assigned to
313/// something else on the tick the entity is added. If you need to know the ID
314/// ahead of time, set this component to the value returned by
315/// [`EntityManager::next_id`] before spawning.
316#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref)]
317pub struct EntityId(i32);
318
319impl EntityId {
320    /// Returns the underlying entity ID as an integer.
321    pub fn get(self) -> i32 {
322        self.0
323    }
324}
325
326/// Returns an entity ID of -1.
327impl Default for EntityId {
328    fn default() -> Self {
329        Self(-1)
330    }
331}
332
333#[derive(Component, Copy, Clone, PartialEq, Default, Debug, Deref, DerefMut)]
334pub struct HeadYaw(pub f32);
335
336/// Entity velocity in m/s.
337#[derive(Component, Copy, Clone, Default, Debug, Deref, DerefMut)]
338pub struct Velocity(pub DVec3);
339
340impl Velocity {
341    pub fn to_packet_units(self) -> chunkedge_protocol::Velocity {
342        chunkedge_protocol::Velocity::from_ms_f64(self.0.into())
343    }
344}
345
346// TODO: don't make statuses and animations components.
347
348#[derive(Component, Copy, Clone, Default, Debug, Deref, DerefMut)]
349pub struct EntityStatuses(pub u64);
350
351impl EntityStatuses {
352    pub fn trigger(&mut self, status: EntityStatus) {
353        self.set(status, true);
354    }
355
356    pub fn set(&mut self, status: EntityStatus, triggered: bool) {
357        self.0 |= u64::from(triggered) << status as u64;
358    }
359
360    pub fn get(&self, status: EntityStatus) -> bool {
361        (self.0 >> status as u64) & 1 == 1
362    }
363}
364
365#[derive(Component, Default, Debug, Copy, Clone, Deref, DerefMut)]
366pub struct EntityAnimations(pub u8);
367
368impl EntityAnimations {
369    pub fn trigger(&mut self, anim: EntityAnimation) {
370        self.set(anim, true);
371    }
372
373    pub fn set(&mut self, anim: EntityAnimation, triggered: bool) {
374        self.0 |= u8::from(triggered) << anim as u8;
375    }
376
377    pub fn get(&self, anim: EntityAnimation) -> bool {
378        (self.0 >> anim as u8) & 1 == 1
379    }
380}
381
382/// Extra integer data passed to the entity spawn packet. The meaning depends on
383/// the type of entity being spawned.
384///
385/// Some examples:
386/// - **Experience Orb**: Experience count
387/// - **(Glowing) Item Frame**: Rotation
388/// - **Painting**: Rotation
389/// - **Falling Block**: Block state
390/// - **Fishing Bobber**: Hook entity ID
391/// - **Warden**: Initial pose
392#[derive(Component, Default, Debug, Deref, DerefMut)]
393pub struct ObjectData(pub i32);
394
395#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
396pub struct VillagerData {
397    pub kind: VillagerKind,
398    pub profession: VillagerProfession,
399    pub level: i32,
400}
401
402impl VillagerData {
403    pub const fn new(kind: VillagerKind, profession: VillagerProfession, level: i32) -> Self {
404        Self {
405            kind,
406            profession,
407            level,
408        }
409    }
410}
411
412impl Default for VillagerData {
413    fn default() -> Self {
414        Self {
415            kind: Default::default(),
416            profession: Default::default(),
417            level: 1,
418        }
419    }
420}
421
422impl Encode for VillagerData {
423    fn encode(&self, mut w: impl std::io::Write) -> anyhow::Result<()> {
424        self.kind.encode(&mut w)?;
425        self.profession.encode(&mut w)?;
426        VarInt(self.level).encode(w)
427    }
428}
429
430impl Decode<'_> for VillagerData {
431    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
432        Ok(Self {
433            kind: VillagerKind::decode(r)?,
434            profession: VillagerProfession::decode(r)?,
435            level: VarInt::decode(r)?.0,
436        })
437    }
438}
439
440#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
441pub enum VillagerKind {
442    Desert,
443    Jungle,
444    #[default]
445    Plains,
446    Savanna,
447    Snow,
448    Swamp,
449    Taiga,
450}
451
452#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
453pub enum VillagerProfession {
454    #[default]
455    None,
456    Armorer,
457    Butcher,
458    Cartographer,
459    Cleric,
460    Farmer,
461    Fisherman,
462    Fletcher,
463    Leatherworker,
464    Librarian,
465    Mason,
466    Nitwit,
467    Shepherd,
468    Toolsmith,
469    Weaponsmith,
470}
471
472#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
473pub enum Pose {
474    #[default]
475    Standing,
476    FallFlying,
477    Sleeping,
478    Swimming,
479    SpinAttack,
480    Sneaking,
481    LongJumping,
482    Dying,
483    Croaking,
484    UsingTongue,
485    Sitting,
486    Roaring,
487    Sniffing,
488    Emerging,
489    Digging,
490    Sliding,
491    Shooting,
492    Inhaling,
493}
494
495#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
496pub enum BoatKind {
497    #[default]
498    Oak,
499    Spruce,
500    Birch,
501    Jungle,
502    Acacia,
503    DarkOak,
504}
505
506#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
507pub enum CatKind {
508    AllBlack,
509    #[default]
510    Black,
511    BritishShorthair,
512    Calico,
513    Jellie,
514    Persian,
515    Ragdoll,
516    Red,
517    Siamese,
518    Tabby,
519    White,
520}
521#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
522pub enum CowKind {
523    Cold,
524    #[default]
525    Temperate,
526    Warm,
527}
528#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
529pub enum WolfKind {
530    Ashen,
531    Black,
532    Chestnut,
533    #[default]
534    Pale,
535    Rusty,
536    Snowy,
537    Spotted,
538    Striped,
539    Woods,
540}
541#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
542pub enum WolfSoundKind {
543    Angry,
544    Big,
545    #[default]
546    Classic,
547    Cute,
548    Grumpy,
549    Puglin,
550    Sad,
551}
552#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
553pub enum ArmadilloState {
554    #[default]
555    Idle,
556    Rolling,
557    Scared,
558    Unrolling,
559}
560
561#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
562pub enum FrogKind {
563    Cold,
564    #[default]
565    Temperate,
566    Warm,
567}
568#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
569pub enum PigKind {
570    Cold,
571    #[default]
572    Temperate,
573    Warm,
574}
575#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
576pub enum ChickenKind {
577    Cold,
578    #[default]
579    Temperate,
580    Warm,
581}
582#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
583pub enum PaintingKind {
584    #[default]
585    Alban,
586    Aztec,
587    Aztec2,
588    Backyard,
589    Baroque,
590    Bomb,
591    Bouquet,
592    BurningSkull,
593    Bust,
594    Cavebird,
595    Changing,
596    Cotan,
597    Courbet,
598    Creebet,
599    DonkeyKong,
600    Earth,
601    Endboss,
602    Fern,
603    Fighters,
604    Finding,
605    Fire,
606    Graham,
607    Humble,
608    Kebab,
609    Lowmist,
610    Match,
611    Meditative,
612    Orb,
613    Owlemons,
614    Passage,
615    Pointer,
616    Pigscene,
617    Plant,
618    Pond,
619    Pool,
620    PrairieRide,
621    Sea,
622    Skeleton,
623    SkullAndRoses,
624    Stage,
625    Sunflowers,
626    Sunset,
627    Tides,
628    Unpacked,
629    Void,
630    Wanderer,
631    Wasteland,
632    Water,
633    Wind,
634    Wither,
635}
636
637impl PaintingKind {
638    pub const ALL: [Self; 50] = [
639        Self::Alban,
640        Self::Aztec,
641        Self::Aztec2,
642        Self::Backyard,
643        Self::Baroque,
644        Self::Bomb,
645        Self::Bouquet,
646        Self::BurningSkull,
647        Self::Bust,
648        Self::Cavebird,
649        Self::Changing,
650        Self::Cotan,
651        Self::Courbet,
652        Self::Creebet,
653        Self::DonkeyKong,
654        Self::Earth,
655        Self::Endboss,
656        Self::Fern,
657        Self::Fighters,
658        Self::Finding,
659        Self::Fire,
660        Self::Graham,
661        Self::Humble,
662        Self::Kebab,
663        Self::Lowmist,
664        Self::Match,
665        Self::Meditative,
666        Self::Orb,
667        Self::Owlemons,
668        Self::Passage,
669        Self::Pointer,
670        Self::Pigscene,
671        Self::Plant,
672        Self::Pond,
673        Self::Pool,
674        Self::PrairieRide,
675        Self::Sea,
676        Self::Skeleton,
677        Self::SkullAndRoses,
678        Self::Stage,
679        Self::Sunflowers,
680        Self::Sunset,
681        Self::Tides,
682        Self::Unpacked,
683        Self::Void,
684        Self::Wanderer,
685        Self::Wasteland,
686        Self::Water,
687        Self::Wind,
688        Self::Wither,
689    ];
690
691    pub fn registry_id(self) -> chunkedge_protocol::RegistryId {
692        chunkedge_protocol::RegistryId::new(self as i32)
693    }
694
695    pub fn from_registry_id(id: chunkedge_protocol::RegistryId) -> Option<Self> {
696        let Ok(idx) = usize::try_from(id.id()) else {
697            return None;
698        };
699
700        Self::ALL.get(idx).copied()
701    }
702}
703
704#[derive(Clone, PartialEq, Debug)]
705pub struct PaintingVariantDefinition {
706    pub width: i32,
707    pub height: i32,
708    pub asset_id: String,
709    pub title: Option<chunkedge_protocol::Text>,
710    pub author: Option<chunkedge_protocol::Text>,
711}
712
713impl Encode for PaintingVariantDefinition {
714    fn encode(&self, mut w: impl std::io::Write) -> anyhow::Result<()> {
715        VarInt(self.width).encode(&mut w)?;
716        VarInt(self.height).encode(&mut w)?;
717        self.asset_id.encode(&mut w)?;
718        self.title.clone().map(TextComponent::from).encode(&mut w)?;
719        self.author.clone().map(TextComponent::from).encode(w)
720    }
721}
722
723#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug, Encode, Decode)]
724pub enum SnifferState {
725    #[default]
726    Idling,
727    FeelingHappy,
728    Scenting,
729    Sniffing,
730    Searching,
731    Digging,
732    Rising,
733}
734
735#[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Encode, Decode)]
736pub struct EulerAngle {
737    pub pitch: f32,
738    pub yaw: f32,
739    pub roll: f32,
740}
741
742#[derive(Copy, Clone)]
743struct OptionalInt(Option<i32>);
744
745impl Encode for OptionalInt {
746    fn encode(&self, w: impl std::io::Write) -> anyhow::Result<()> {
747        if let Some(n) = self.0 {
748            VarInt(n.wrapping_add(1))
749        } else {
750            VarInt(0)
751        }
752        .encode(w)
753    }
754}
755
756impl Decode<'_> for OptionalInt {
757    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
758        let n = VarInt::decode(r)?.0;
759
760        Ok(Self(if n == 0 {
761            None
762        } else {
763            Some(n.wrapping_sub(1))
764        }))
765    }
766}
767
768#[derive(Clone, Copy)]
769struct OptionalBlockState(Option<chunkedge_protocol::BlockState>);
770
771impl Encode for OptionalBlockState {
772    fn encode(&self, w: impl std::io::Write) -> anyhow::Result<()> {
773        match self.0 {
774            None => VarInt(0).encode(w),
775            Some(state) => {
776                let id = i32::from(state.to_raw());
777
778                if id == 0 {
779                    anyhow::bail!("air cannot be encoded as optional block state");
780                }
781
782                VarInt(id).encode(w)
783            }
784        }
785    }
786}
787
788impl Decode<'_> for OptionalBlockState {
789    fn decode(r: &mut &[u8]) -> anyhow::Result<Self> {
790        let id = VarInt::decode(r)?.0;
791
792        if id == 0 {
793            return Ok(Self(None));
794        }
795
796        let id =
797            u16::try_from(id).map_err(|_| anyhow::anyhow!("invalid optional block state ID"))?;
798        let state = chunkedge_protocol::BlockState::from_raw(id)
799            .ok_or_else(|| anyhow::anyhow!("invalid optional block state ID"))?;
800
801        Ok(Self(Some(state)))
802    }
803}
804
805#[derive(Clone, Copy)]
806struct PaintingVariant<'a>(&'a IdOr<PaintingVariantDefinition>);
807
808impl Encode for PaintingVariant<'_> {
809    fn encode(&self, w: impl std::io::Write) -> anyhow::Result<()> {
810        self.0.encode(w)
811    }
812}