From 96ccf323a77ef563b5693524a95d3026d9e313aa Mon Sep 17 00:00:00 2001 From: SrLicht Date: Mon, 2 Sep 2024 15:06:37 -0300 Subject: [PATCH] ``[Exiled::CustomItems]`` ``[Exiled::API]`` ``[Exiled::CustomRoles]`` Adding news Spawnpoints, Wrapper for Locker and added LockerType (#77) * QoL * Added ``RoomSpawnPoint`` for spawning things in a room with a offset property. * Now Items will be spawned in the MapGenerated event instead of RoundStart avoiding the micro-log for spawning to many pickups in one frame. * YES YAMATO I USE NULLEABLE * Shut * Done * Created a Wrapper for Locker * Created LockerSpawnPoint * I NEED HELP FOR IMPLEMENTING SUPPLYLOCKER I DONT FUCKING KNOW HOW MAKE A TRANSPILER * I hate you Yamato :D * Boop * Why Exiled use MONO.POSIX * And dont use the nuget for it :skull: * Now it will compile in the page * Removing Else if (spawnPoint is RoleSpawnPoint roleSpawnPoint) since its not necessary spawnpoint.Position does the same. * Supressing CS0618 due the obsolet in SpawnLocationType.InsideLocker. * Note: SpawnLocationType.InsideLocker its only used in CustomItem.SpawnAll() so it will be fine to deleted this in futures releases. * Fixing compile action due the warnings * Adding support for Offset Almost forget about this :P * Sorry I cant resist adding more things * Adding support for CustomRoles * Implementing SupplyLocker * I literally copy the generator transpiler * Its works i test it. * Give me my exiled contributor role. * LockerType enum * Code part of the code has been taken from MER (https://github.com/Michal78900/MapEditorReborn/blob/dev/MapEditorReborn/API/Extensions/LockerExtensions.cs) - Credits to Michal, i ask him i can use it, not answer yet but if the say no i will use another way. * SupplyLocker now have a LockerType Property to know what type of Locker is * LockerSpawnPoint can now chose what locker want to use * Mimimi warnings * Re-implementing Locker API * Re-implementing locker api of https://github.com/Exiled-Team/EXILED/pull/2026 * Update EXILED/Exiled.API/Enums/LockerType.cs Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> * I dont like the name of ExiledLockers but * Resolving https://github.com/ExMod-Team/EXILED/pull/77#discussion_r1734360930 * Resolving https://github.com/ExMod-Team/EXILED/pull/77#discussion_r1734360419 * Cleaning Chambers List. * Fixing CustomWeapon * Fixing a Bug with custom items with spawning in old SpawnLocationType.InsideLocker * Update Map.cs Removing blank line. * MORE * Added GetRandomSpawnPoint() in Chamber * Added AddItemToSpawn((ItemType itemType, int quantity = 1, bool spawnIfIsOpen = false)) in Chamber * Added IsOpen in chamber. * Fixing obsolet use * Sorry @VALERA771 * Resolve https://github.com/ExMod-Team/EXILED/pull/77#pullrequestreview-2267004377 * Update Exiled.Loader.csproj * Resolving https://github.com/ExMod-Team/EXILED/pull/77#discussion_r1734047353 * Update MapHandler.cs Reduce the delay on spawning items, its not necessary to be to long * Ups Ups --------- Co-authored-by: Nameless <85962933+Misfiy@users.noreply.github.com> --- EXILED/Exiled.API/Enums/LockerType.cs | 50 ++++ EXILED/Exiled.API/Enums/SpawnLocationType.cs | 4 +- .../Exiled.API/Extensions/LockerExtensions.cs | 43 ++++ EXILED/Exiled.API/Features/Lockers/Chamber.cs | 224 +++++++++++++++++ EXILED/Exiled.API/Features/Lockers/Locker.cs | 230 ++++++++++++++++++ EXILED/Exiled.API/Features/Map.cs | 26 +- EXILED/Exiled.API/Features/Player.cs | 4 +- .../Features/Spawn/LockerSpawnPoint.cs | 72 ++++++ .../Features/Spawn/RoomSpawnPoint.cs | 56 +++++ .../Features/Spawn/SpawnProperties.cs | 12 +- .../API/Features/CustomItem.cs | 26 +- EXILED/Exiled.CustomItems/Commands/Info.cs | 11 +- EXILED/Exiled.CustomItems/CustomItems.cs | 8 +- .../Exiled.CustomItems/Events/MapHandler.cs | 12 +- .../API/Features/CustomRole.cs | 10 + .../Handlers/Internal/MapGenerated.cs | 3 +- .../Patches/Generic/LockerList.cs | 12 +- 17 files changed, 761 insertions(+), 42 deletions(-) create mode 100644 EXILED/Exiled.API/Enums/LockerType.cs create mode 100644 EXILED/Exiled.API/Extensions/LockerExtensions.cs create mode 100644 EXILED/Exiled.API/Features/Lockers/Chamber.cs create mode 100644 EXILED/Exiled.API/Features/Lockers/Locker.cs create mode 100644 EXILED/Exiled.API/Features/Spawn/LockerSpawnPoint.cs create mode 100644 EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs diff --git a/EXILED/Exiled.API/Enums/LockerType.cs b/EXILED/Exiled.API/Enums/LockerType.cs new file mode 100644 index 000000000..c44037a3a --- /dev/null +++ b/EXILED/Exiled.API/Enums/LockerType.cs @@ -0,0 +1,50 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + /// + /// Unique identifier for different types of s. + /// + public enum LockerType + { + /// + /// The pedestal used by SCP items. + /// + Pedestal, + + /// + /// Large weapon locker. + /// + LargeGun, + + /// + /// Locker for rifles, known as a rifle rack. + /// + RifleRack, + + /// + /// Miscellaneous locker for various items. + /// + Misc, + + /// + /// Locker that contains medkits. + /// + Medkit, + + /// + /// Locker that contains adrenaline. + /// + Adrenaline, + + /// + /// Unknow type of locker. + /// + Unknow, + } +} diff --git a/EXILED/Exiled.API/Enums/SpawnLocationType.cs b/EXILED/Exiled.API/Enums/SpawnLocationType.cs index 852e07f43..63568ba3b 100644 --- a/EXILED/Exiled.API/Enums/SpawnLocationType.cs +++ b/EXILED/Exiled.API/Enums/SpawnLocationType.cs @@ -4,9 +4,10 @@ // Licensed under the CC BY-SA 3.0 license. // // ----------------------------------------------------------------------- - namespace Exiled.API.Enums { + using System; + /// /// All of the valid spawn location types. /// @@ -150,6 +151,7 @@ public enum SpawnLocationType /// /// Inside a random locker on the map. /// + [Obsolete("Use LockerSpawnPoint instead")] InsideLocker, } } \ No newline at end of file diff --git a/EXILED/Exiled.API/Extensions/LockerExtensions.cs b/EXILED/Exiled.API/Extensions/LockerExtensions.cs new file mode 100644 index 000000000..7304cbd4e --- /dev/null +++ b/EXILED/Exiled.API/Extensions/LockerExtensions.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Extensions +{ + using System; + + using Exiled.API.Enums; + using MapGeneration.Distributors; + + /// + /// A set of extensions for . + /// + public static class LockerExtensions + { + /// + /// Gets the from the given object. + /// + /// The to check. + /// The corresponding . + public static LockerType GetLockerType(this Locker locker) => locker.name.GetLockerTypeByName(); + + /// + /// Gets the by name. + /// + /// The name to check. + /// The corresponding . + public static LockerType GetLockerTypeByName(this string name) => name.Replace("(Clone)", string.Empty) switch + { + "Scp500PedestalStructure Variant" => LockerType.Pedestal, + "LargeGunLockerStructure" => LockerType.LargeGun, + "RifleRackStructure" => LockerType.RifleRack, + "MiscLocker" => LockerType.Misc, + "RegularMedkitStructure" => LockerType.Medkit, + "AdrenalineMedkitStructure" => LockerType.Adrenaline, + _ => LockerType.Unknow, + }; + } +} diff --git a/EXILED/Exiled.API/Features/Lockers/Chamber.cs b/EXILED/Exiled.API/Features/Lockers/Chamber.cs new file mode 100644 index 000000000..25f9385b8 --- /dev/null +++ b/EXILED/Exiled.API/Features/Lockers/Chamber.cs @@ -0,0 +1,224 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.API.Features.Lockers +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features.Pickups; + using Exiled.API.Interfaces; + using MapGeneration.Distributors; + using UnityEngine; + + /// + /// A wrapper for . + /// + public class Chamber : IWrapper, IWorldSpace + { + /// + /// with and . + /// + internal static readonly Dictionary Chambers = new(); + + /// + /// Initializes a new instance of the class. + /// + /// instance. + /// where this chamber is located. + public Chamber(LockerChamber chamber, Locker locker) + { + Base = chamber; + Locker = locker; + + Chambers.Add(chamber, this); + } + + /// + /// Gets a of which contains all the instances. + /// + public static IReadOnlyCollection List => Chambers.Values; + + /// + public LockerChamber Base { get; } + + /// + /// Gets the where this chamber is located at. + /// + public Locker Locker { get; } + + /// + public Vector3 Position => Base.transform.position; + + /// + public Quaternion Rotation => Base.transform.rotation; + + /// + /// Gets or sets all pickups that should be spawned when the door is initially opened. + /// + public IEnumerable ToBeSpawned + { + get => Base._toBeSpawned.Select(Pickup.Get); + set + { + Base._toBeSpawned.Clear(); + + foreach (Pickup pickup in value) + Base._toBeSpawned.Add(pickup.Base); + } + } + + /// + /// Gets or sets all spawn points. + /// + /// + /// Used if is set to . + /// + public IEnumerable Spawnpoints + { + get => Base._spawnpoints; + set => Base._spawnpoints = value.ToArray(); + } + + /// + /// Gets or sets all the acceptable items which can be spawned in this chamber. + /// + public IEnumerable AcceptableTypes + { + get => Base.AcceptableItems; + set => Base.AcceptableItems = value.ToArray(); + } + + /// + /// Gets or sets required permissions to open this chamber. + /// + public KeycardPermissions RequiredPermissions + { + get => (KeycardPermissions)Base.RequiredPermissions; + set => Base.RequiredPermissions = (Interactables.Interobjects.DoorUtils.KeycardPermissions)value; + } + + /// + /// Gets or sets a value indicating whether multiple spawn points should be used. + /// + /// + /// If , will be used over . + /// + public bool UseMultipleSpawnpoints + { + get => Base._useMultipleSpawnpoints; + set => Base._useMultipleSpawnpoints = value; + } + + /// + /// Gets or sets a spawn point for the items in the chamber. + /// + /// + /// Used if is set to . + /// + public Transform Spawnpoint + { + get => Base._spawnpoint; + set => Base._spawnpoint = value; + } + + /// + /// Gets or sets a value indicating whether or not items should be spawned as soon as they one chamber is opened. + /// + public bool InitiallySpawn + { + get => Base._spawnOnFirstChamberOpening; + set => Base._spawnOnFirstChamberOpening = value; + } + + /// + /// Gets or sets the amount of time before a player can interact with the chamber again. + /// + public float Cooldown + { + get => Base._targetCooldown; + set => Base._targetCooldown = value; + } + + /// + /// Gets a value indicating whether the chamber is currently open. + /// + public bool IsOpen => Base.IsOpen; + + /// + /// Gets the of current cooldown. + /// + /// Used in check. + public Stopwatch CurrentCooldown => Base._stopwatch; + + /// + /// Gets a value indicating whether the chamber is interactable. + /// + public bool CanInteract => Base.CanInteract; + + /// + /// Spawns a specified item from . + /// + /// from . + /// Amount of items that should be spawned. + public void SpawnItem(ItemType type, int amount) => Base.SpawnItem(type, amount); + + /// + /// Adds an item of the specified type to the chamber's spawn list. + /// If the chamber is open and is set to , + /// the item is spawned immediately at a random spawn point within the chamber. + /// + /// The type of item to add to the spawn list. + /// The number of items to add. Defaults to 1. + /// + /// If and the chamber is open, the item is immediately spawned at a random spawn point. + /// Otherwise, the item is added to the spawn list and will spawn when the chamber is opened. + /// + public void AddItemToSpawn(ItemType itemType, int quantity = 1, bool spawnIfIsOpen = false) + { + for (int i = 0; i < quantity; i++) + { + Pickup pickup = Pickup.Create(itemType); + + if (spawnIfIsOpen && IsOpen) + { + pickup.Position = GetRandomSpawnPoint(); + pickup.Spawn(); + continue; + } + + Base._toBeSpawned.Add(pickup.Base); + } + } + + /// + /// Gets a random spawn point within the chamber. + /// If multiple spawn points are available and is , + /// a random spawn point is selected from the available points. + /// Otherwise, the default spawn point is used. + /// + /// A representing the position of the selected spawn point. + public Vector3 GetRandomSpawnPoint() + { + if (UseMultipleSpawnpoints && Spawnpoints.Any()) + { + return Spawnpoints.GetRandomValue().position; + } + + return Spawnpoint.position; + } + + /// + /// Gets the chamber by its . + /// + /// . + /// . + internal static Chamber Get(LockerChamber chamber) => Chambers.TryGetValue(chamber, out Chamber chmb) ? chmb : new(chamber, Locker.Get(x => x.Chambers.Any(x => x.Base == chamber)).FirstOrDefault()); + } +} diff --git a/EXILED/Exiled.API/Features/Lockers/Locker.cs b/EXILED/Exiled.API/Features/Lockers/Locker.cs new file mode 100644 index 000000000..73108359b --- /dev/null +++ b/EXILED/Exiled.API/Features/Lockers/Locker.cs @@ -0,0 +1,230 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.API.Features.Lockers +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features; + using Exiled.API.Features.Pickups; + using Exiled.API.Interfaces; + + using InventorySystem.Items.Pickups; + using MapGeneration.Distributors; + + using Mirror; + using UnityEngine; + + using BaseLocker = MapGeneration.Distributors.Locker; +#nullable enable + /// + /// The in-game Locker. + /// + public class Locker : IWrapper, IWorldSpace + { + /// + /// A containing all known s and their corresponding . + /// + internal static readonly Dictionary BaseToExiledLockers = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The encapsulated . + public Locker(BaseLocker locker) + { + Base = locker; + BaseToExiledLockers.Add(locker, this); + + Chambers = locker.Chambers.Select(x => new Chamber(x, this)).ToList(); + Type = locker.GetLockerType(); + } + + /// + /// Gets a of which contains all the instances. + /// + public static IReadOnlyCollection List => BaseToExiledLockers.Values; + + /// + public BaseLocker Base { get; } + + /// + /// Gets the of the . + /// + public LockerType Type { get; } + + /// + /// Gets the . + /// + public Transform Transform => Base.transform; + + /// + public Vector3 Position => Base.transform.position; + + /// + public Quaternion Rotation => Base.transform.rotation; + + /// + /// Gets the in which the is located. + /// + public Room? Room => Room.Get(Position); + + /// + /// Gets the in which the locker is located. + /// + public ZoneType Zone => Room?.Zone ?? ZoneType.Unspecified; + + /// + /// Gets the all in this locker. + /// + public IReadOnlyCollection Chambers { get; } + + /// + /// Gets or sets an id for manipulating opened chambers. + /// + public ushort OpenedChambers + { + get => Base.OpenedChambers; + set => Base.NetworkOpenedChambers = value; + } + + /// + /// Gets a random position from one of the . + /// + public Vector3 RandomChamberPosition + { + get + { + Chamber randomChamber = Chambers.GetRandomValue(); + + // Determine if the chamber uses multiple spawn points and has at least one available spawn point. + if (randomChamber.UseMultipleSpawnpoints && randomChamber.Spawnpoints.Count() > 0) + { + // Return the position of a random spawn point within the chamber. + return randomChamber.Spawnpoints.GetRandomValue().position; + } + + // Return the position of the main spawn point for the chamber. + return randomChamber.Spawnpoint.position; + } + } + + /// + /// Gets the belonging to the , if any. + /// + /// The to get. + /// A or if not found. + public static Locker? Get(BaseLocker locker) => locker == null ? null : + BaseToExiledLockers.TryGetValue(locker, out Locker supply) ? supply : new Locker(locker); + + /// + /// Gets a of given the specified . + /// + /// The to search for. + /// The with the given or if not found. + public static IEnumerable Get(ZoneType zoneType) => Get(room => room.Zone.HasFlag(zoneType)); + + /// + /// Gets a of filtered based on a predicate. + /// + /// The condition to satify. + /// A of which contains elements that satify the condition. + public static IEnumerable Get(Func predicate) => List.Where(predicate); + + /// + /// Gets a random based on the specified filters. + /// + /// The to filter by. If unspecified, all zones are considered. + /// The to filter by. If unspecified, all locker types are considered. + /// A random object, or if no matching locker is found. + public static Locker? Random(ZoneType zone = ZoneType.Unspecified, LockerType lockerType = LockerType.Unknow) + { + IEnumerable filteredLockers = List; + + if (lockerType != LockerType.Unknow) + filteredLockers = filteredLockers.Where(l => l.Type == lockerType); + + if (zone != ZoneType.Unspecified) + filteredLockers = filteredLockers.Where(l => l.Zone == zone); + + return filteredLockers.GetRandomValue(); + } + + /// + /// Adds an item to a randomly selected locker chamber. + /// + /// The to be added to the locker chamber. + public void AddItem(Pickup item) + { + // Select a random chamber from the available locker chambers. + Chamber chamber = Chambers.GetRandomValue(); + + // Determine the parent transform where the item will be placed. + Transform parentTransform = chamber.UseMultipleSpawnpoints && chamber.Spawnpoints.Count() > 0 + ? chamber.Spawnpoints.GetRandomValue() + : chamber.Spawnpoint; + + // If the chamber is open, immediately set the item's parent and spawn it. + if (chamber.Base.IsOpen) + { + item.Transform.SetParent(parentTransform); + item.Spawn(); + } + else + { + // If the item is already spawned on the network, unspawn it before proceeding. + if (NetworkServer.spawned.ContainsKey(item.Base.netId)) + NetworkServer.UnSpawn(item.GameObject); + + // Set the item's parent transform. + item.Transform.SetParent(parentTransform); + + // Lock the item in place. + item.IsLocked = true; + + // Notify any pickup distributor triggers. + (item.Base as IPickupDistributorTrigger)?.OnDistributed(); + + // If the item has a Rigidbody component, make it kinematic and reset its position and rotation. + if (item.Rigidbody != null) + { + item.Rigidbody.isKinematic = true; + item.Rigidbody.transform.localPosition = Vector3.zero; + item.Rigidbody.transform.localRotation = Quaternion.identity; + + // Add the Rigidbody to the list of bodies to be unfrozen later. + SpawnablesDistributorBase.BodiesToUnfreeze.Add(item.Rigidbody); + } + + // If the chamber is configured to spawn items on the first opening, add the item to the list of items to be spawned. + // Otherwise, spawn the item immediately. + if (chamber.InitiallySpawn) + chamber.Base._toBeSpawned.Add(item.Base); + else + ItemDistributor.SpawnPickup(item.Base); + } + } + + /// + /// Spawns an item of the specified to the locker by creating a new . + /// + /// The type of item to be added. + public void AddItem(ItemType type) => AddItem(Pickup.Create(type)); + + /// + /// Clears the cached lockers in the dictionary. + /// + internal static void ClearCache() + { + BaseToExiledLockers.Clear(); + Chamber.Chambers.Clear(); + } + } +} diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index fa8db85b0..9df9bbd37 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -16,6 +16,7 @@ namespace Exiled.API.Features using Enums; using Exiled.API.Extensions; using Exiled.API.Features.Hazards; + using Exiled.API.Features.Lockers; using Exiled.API.Features.Pickups; using Exiled.API.Features.Toys; using global::Hazards; @@ -42,11 +43,6 @@ namespace Exiled.API.Features /// public static class Map { - /// - /// A list of s on the map. - /// - internal static readonly List LockersValue = new(35); - /// /// A list of s on the map. /// @@ -84,9 +80,13 @@ DecontaminationController.Singleton.NetworkDecontaminationOverride is Decontamin public static ReadOnlyCollection PocketDimensionTeleports { get; } = TeleportsValue.AsReadOnly(); /// - /// Gets all objects. + /// Gets all objects in the current map. /// - public static ReadOnlyCollection Lockers { get; } = LockersValue.AsReadOnly(); + /// + /// This property is obsolete. Use instead to retrieve a collection of all instances. + /// + [Obsolete("Use Locker.List instead.")] + public static ReadOnlyCollection Lockers { get; } = Features.Lockers.Locker.BaseToExiledLockers.Keys.ToList().AsReadOnly(); /// /// Gets all objects. @@ -224,10 +224,14 @@ public static void ResetLightsColor() } /// - /// Gets a random . + /// Gets a random object from the current map. /// - /// object. - public static Locker GetRandomLocker() => Lockers.GetRandomValue(); + /// + /// This method is obsolete. Use instead to get a random instance. + /// + /// A randomly selected object. + [Obsolete("Use Locker.Random() instead.")] + public static MapGeneration.Distributors.Locker GetRandomLocker() => Lockers.GetRandomValue(); /// /// Gets a random . @@ -401,8 +405,6 @@ internal static void ClearCache() { Item.BaseToItem.Clear(); - LockersValue.RemoveAll(locker => locker == null); - Ragdoll.BasicRagdollToRagdoll.Clear(); Items.Firearm.ItemTypeToFirearmInstance.Clear(); diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 24ec22c7c..bf67ae13d 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -3607,11 +3607,11 @@ public void RandomTeleport(Type type) nameof(Player) => Dictionary.Values.GetRandomValue(), nameof(Pickup) => Pickup.BaseToPickup.GetRandomValue().Value, nameof(Ragdoll) => Ragdoll.List.GetRandomValue(), - nameof(Locker) => Map.GetRandomLocker(), + nameof(Locker) => Lockers.Locker.Random().Base, nameof(Generator) => Generator.List.GetRandomValue(), nameof(Window) => Window.List.GetRandomValue(), nameof(Scp914) => Scp914.Scp914Controller, - nameof(LockerChamber) => Map.GetRandomLocker().Chambers.GetRandomValue(), + nameof(LockerChamber) => Lockers.Locker.Random().Chambers.GetRandomValue().Base, _ => null, }; diff --git a/EXILED/Exiled.API/Features/Spawn/LockerSpawnPoint.cs b/EXILED/Exiled.API/Features/Spawn/LockerSpawnPoint.cs new file mode 100644 index 000000000..ecc434127 --- /dev/null +++ b/EXILED/Exiled.API/Features/Spawn/LockerSpawnPoint.cs @@ -0,0 +1,72 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.API.Features.Spawn +{ + using System; + using System.Linq; + + using Exiled.API.Enums; + using Exiled.API.Features.Lockers; + using UnityEngine; + using YamlDotNet.Serialization; + + /// + /// Handles the spawn point inside a locker. + /// + public class LockerSpawnPoint : SpawnPoint + { + /// + /// Gets or sets the zone where the locker is located. + /// + public ZoneType Zone { get; set; } = ZoneType.Unspecified; + + /// + /// Gets or sets a value indicating whether to use a random locker chamber's position for spawning. + /// If , will be ignored. + /// + public bool UseChamber { get; set; } + + /// + /// Gets or sets the offset position within the locker where the spawn point is located, relative to the locker's origin. + /// + public Vector3 Offset { get; set; } = Vector3.zero; + + /// + /// Gets or sets the type of the . + /// + public LockerType Type { get; set; } = LockerType.Unknow; + + /// + public override float Chance { get; set; } + + /// + [YamlIgnore] + public override string Name + { + get => Zone.ToString(); + set => throw new InvalidOperationException("The name of this type of SpawnPoint cannot be changed."); + } + + /// + [YamlIgnore] + public override Vector3 Position + { + get + { + Locker foundLocker = Locker.Random(Zone, Type) ?? throw new NullReferenceException("No locker found in the specified zone."); + + // If UseChamber is true, use a random chamber's position. + if (UseChamber) + return foundLocker.RandomChamberPosition; + + // Otherwise, use the Offset if provided, or the locker's position. + return Offset != Vector3.zero ? foundLocker.Transform.TransformPoint(Offset) : foundLocker.Position; + } + set => throw new InvalidOperationException("The position of this type of SpawnPoint cannot be changed."); + } + } +} diff --git a/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs b/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs new file mode 100644 index 000000000..92e24e712 --- /dev/null +++ b/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- +namespace Exiled.API.Features.Spawn +{ + using System; + + using Exiled.API.Enums; + + using UnityEngine; + + using YamlDotNet.Serialization; + + /// + /// Represents a spawn point within a specific room in the game. + /// + public class RoomSpawnPoint : SpawnPoint + { + /// + /// Gets or sets the room type used for this spawn. + /// + public RoomType Room { get; set; } + + /// + /// Gets or sets the offset position within the room where the spawn point is located, relative to the room's origin. + /// + public Vector3 Offset { get; set; } = Vector3.zero; + + /// + public override float Chance { get; set; } + + /// + [YamlIgnore] + public override string Name + { + get => Room.ToString(); + set => throw new InvalidOperationException("The name of this type of SpawnPoint cannot be changed."); + } + + /// + [YamlIgnore] + public override Vector3 Position + { + get + { + Room roomInstance = Features.Room.Get(Room) ?? throw new InvalidOperationException("The room instance could not be found."); + + return Offset != Vector3.zero ? roomInstance.transform.TransformPoint(Offset) : roomInstance.Position; + } + set => throw new InvalidOperationException("The position of this type of SpawnPoint cannot be changed."); + } + } +} diff --git a/EXILED/Exiled.API/Features/Spawn/SpawnProperties.cs b/EXILED/Exiled.API/Features/Spawn/SpawnProperties.cs index ee8ddef4b..4cdf3345b 100644 --- a/EXILED/Exiled.API/Features/Spawn/SpawnProperties.cs +++ b/EXILED/Exiled.API/Features/Spawn/SpawnProperties.cs @@ -34,10 +34,20 @@ public class SpawnProperties /// public List RoleSpawnPoints { get; set; } = new(); + /// + /// Gets or sets a of possible room-based spawn points. + /// + public List RoomSpawnPoints { get; set; } = new(); + + /// + /// Gets or sets a of possible locker-based spawn points. + /// + public List LockerSpawnPoints { get; set; } = new(); + /// /// Counts how many spawn points are in this instance. /// /// How many spawn points there are. - public int Count() => DynamicSpawnPoints.Count + StaticSpawnPoints.Count + RoleSpawnPoints.Count; + public int Count() => DynamicSpawnPoints.Count + StaticSpawnPoints.Count + RoleSpawnPoints.Count + RoomSpawnPoints.Count + LockerSpawnPoints.Count; } } \ No newline at end of file diff --git a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs index 23359d6b0..c8598a98b 100644 --- a/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs +++ b/EXILED/Exiled.CustomItems/API/Features/CustomItem.cs @@ -619,6 +619,7 @@ public virtual uint Spawn(IEnumerable spawnPoints, uint limit) spawned++; +#pragma warning disable CS0618 // Type or member is obsolete \\ TODO: REMOVE THIS if (spawnPoint is DynamicSpawnPoint dynamicSpawnPoint && dynamicSpawnPoint.Location == SpawnLocationType.InsideLocker) { for (int i = 0; i < 50; i++) @@ -660,16 +661,18 @@ public virtual uint Spawn(IEnumerable spawnPoints, uint limit) } Vector3 position = chamber._spawnpoint.transform.position; - Spawn(position, null); - Log.Debug($"Spawned {Name} at {position} ({spawnPoint.Name})"); + Pickup? pickup = Spawn(position, null); + if (pickup?.Base is BaseFirearmPickup firearmPickup && this is CustomWeapon customWeapon) + { + firearmPickup.Status = new FirearmStatus(customWeapon.ClipSize, firearmPickup.Status.Flags, firearmPickup.Status.Attachments); + firearmPickup.NetworkStatus = firearmPickup.Status; + } + + Log.Debug($"Spawned {Name} at {position} ({spawnPoint.Name})"); break; } } - else if (spawnPoint is RoleSpawnPoint roleSpawnPoint) - { - Spawn(roleSpawnPoint.Role.GetRandomSpawnLocation().Position, null); - } else { Pickup? pickup = Spawn(spawnPoint.Position, null); @@ -681,6 +684,7 @@ public virtual uint Spawn(IEnumerable spawnPoints, uint limit) Log.Debug($"Spawned {Name} at {spawnPoint.Position} ({spawnPoint.Name})"); } +#pragma warning restore CS0618 // Type or member is obsolete } return spawned; @@ -694,8 +698,8 @@ public virtual void SpawnAll() if (SpawnProperties is null) return; - // This will go over each spawn property type (static, dynamic and role) to try and spawn the item. - // It will attempt to spawn in role-based locations, and then dynamic ones, and finally static. + // This will go over each spawn property type (static, dynamic, role-based, room-based, and locker-based) to try and spawn the item. + // It will attempt to spawn in role-based locations, then dynamic ones, followed by room-based, locker-based, and finally static. // Math.Min is used here to ensure that our recursive Spawn() calls do not result in exceeding the spawn limit config. // This is the same as: // int spawned = 0; @@ -703,8 +707,12 @@ public virtual void SpawnAll() // if (spawned < SpawnProperties.Limit) // spawned += Spawn(SpawnProperties.DynamicSpawnPoints, SpawnProperties.Limit - spawned); // if (spawned < SpawnProperties.Limit) + // spawned += Spawn(SpawnProperties.RoomSpawnPoints, SpawnProperties.Limit - spawned); + // if (spawned < SpawnProperties.Limit) + // spawned += Spawn(SpawnProperties.LockerSpawnPoints, SpawnProperties.Limit - spawned); + // if (spawned < SpawnProperties.Limit) // Spawn(SpawnProperties.StaticSpawnPoints, SpawnProperties.Limit - spawned); - Spawn(SpawnProperties.StaticSpawnPoints, Math.Min(0, SpawnProperties.Limit - Math.Min(0, Spawn(SpawnProperties.DynamicSpawnPoints, SpawnProperties.Limit) - Spawn(SpawnProperties.RoleSpawnPoints, SpawnProperties.Limit)))); + Spawn(SpawnProperties.StaticSpawnPoints, Math.Min(SpawnProperties.Limit, SpawnProperties.Limit - Math.Min(Spawn(SpawnProperties.DynamicSpawnPoints, SpawnProperties.Limit), SpawnProperties.Limit - Math.Min(Spawn(SpawnProperties.RoleSpawnPoints, SpawnProperties.Limit), SpawnProperties.Limit - Math.Min(Spawn(SpawnProperties.RoomSpawnPoints, SpawnProperties.Limit), SpawnProperties.Limit - Spawn(SpawnProperties.LockerSpawnPoints, SpawnProperties.Limit)))))); } /// diff --git a/EXILED/Exiled.CustomItems/Commands/Info.cs b/EXILED/Exiled.CustomItems/Commands/Info.cs index f0de9f1a9..0b6c6e549 100644 --- a/EXILED/Exiled.CustomItems/Commands/Info.cs +++ b/EXILED/Exiled.CustomItems/Commands/Info.cs @@ -68,7 +68,7 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s .Append("- ").AppendLine(item?.Description) .AppendLine(item?.Type.ToString()) .Append("- Spawn Limit: ").AppendLine(item?.SpawnProperties?.Limit.ToString()).AppendLine() - .Append("[Spawn Locations (").Append(item?.SpawnProperties?.DynamicSpawnPoints.Count + item?.SpawnProperties?.StaticSpawnPoints.Count).AppendLine(")]"); + .Append("[Spawn Locations (").Append(item?.SpawnProperties?.Count()).AppendLine(")]"); foreach (DynamicSpawnPoint spawnPoint in item?.SpawnProperties?.DynamicSpawnPoints!) message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); @@ -76,6 +76,15 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s foreach (StaticSpawnPoint spawnPoint in item.SpawnProperties.StaticSpawnPoints) message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); + foreach (RoleSpawnPoint spawnPoint in item.SpawnProperties.RoleSpawnPoints) + message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); + + foreach (LockerSpawnPoint spawnPoint in item.SpawnProperties.LockerSpawnPoints) + message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); + + foreach (RoomSpawnPoint spawnPoint in item.SpawnProperties.RoomSpawnPoints) + message.Append(spawnPoint.Name).Append(' ').Append(spawnPoint.Position).Append(" Chance: ").Append(spawnPoint.Chance).AppendLine("%"); + response = StringBuilderPool.Pool.ToStringReturn(message); return true; } diff --git a/EXILED/Exiled.CustomItems/CustomItems.cs b/EXILED/Exiled.CustomItems/CustomItems.cs index e11a0df2c..855186c00 100644 --- a/EXILED/Exiled.CustomItems/CustomItems.cs +++ b/EXILED/Exiled.CustomItems/CustomItems.cs @@ -19,7 +19,7 @@ namespace Exiled.CustomItems /// public class CustomItems : Plugin { - private MapHandler? mapHandler; + private MapHandler? roundHandler; private PlayerHandler? playerHandler; private Harmony? harmony; @@ -32,10 +32,10 @@ public class CustomItems : Plugin public override void OnEnabled() { Instance = this; - mapHandler = new MapHandler(); + roundHandler = new MapHandler(); playerHandler = new PlayerHandler(); - Exiled.Events.Handlers.Map.Generated += mapHandler.OnMapGenerated; + Exiled.Events.Handlers.Map.Generated += roundHandler.OnMapGenerated; Exiled.Events.Handlers.Player.ChangingItem += playerHandler.OnChangingItem; @@ -50,7 +50,7 @@ public override void OnEnabled() /// public override void OnDisabled() { - Exiled.Events.Handlers.Map.Generated -= mapHandler!.OnMapGenerated; + Exiled.Events.Handlers.Map.Generated -= roundHandler!.OnMapGenerated; Exiled.Events.Handlers.Player.ChangingItem -= playerHandler!.OnChangingItem; diff --git a/EXILED/Exiled.CustomItems/Events/MapHandler.cs b/EXILED/Exiled.CustomItems/Events/MapHandler.cs index 678d0c404..4e0c2442f 100644 --- a/EXILED/Exiled.CustomItems/Events/MapHandler.cs +++ b/EXILED/Exiled.CustomItems/Events/MapHandler.cs @@ -8,19 +8,21 @@ namespace Exiled.CustomItems.Events { using Exiled.CustomItems.API.Features; + using MEC; /// /// Event Handlers for the CustomItem API. /// internal sealed class MapHandler { - /// - /// Handle spawning Custom Items. - /// + /// public void OnMapGenerated() { - foreach (CustomItem customItem in CustomItem.Registered) - customItem?.SpawnAll(); + Timing.CallDelayed(0.5f, () => // Delay its necessary for the spawnpoints of lockers and rooms to be generated. + { + foreach (CustomItem customItem in CustomItem.Registered) + customItem?.SpawnAll(); + }); } } } \ No newline at end of file diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 61f88ec2c..29151b1f4 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -840,6 +840,16 @@ protected Vector3 GetSpawnPosition() } } + if (SpawnProperties.RoomSpawnPoints.Count > 0) + { + foreach ((float chance, Vector3 pos) in SpawnProperties.RoomSpawnPoints) + { + double r = Loader.Random.NextDouble() * 100; + if (r <= chance) + return pos; + } + } + return Vector3.zero; } diff --git a/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs b/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs index f75f2e91f..30ad47c9f 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/MapGenerated.cs @@ -18,7 +18,7 @@ namespace Exiled.Events.Handlers.Internal using Exiled.API.Enums; using Exiled.API.Extensions; - + using Exiled.API.Features.Lockers; using InventorySystem.Items.Firearms.Attachments; using InventorySystem.Items.Firearms.Attachments.Components; @@ -47,6 +47,7 @@ public static void OnMapGenerated() { Map.ClearCache(); PrefabHelper.LoadPrefabs(); + Locker.ClearCache(); // TODO: Fix For (https://git.scpslgame.com/northwood-qa/scpsl-bug-reporting/-/issues/377) PlayerRoles.RoleAssign.HumanSpawner.Handlers[PlayerRoles.Team.ChaosInsurgency] = new PlayerRoles.RoleAssign.OneRoleHumanSpawner(PlayerRoles.RoleTypeId.ChaosConscript); diff --git a/EXILED/Exiled.Events/Patches/Generic/LockerList.cs b/EXILED/Exiled.Events/Patches/Generic/LockerList.cs index 26b0732ab..7dadd8817 100644 --- a/EXILED/Exiled.Events/Patches/Generic/LockerList.cs +++ b/EXILED/Exiled.Events/Patches/Generic/LockerList.cs @@ -12,7 +12,7 @@ namespace Exiled.Events.Patches.Generic using API.Features; using API.Features.Pools; - + using Exiled.API.Features.Lockers; using HarmonyLib; using MapGeneration.Distributors; @@ -20,23 +20,23 @@ namespace Exiled.Events.Patches.Generic using static HarmonyLib.AccessTools; /// - /// Patches . + /// Patches . /// - [HarmonyPatch(typeof(Locker), nameof(Locker.Start))] + [HarmonyPatch(typeof(MapGeneration.Distributors.Locker), nameof(MapGeneration.Distributors.Locker.Start))] internal class LockerList { private static IEnumerable Transpiler(IEnumerable codeInstructions) { List newInstructions = ListPool.Pool.Get(codeInstructions); - // Map.LockersValue.Add(this); + // new Locker(this) newInstructions.InsertRange( 0, new CodeInstruction[] { - new(OpCodes.Ldsfld, Field(typeof(Map), nameof(Map.LockersValue))), new(OpCodes.Ldarg_0), - new(OpCodes.Callvirt, Method(typeof(List), nameof(List.Add), new[] { typeof(Locker) })), + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(API.Features.Lockers.Locker))[0]), + new(OpCodes.Pop), }); for (int z = 0; z < newInstructions.Count; z++)