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