diff --git a/EXILED/Exiled.API/Features/Room.cs b/EXILED/Exiled.API/Features/Room.cs
index 846a76509c..01fa683790 100644
--- a/EXILED/Exiled.API/Features/Room.cs
+++ b/EXILED/Exiled.API/Features/Room.cs
@@ -500,7 +500,7 @@ private static RoomType FindType(GameObject gameObject)
"HCZ_Corner_Deep" => RoomType.HczCornerDeep,
"HCZ_Straight" => RoomType.HczStraight,
"HCZ_Straight_C" => RoomType.HczStraightC,
- "HCZ_Straight_PipeRoom"=> RoomType.HczStraightPipeRoom,
+ "HCZ_Straight_PipeRoom" => RoomType.HczStraightPipeRoom,
"HCZ_Straight Variant" => RoomType.HczStraightVariant,
"HCZ_ChkpA" => RoomType.HczElevatorA,
"HCZ_ChkpB" => RoomType.HczElevatorB,
diff --git a/EXILED/Exiled.API/Structs/AttachmentIdentifier.cs b/EXILED/Exiled.API/Structs/AttachmentIdentifier.cs
index cc27b57a05..2c58c8dfd1 100644
--- a/EXILED/Exiled.API/Structs/AttachmentIdentifier.cs
+++ b/EXILED/Exiled.API/Structs/AttachmentIdentifier.cs
@@ -12,7 +12,7 @@ namespace Exiled.API.Structs
using System.Linq;
using Exiled.API.Enums;
-
+ using Exiled.API.Features.Items;
using InventorySystem.Items.Firearms.Attachments;
using InventorySystem.Items.Firearms.Attachments.Components;
@@ -129,33 +129,13 @@ internal AttachmentIdentifier(uint code, AttachmentName name, AttachmentSlot slo
/// A value representing the subtraction between the two operands.
public static uint operator -(uint left, AttachmentIdentifier right) => left - right.Code;
- ///
- /// Converts the string representation of a to its equivalent.
- /// A return value indicates whether the conversion is succeeded or failed.
- ///
- /// The to convert.
- /// The converted .
- /// if was converted successfully; otherwise, .
- public static bool TryParse(string s, out AttachmentIdentifier identifier)
- {
- identifier = default;
-
- foreach (AttachmentIdentifier attId in Features.Items.Firearm.AvailableAttachments.Values.SelectMany(kvp => kvp.Where(kvp2 => kvp2.Name.ToString() == s)))
- {
- identifier = attId;
- return true;
- }
-
- return false;
- }
-
///
/// Gets a by name.
///
/// Weapons .
/// Attachment name.
/// instance.
- public static AttachmentIdentifier Get(FirearmType type, AttachmentName name) => Features.Items.Firearm.AvailableAttachments[type].FirstOrDefault(identifier => identifier.Name == name);
+ public static AttachmentIdentifier Get(FirearmType type, AttachmentName name) => Firearm.AvailableAttachments[type].FirstOrDefault(identifier => identifier.Name == name);
///
/// Gets the all 's for type, by slot.
@@ -163,7 +143,7 @@ public static bool TryParse(string s, out AttachmentIdentifier identifier)
/// Weapons .
/// Attachment slot.
/// instance.
- public static IEnumerable Get(FirearmType type, AttachmentSlot slot) => Features.Items.Firearm.AvailableAttachments[type].Where(identifier => identifier.Slot == slot);
+ public static IEnumerable Get(FirearmType type, AttachmentSlot slot) => Firearm.AvailableAttachments[type].Where(identifier => identifier.Slot == slot);
///
/// Converts the string representation of a to its equivalent.
diff --git a/EXILED/Exiled.CustomRoles/CustomRoles.cs b/EXILED/Exiled.CustomRoles/CustomRoles.cs
index a66457b3bd..f52f08f3b3 100644
--- a/EXILED/Exiled.CustomRoles/CustomRoles.cs
+++ b/EXILED/Exiled.CustomRoles/CustomRoles.cs
@@ -36,7 +36,8 @@ public CustomRoles()
Loader.Deserializer = new DeserializerBuilder()
.WithTypeConverter(new VectorsConverter())
.WithTypeConverter(new ColorConverter())
- .WithTypeConverter(new AttachmentIdentifiersConverter())
+ .WithTypeConverter(new AttachmentNameConverter())
+ .WithTypeConverter(new AttachmentIdentifierConverter())
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.WithNodeDeserializer(inner => new AbstractClassNodeTypeResolver(inner, new AggregateExpectationTypeResolver(UnderscoredNamingConvention.Instance)), s => s.InsteadOf())
.IgnoreFields()
diff --git a/EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentIdentifierConverter.cs b/EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentIdentifierConverter.cs
new file mode 100644
index 0000000000..1feb3cd2c6
--- /dev/null
+++ b/EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentIdentifierConverter.cs
@@ -0,0 +1,69 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Loader.Features.Configs.CustomConverters
+{
+ using System;
+ using System.Linq;
+
+ using API.Structs;
+
+ using Exiled.API.Enums;
+ using Exiled.API.Features.Items;
+
+ using InventorySystem.Items.Firearms.Attachments;
+
+ using YamlDotNet.Core;
+ using YamlDotNet.Core.Events;
+ using YamlDotNet.Serialization;
+
+ ///
+ /// Converter and to .
+ ///
+ public sealed class AttachmentIdentifierConverter : IYamlTypeConverter
+ {
+ ///
+ public bool Accepts(Type type) => type == typeof(AttachmentIdentifier);
+
+ ///
+ public object ReadYaml(IParser parser, Type type)
+ {
+ if (!parser.TryConsume(out Scalar scalar))
+ throw new InvalidOperationException($"Expected a scalar for {nameof(AttachmentIdentifier)}.");
+
+ string[] parts = scalar?.Value?.Split(':');
+ if (parts.Length != 3)
+ throw new InvalidOperationException($"Invalid AttachmentIdentifier format: '{scalar?.Value}'. Expected \"AttachmentName:AttachmentSlot:AttachmentCode\".");
+
+ if (!Enum.TryParse(parts[0], true, out AttachmentName attachmentName))
+ throw new InvalidOperationException($"Invalid AttachmentName: '{parts[0]}'.");
+
+ if (!Enum.TryParse(parts[1], true, out AttachmentSlot attachmentSlot))
+ throw new InvalidOperationException($"Invalid AttachmentSlot: '{parts[1]}'.");
+
+ if (!uint.TryParse(parts[2], out uint code))
+ throw new InvalidOperationException($"Invalid AttachmentCode: '{parts[2]}'.");
+
+ return Firearm.AvailableAttachments
+ .SelectMany(kvp => kvp.Value)
+ .FirstOrDefault(att => att.Name == attachmentName && att.Slot == attachmentSlot && att.Code == code);
+ }
+
+ ///
+ public void WriteYaml(IEmitter emitter, object value, Type type)
+ {
+ AttachmentIdentifier attId = default;
+
+ if (value is AttachmentIdentifier castAttId)
+ attId = castAttId;
+
+ // If anyone ever looks at this code and doesn't understand why it's implemented the way it is, here's an explanation. The problem is that the NW code doesn't provide a way to properly serialize attachments into a string so that this string can then be deserialized back into an object while maintaining integrity. Therefore, literally the only way to obtain an object is to store it as three properties. Storing only "AttachmentName" will cause problems. Storing it as "FirearmType:AttachmentName" will also cause problems.
+ // https://discord.com/channels/656673194693885975/1002713309876854924/1488655471697989682
+ emitter.Emit(new Scalar($"{attId.Name}:{attId.Slot}:{attId.Code}"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentIdentifiersConverter.cs b/EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentNameConverter.cs
similarity index 91%
rename from EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentIdentifiersConverter.cs
rename to EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentNameConverter.cs
index 950078e802..d40ea7d446 100644
--- a/EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentIdentifiersConverter.cs
+++ b/EXILED/Exiled.Loader/Features/Configs/CustomConverters/AttachmentNameConverter.cs
@@ -1,5 +1,5 @@
// -----------------------------------------------------------------------
-//
+//
// Copyright (c) ExMod Team. All rights reserved.
// Licensed under the CC BY-SA 3.0 license.
//
@@ -22,7 +22,7 @@ namespace Exiled.Loader.Features.Configs.CustomConverters
///
/// Converts a of to Yaml configs and vice versa.
///
- public sealed class AttachmentIdentifiersConverter : IYamlTypeConverter
+ public sealed class AttachmentNameConverter : IYamlTypeConverter
{
///
public bool Accepts(Type type) => type == typeof(AttachmentName);
diff --git a/EXILED/Exiled.Loader/Loader.cs b/EXILED/Exiled.Loader/Loader.cs
index 00754c8c09..0fe42a2d58 100644
--- a/EXILED/Exiled.Loader/Loader.cs
+++ b/EXILED/Exiled.Loader/Loader.cs
@@ -106,7 +106,8 @@ public Loader()
public static ISerializer Serializer { get; set; } = new SerializerBuilder()
.WithTypeConverter(new VectorsConverter())
.WithTypeConverter(new ColorConverter())
- .WithTypeConverter(new AttachmentIdentifiersConverter())
+ .WithTypeConverter(new AttachmentNameConverter())
+ .WithTypeConverter(new AttachmentIdentifierConverter())
.WithEventEmitter(eventEmitter => new TypeAssigningEventEmitter(eventEmitter))
.WithTypeInspector(inner => new CommentGatheringTypeInspector(inner))
.WithEmissionPhaseObjectGraphVisitor(args => new CommentsObjectGraphVisitor(args.InnerVisitor))
@@ -121,7 +122,8 @@ public Loader()
public static IDeserializer Deserializer { get; set; } = new DeserializerBuilder()
.WithTypeConverter(new VectorsConverter())
.WithTypeConverter(new ColorConverter())
- .WithTypeConverter(new AttachmentIdentifiersConverter())
+ .WithTypeConverter(new AttachmentNameConverter())
+ .WithTypeConverter(new AttachmentIdentifierConverter())
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.WithNodeDeserializer(inner => new ValidatingNodeDeserializer(inner), deserializer => deserializer.InsteadOf())
.IgnoreFields()