diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index a4e5a63b69..9dfffe99fd 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 9.6.1 + 9.6.2 false diff --git a/EXILED/Exiled.API/Enums/AspectRatioType.cs b/EXILED/Exiled.API/Enums/AspectRatioType.cs new file mode 100644 index 0000000000..79e4f4c96e --- /dev/null +++ b/EXILED/Exiled.API/Enums/AspectRatioType.cs @@ -0,0 +1,60 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Enums +{ + /// + /// All available screen aspect ratio types. + /// + public enum AspectRatioType : byte + { + /// + /// Unknown aspect ratio. + /// + Unknown, + + /// + /// 1:1 aspect ratio (square screen). + /// + Ratio1_1, + + /// + /// 3:2 aspect ratio. + /// + Ratio3_2, + + /// + /// 4:3 aspect ratio (standard definition TVs, older monitors). + /// + Ratio4_3, + + /// + /// 5:4 aspect ratio (some older computer monitors). + /// + Ratio5_4, + + /// + /// 16:9 aspect ratio (modern widescreen displays, HDTV). + /// + Ratio16_9, + + /// + /// 16:10 aspect ratio (common in productivity monitors and laptops). + /// + Ratio16_10, + + /// + /// 21:9 aspect ratio (ultrawide displays). + /// + Ratio21_9, + + /// + /// 32:9 aspect ratio (super ultrawide displays). + /// + Ratio32_9, + } +} diff --git a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs index 6abeea8a2f..eaba7fe2bd 100644 --- a/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs +++ b/EXILED/Exiled.API/Extensions/DamageTypeExtensions.cs @@ -124,12 +124,12 @@ public static class DamageTypeExtensions /// Check if a damage type is caused by a weapon. /// /// The damage type to be checked. - /// Indicates whether the MicroHid damage type should be taken into account. + /// Indicates whether the MicroHid and Jailbird damage type should be taken into account. /// Returns whether the is caused by weapon. - public static bool IsWeapon(this DamageType type, bool checkMicro = true) => type switch + public static bool IsWeapon(this DamageType type, bool checkNonFirearm = true) => type switch { DamageType.Crossvec or DamageType.Logicer or DamageType.Revolver or DamageType.Shotgun or DamageType.AK or DamageType.Com15 or DamageType.Com18 or DamageType.E11Sr or DamageType.Fsp9 or DamageType.ParticleDisruptor or DamageType.Com45 or DamageType.Frmg0 or DamageType.A7 => true, - DamageType.MicroHid when checkMicro => true, + DamageType.MicroHid or DamageType.Jailbird when checkNonFirearm => true, _ => false, }; diff --git a/EXILED/Exiled.API/Extensions/FloatExtensions.cs b/EXILED/Exiled.API/Extensions/FloatExtensions.cs new file mode 100644 index 0000000000..b7a32af87f --- /dev/null +++ b/EXILED/Exiled.API/Extensions/FloatExtensions.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Extensions +{ + using System; + using System.Collections.Generic; + + using Exiled.API.Enums; + + /// + /// A set of extensions for . + /// + public static class FloatExtensions + { + private static readonly Dictionary AspectRatioReferences = new() + { + { AspectRatioType.Unknown, 0f }, + { AspectRatioType.Ratio1_1, 1f }, + { AspectRatioType.Ratio3_2, 3f / 2f }, + { AspectRatioType.Ratio4_3, 4f / 3f }, + { AspectRatioType.Ratio5_4, 5f / 4f }, + { AspectRatioType.Ratio16_9, 16f / 9f }, + { AspectRatioType.Ratio16_10, 16f / 10f }, + { AspectRatioType.Ratio21_9, 21f / 9f }, + { AspectRatioType.Ratio32_9, 32f / 9f }, + }; + + /// + /// Gets the closest for a given aspect ratio value. + /// + /// The aspect ratio value to compare. + /// The closest matching . + public static AspectRatioType GetAspectRatioLabel(this float ratio) + { + float closestDiff = float.MaxValue; + AspectRatioType closestRatio = AspectRatioType.Unknown; + + foreach (KeyValuePair kvp in AspectRatioReferences) + { + float diff = Math.Abs(ratio - kvp.Value); + if (diff < closestDiff) + { + closestDiff = diff; + closestRatio = kvp.Key; + } + } + + return closestRatio; + } + } +} diff --git a/EXILED/Exiled.API/Extensions/ItemExtensions.cs b/EXILED/Exiled.API/Extensions/ItemExtensions.cs index 458fd10bc4..1c03c97d9c 100644 --- a/EXILED/Exiled.API/Extensions/ItemExtensions.cs +++ b/EXILED/Exiled.API/Extensions/ItemExtensions.cs @@ -38,9 +38,9 @@ public static class ItemExtensions /// Check if an item is a weapon. /// /// The item to be checked. - /// Indicates whether the MicroHID item should be taken into account. + /// Indicates whether the MicroHID and Jailbird item should be taken into account. /// Returns whether the is a weapon. - public static bool IsWeapon(this ItemType type, bool checkMicro = true) => type.GetFirearmType() is not FirearmType.None || (checkMicro && type is ItemType.MicroHID); + public static bool IsWeapon(this ItemType type, bool checkNonFirearm = true) => type.GetFirearmType() is not FirearmType.None || (checkNonFirearm && type is ItemType.MicroHID or ItemType.Jailbird); /// /// Check if an item is an SCP. diff --git a/EXILED/Exiled.API/Features/Doors/Door.cs b/EXILED/Exiled.API/Features/Doors/Door.cs index c2cea9a761..22b0915132 100644 --- a/EXILED/Exiled.API/Features/Doors/Door.cs +++ b/EXILED/Exiled.API/Features/Doors/Door.cs @@ -600,7 +600,7 @@ private DoorType GetDoorType() { RoomType.EzCheckpointHallwayA => DoorType.CheckpointGateA, RoomType.EzCheckpointHallwayB => DoorType.CheckpointGateB, - RoomType.Hcz049 => Position.y < -805 ? DoorType.Scp049Gate : DoorType.Scp173NewGate, + RoomType.Hcz049 => Position.y < -10 ? DoorType.Scp049Gate : DoorType.Scp173NewGate, _ => DoorType.UnknownGate, }, "Elevator Door" or "Nuke Elevator Door" or "Elevator Door 02" => (Base as Interactables.Interobjects.ElevatorDoor)?.Group switch diff --git a/EXILED/Exiled.API/Features/Lift.cs b/EXILED/Exiled.API/Features/Lift.cs index 43d83f09ad..01f0219a97 100644 --- a/EXILED/Exiled.API/Features/Lift.cs +++ b/EXILED/Exiled.API/Features/Lift.cs @@ -137,6 +137,7 @@ public ElevatorSequence Status ElevatorGroup.Scp049 => ElevatorType.Scp049, ElevatorGroup.GateA => ElevatorType.GateA, ElevatorGroup.GateB => ElevatorType.GateB, + ElevatorGroup.ServerRoom => ElevatorType.ServerRoom, ElevatorGroup.LczA01 or ElevatorGroup.LczA02 => ElevatorType.LczA, ElevatorGroup.LczB01 or ElevatorGroup.LczB02 => ElevatorType.LczB, ElevatorGroup.Nuke01 or ElevatorGroup.Nuke02 => ElevatorType.Nuke, diff --git a/EXILED/Exiled.API/Features/Map.cs b/EXILED/Exiled.API/Features/Map.cs index f64bf32c95..7bd9a19193 100644 --- a/EXILED/Exiled.API/Features/Map.cs +++ b/EXILED/Exiled.API/Features/Map.cs @@ -67,7 +67,7 @@ DecontaminationController.Singleton.NetworkDecontaminationOverride is Decontamin /// /// The remaining time in seconds for the decontamination process. /// - public static float RemainingDecontaminationTime => Mathf.Min(0, (float)(DecontaminationController.Singleton.DecontaminationPhases[DecontaminationController.Singleton.DecontaminationPhases.Length - 1].TimeTrigger - DecontaminationController.GetServerTime)); + public static float RemainingDecontaminationTime => Mathf.Max(0, (float)(DecontaminationController.Singleton.DecontaminationPhases[DecontaminationController.Singleton.DecontaminationPhases.Length - 1].TimeTrigger - DecontaminationController.GetServerTime)); /// /// Gets all objects. @@ -419,4 +419,4 @@ internal static void ClearCache() #pragma warning restore CS0618 } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 4dfcf09409..323b4ec463 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -343,6 +343,11 @@ public PlayerInfoArea InfoArea set => ReferenceHub.nicknameSync.Network_playerInfoToShow = value; } + /// + /// Gets the player's current aspect ratio type. + /// + public AspectRatioType AspectRatio => ReferenceHub.aspectRatioSync.AspectRatio.GetAspectRatioLabel(); + /// /// Gets or sets the player's custom player info string. This string is displayed along with the player's . /// @@ -3020,9 +3025,36 @@ public void ThrowItem(Throwable throwable, bool fullForce = true) /// The message to be shown. /// The duration the text will be on screen. public void ShowHint(string message, float duration = 3f) + { + ShowHint(message, new HintParameter[] { new StringHintParameter(message) }, null, duration); + } + + /// + /// Shows a hint to the player with the specified message, hint effects, and duration. + /// + /// The message to be shown as a hint. + /// The array of hint effects to apply. + /// The duration the hint will be displayed, in seconds. + public void ShowHint(string message, HintEffect[] hintEffects, float duration = 3f) + { + ShowHint(message, new HintParameter[] { new StringHintParameter(message) }, hintEffects, duration); + } + + /// + /// Shows a hint to the player with the specified message, hint parameters, hint effects, and duration. + /// + /// The message to be shown as a hint. + /// The array of hint parameters to use. + /// The array of hint effects to apply. + /// The duration the hint will be displayed, in seconds. + public void ShowHint(string message, HintParameter[] hintParameters, HintEffect[] hintEffects, float duration = 3f) { message ??= string.Empty; - HintDisplay.Show(new TextHint(message, new HintParameter[] { new StringHintParameter(message) }, null, duration)); + HintDisplay.Show(new TextHint( + message, + (!hintParameters.IsEmpty()) ? hintParameters : new HintParameter[] { new StringHintParameter(message) }, + hintEffects, + duration)); } /// diff --git a/EXILED/Exiled.API/Features/Server.cs b/EXILED/Exiled.API/Features/Server.cs index 5de5dd5655..8cd65dc079 100644 --- a/EXILED/Exiled.API/Features/Server.cs +++ b/EXILED/Exiled.API/Features/Server.cs @@ -238,6 +238,15 @@ public static bool IsIdleModeEnabled /// public static void Restart() => Round.Restart(false, true, ServerStatic.NextRoundAction.Restart); + /// + /// Restarts the server with specified options. + /// + /// Indicates whether the restart should be fast. + /// Indicates whether to override the default restart action. + /// Specifies the action to perform after the restart. + public static void Restart(bool fastRestart, bool overrideRestartAction = false, ServerStatic.NextRoundAction restartAction = ServerStatic.NextRoundAction.DoNothing) => + Round.Restart(fastRestart, overrideRestartAction, restartAction); + /// /// Shutdowns the server, disconnects all players. /// @@ -266,6 +275,19 @@ public static bool RestartRedirect(ushort redirectPort) return true; } + /// + /// Redirects players to a server on another port, restarts the current server. + /// + /// The port to redirect players to. + /// Indicates whether the restart should be fast. + /// Indicates whether to override the default restart action. + /// Specifies the action to perform after the restart. + public static void RestartRedirect(ushort redirectPort, bool fastRestart, bool overrideRestartAction = false, ServerStatic.NextRoundAction restartAction = ServerStatic.NextRoundAction.DoNothing) + { + NetworkServer.SendToAll(new RoundRestartMessage(RoundRestartType.RedirectRestart, 0.0f, redirectPort, true, false)); + Timing.CallDelayed(0.5f, () => { Restart(fastRestart, overrideRestartAction, restartAction); }); + } + /// /// Redirects players to a server on another port, shutdowns the current server. /// @@ -280,6 +302,18 @@ public static bool ShutdownRedirect(ushort redirectPort) return true; } + /// + /// Redirects players to a server on another port, shutdowns the current server. + /// + /// The port to redirect players to. + /// Indicates whether to terminate the application after shutting down the server. + /// Indicates whether to suppress the broadcast notification about the shutdown. + public static void ShutdownRedirect(ushort redirectPort, bool quit, bool suppressShutdownBroadcast = false) + { + NetworkServer.SendToAll(new RoundRestartMessage(RoundRestartType.RedirectRestart, 0.0f, redirectPort, true, false)); + Timing.CallDelayed(0.5f, () => { Shutdown(quit, suppressShutdownBroadcast); }); + } + /// /// Executes a server command. /// @@ -307,4 +341,4 @@ public static bool TryGetSessionVariable(string key, out T result) return false; } } -} +} \ No newline at end of file diff --git a/EXILED/Exiled.API/Features/Workstation.cs b/EXILED/Exiled.API/Features/Workstation.cs new file mode 100644 index 0000000000..7e7978a7ff --- /dev/null +++ b/EXILED/Exiled.API/Features/Workstation.cs @@ -0,0 +1,167 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.API.Features +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + using Exiled.API.Enums; + using Exiled.API.Interfaces; + using InventorySystem.Items.Firearms.Attachments; + using Mirror; + using UnityEngine; + + /// + /// A wrapper class for . + /// + public class Workstation : IWrapper, IWorldSpace + { + /// + /// A dictionary mapping to . + /// + internal static readonly Dictionary WorkstationControllerToWorkstation = new(new ComponentsEqualityComparer()); + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + internal Workstation(WorkstationController workstationController) + { + WorkstationControllerToWorkstation.Add(workstationController, this); + Base = workstationController; + } + + /// + /// Gets a read-only collection of all instances. + /// + public static IReadOnlyCollection List => WorkstationControllerToWorkstation.Values; + + /// + /// Gets the underlying instance. + /// + public WorkstationController Base { get; } + + /// + /// Gets the of the workstation. + /// + public GameObject GameObject => Base.gameObject; + + /// + /// Gets the of the workstation. + /// + public Transform Transform => Base.transform; + + /// + /// Gets the the workstation is located in. + /// + public Room Room => Room.Get(Position); + + /// + /// Gets the of the workstation's room. + /// + public ZoneType Zone => Room.Zone; + + /// + /// Gets or sets the position of the workstation. + /// + public Vector3 Position + { + get => Transform.position; + set + { + NetworkServer.UnSpawn(GameObject); + Transform.position = value; + NetworkServer.Spawn(GameObject); + } + } + + /// + /// Gets or sets the rotation of the workstation. + /// + public Quaternion Rotation + { + get => Transform.rotation; + set + { + NetworkServer.UnSpawn(GameObject); + Transform.rotation = value; + NetworkServer.Spawn(GameObject); + } + } + + /// + /// Gets or sets the status of the workstation. + /// + public WorkstationController.WorkstationStatus Status + { + get => (WorkstationController.WorkstationStatus)Base.Status; + set => Base.NetworkStatus = (byte)value; + } + + /// + /// Gets the used by the workstation. + /// + public Stopwatch Stopwatch => Base.ServerStopwatch; + + /// + /// Gets or sets the player known to be using the workstation. + /// + public Player KnownUser + { + get => Player.Get(Base.KnownUser); + set => Base.KnownUser = value.ReferenceHub; + } + + /// + /// Gets a given a instance. + /// + /// The instance. + /// The instance. + public static Workstation Get(WorkstationController workstationController) => WorkstationControllerToWorkstation.TryGetValue(workstationController, out Workstation workstation) ? workstation : new(workstationController); + + /// + /// Gets all instances that match the specified predicate. + /// + /// The predicate to filter workstations. + /// An of matching workstations. + public static IEnumerable Get(Func predicate) => List.Where(predicate); + + /// + /// Tries to get all instances that match the specified predicate. + /// + /// The predicate to filter workstations. + /// The matching workstations, if any. + /// true if any workstations were found; otherwise, false. + public static bool TryGet(Func predicate, out IEnumerable workstations) + { + workstations = Get(predicate); + return workstations.Any(); + } + + /// + /// Determines whether the specified player is in range of the workstation. + /// + /// The player to check. + /// true if the player is in range; otherwise, false. + public bool IsInRange(Player player) => Base.IsInRange(player.ReferenceHub); + + /// + /// Interacts with the workstation as the specified player. + /// + /// The player to interact as. + public void Interact(Player player) => Base.ServerInteract(player.ReferenceHub, Base.ActivateCollider.ColliderId); + + /// + /// Returns the Room in a human-readable format. + /// + /// A string containing Workstation-related data. + public override string ToString() => $"{GameObject.name} ({Zone}) [{Room}]"; + } +} \ No newline at end of file diff --git a/EXILED/Exiled.CustomRoles/API/Extensions.cs b/EXILED/Exiled.CustomRoles/API/Extensions.cs index a2d6bb25fd..8a0821216d 100644 --- a/EXILED/Exiled.CustomRoles/API/Extensions.cs +++ b/EXILED/Exiled.CustomRoles/API/Extensions.cs @@ -41,6 +41,25 @@ public static ReadOnlyCollection GetCustomRoles(this Player player) return roles.AsReadOnly(); } + /// + /// Checks whether the player has any custom role assigned. + /// + /// The to check. + /// true if the player has at least one custom role; otherwise, false. + public static bool HasAnyCustomRole(this Player player) + { + if (player == null) + return false; + + foreach (CustomRole role in CustomRole.Registered) + { + if (role.Check(player)) + return true; + } + + return false; + } + /// /// Registers an of s. /// @@ -105,4 +124,4 @@ public static void Unregister(this IEnumerable customRoles) /// The the has selected, or . public static ActiveAbility? GetSelectedAbility(this Player player) => !ActiveAbility.AllActiveAbilities.TryGetValue(player, out HashSet abilities) ? null : abilities.FirstOrDefault(a => a.Check(player, CheckType.Selected)); } -} \ 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 ba6ecc95f8..5329d0f954 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -37,6 +37,14 @@ namespace Exiled.CustomRoles.API.Features /// public abstract class CustomRole { + /// + /// Gets or sets the number of players that naturally spawned with this custom role. + /// + [YamlIgnore] + #pragma warning disable SA1401 // Fields should be private + public int SpawnedPlayers = 0; + #pragma warning restore SA1401 // Fields should be private + private const float AddRoleDelay = 0.25f; private static Dictionary typeLookupTable = new(); @@ -578,7 +586,9 @@ public virtual void AddRole(Player player) ability.AddAbility(player); } - ShowMessage(player); + if (CustomRoles.Instance!.Config.GotRoleHint.Show) + ShowMessage(player); + ShowBroadcast(player); RoleAdded(player); player.TryAddCustomRoleFriendlyFire(Name, CustomRoleFFMultiplier); @@ -814,46 +824,68 @@ protected bool TryAddItem(Player player, string itemName) protected Vector3 GetSpawnPosition() { if (SpawnProperties is null || SpawnProperties.Count() == 0) + { return Vector3.zero; + } + + float totalchance = 0f; + List<(float chance, Vector3 pos)> spawnPointPool = new(4); + + void Add(Vector3 pos, float chance) + { + if (chance <= 0f) + return; + + spawnPointPool.Add((chance, pos)); + totalchance += chance; + } + + if (!SpawnProperties.StaticSpawnPoints.IsEmpty()) + { + foreach (StaticSpawnPoint sp in SpawnProperties.StaticSpawnPoints) + { + Add(sp.Position, sp.Chance); + } + } - if (SpawnProperties.StaticSpawnPoints.Count > 0) + if (!SpawnProperties.DynamicSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.StaticSpawnPoints) + foreach (DynamicSpawnPoint sp in SpawnProperties.DynamicSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.DynamicSpawnPoints.Count > 0) + if (!SpawnProperties.RoleSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.DynamicSpawnPoints) + foreach (RoleSpawnPoint sp in SpawnProperties.RoleSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.RoleSpawnPoints.Count > 0) + if (!SpawnProperties.RoomSpawnPoints.IsEmpty()) { - foreach ((float chance, Vector3 pos) in SpawnProperties.RoleSpawnPoints) + foreach (RoomSpawnPoint sp in SpawnProperties.RoomSpawnPoints) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + Add(sp.Position, sp.Chance); } } - if (SpawnProperties.RoomSpawnPoints.Count > 0) + if (spawnPointPool.Count == 0 || totalchance <= 0f) { - foreach ((float chance, Vector3 pos) in SpawnProperties.RoomSpawnPoints) + return Vector3.zero; + } + + float randomRoll = (float)(Loader.Random.NextDouble() * totalchance); + foreach ((float chance, Vector3 pos) in spawnPointPool) + { + if (randomRoll < chance) { - double r = Loader.Random.NextDouble() * 100; - if (r <= chance) - return pos; + return pos; } + + randomRoll -= chance; } return Vector3.zero; @@ -867,7 +899,6 @@ protected virtual void SubscribeEvents() Log.Debug($"{Name}: Loading events."); Exiled.Events.Handlers.Player.ChangingNickname += OnInternalChangingNickname; Exiled.Events.Handlers.Player.ChangingRole += OnInternalChangingRole; - Exiled.Events.Handlers.Player.Spawned += OnInternalSpawned; Exiled.Events.Handlers.Player.SpawningRagdoll += OnSpawningRagdoll; Exiled.Events.Handlers.Player.Destroying += OnDestroying; } @@ -883,7 +914,6 @@ protected virtual void UnsubscribeEvents() Log.Debug($"{Name}: Unloading events."); Exiled.Events.Handlers.Player.ChangingNickname -= OnInternalChangingNickname; Exiled.Events.Handlers.Player.ChangingRole -= OnInternalChangingRole; - Exiled.Events.Handlers.Player.Spawned -= OnInternalSpawned; Exiled.Events.Handlers.Player.SpawningRagdoll -= OnSpawningRagdoll; Exiled.Events.Handlers.Player.Destroying -= OnDestroying; } @@ -922,12 +952,6 @@ private void OnInternalChangingNickname(ChangingNicknameEventArgs ev) ev.Player.CustomInfo = $"{ev.NewName}\n{CustomInfo}"; } - private void OnInternalSpawned(SpawnedEventArgs ev) - { - if (!IgnoreSpawnSystem && SpawnChance > 0 && !Check(ev.Player) && ev.Player.Role.Type == Role && Loader.Random.NextDouble() * 100 <= SpawnChance) - AddRole(ev.Player); - } - private void OnInternalChangingRole(ChangingRoleEventArgs ev) { if (ev.IsAllowed && ev.Reason != SpawnReason.Destroyed && Check(ev.Player) && ((ev.NewRole == RoleTypeId.Spectator && !KeepRoleOnDeath) || (ev.NewRole != RoleTypeId.Spectator && !KeepRoleOnChangingRole))) @@ -936,7 +960,7 @@ private void OnInternalChangingRole(ChangingRoleEventArgs ev) private void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { - if (Check(ev.Player)) + if (Check(ev.Player) && Role.IsFpcRole()) ev.Role = Role; } diff --git a/EXILED/Exiled.CustomRoles/CustomRoles.cs b/EXILED/Exiled.CustomRoles/CustomRoles.cs index 4ea4c77f1d..a66457b3bd 100644 --- a/EXILED/Exiled.CustomRoles/CustomRoles.cs +++ b/EXILED/Exiled.CustomRoles/CustomRoles.cs @@ -62,16 +62,24 @@ public override void OnEnabled() if (Config.UseKeypressActivation) keypressActivator = new(); + + Exiled.Events.Handlers.Player.Spawned += playerHandlers.OnSpawned; Exiled.Events.Handlers.Player.SpawningRagdoll += playerHandlers.OnSpawningRagdoll; + + Exiled.Events.Handlers.Server.WaitingForPlayers += playerHandlers.OnWaitingForPlayers; base.OnEnabled(); } /// public override void OnDisabled() { + Exiled.Events.Handlers.Player.Spawned -= playerHandlers!.OnSpawned; Exiled.Events.Handlers.Player.SpawningRagdoll -= playerHandlers!.OnSpawningRagdoll; + + Exiled.Events.Handlers.Server.WaitingForPlayers -= playerHandlers!.OnWaitingForPlayers; + keypressActivator = null; base.OnDisabled(); } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs index b53556f007..5c0aa7356e 100644 --- a/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs +++ b/EXILED/Exiled.CustomRoles/Events/PlayerHandlers.cs @@ -7,6 +7,14 @@ namespace Exiled.CustomRoles.Events { + using System; + using System.Collections.Generic; + using System.Threading; + + using Exiled.API.Enums; + using Exiled.API.Features; + using Exiled.CustomRoles.API; + using Exiled.CustomRoles.API.Features; using Exiled.Events.EventArgs.Player; /// @@ -15,6 +23,15 @@ namespace Exiled.CustomRoles.Events public class PlayerHandlers { private readonly CustomRoles plugin; + private readonly HashSet validSpawnReasons = new() + { + SpawnReason.RoundStart, + SpawnReason.Respawn, + SpawnReason.LateJoin, + SpawnReason.Revived, + SpawnReason.Escaped, + SpawnReason.ItemUsage, + }; /// /// Initializes a new instance of the class. @@ -25,6 +42,15 @@ public PlayerHandlers(CustomRoles plugin) this.plugin = plugin; } + /// + internal void OnWaitingForPlayers() + { + foreach (CustomRole role in CustomRole.Registered) + { + role.SpawnedPlayers = 0; + } + } + /// internal void OnSpawningRagdoll(SpawningRagdollEventArgs ev) { @@ -34,5 +60,66 @@ internal void OnSpawningRagdoll(SpawningRagdollEventArgs ev) plugin.StopRagdollPlayers.Remove(ev.Player); } } + + /// + internal void OnSpawned(SpawnedEventArgs ev) + { + if (!validSpawnReasons.Contains(ev.Reason) || ev.Player.HasAnyCustomRole()) + { + return; + } + + float totalChance = 0f; + List eligibleRoles = new(8); + + foreach (CustomRole role in CustomRole.Registered) + { + if (role.Role == ev.Player.Role.Type && !role.IgnoreSpawnSystem && role.SpawnChance > 0 && !role.Check(ev.Player) && (role.SpawnProperties is null || role.SpawnedPlayers < role.SpawnProperties.Limit)) + { + eligibleRoles.Add(role); + totalChance += role.SpawnChance; + } + } + + if (eligibleRoles.Count == 0) + { + return; + } + + float lotterySize = Math.Max(100f, totalChance); + float randomRoll = (float)Loader.Loader.Random.NextDouble() * lotterySize; + + if (randomRoll >= totalChance) + { + return; + } + + foreach (CustomRole candidateRole in eligibleRoles) + { + if (randomRoll >= candidateRole.SpawnChance) + { + randomRoll -= candidateRole.SpawnChance; + continue; + } + + if (candidateRole.SpawnProperties is null) + { + candidateRole.AddRole(ev.Player); + break; + } + + int newSpawnCount = Interlocked.Increment(ref candidateRole.SpawnedPlayers); + if (newSpawnCount <= candidateRole.SpawnProperties.Limit) + { + candidateRole.AddRole(ev.Player); + break; + } + else + { + Interlocked.Decrement(ref candidateRole.SpawnedPlayers); + randomRoll -= candidateRole.SpawnChance; + } + } + } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs new file mode 100644 index 0000000000..5df04b729e --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Player/ChangedRatioEventArgs.cs @@ -0,0 +1,54 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Player +{ + using Exiled.API.Enums; + using Exiled.API.Extensions; + using Exiled.API.Features; + using Exiled.Events.EventArgs.Interfaces; + + /// + /// Contains all information after a player's Aspect Ratio changes. + /// + public class ChangedRatioEventArgs : IPlayerEvent + { + /// + /// Initializes a new instance of the class. + /// + /// The player who is changed ratio. + /// + /// + /// Old aspect ratio of the player. + /// + /// + /// New aspect ratio of the player. + /// + /// + public ChangedRatioEventArgs(ReferenceHub player, float oldratio, float newratio) + { + Player = Player.Get(player); + OldRatio = oldratio.GetAspectRatioLabel(); + NewRatio = newratio.GetAspectRatioLabel(); + } + + /// + /// Gets the player who is changed ratio. + /// + public Player Player { get; } + + /// + /// Gets the players old ratio. + /// + public AspectRatioType OldRatio { get; } + + /// + /// Gets the players new ratio. + /// + public AspectRatioType NewRatio { get; } + } +} diff --git a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs index 4a4fa0dcba..532c40f2f0 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/EscapingEventArgs.cs @@ -39,7 +39,7 @@ public EscapingEventArgs(ReferenceHub referenceHub, RoleTypeId newRole, EscapeSc Player = Player.Get(referenceHub); NewRole = newRole; EscapeScenario = escapeScenario; - IsAllowed = escapeScenario is not EscapeScenario.None and not EscapeScenario.CustomEscape; + IsAllowed = true; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedInventoryItemEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedInventoryItemEventArgs.cs new file mode 100644 index 0000000000..4bd590dc55 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedInventoryItemEventArgs.cs @@ -0,0 +1,65 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp914 +{ + using API.Features; + using API.Features.Items; + using global::Scp914; + using Interfaces; + using InventorySystem.Items; + using InventorySystem.Items.Pickups; + + /// + /// Contains all information before SCP-914 upgrades an item. + /// + public class UpgradedInventoryItemEventArgs : IItemEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UpgradedInventoryItemEventArgs(Player player, ItemBase item, Scp914KnobSetting knobSetting, ItemBase[] result) + { + Player = player; + Item = Item.Get(item); + KnobSetting = knobSetting; + Result = result; + } + + /// + /// Gets SCP-914 working knob setting. + /// + public Scp914KnobSetting KnobSetting { get; } + + /// + /// Gets a list of items to be upgraded inside SCP-914. + /// + public Item Item { get; } + + /// + /// Gets the who owns the item to be upgraded. + /// + public Player Player { get; } + + /// + /// Gets the array of items created as a result of SCP-914 upgraded. + /// + public ItemBase[] Result { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedPickupEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedPickupEventArgs.cs new file mode 100644 index 0000000000..5bd2c9d952 --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Scp914/UpgradedPickupEventArgs.cs @@ -0,0 +1,64 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Scp914 +{ + using Exiled.API.Features.Pickups; + using Exiled.Events.EventArgs.Interfaces; + using global::Scp914; + using InventorySystem.Items.Pickups; + using UnityEngine; + + /// + /// Contains all information before SCP-914 upgrades an item. + /// + public class UpgradedPickupEventArgs : IPickupEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public UpgradedPickupEventArgs(ItemPickupBase item, Vector3 newPos, Scp914KnobSetting knobSetting, ItemPickupBase[] result) + { + Pickup = Pickup.Get(item); + OutputPosition = newPos; + KnobSetting = knobSetting; + Result = result; + } + + /// + /// Gets a list of items to be upgraded inside SCP-914. + /// + public Pickup Pickup { get; } + + /// + /// Gets the position the item will be output to. + /// + public Vector3 OutputPosition { get; } + + /// + /// Gets SCP-914 working knob setting. + /// + public Scp914KnobSetting KnobSetting { get; } + + /// + /// Gets the array of items created as a result of SCP-914 upgraded. + /// + public ItemPickupBase[] Result { get; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Events.cs b/EXILED/Exiled.Events/Events.cs index 103ba58829..80173fa27a 100644 --- a/EXILED/Exiled.Events/Events.cs +++ b/EXILED/Exiled.Events/Events.cs @@ -68,6 +68,7 @@ public override void OnEnabled() Handlers.Server.RestartingRound += Handlers.Internal.Round.OnRestartingRound; Handlers.Server.RoundStarted += Handlers.Internal.Round.OnRoundStarted; Handlers.Player.ChangingRole += Handlers.Internal.Round.OnChangingRole; + Handlers.Player.SpawningRagdoll += Handlers.Internal.Round.OnSpawningRagdoll; Handlers.Scp049.ActivatingSense += Handlers.Internal.Round.OnActivatingSense; Handlers.Player.Verified += Handlers.Internal.Round.OnVerified; Handlers.Map.ChangedIntoGrenade += Handlers.Internal.ExplodingGrenade.OnChangedIntoGrenade; @@ -105,6 +106,7 @@ public override void OnDisabled() Handlers.Server.RestartingRound -= Handlers.Internal.Round.OnRestartingRound; Handlers.Server.RoundStarted -= Handlers.Internal.Round.OnRoundStarted; Handlers.Player.ChangingRole -= Handlers.Internal.Round.OnChangingRole; + Handlers.Player.SpawningRagdoll -= Handlers.Internal.Round.OnSpawningRagdoll; Handlers.Scp049.ActivatingSense -= Handlers.Internal.Round.OnActivatingSense; Handlers.Player.Verified -= Handlers.Internal.Round.OnVerified; Handlers.Map.ChangedIntoGrenade -= Handlers.Internal.ExplodingGrenade.OnChangedIntoGrenade; @@ -161,4 +163,4 @@ public void Unpatch() Log.Debug("All events have been unpatched complete. Goodbye!"); } } -} \ No newline at end of file +} diff --git a/EXILED/Exiled.Events/Handlers/Internal/Round.cs b/EXILED/Exiled.Events/Handlers/Internal/Round.cs index 99212af062..f81b0886b9 100644 --- a/EXILED/Exiled.Events/Handlers/Internal/Round.cs +++ b/EXILED/Exiled.Events/Handlers/Internal/Round.cs @@ -82,6 +82,13 @@ public static void OnChangingRole(ChangingRoleEventArgs ev) ev.Player.Inventory.ServerDropEverything(); } + /// + public static void OnSpawningRagdoll(SpawningRagdollEventArgs ev) + { + if (ev.Role.IsDead() || !ev.Role.IsFpcRole()) + ev.IsAllowed = false; + } + /// public static void OnActivatingSense(ActivatingSenseEventArgs ev) { diff --git a/EXILED/Exiled.Events/Handlers/Player.cs b/EXILED/Exiled.Events/Handlers/Player.cs index e35df7b044..b444aa4b4f 100644 --- a/EXILED/Exiled.Events/Handlers/Player.cs +++ b/EXILED/Exiled.Events/Handlers/Player.cs @@ -103,6 +103,11 @@ public class Player /// public static Event CancelledItemUse { get; set; } = new(); + /// + /// Invoked after a 's aspect ratio has changed. + /// + public static Event ChangedRatio { get; set; } = new(); + /// /// Invoked after a interacted with something. /// @@ -684,6 +689,12 @@ public class Player /// The instance. public static void OnCancelledItemUse(CancelledItemUseEventArgs ev) => CancelledItemUse.InvokeSafely(ev); + /// + /// Called after a 's aspect ratio changes. + /// + /// The instance. + public static void OnChangedRatio(ChangedRatioEventArgs ev) => ChangedRatio.InvokeSafely(ev); + /// /// Called after a interacted with something. /// diff --git a/EXILED/Exiled.Events/Handlers/Scp914.cs b/EXILED/Exiled.Events/Handlers/Scp914.cs index a9efe220a1..8d271661df 100644 --- a/EXILED/Exiled.Events/Handlers/Scp914.cs +++ b/EXILED/Exiled.Events/Handlers/Scp914.cs @@ -22,11 +22,21 @@ public static class Scp914 /// public static Event UpgradingPickup { get; set; } = new(); + /// + /// Invoked after SCP-914 upgrades a Pickup. + /// + public static Event UpgradedPickup { get; set; } = new(); + /// /// Invoked before SCP-914 upgrades an item in a player's inventory. /// public static Event UpgradingInventoryItem { get; set; } = new(); + /// + /// Invoked after SCP-914 upgrades an item in a player's inventory. + /// + public static Event UpgradedInventoryItem { get; set; } = new(); + /// /// Invoked before SCP-914 upgrades a player. /// @@ -48,12 +58,24 @@ public static class Scp914 /// The instance. public static void OnUpgradingPickup(UpgradingPickupEventArgs ev) => UpgradingPickup.InvokeSafely(ev); + /// + /// Called after SCP-914 upgrades a item. + /// + /// The instance. + public static void OnUpgradedPickup(UpgradedPickupEventArgs ev) => UpgradedPickup.InvokeSafely(ev); + /// /// Called before SCP-914 upgrades an item in a player's inventory. /// /// The instance. public static void OnUpgradingInventoryItem(UpgradingInventoryItemEventArgs ev) => UpgradingInventoryItem.InvokeSafely(ev); + /// + /// Called after SCP-914 upgrades an item in a player's inventory. + /// + /// The instance. + public static void OnUpgradedInventoryItem(UpgradedInventoryItemEventArgs ev) => UpgradedInventoryItem.InvokeSafely(ev); + /// /// Called before SCP-914 upgrades a player. /// diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs new file mode 100644 index 0000000000..8a12f60f94 --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangedAspectRatio.cs @@ -0,0 +1,85 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Player +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using CentralAuth; + using Exiled.Events.EventArgs.Player; + using HarmonyLib; + using UnityEngine; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event. + /// + [HarmonyPatch(typeof(AspectRatioSync), nameof(AspectRatioSync.UserCode_CmdSetAspectRatio__Single))] + internal class ChangedAspectRatio + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + LocalBuilder oldratio = generator.DeclareLocal(typeof(float)); + LocalBuilder hub = generator.DeclareLocal(typeof(ReferenceHub)); + + Label retLabel = generator.DefineLabel(); + + newInstructions.InsertRange(0, new CodeInstruction[] + { + // float OldRatio = this.AspectRatio; + new(OpCodes.Ldarg_0), + new(OpCodes.Callvirt, PropertyGetter(typeof(AspectRatioSync), nameof(AspectRatioSync.AspectRatio))), + new(OpCodes.Stloc_S, oldratio.LocalIndex), + }); + + int index = newInstructions.FindLastIndex(i => i.opcode == OpCodes.Ret); + newInstructions[index].WithLabels(retLabel); + + newInstructions.InsertRange(index, new CodeInstruction[] + { + // ReferenceHub hub = this.GetComponent(); + new(OpCodes.Ldarg_0), + new(OpCodes.Call, Method(typeof(Component), nameof(Component.GetComponent)).MakeGenericMethod(typeof(ReferenceHub))), + new(OpCodes.Dup), + new(OpCodes.Stloc_S, hub.LocalIndex), + + // if (hub.authManager._targetInstanceMode != ClientInstanceMode.ReadyClient) return; + new(OpCodes.Ldfld, Field(typeof(ReferenceHub), nameof(ReferenceHub.authManager))), + new(OpCodes.Ldfld, Field(typeof(PlayerAuthenticationManager), nameof(PlayerAuthenticationManager._targetInstanceMode))), + new(OpCodes.Ldc_I4, 1), + new(OpCodes.Bne_Un_S, retLabel), + + // hub + new(OpCodes.Ldloc, hub.LocalIndex), + + // OldRatio + new(OpCodes.Ldloc, oldratio.LocalIndex), + + // this.AspectRatio + new(OpCodes.Ldarg_0), + new(OpCodes.Call, PropertyGetter(typeof(AspectRatioSync), nameof(AspectRatioSync.AspectRatio))), + + // ChangedRatioEventArgs ev = new ChangedRatioEventArgs(ReferenceHub, float, float) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(ChangedRatioEventArgs))[0]), + + // Handlers.Player.OnChangedRatio(ev); + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnChangedRatio))), + }); + + for (int i = 0; i < newInstructions.Count; i++) + yield return newInstructions[i]; + + ListPool.Pool.Return(newInstructions); + } + } +} diff --git a/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradedPickup.cs b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradedPickup.cs new file mode 100644 index 0000000000..64842911da --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradedPickup.cs @@ -0,0 +1,73 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Scp914 +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Scp914; + + using global::Scp914; + + using Handlers; + + using HarmonyLib; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// Adds the event. + /// + [EventPatch(typeof(Scp914), nameof(Scp914.UpgradedPickup))] + [HarmonyPatch(typeof(Scp914Upgrader), nameof(Scp914Upgrader.ProcessPickup))] + internal static class UpgradedPickup + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int index = newInstructions.FindLastIndex(instruction => instruction.opcode == OpCodes.Ldloc_2); + + List [EventPatch(typeof(Scp914), nameof(Scp914.UpgradingPickup))] [HarmonyPatch(typeof(Scp914Upgrader), nameof(Scp914Upgrader.ProcessPickup))] - internal static class UpgradingItem + internal static class UpgradingPickup { private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { diff --git a/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs index cffd499ea9..3927ed9a55 100644 --- a/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs +++ b/EXILED/Exiled.Events/Patches/Events/Scp914/UpgradingPlayer.cs @@ -116,14 +116,14 @@ private static IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Stloc_S && x.operand is LocalBuilder { LocalIndex: 10 }) + offset; - ConstructorInfo plugin_api_constructor = typeof(LabApi.Events.Arguments.Scp914Events.Scp914ProcessingInventoryItemEventArgs) + ConstructorInfo lab_api_constructor = typeof(LabApi.Events.Arguments.Scp914Events.Scp914ProcessingInventoryItemEventArgs) .GetConstructor(new[] { typeof(InventorySystem.Items.ItemBase), typeof(Scp914KnobSetting), typeof(ReferenceHub), }); - index = newInstructions.FindIndex(x => x.Is(OpCodes.Newobj, plugin_api_constructor)) + offset; + index = newInstructions.FindIndex(x => x.Is(OpCodes.Newobj, lab_api_constructor)) + offset; // ridtp lcz914 // noclip diff --git a/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs b/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs index bbcdd724e7..498c487f0c 100644 --- a/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs +++ b/EXILED/Exiled.Events/Patches/Events/Warhead/ChangingLeverStatus.cs @@ -8,6 +8,7 @@ namespace Exiled.Events.Patches.Events.Warhead { using System.Collections.Generic; + using System.Reflection; using System.Reflection.Emit; using API.Features; @@ -36,7 +37,7 @@ private static IEnumerable Transpiler(IEnumerable instruction.opcode == OpCodes.Call); + int index = newInstructions.FindIndex(x => x.operand == (object)PropertySetter(typeof(AlphaWarheadNukesitePanel), nameof(AlphaWarheadNukesitePanel.Networkenabled))) - 5; newInstructions.InsertRange( index, diff --git a/EXILED/Exiled.Events/Patches/Generic/WorkstationListAdd.cs b/EXILED/Exiled.Events/Patches/Generic/WorkstationListAdd.cs new file mode 100644 index 0000000000..544ef5086e --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Generic/WorkstationListAdd.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) ExMod Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Generic +{ +#pragma warning disable SA1313 +#pragma warning disable SA1402 + + using HarmonyLib; + using InventorySystem.Items.Firearms.Attachments; + + /// + /// Patch for adding to list. + /// + [HarmonyPatch(typeof(WorkstationController), nameof(WorkstationController.Start))] + internal class WorkstationListAdd + { + private static void Postfix(WorkstationController __instance) + { + API.Features.Workstation.Get(__instance); + } + } + + /// + /// Patch for removing to list. + /// + [HarmonyPatch(typeof(WorkstationController), nameof(WorkstationController.OnDestroy))] + internal class WorkstationListRemove + { + private static void Postfix(WorkstationController __instance) + { + API.Features.Workstation.WorkstationControllerToWorkstation.Remove(__instance); + } + } +} \ No newline at end of file diff --git a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md index bfc0f31671..f4d027231f 100644 --- a/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md +++ b/EXILED/docs/articles/SCPSLRessources/NW_Documentation.md @@ -17,7 +17,7 @@ title: NW Documentation --- -Last Update (14.1.0.0) +Last Update (14.1.0.1) ### Index @@ -5611,7 +5611,7 @@ Last Update (14.1.0.0)
Damage Handlers -```md title="Latest Updated: 14.1.0.0" +```md title="Latest Updated: 14.1.0.1" All available DamageHandlers + Symbol ':' literally means "inherits from"