diff --git a/SecretAPI/Features/DecalHelpers.cs b/SecretAPI/Features/DecalHelpers.cs new file mode 100644 index 0000000..2283287 --- /dev/null +++ b/SecretAPI/Features/DecalHelpers.cs @@ -0,0 +1,111 @@ +namespace SecretAPI.Features; + +using System; +using Decals; +using InventorySystem.Items; +using InventorySystem.Items.Autosync; +using InventorySystem.Items.Firearms.Modules; +using Mirror; +using RelativePositioning; +using UnityEngine; +using Utils.Networking; + +/// +/// Helps with handling Decals () and sending them to clients without the need for a firearm with the . +/// +public static class DecalHelpers +{ + private static bool hasData = false; + private static ItemType itemType = ItemType.None; + private static byte subcomponentIndex = 0; + + /// + /// Creates an for spawning a decal that can be sent to players. + /// + /// The position where the decal will spawn. + /// The position from which the decal will be applied to a surface. + /// The type of decal to spawn. + /// The message which is to be sent to players. + public static AutosyncMessage GetDecalMessage(Vector3 position, Vector3 startPosition, DecalPoolType type) + { + RelativePosition hitPoint = new(position); + RelativePosition startRaycastPoint = new(startPosition); + + (ItemType itemTypeId, byte moduleIndex) = GetItemData(); + + using NetworkWriterPooled? writer = NetworkWriterPool.Get(); + writer.WriteByte(moduleIndex); + writer.WriteSubheader(ImpactEffectsModule.RpcType.ImpactDecal); + writer.WriteByte((byte)type); + writer.WriteRelativePosition(hitPoint); + writer.WriteRelativePosition(startRaycastPoint); + + return new AutosyncMessage(writer, new ItemIdentifier(itemTypeId, 0)); + } + + /// + /// Spawns a decal. + /// + /// The position where the decal will spawn. + /// The position from which the decal will be applied to a surface. + /// The type of decal to spawn. + public static void SpawnDecalFromPosition(Vector3 position, Vector3 startPosition, DecalPoolType type = DecalPoolType.Blood) + => GetDecalMessage(position, startPosition, type).SendToAuthenticated(); + + /// + /// Spawns a decal. + /// + /// The position where the decal will spawn. + /// The normal of the face the decal is on. + /// The type of decal to spawn. + public static void SpawnDecalFromNormal(Vector3 position, Vector3 normal, DecalPoolType type = DecalPoolType.Blood) + => GetDecalMessage(position, position + normal, type).SendToAuthenticated(); + + /// + /// Spawns a decal. + /// + /// The position where the decal will spawn. + /// The normal rotation of the face the decal is on. + /// The type of decal to spawn. + public static void SpawnDecalFromNormal(Vector3 position, Quaternion normal, DecalPoolType type = DecalPoolType.Blood) + => GetDecalMessage(position, position + (normal * Vector3.forward), type).SendToAuthenticated(); + + /// + /// Spawns a decal. + /// + /// The position where the decal will spawn. + /// The direction opposite to the normal of the face the decal will be placed on. + /// The type of decal to spawn. + public static void SpawnDecalFromDirection(Vector3 position, Vector3 direction, DecalPoolType type = DecalPoolType.Blood) + => GetDecalMessage(position, position - direction, type).SendToAuthenticated(); + + /// + /// Spawns a decal. + /// + /// The position where the decal will spawn. + /// The direction opposite to the normal of the face the decal will be placed on. + /// The type of decal to spawn. + public static void SpawnDecalFromDirection(Vector3 position, Quaternion direction, DecalPoolType type = DecalPoolType.Blood) + => GetDecalMessage(position, position - (direction * Vector3.forward), type).SendToAuthenticated(); + + private static (ItemType ItemType, byte SubcomponentIndex) GetItemData() + { + if (hasData) + return (itemType, subcomponentIndex); + + foreach (ModularAutosyncItem? autoItem in ModularAutosyncItem.AllTemplates) + { + for (byte b = 0; b < autoItem.AllSubcomponents.Length; b++) + { + if (autoItem.AllSubcomponents[b] is not ImpactEffectsModule) + continue; + subcomponentIndex = b; + itemType = autoItem.ItemTypeId; + hasData = true; + return (itemType, subcomponentIndex); + } + } + + throw new InvalidOperationException("Couldn't find the `InventorySystem.Items.Firearms.Modules.ImpactEffectsModule` in the any ModularAutosyncItem!"); + } +} \ No newline at end of file