Skip to main content

sl_types/
attachment.rs

1//! Types related to attachments
2
3#[cfg(feature = "chumsky")]
4use chumsky::{
5    Parser,
6    prelude::{choice, just},
7};
8
9/// avatar attachment points
10#[derive(
11    Debug,
12    Clone,
13    Hash,
14    PartialEq,
15    Eq,
16    strum::FromRepr,
17    strum::EnumIs,
18    serde::Serialize,
19    serde::Deserialize,
20)]
21pub enum AvatarAttachmentPoint {
22    /// Skull
23    Skull = 2,
24    /// Nose
25    Nose = 17,
26    /// Mouth
27    Mouth = 11,
28    /// Tongue
29    Tongue = 52,
30    /// Chin
31    Chin = 12,
32    /// Jaw
33    Jaw = 47,
34    /// Left Ear
35    LeftEar = 13,
36    /// Right Ear
37    RightEar = 14,
38    /// Alt Left Ear
39    AltLeftEar = 48,
40    /// Alt Right Ear
41    AltRightEar = 49,
42    /// Left Eye
43    LeftEye = 15,
44    /// Right Eye
45    RightEye = 16,
46    /// Alt Left Ear
47    AltLeftEye = 50,
48    /// Alt Right Ear
49    AltRightEye = 51,
50    /// Neck
51    Neck = 39,
52    /// Left Shoulder
53    LeftShoulder = 3,
54    /// Right Shoulder
55    RightShoulder = 4,
56    /// L Upper Arm
57    LeftUpperArm = 20,
58    /// R Upper Arm
59    RightUpperArm = 18,
60    /// L Lower Arm
61    LeftLowerArm = 21,
62    /// R Lower Arm
63    RightLowerArm = 19,
64    /// Left Hand
65    LeftHand = 5,
66    /// Right Hand
67    RightHand = 6,
68    /// Left Ring Finger
69    LeftRingFinger = 41,
70    /// Right Ring Finger
71    RightRingFinger = 42,
72    /// Left Wing
73    LeftWing = 45,
74    /// Right Wing
75    RightWing = 46,
76    /// Chest
77    Chest = 1,
78    /// Left Pec
79    LeftPec = 29,
80    /// Right Pec
81    RightPec = 30,
82    /// Stomach
83    Stomach = 28,
84    /// Spine
85    Spine = 9,
86    /// Tail Base
87    TailBase = 43,
88    /// Tail Tip
89    TailTip = 44,
90    /// Avatar Center
91    AvatarCenter = 40,
92    /// Pelvis
93    Pelvis = 10,
94    /// Groin
95    Groin = 53,
96    /// Left Hip
97    LeftHip = 25,
98    /// Right Hip
99    RightHip = 22,
100    /// L Upper Leg
101    LeftUpperLeg = 26,
102    /// R Upper Leg
103    RightUpperLeg = 23,
104    /// L Lower Leg
105    LeftLowerLeg = 24,
106    /// R Lower Leg
107    RightLowerLeg = 27,
108    /// Left Foot
109    LeftFoot = 7,
110    /// Right Foot
111    RightFoot = 8,
112    /// Left Hind Foot
113    LeftHindFoot = 54,
114    /// Right Hind Foot
115    RightHindFoot = 55,
116}
117
118impl AvatarAttachmentPoint {
119    /// returns true if the attachment point requires Bento
120    #[must_use]
121    pub const fn requires_bento(&self) -> bool {
122        matches!(
123            self,
124            Self::Tongue
125                | Self::AltLeftEar
126                | Self::AltRightEar
127                | Self::AltLeftEye
128                | Self::AltRightEye
129                | Self::LeftRingFinger
130                | Self::RightRingFinger
131                | Self::LeftWing
132                | Self::RightWing
133                | Self::TailBase
134                | Self::TailTip
135                | Self::Groin
136                | Self::LeftHindFoot
137                | Self::RightHindFoot
138        )
139    }
140}
141
142impl std::fmt::Display for AvatarAttachmentPoint {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        match self {
145            Self::Skull => write!(f, "Skull"),
146            Self::Nose => write!(f, "Nose"),
147            Self::Mouth => write!(f, "Mouth"),
148            Self::Tongue => write!(f, "Tongue"),
149            Self::Chin => write!(f, "Chin"),
150            Self::Jaw => write!(f, "Jaw"),
151            Self::LeftEar => write!(f, "Left Ear"),
152            Self::RightEar => write!(f, "Right Ear"),
153            Self::AltLeftEar => write!(f, "Alt Left Ear"),
154            Self::AltRightEar => write!(f, "Alt Right Ear"),
155            Self::LeftEye => write!(f, "Left Eye"),
156            Self::RightEye => write!(f, "Right Eye"),
157            Self::AltLeftEye => write!(f, "Alt Left Eye"),
158            Self::AltRightEye => write!(f, "Alt Right Eye"),
159            Self::Neck => write!(f, "Neck"),
160            Self::LeftShoulder => write!(f, "Left Shoulder"),
161            Self::RightShoulder => write!(f, "Right Shoulder"),
162            Self::LeftUpperArm => write!(f, "L Upper Arm"),
163            Self::RightUpperArm => write!(f, "R Upper Arm"),
164            Self::LeftLowerArm => write!(f, "L Lower Arm"),
165            Self::RightLowerArm => write!(f, "R Lower Arm"),
166            Self::LeftHand => write!(f, "Left Hand"),
167            Self::RightHand => write!(f, "Right Hand"),
168            Self::LeftRingFinger => write!(f, "Left Ring Finger"),
169            Self::RightRingFinger => write!(f, "Right Ring Finger"),
170            Self::LeftWing => write!(f, "Left Wing"),
171            Self::RightWing => write!(f, "Right Wing"),
172            Self::Chest => write!(f, "Chest"),
173            Self::LeftPec => write!(f, "Left Pec"),
174            Self::RightPec => write!(f, "Right Pec"),
175            Self::Stomach => write!(f, "Stomach"),
176            Self::Spine => write!(f, "Spine"),
177            Self::TailBase => write!(f, "Tail Base"),
178            Self::TailTip => write!(f, "Tail Tip"),
179            Self::AvatarCenter => write!(f, "Avatar Center"),
180            Self::Pelvis => write!(f, "Pelvis"),
181            Self::Groin => write!(f, "Groin"),
182            Self::LeftHip => write!(f, "Left Hip"),
183            Self::RightHip => write!(f, "Right Hip"),
184            Self::LeftUpperLeg => write!(f, "L Upper Leg"),
185            Self::RightUpperLeg => write!(f, "R Upper Leg"),
186            Self::LeftLowerLeg => write!(f, "L Lower Leg"),
187            Self::RightLowerLeg => write!(f, "R Lower Leg"),
188            Self::LeftFoot => write!(f, "Left Foot"),
189            Self::RightFoot => write!(f, "Right Foot"),
190            Self::LeftHindFoot => write!(f, "Left Hind Foot"),
191            Self::RightHindFoot => write!(f, "Right Hind Foot"),
192        }
193    }
194}
195
196/// Error deserializing AvatarAttachmentPoint from String
197#[derive(Debug, Clone)]
198pub struct AvatarAttachmentPointParseError {
199    /// the value that could not be parsed
200    value: String,
201}
202
203impl std::fmt::Display for AvatarAttachmentPointParseError {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        write!(
206            f,
207            "Could not parse as AvatarAttachmentPoint: {}",
208            self.value
209        )
210    }
211}
212
213impl std::str::FromStr for AvatarAttachmentPoint {
214    type Err = AvatarAttachmentPointParseError;
215
216    fn from_str(s: &str) -> Result<Self, Self::Err> {
217        match s {
218            "ATTACH_HEAD" | "Skull" | "head" => Ok(Self::Skull),
219            "ATTACH_NOSE" | "Nose" | "nose" => Ok(Self::Nose),
220            "ATTACH_MOUTH" | "Mouth" | "mouth" => Ok(Self::Mouth),
221            "ATTACH_FACE_TONGUE" | "Tongue" | "tongue" => Ok(Self::Tongue),
222            "ATTACH_CHIN" | "Chin" | "chin" => Ok(Self::Chin),
223            "ATTACH_FACE_JAW" | "Jaw" | "jaw" => Ok(Self::Jaw),
224            "ATTACH_LEAR" | "Left Ear" | "left ear" => Ok(Self::LeftEar),
225            "ATTACH_REAR" | "Right Ear" | "right ear" => Ok(Self::RightEar),
226            "ATTACH_FACE_LEAR" | "Alt Left Ear" | "left ear (extended)" => Ok(Self::AltLeftEar),
227            "ATTACH_FACE_REAR" | "Alt Right Ear" | "right ear (extended)" => Ok(Self::AltRightEar),
228            "ATTACH_LEYE" | "Left Eye" | "left eye" => Ok(Self::LeftEye),
229            "ATTACH_REYE" | "Right Eye" | "right eye" => Ok(Self::RightEye),
230            "ATTACH_FACE_LEYE" | "Alt Left Eye" | "left eye (extended)" => Ok(Self::AltLeftEye),
231            "ATTACH_FACE_REYE" | "Alt Right Eye" | "right eye (extended)" => Ok(Self::AltRightEye),
232            "ATTACH_NECK" | "Neck" | "neck" => Ok(Self::Neck),
233            "ATTACH_LSHOULDER" | "Left Shoulder" | "left shoulder" => Ok(Self::LeftShoulder),
234            "ATTACH_RSHOULDER" | "Right Shoulder" | "right shoulder" => Ok(Self::RightShoulder),
235            "ATTACH_LUARM" | "L Upper Arm" | "left upper arm" => Ok(Self::LeftUpperArm),
236            "ATTACH_RUARM" | "R Upper Arm" | "right upper arm" => Ok(Self::RightUpperArm),
237            "ATTACH_LLARM" | "L Lower Arm" | "left lower arm" => Ok(Self::LeftLowerArm),
238            "ATTACH_RLARM" | "R Lower Arm" | "right lower arm" => Ok(Self::RightLowerArm),
239            "ATTACH_LHAND" | "Left Hand" | "left hand" => Ok(Self::LeftHand),
240            "ATTACH_RHAND" | "Right Hand" | "right hand" => Ok(Self::RightHand),
241            "ATTACH_LHAND_RING1" | "Left Ring Finger" | "left ring finger" => {
242                Ok(Self::LeftRingFinger)
243            }
244            "ATTACH_RHAND_RING1" | "Right Ring Finger" | "right ring finger" => {
245                Ok(Self::RightRingFinger)
246            }
247            "ATTACH_LWING" | "Left Wing" | "left wing" => Ok(Self::LeftWing),
248            "ATTACH_RWING" | "Right Wing" | "right wing" => Ok(Self::RightWing),
249            "ATTACH_CHEST" | "Chest" | "chest/sternum" | "chest" | "sternum" => Ok(Self::Chest),
250            "ATTACH_LEFT_PEC" | "Left Pec" | "left pectoral" => Ok(Self::LeftPec),
251            "ATTACH_RIGHT_PEC" | "Right Pec" | "right pectoral" => Ok(Self::RightPec),
252            "ATTACH_BELLY" | "Stomach" | "belly/stomach/tummy" | "belly" | "stomach" | "tummy" => {
253                Ok(Self::Stomach)
254            }
255            "ATTACH_BACK" | "Spine" | "back" => Ok(Self::Spine),
256            "ATTACH_TAIL_BASE" | "Tail Base" | "tail base" => Ok(Self::TailBase),
257            "ATTACH_TAIL_TIP" | "Tail Tip" | "tail tip" => Ok(Self::TailTip),
258            "ATTACH_AVATAR_CENTER"
259            | "Avatar Center"
260            | "avatar center/root"
261            | "avatar center"
262            | "root" => Ok(Self::AvatarCenter),
263            "ATTACH_PELVIS" | "Pelvis" | "pelvis" => Ok(Self::Pelvis),
264            "ATTACH_GROIN" | "Groin" | "groin" => Ok(Self::Groin),
265            "ATTACH_LHIP" | "Left Hip" | "left hip" => Ok(Self::LeftHip),
266            "ATTACH_RHIP" | "Right Hip" | "right hip" => Ok(Self::RightHip),
267            "ATTACH_LULEG" | "L Upper Leg" | "left upper leg" => Ok(Self::LeftUpperLeg),
268            "ATTACH_RULEG" | "R Upper Leg" | "right upper leg" => Ok(Self::RightUpperLeg),
269            "ATTACH_RLLEG" | "R Lower Leg" | "right lower leg" => Ok(Self::LeftLowerLeg),
270            "ATTACH_LLLEG" | "L Lower Leg" | "left lower leg" => Ok(Self::RightLowerLeg),
271            "ATTACH_LFOOT" | "Left Foot" | "left foot" => Ok(Self::LeftFoot),
272            "ATTACH_RFOOT" | "Right Foot" | "right foot" => Ok(Self::RightFoot),
273            "ATTACH_HIND_LFOOT" | "Left Hind Foot" | "left hind foot" => Ok(Self::LeftHindFoot),
274            "ATTACH_HIND_RFOOT" | "Right Hind Foot" | "right hind foot" => Ok(Self::RightHindFoot),
275            _ => Err(AvatarAttachmentPointParseError {
276                value: s.to_string(),
277            }),
278        }
279    }
280}
281
282/// parse an avatar attachment point
283///
284/// # Errors
285///
286/// returns an error if the string could not be parsed
287#[cfg(feature = "chumsky")]
288#[must_use]
289pub fn avatar_attachment_point_parser<'src>() -> impl Parser<
290    'src,
291    &'src str,
292    AvatarAttachmentPoint,
293    chumsky::extra::Err<chumsky::error::Rich<'src, char>>,
294> {
295    choice([
296        just("ATTACH_HEAD")
297            .or(just("Skull"))
298            .or(just("head"))
299            .to(AvatarAttachmentPoint::Skull)
300            .boxed(),
301        just("ATTACH_NOSE")
302            .or(just("Nose"))
303            .or(just("nose"))
304            .to(AvatarAttachmentPoint::Nose)
305            .boxed(),
306        just("ATTACH_MOUTH")
307            .or(just("Mouth"))
308            .or(just("mouth"))
309            .to(AvatarAttachmentPoint::Mouth)
310            .boxed(),
311        just("ATTACH_FACE_TONGUE")
312            .or(just("Tongue"))
313            .or(just("tongue"))
314            .to(AvatarAttachmentPoint::Tongue)
315            .boxed(),
316        just("ATTACH_CHIN")
317            .or(just("Chin"))
318            .or(just("chin"))
319            .to(AvatarAttachmentPoint::Chin)
320            .boxed(),
321        just("ATTACH_FACE_JAW")
322            .or(just("Jaw"))
323            .or(just("jaw"))
324            .to(AvatarAttachmentPoint::Jaw)
325            .boxed(),
326        just("ATTACH_LEAR")
327            .or(just("Left Ear"))
328            .or(just("left ear"))
329            .to(AvatarAttachmentPoint::LeftEar)
330            .boxed(),
331        just("ATTACH_REAR")
332            .or(just("Right Ear"))
333            .or(just("right ear"))
334            .to(AvatarAttachmentPoint::RightEar)
335            .boxed(),
336        just("ATTACH_FACE_LEAR")
337            .or(just("Alt Left Ear"))
338            .or(just("left ear (extended)"))
339            .to(AvatarAttachmentPoint::AltLeftEar)
340            .boxed(),
341        just("ATTACH_FACE_REAR")
342            .or(just("Alt Right Ear"))
343            .or(just("right ear (extended)"))
344            .to(AvatarAttachmentPoint::AltRightEar)
345            .boxed(),
346        just("ATTACH_LEYE")
347            .or(just("Left Eye"))
348            .or(just("left eye"))
349            .to(AvatarAttachmentPoint::LeftEye)
350            .boxed(),
351        just("ATTACH_REYE")
352            .or(just("Right Eye"))
353            .or(just("right eye"))
354            .to(AvatarAttachmentPoint::RightEye)
355            .boxed(),
356        just("ATTACH_FACE_LEYE")
357            .or(just("Alt Left Eye"))
358            .or(just("left eye (extended)"))
359            .to(AvatarAttachmentPoint::AltLeftEye)
360            .boxed(),
361        just("ATTACH_FACE_REYE")
362            .or(just("Alt Right Eye"))
363            .or(just("right eye (extended)"))
364            .to(AvatarAttachmentPoint::AltRightEye)
365            .boxed(),
366        just("ATTACH_NECK")
367            .or(just("Neck"))
368            .or(just("neck"))
369            .to(AvatarAttachmentPoint::Neck)
370            .boxed(),
371        just("ATTACH_LSHOULDER")
372            .or(just("Left Shoulder"))
373            .or(just("left shoulder"))
374            .to(AvatarAttachmentPoint::LeftShoulder)
375            .boxed(),
376        just("ATTACH_RSHOULDER")
377            .or(just("Right Shoulder"))
378            .or(just("right shoulder"))
379            .to(AvatarAttachmentPoint::RightShoulder)
380            .boxed(),
381        just("ATTACH_LUARM")
382            .or(just("L Upper Arm"))
383            .or(just("left upper arm"))
384            .to(AvatarAttachmentPoint::LeftUpperArm)
385            .boxed(),
386        just("ATTACH_RUARM")
387            .or(just("R Upper Arm"))
388            .or(just("right upper arm"))
389            .to(AvatarAttachmentPoint::RightUpperArm)
390            .boxed(),
391        just("ATTACH_LLARM")
392            .or(just("L Lower Arm"))
393            .or(just("left lower arm"))
394            .to(AvatarAttachmentPoint::LeftLowerArm)
395            .boxed(),
396        just("ATTACH_RLARM")
397            .or(just("R Lower Arm"))
398            .or(just("right lower arm"))
399            .to(AvatarAttachmentPoint::RightLowerArm)
400            .boxed(),
401        just("ATTACH_LHAND")
402            .or(just("Left Hand"))
403            .or(just("left hand"))
404            .to(AvatarAttachmentPoint::LeftHand)
405            .boxed(),
406        just("ATTACH_RHAND")
407            .or(just("Right Hand"))
408            .or(just("right hand"))
409            .to(AvatarAttachmentPoint::RightHand)
410            .boxed(),
411        just("ATTACH_LHAND_RING1")
412            .or(just("Left Ring Finger"))
413            .or(just("left ring finger"))
414            .to(AvatarAttachmentPoint::LeftRingFinger)
415            .boxed(),
416        just("ATTACH_RHAND_RING1")
417            .or(just("Right Ring Finger"))
418            .or(just("right ring finger"))
419            .to(AvatarAttachmentPoint::RightRingFinger)
420            .boxed(),
421        just("ATTACH_LWING")
422            .or(just("Left Wing"))
423            .or(just("left wing"))
424            .to(AvatarAttachmentPoint::LeftWing)
425            .boxed(),
426        just("ATTACH_RWING")
427            .or(just("Right Wing"))
428            .or(just("right wing"))
429            .to(AvatarAttachmentPoint::RightWing)
430            .boxed(),
431        just("ATTACH_CHEST")
432            .or(just("Chest"))
433            .or(just("chest/sternum"))
434            .or(just("chest"))
435            .or(just("sternum"))
436            .to(AvatarAttachmentPoint::Chest)
437            .boxed(),
438        just("ATTACH_LEFT_PEC")
439            .or(just("Left Pec"))
440            .or(just("left pectoral"))
441            .to(AvatarAttachmentPoint::LeftPec)
442            .boxed(),
443        just("ATTACH_RIGHT_PEC")
444            .or(just("Right Pec"))
445            .or(just("right pectoral"))
446            .to(AvatarAttachmentPoint::RightPec)
447            .boxed(),
448        just("ATTACH_BELLY")
449            .or(just("Stomach"))
450            .or(just("belly/stomach/tummy"))
451            .or(just("belly"))
452            .or(just("stomach"))
453            .or(just("tummy"))
454            .to(AvatarAttachmentPoint::Stomach)
455            .boxed(),
456        just("ATTACH_BACK")
457            .or(just("Spine"))
458            .or(just("back"))
459            .to(AvatarAttachmentPoint::Spine)
460            .boxed(),
461        just("ATTACH_TAIL_BASE")
462            .or(just("Tail Base"))
463            .or(just("tail base"))
464            .to(AvatarAttachmentPoint::TailBase)
465            .boxed(),
466        just("ATTACH_TAIL_TIP")
467            .or(just("Tail Tip"))
468            .or(just("tail tip"))
469            .to(AvatarAttachmentPoint::TailTip)
470            .boxed(),
471        just("ATTACH_AVATAR_CENTER")
472            .or(just("Avatar Center"))
473            .or(just("avatar center/root"))
474            .or(just("avatar center"))
475            .or(just("root"))
476            .to(AvatarAttachmentPoint::AvatarCenter)
477            .boxed(),
478        just("ATTACH_PELVIS")
479            .or(just("Pelvis"))
480            .or(just("pelvis"))
481            .to(AvatarAttachmentPoint::Pelvis)
482            .boxed(),
483        just("ATTACH_GROIN")
484            .or(just("Groin"))
485            .or(just("groin"))
486            .to(AvatarAttachmentPoint::Groin)
487            .boxed(),
488        just("ATTACH_LHIP")
489            .or(just("Left Hip"))
490            .or(just("left hip"))
491            .to(AvatarAttachmentPoint::LeftHip)
492            .boxed(),
493        just("ATTACH_RHIP")
494            .or(just("Right Hip"))
495            .or(just("right hip"))
496            .to(AvatarAttachmentPoint::RightHip)
497            .boxed(),
498        just("ATTACH_LULEG")
499            .or(just("L Upper Leg"))
500            .or(just("left upper leg"))
501            .to(AvatarAttachmentPoint::LeftUpperLeg)
502            .boxed(),
503        just("ATTACH_RULEG")
504            .or(just("R Upper Leg"))
505            .or(just("right upper leg"))
506            .to(AvatarAttachmentPoint::RightUpperLeg)
507            .boxed(),
508        just("ATTACH_RLLEG")
509            .or(just("R Lower Leg"))
510            .or(just("right lower leg"))
511            .to(AvatarAttachmentPoint::LeftLowerLeg)
512            .boxed(),
513        just("ATTACH_LLLEG")
514            .or(just("L Lower Leg"))
515            .or(just("left lower leg"))
516            .to(AvatarAttachmentPoint::RightLowerLeg)
517            .boxed(),
518        just("ATTACH_LFOOT")
519            .or(just("Left Foot"))
520            .or(just("left foot"))
521            .to(AvatarAttachmentPoint::LeftFoot)
522            .boxed(),
523        just("ATTACH_RFOOT")
524            .or(just("Right Foot"))
525            .or(just("right foot"))
526            .to(AvatarAttachmentPoint::RightFoot)
527            .boxed(),
528        just("ATTACH_HIND_LFOOT")
529            .or(just("Left Hind Foot"))
530            .or(just("left hind foot"))
531            .to(AvatarAttachmentPoint::LeftHindFoot)
532            .boxed(),
533        just("ATTACH_HIND_RFOOT")
534            .or(just("Right Hind Foot"))
535            .or(just("right hind foot"))
536            .to(AvatarAttachmentPoint::RightHindFoot)
537            .boxed(),
538    ])
539}
540
541/// HUD attachment point
542#[derive(
543    Debug,
544    Clone,
545    Hash,
546    PartialEq,
547    Eq,
548    strum::FromRepr,
549    strum::EnumIs,
550    serde::Serialize,
551    serde::Deserialize,
552)]
553pub enum HudAttachmentPoint {
554    /// HUD Center 2
555    Center2 = 31,
556    /// HUD Top Right
557    TopRight = 32,
558    /// HUD Top
559    Top = 33,
560    /// HUD Top Left
561    TopLeft = 34,
562    /// HUD Center
563    Center = 35,
564    /// HUD Bottom Left
565    BottomLeft = 36,
566    /// HUD Bottom
567    Bottom = 37,
568    /// HUT Bottom Right
569    BottomRight = 38,
570}
571
572impl std::fmt::Display for HudAttachmentPoint {
573    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574        match self {
575            Self::Center2 => write!(f, "HUD Center 2"),
576            Self::TopRight => write!(f, "HUD Top Right"),
577            Self::Top => write!(f, "HUD Top"),
578            Self::TopLeft => write!(f, "HUD Top Left"),
579            Self::Center => write!(f, "HUD Center"),
580            Self::BottomLeft => write!(f, "HUD Bottom Left"),
581            Self::Bottom => write!(f, "HUD Bottom"),
582            Self::BottomRight => write!(f, "HUD Bottom Right"),
583        }
584    }
585}
586
587/// Error deserializing HudAttachmentPoint from String
588#[derive(Debug, Clone)]
589pub struct HudAttachmentPointParseError {
590    /// the value that could not be parsed
591    value: String,
592}
593
594impl std::fmt::Display for HudAttachmentPointParseError {
595    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
596        write!(f, "Could not parse as HudAttachmentPoint: {}", self.value)
597    }
598}
599
600impl std::str::FromStr for HudAttachmentPoint {
601    type Err = HudAttachmentPointParseError;
602
603    fn from_str(s: &str) -> Result<Self, Self::Err> {
604        match s {
605            "ATTACH_HUD_CENTER_2" | "HUD Center 2" | "Center 2" => Ok(Self::Center2),
606            "ATTACH_HUD_TOP_RIGHT" | "HUD Top Right" | "Top Right" => Ok(Self::TopRight),
607            "ATTACH_HUD_TOP_CENTER" | "HUD Top" | "Top" => Ok(Self::Top),
608            "ATTACH_HUD_TOP_LEFT" | "HUD Top Left" | "Top Left" => Ok(Self::TopLeft),
609            "ATTACH_HUD_CENTER_1" | "HUD Center" | "Center" => Ok(Self::Center),
610            "ATTACH_HUD_BOTTOM_LEFT" | "HUD Bottom Left" | "Bottom Left" => Ok(Self::BottomLeft),
611            "ATTACH_HUD_BOTTOM" | "HUD Bottom" | "Bottom" => Ok(Self::Bottom),
612            "ATTACH_HUD_BOTTOM_RIGHT" | "HUD Bottom Right " | "Bottom Right" => {
613                Ok(Self::BottomRight)
614            }
615            _ => Err(HudAttachmentPointParseError {
616                value: s.to_string(),
617            }),
618        }
619    }
620}
621
622/// parse a HUD attachment point
623///
624/// # Errors
625///
626/// returns an error if the string could not be parsed
627#[cfg(feature = "chumsky")]
628#[must_use]
629pub fn hud_attachment_point_parser<'src>() -> impl Parser<
630    'src,
631    &'src str,
632    HudAttachmentPoint,
633    chumsky::extra::Err<chumsky::error::Rich<'src, char>>,
634> {
635    choice([
636        just("ATTACH_HUD_CENTER_2")
637            .or(just("HUD Center 2"))
638            .or(just("Center 2"))
639            .to(HudAttachmentPoint::Center2),
640        just("ATTACH_HUD_TOP_RIGHT")
641            .or(just("HUD Top Right"))
642            .or(just("Top Right"))
643            .to(HudAttachmentPoint::TopRight),
644        just("ATTACH_HUD_TOP_LEFT")
645            .or(just("HUD Top Left"))
646            .or(just("Top Left"))
647            .to(HudAttachmentPoint::TopLeft),
648        just("ATTACH_HUD_TOP_CENTER")
649            .or(just("HUD Top"))
650            .or(just("Top"))
651            .to(HudAttachmentPoint::Top),
652        just("ATTACH_HUD_CENTER_1")
653            .or(just("HUD Center"))
654            .or(just("Center"))
655            .to(HudAttachmentPoint::Center),
656        just("ATTACH_HUD_BOTTOM_LEFT")
657            .or(just("HUD Bottom Left"))
658            .or(just("Bottom Left"))
659            .to(HudAttachmentPoint::BottomLeft),
660        just("ATTACH_HUD_BOTTOM_RIGHT")
661            .or(just("HUD Bottom Right "))
662            .or(just("Bottom Right"))
663            .to(HudAttachmentPoint::BottomRight),
664        just("ATTACH_HUD_BOTTOM")
665            .or(just("HUD Bottom"))
666            .or(just("Bottom"))
667            .to(HudAttachmentPoint::Bottom),
668    ])
669}
670
671/// avatar and HUD attachment points
672#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
673#[expect(
674    clippy::module_name_repetitions,
675    reason = "the type is going to be used outside of the module"
676)]
677pub enum AttachmentPoint {
678    /// avatar attachment point
679    Avatar(AvatarAttachmentPoint),
680    /// HUD attachment point
681    Hud(HudAttachmentPoint),
682}
683
684impl AttachmentPoint {
685    /// converts the numeric enum discriminant used in the LSL (and presumably
686    /// C++) code for the attachment point into the respective enum variant
687    ///
688    /// <https://wiki.secondlife.com/wiki/Category:LSL_Attachment>
689    ///
690    #[must_use]
691    pub fn from_repr(repr: usize) -> Option<Self> {
692        AvatarAttachmentPoint::from_repr(repr)
693            .map(Self::Avatar)
694            .or_else(|| HudAttachmentPoint::from_repr(repr).map(Self::Hud))
695    }
696}
697
698impl std::fmt::Display for AttachmentPoint {
699    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
700        match self {
701            Self::Avatar(avatar_attachment_point) => {
702                write!(f, "{avatar_attachment_point}")
703            }
704            Self::Hud(hud_attachment_point) => write!(f, "{hud_attachment_point}"),
705        }
706    }
707}
708
709/// Error deserializing AttachmentPoint from String
710#[derive(Debug, Clone)]
711#[expect(
712    clippy::module_name_repetitions,
713    reason = "the error is going to be used outside of the module"
714)]
715pub struct AttachmentPointParseError {
716    /// the value that could not be parsed
717    value: String,
718}
719
720impl std::fmt::Display for AttachmentPointParseError {
721    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
722        write!(f, "Could not parse as AttachmentPoint: {}", self.value)
723    }
724}
725
726impl std::str::FromStr for AttachmentPoint {
727    type Err = AttachmentPointParseError;
728
729    fn from_str(s: &str) -> Result<Self, Self::Err> {
730        if let Ok(avatar_attachment_point) =
731            <AvatarAttachmentPoint as std::str::FromStr>::from_str(s)
732        {
733            Ok(Self::Avatar(avatar_attachment_point))
734        } else if let Ok(hud_attachment_point) =
735            <HudAttachmentPoint as std::str::FromStr>::from_str(s)
736        {
737            Ok(Self::Hud(hud_attachment_point))
738        } else {
739            Err(AttachmentPointParseError {
740                value: s.to_string(),
741            })
742        }
743    }
744}
745
746/// parse an attachment point
747///
748/// # Errors
749///
750/// returns an error if the string could not be parsed
751#[cfg(feature = "chumsky")]
752#[must_use]
753#[expect(
754    clippy::module_name_repetitions,
755    reason = "the parser is going to be used outside of the module"
756)]
757pub fn attachment_point_parser<'src>()
758-> impl Parser<'src, &'src str, AttachmentPoint, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
759{
760    avatar_attachment_point_parser()
761        .map(AttachmentPoint::Avatar)
762        .or(hud_attachment_point_parser().map(AttachmentPoint::Hud))
763}
764
765#[cfg(test)]
766mod test {
767    #[cfg(feature = "chumsky")]
768    use super::{AttachmentPoint, HudAttachmentPoint, attachment_point_parser};
769    #[cfg(feature = "chumsky")]
770    use chumsky::Parser as _;
771    #[cfg(feature = "chumsky")]
772    use pretty_assertions::assert_eq;
773
774    #[cfg(feature = "chumsky")]
775    #[test]
776    fn test_parse_attachment_point_bottom_left() {
777        assert_eq!(
778            attachment_point_parser().parse("Bottom Left").into_result(),
779            Ok(AttachmentPoint::Hud(HudAttachmentPoint::BottomLeft)),
780        );
781    }
782
783    #[cfg(feature = "chumsky")]
784    #[test]
785    fn test_parse_attachment_point_bottom() {
786        assert_eq!(
787            attachment_point_parser().parse("Bottom").into_result(),
788            Ok(AttachmentPoint::Hud(HudAttachmentPoint::Bottom)),
789        );
790    }
791
792    #[cfg(feature = "chumsky")]
793    #[test]
794    fn test_parse_attachment_point_bottom_right() {
795        assert_eq!(
796            attachment_point_parser()
797                .parse("Bottom Right")
798                .into_result(),
799            Ok(AttachmentPoint::Hud(HudAttachmentPoint::BottomRight)),
800        );
801    }
802}