diff --git a/EXILED.props b/EXILED.props index ecc92bf069..399e8b0c07 100644 --- a/EXILED.props +++ b/EXILED.props @@ -15,7 +15,7 @@ - 9.0.0-alpha.19 + 9.0.0-beta.2 false diff --git a/Exiled.API/Extensions/MathExtensions.cs b/Exiled.API/Extensions/MathExtensions.cs index c00ebe0f47..a15039ca9a 100644 --- a/Exiled.API/Extensions/MathExtensions.cs +++ b/Exiled.API/Extensions/MathExtensions.cs @@ -27,7 +27,7 @@ public static class MathExtensions /// The minimum value to include in the range. /// The maximum value to include in the range. /// if the probability occurred, otherwise . - public static bool EvaluateProbability(this int probability, int minInclusive = 0, int maxInclusive = 100) => Random.Range(minInclusive, ++maxInclusive) <= probability; + public static bool EvaluateProbability(this int probability, int minInclusive = 0, int maxInclusive = 101) => probability == 100 || Random.Range(minInclusive, maxInclusive) <= probability; /// /// Evaluates a probability. diff --git a/Exiled.API/Features/Core/EActor.cs b/Exiled.API/Features/Core/EActor.cs index 2a6f72eb9c..7d8b7fe023 100644 --- a/Exiled.API/Features/Core/EActor.cs +++ b/Exiled.API/Features/Core/EActor.cs @@ -45,9 +45,6 @@ protected EActor() { IsEditable = true; fixedTickRate = DEFAULT_FIXED_TICK_RATE; - PostInitialize(); - Timing.CallDelayed(fixedTickRate, OnBeginPlay); - Timing.CallDelayed(fixedTickRate * 2, () => serverTick = Timing.RunCoroutine(ServerTick())); } /// @@ -454,14 +451,24 @@ public bool TryGetComponent(Type type, out EActor component) /// public bool HasComponent(bool depthInheritance = false) => depthInheritance - ? ComponentsInChildren.Any(comp => typeof(T).IsAssignableFrom(comp.GetType())) + ? ComponentsInChildren.Any(comp => comp is T) : ComponentsInChildren.Any(comp => typeof(T) == comp.GetType()); /// public bool HasComponent(Type type, bool depthInheritance = false) => depthInheritance - ? ComponentsInChildren.Any(comp => type.IsAssignableFrom(comp.GetType())) + ? ComponentsInChildren.Any(type.IsInstanceOfType) : ComponentsInChildren.Any(comp => type == comp.GetType()); + /// + /// Called when the is initialized. + /// + public void ComponentInitialize() + { + PostInitialize(); + Timing.CallDelayed(fixedTickRate, OnBeginPlay); + Timing.CallDelayed(fixedTickRate * 2, () => serverTick = Timing.RunCoroutine(ServerTick())); + } + /// /// Fired after the instance is created. /// @@ -474,7 +481,6 @@ protected virtual void PostInitialize() /// protected virtual void OnBeginPlay() { - SubscribeEvents(); } /// @@ -485,7 +491,6 @@ protected virtual void Tick() if (DestroyNextTick) { Destroy(); - return; } } diff --git a/Exiled.API/Features/Core/EObject.cs b/Exiled.API/Features/Core/EObject.cs index c1543f15eb..a0af7ac800 100644 --- a/Exiled.API/Features/Core/EObject.cs +++ b/Exiled.API/Features/Core/EObject.cs @@ -62,12 +62,12 @@ protected EObject(GameObject gameObject = null) /// /// Gets or sets the name of the instance. /// - public string Name { get; set; } + public string Name { get; set; } = string.Empty; /// /// Gets or sets the tag of the instance. /// - public string Tag { get; set; } + public string Tag { get; set; } = string.Empty; /// /// Gets or sets a value indicating whether the values can be edited. @@ -357,7 +357,13 @@ public static Type GetObjectTypeFromRegisteredTypes(Type type, string name) public static EObject CreateDefaultSubobject(Type type, params object[] parameters) { const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; - EObject @object = Activator.CreateInstance(type, flags, null, parameters, null) as EObject; + EObject @object = Activator.CreateInstance(type, flags, null, null, null) as EObject; + + if (@object is not null && Player.DEFAULT_ROLE_BEHAVIOUR is not null && type.BaseType == Player.DEFAULT_ROLE_BEHAVIOUR) + { + @object.Base = parameters[0] as GameObject; + @object.Cast().ComponentInitialize(); + } // Do not use implicit bool conversion as @object may be null if (@object != null) @@ -371,6 +377,65 @@ public static EObject CreateDefaultSubobject(Type type, params object[] paramete throw new NullReferenceException($"Couldn't create an EObject instance of type {type.Name}."); } + /// + /// Initializes an instance of the specified generic type . + /// If no constructors are found, the base type is initialized. + /// + /// The type that inherits from . + /// An instance of the specified type . + public static T InitializeBaseType() + where T : EObject + { + return InitializeBaseType(typeof(T)).Cast(); + } + + /// + /// Initializes an instance of the specified type. + /// If no constructors are found, the base type is initialized. + /// + /// The type to be initialized. + /// Constructor parameters for the type. + /// An instance of the specified type. + /// + /// Thrown if the provided is not a subclass of . + /// + public static EObject InitializeBaseType(Type type, params object[] parameters) + { + const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; + if (type.BaseType != typeof(EObject) && !type.IsSubclassOf(typeof(EObject)) && type != typeof(EObject)) + throw new InvalidOperationException("The requested type is not a subclass of EObject."); + + // Get the base type of T + Type baseType = type.BaseType; + + if (baseType == null) + { + throw new InvalidOperationException("The requested type does not have a base type."); + } + + // Get the constructors of the base type + ConstructorInfo[] constructors = baseType.GetConstructors(flags); + + if (constructors.Length == 0) + { + throw new InvalidOperationException("The base type does not have public constructors."); + } + + // Here we assume you want to use the default constructor if available + ConstructorInfo constructor = Array.Find(constructors, c => c.GetParameters().Length == 0); + + if (constructor == null) + { + throw new InvalidOperationException("The base type does not have a parameterless constructor."); + } + + // Create an instance of the base type + object baseInstance = constructor.Invoke(null); + + // Return the instance as the requested type + return baseInstance as EObject; + } + /// /// Creates a new instance of the class. /// @@ -853,4 +918,4 @@ protected virtual void OnDestroyed() { } } -} +} \ No newline at end of file diff --git a/Exiled.API/Features/Core/Generic/EBehaviour.cs b/Exiled.API/Features/Core/Generic/EBehaviour.cs index 778f61b773..a2b3fd611e 100644 --- a/Exiled.API/Features/Core/Generic/EBehaviour.cs +++ b/Exiled.API/Features/Core/Generic/EBehaviour.cs @@ -29,7 +29,6 @@ public abstract class EBehaviour : EActor /// Initializes a new instance of the class. /// protected EBehaviour() - : base() { } @@ -78,6 +77,7 @@ protected override void PostInitialize() base.PostInitialize(); FindOwner(); + if (!Owner && DisposeOnNullOwner) { Destroy(); @@ -85,6 +85,12 @@ protected override void PostInitialize() } } + /// + protected override void OnBeginPlay() + { + base.OnBeginPlay(); + } + /// protected override void Tick() { diff --git a/Exiled.API/Features/Core/Generic/UniqueUnmanagedEnumClass.cs b/Exiled.API/Features/Core/Generic/UniqueUnmanagedEnumClass.cs index 67f0b8331a..0db2f2b9d7 100644 --- a/Exiled.API/Features/Core/Generic/UniqueUnmanagedEnumClass.cs +++ b/Exiled.API/Features/Core/Generic/UniqueUnmanagedEnumClass.cs @@ -15,6 +15,7 @@ namespace Exiled.API.Features.Core.Generic using Exiled.API.Features.Core.Generic.Pools; using Exiled.API.Interfaces; using LiteNetLib.Utils; + using YamlDotNet.Serialization; /// /// A class which allows data implicit conversions and ensures unique values. @@ -60,16 +61,19 @@ public UniqueUnmanagedEnumClass() /// /// Gets all object instances. /// + [YamlIgnore] public static IEnumerable Values => values.Values; /// /// Gets the value of the enum item. /// + [YamlIgnore] public TSource Value { get; } /// /// Gets the name determined from reflection. /// + [YamlMember(Alias = "name")] public string Name { get diff --git a/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs b/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs index 1f01dc4328..2d396b1e6a 100644 --- a/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs +++ b/Exiled.API/Features/Core/Generic/UnmanagedEnumClass.cs @@ -15,6 +15,7 @@ namespace Exiled.API.Features.Core.Generic using Exiled.API.Features.Core.Generic.Pools; using Exiled.API.Interfaces; using LiteNetLib.Utils; + using YamlDotNet.Serialization; /// /// A class which allows data implicit conversions. @@ -51,11 +52,13 @@ protected UnmanagedEnumClass(TSource value) /// /// Gets the value of the enum item. /// + [YamlIgnore] public TSource Value { get; } /// /// Gets the name determined from reflection. /// + [YamlMember(Alias = "name")] public string Name { get diff --git a/Exiled.API/Features/Core/StaticActor.cs b/Exiled.API/Features/Core/StaticActor.cs index 5a2fcd6f1f..501b10b4fa 100644 --- a/Exiled.API/Features/Core/StaticActor.cs +++ b/Exiled.API/Features/Core/StaticActor.cs @@ -54,9 +54,10 @@ public static StaticActor CreateNewInstance() /// The created or already existing instance. public static StaticActor CreateNewInstance(Type type) { - EObject @object = CreateDefaultSubobject(type); + EActor @object = CreateDefaultSubobject(type); @object.Name = "__" + type.Name + " (StaticActor)"; @object.SearchForHostObjectIfNull = true; + @object.ComponentInitialize(); return @object.Cast(); } @@ -181,6 +182,7 @@ protected virtual void PostInitialize_Static() /// protected virtual void BeginPlay_Static() { + SubscribeEvents(); } /// diff --git a/Exiled.API/Features/Player.cs b/Exiled.API/Features/Player.cs index d7eec737f2..f48708b733 100644 --- a/Exiled.API/Features/Player.cs +++ b/Exiled.API/Features/Player.cs @@ -127,6 +127,11 @@ public class Player : GameEntity public const string INFO_CATEGORY = "Player_Info"; #pragma warning disable SA1401 + /// + /// The default role behaviour class. + /// + public static Type DEFAULT_ROLE_BEHAVIOUR = null; + /// /// The default player class. /// @@ -664,10 +669,10 @@ public PlayerPermissions RemoteAdminPermissions /// This role is automatically cached until it changes, and it is recommended to use this property directly rather than storing the property yourself. /// /// - /// Roles and RoleTypeIds can be compared directly. Player.Role == RoleTypeId.Scp079 is valid and will return if the player is SCP-079. To set the player's role, see . + /// Roles and RoleTypeIds can be compared directly. Player.Role == RoleTypeId.Scp079 is valid and will return if the player is SCP-079. To set the player's role, see . /// /// - /// + /// [EProperty(readOnly: true, category: ROLES_CATEGORY)] public Role Role { @@ -3988,8 +3993,9 @@ public void PlayGunSound(Vector3 position, ItemType itemType, byte volume, byte /// The UnitNameId to use for the player's new role, if the player's new role uses unit names. (is NTF). public void ChangeAppearance(RoleTypeId type, IEnumerable playersToAffect, bool skipJump = false, byte unitId = 0) { - if (ReferenceHub.gameObject == null || !RoleExtensions.TryGetRoleBase(type, out PlayerRoleBase roleBase)) + if (ReferenceHub.gameObject == null || !type.TryGetRoleBase(out PlayerRoleBase roleBase)) return; + bool isRisky = type.GetRoleBase().Team is Team.Dead || IsDead; NetworkWriterPooled writer = NetworkWriterPool.Get(); @@ -4001,6 +4007,7 @@ public void ChangeAppearance(RoleTypeId type, IEnumerable playersToAffec { if (Role.Base is not PHumanRole) isRisky = true; + writer.WriteByte(unitId); } @@ -4011,8 +4018,7 @@ public void ChangeAppearance(RoleTypeId type, IEnumerable playersToAffec else fpc = playerfpc; - ushort value = 0; - fpc?.FpcModule.MouseLook.GetSyncValues(0, out value, out ushort _); + fpc.FpcModule.MouseLook.GetSyncValues(0, out ushort value, out ushort _); writer.WriteRelativePosition(RelativePosition); writer.WriteUShort(value); } diff --git a/Exiled.API/Features/Roles/Role.cs b/Exiled.API/Features/Roles/Role.cs index 459ea7b1a7..ecfbc9bc5b 100644 --- a/Exiled.API/Features/Roles/Role.cs +++ b/Exiled.API/Features/Roles/Role.cs @@ -261,8 +261,8 @@ public static RoleTypeId Random(bool includeNonPlayableRoles = false, IEnumerabl /// Sets the player's . /// /// The new to be set. - /// The defining why the player's role was changed. - public virtual void Set(RoleTypeId newRole, SpawnReason reason = null) => Set(newRole, reason ?? Enums.SpawnReason.ForceClass, RoleSpawnFlags.All); + /// The defining why the player's role was changed. + public virtual void Set(RoleTypeId newRole, RoleChangeReason reason = RoleChangeReason.RemoteAdmin) => Set(newRole, reason, RoleSpawnFlags.All); /// S /// Sets the player's . @@ -275,9 +275,9 @@ public static RoleTypeId Random(bool includeNonPlayableRoles = false, IEnumerabl /// Sets the player's . /// /// The new to be set. - /// The defining why the player's role was changed. + /// The defining why the player's role was changed. /// The defining player spawn logic. - public virtual void Set(RoleTypeId newRole, SpawnReason reason, RoleSpawnFlags spawnFlags) => + public virtual void Set(RoleTypeId newRole, RoleChangeReason reason, RoleSpawnFlags spawnFlags) => Owner.RoleManager.ServerSetRole(newRole, reason, spawnFlags); /// diff --git a/Exiled.CustomModules/API/Commands/CustomItem/Give.cs b/Exiled.CustomModules/API/Commands/CustomItem/Give.cs index 5e167f819c..0d68c1bf49 100644 --- a/Exiled.CustomModules/API/Commands/CustomItem/Give.cs +++ b/Exiled.CustomModules/API/Commands/CustomItem/Give.cs @@ -52,11 +52,11 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s if (arguments.Count == 0) { - response = "give [Nickname/PlayerID/UserID/all/*]"; + response = "give [Nickname/PlayerID/UserID/all/*]"; return false; } - if (!CustomItem.TryGet(arguments.At(0), out CustomItem item)) + if (!CustomItem.TryGet(arguments.At(0), out CustomItem item) && (!uint.TryParse(arguments.At(0), out uint id) || !CustomItem.TryGet(id, out item)) && item is null) { response = $"Custom item {arguments.At(0)} not found!"; return false; diff --git a/Exiled.CustomModules/API/Commands/CustomRoles/Give.cs b/Exiled.CustomModules/API/Commands/CustomRoles/Give.cs index ceca249863..224e15b997 100644 --- a/Exiled.CustomModules/API/Commands/CustomRoles/Give.cs +++ b/Exiled.CustomModules/API/Commands/CustomRoles/Give.cs @@ -39,7 +39,7 @@ private Give() public string[] Aliases { get; } = { "g" }; /// - public string Description { get; } = "Gives the specified custom role to the indicated player(s)."; + public string Description { get; } = "Gives the specified custom role (using ID) to the indicated player(s)."; /// public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) @@ -52,19 +52,19 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s return false; } - if (arguments.Count == 0) + if (arguments.Count < 2) { - response = "give [Nickname/PlayerID/UserID/all/*]"; + response = "give Custom role ID> [Nickname/PlayerID/UserID/all/*]"; return false; } - if (!CustomRole.TryGet(arguments.At(0), out CustomRole role) || role is null) + if (!CustomRole.TryGet(arguments.At(0), out CustomRole role) && (!uint.TryParse(arguments.At(0), out uint id) || !CustomRole.TryGet(id, out role)) && role is null) { response = $"Custom role {arguments.At(0)} not found!"; return false; } - if (arguments.Count == 1) + if (arguments.Count == 2) { Pawn player = Player.Get(arguments.At(1)).Cast(); @@ -74,7 +74,7 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s return false; } - role.Spawn(player); + role.Spawn(player, false, force: true); response = $"{role.Name} given to {player.Nickname}."; return true; } @@ -86,7 +86,7 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s case "*": case "all": List players = ListPool.Pool.Get(Player.List).Select(player => player.Cast()).ToList(); - role.Spawn(players); + role.Spawn(players, true); response = $"Custom role {role.Name} given to all players."; ListPool.Pool.Return(players); diff --git a/Exiled.CustomModules/API/Commands/CustomRoles/Info.cs b/Exiled.CustomModules/API/Commands/CustomRoles/Info.cs index 1591041638..aa45373fef 100644 --- a/Exiled.CustomModules/API/Commands/CustomRoles/Info.cs +++ b/Exiled.CustomModules/API/Commands/CustomRoles/Info.cs @@ -14,6 +14,7 @@ namespace Exiled.CustomModules.API.Commands.CustomRoles using Exiled.API.Features.Core.Generic.Pools; using Exiled.CustomModules.API.Features.CustomRoles; using Exiled.Permissions.Extensions; + using PlayerRoles; /// /// The command to view info about a specific role. @@ -63,9 +64,13 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s builder.Append("- ").Append(role.Name) .Append(" (").Append(role.Id).Append(")") + .AppendLine("- Probability: ").Append(role.Probability) .Append("- ").AppendLine(role.Description) .AppendLine(role.Role.ToString()) - .Append("- Health: ").AppendLine(role.Settings.MaxHealth.ToString()).AppendLine(); + .Append("- Health: ").AppendLine(role.Settings.MaxHealth.ToString()) + .AppendLine("- Team: "); + foreach (Team team in role.TeamsOwnership) + builder.AppendLine(team.ToString()); response = StringBuilderPool.Pool.ToStringReturn(builder); return true; diff --git a/Exiled.CustomModules/API/Extensions/PlayerExtensions.cs b/Exiled.CustomModules/API/Extensions/PlayerExtensions.cs index 837ba0c789..324f53bdd2 100644 --- a/Exiled.CustomModules/API/Extensions/PlayerExtensions.cs +++ b/Exiled.CustomModules/API/Extensions/PlayerExtensions.cs @@ -24,36 +24,39 @@ public static class PlayerExtensions /// public static CustomRole Get(this Player player) => CustomRole.Get(player.Cast()); - /// + /// public static bool Spawn( this Player player, CustomRole customRole, bool shouldKeepPosition = false, SpawnReason spawnReason = null, - RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) => - CustomRole.Spawn(player.Cast(), customRole, shouldKeepPosition, spawnReason, roleSpawnFlags); + RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All, + bool force = false) => + CustomRole.Spawn(player.Cast(), customRole, shouldKeepPosition, spawnReason, roleSpawnFlags, force); /// public static bool Spawn(this Player player) where T : CustomRole => CustomRole.Spawn(player.Cast()); - /// + /// public static bool Spawn( this Player player, string name, bool shouldKeepPosition = false, SpawnReason spawnReason = null, - RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) => - CustomRole.Spawn(player.Cast(), name, shouldKeepPosition, spawnReason, roleSpawnFlags); + RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All, + bool force = false) => + CustomRole.Spawn(player.Cast(), name, shouldKeepPosition, spawnReason, roleSpawnFlags, force); - /// + /// public static bool Spawn( this Player player, uint id, bool shouldKeepPosition = false, SpawnReason spawnReason = null, - RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) => - CustomRole.Spawn(player.Cast(), id, shouldKeepPosition, spawnReason, roleSpawnFlags); + RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All, + bool force = false) => + CustomRole.Spawn(player.Cast(), id, shouldKeepPosition, spawnReason, roleSpawnFlags, force); /// public static bool TrySpawn(this Player player, CustomRole customRole) => CustomRole.TrySpawn(player.Cast(), customRole); diff --git a/Exiled.CustomModules/API/Features/CustomAbilities/ISelectableAbility.cs b/Exiled.CustomModules/API/Features/CustomAbilities/ISelectableAbility.cs index af8b4cfacb..fc066d0799 100644 --- a/Exiled.CustomModules/API/Features/CustomAbilities/ISelectableAbility.cs +++ b/Exiled.CustomModules/API/Features/CustomAbilities/ISelectableAbility.cs @@ -7,10 +7,6 @@ namespace Exiled.CustomModules.API.Features.CustomAbilities { - using Exiled.API.Features.Core; - using Exiled.API.Features.Core.Interfaces; - using Exiled.CustomModules.API.Features.PlayerAbilities; - /// /// Represents a marker interface for custom ability that can be selected. /// diff --git a/Exiled.CustomModules/API/Features/CustomAbilities/Settings/AbilitySettings.cs b/Exiled.CustomModules/API/Features/CustomAbilities/Settings/AbilitySettings.cs index 28f4f8af64..f702028edd 100644 --- a/Exiled.CustomModules/API/Features/CustomAbilities/Settings/AbilitySettings.cs +++ b/Exiled.CustomModules/API/Features/CustomAbilities/Settings/AbilitySettings.cs @@ -7,11 +7,8 @@ namespace Exiled.CustomModules.API.Features.CustomAbilities.Settings { - using Exiled.API.Features; using Exiled.API.Features.Core; using Exiled.API.Features.Core.Interfaces; - using Exiled.CustomModules.API.Features.CustomAbilities; - using Exiled.CustomModules.API.Features.CustomRoles; /// /// Represents the base class for player-specific ability behaviors. diff --git a/Exiled.CustomModules/API/Features/CustomAbilities/Settings/LevelAbilitySettings.cs b/Exiled.CustomModules/API/Features/CustomAbilities/Settings/LevelAbilitySettings.cs index ec013793e6..9e0f9ea6f0 100644 --- a/Exiled.CustomModules/API/Features/CustomAbilities/Settings/LevelAbilitySettings.cs +++ b/Exiled.CustomModules/API/Features/CustomAbilities/Settings/LevelAbilitySettings.cs @@ -8,9 +8,6 @@ namespace Exiled.CustomModules.API.Features.CustomAbilities.Settings { using Exiled.API.Features; - using Exiled.API.Features.Core; - using Exiled.API.Features.Core.Interfaces; - using Exiled.CustomModules.API.Features.CustomRoles; /// /// Represents the base class for player-specific ability behaviors. diff --git a/Exiled.CustomModules/API/Features/CustomAbilities/Types/AbilityBehaviourBase.cs b/Exiled.CustomModules/API/Features/CustomAbilities/Types/AbilityBehaviourBase.cs index c56ea62b5e..ff3e6acda8 100644 --- a/Exiled.CustomModules/API/Features/CustomAbilities/Types/AbilityBehaviourBase.cs +++ b/Exiled.CustomModules/API/Features/CustomAbilities/Types/AbilityBehaviourBase.cs @@ -11,7 +11,6 @@ namespace Exiled.CustomModules.API.Features.CustomAbilities using Exiled.API.Features; using Exiled.API.Features.Core; - using Exiled.API.Features.Core.Generic; using Exiled.API.Features.Core.Interfaces; using Exiled.API.Features.DynamicEvents; using Exiled.CustomModules.API.Features.CustomAbilities.Settings; diff --git a/Exiled.CustomModules/API/Features/CustomItems/CustomItem.cs b/Exiled.CustomModules/API/Features/CustomItems/CustomItem.cs index a6d462c63d..6733756142 100644 --- a/Exiled.CustomModules/API/Features/CustomItems/CustomItem.cs +++ b/Exiled.CustomModules/API/Features/CustomItems/CustomItem.cs @@ -152,14 +152,14 @@ public abstract class CustomItem : CustomModule, IAdditiveBehaviour /// /// The custom item id to retrieve. /// The retrieved instance if found and enabled; otherwise, . - public static CustomItem Get(uint id) => IdLookupTable[id]; + public static CustomItem Get(uint id) => IdLookupTable.ContainsKey(id) ? IdLookupTable[id] : null; /// /// Retrieves a instance based on the specified item name. /// /// The name of the custom item to retrieve. /// The retrieved instance if found; otherwise, . - public static CustomItem Get(string name) => NameLookupTable[name]; + public static CustomItem Get(string name) => NameLookupTable.ContainsKey(name) ? NameLookupTable[name] : null; /// /// Retrieves a instance based on the specified type. @@ -216,7 +216,11 @@ public static CustomItem Get(Pickup item) /// The id or of the custom item. /// When this method returns, contains the associated with the specified id, if the id was found; otherwise, . /// if a was found; otherwise, . - public static bool TryGet(object id, out CustomItem customItem) => customItem = Get(id); + public static bool TryGet(object id, out CustomItem customItem) + { + customItem = Get(id); + return customItem is not null; + } /// /// Tries to retrieve a instance based on the specified custom item id. @@ -224,7 +228,11 @@ public static CustomItem Get(Pickup item) /// The custom item id to retrieve. /// The retrieved instance, if successful; otherwise, . /// if the retrieval is successful; otherwise, . - public static bool TryGet(uint id, out CustomItem customItem) => customItem = Get(id); + public static bool TryGet(uint id, out CustomItem customItem) + { + customItem = Get(id); + return customItem is not null; + } /// /// Tries to retrieve a instance based on the specified item name. @@ -232,7 +240,11 @@ public static CustomItem Get(Pickup item) /// The name of the custom item to retrieve. /// The retrieved instance, if successful; otherwise, . /// if the retrieval is successful; otherwise, . - public static bool TryGet(string name, out CustomItem customItem) => customItem = Get(name); + public static bool TryGet(string name, out CustomItem customItem) + { + customItem = Get(name); + return customItem is not null; + } /// /// Tries to retrieve a instance based on the specified instance. @@ -240,7 +252,11 @@ public static CustomItem Get(Pickup item) /// The instance to retrieve the custom item for. /// The retrieved instance, if successful; otherwise, . /// if the retrieval is successful; otherwise, . - public static bool TryGet(Item item, out CustomItem customItem) => customItem = Get(item); + public static bool TryGet(Item item, out CustomItem customItem) + { + customItem = Get(item); + return customItem is not null; + } /// /// Tries to retrieve a instance based on the specified instance. @@ -248,7 +264,11 @@ public static CustomItem Get(Pickup item) /// The instance to retrieve the custom item for. /// The retrieved instance, if successful; otherwise, . /// if the retrieval is successful; otherwise, . - public static bool TryGet(Pickup pickup, out CustomItem customItem) => customItem = Get(pickup); + public static bool TryGet(Pickup pickup, out CustomItem customItem) + { + customItem = Get(pickup); + return customItem is not null; + } /// /// Tries to retrieve a instance based on the specified type. @@ -256,7 +276,11 @@ public static CustomItem Get(Pickup item) /// The type to retrieve the custom item for. /// The retrieved instance, if successful; otherwise, . /// if the retrieval is successful; otherwise, . - public static bool TryGet(Type type, out CustomItem customItem) => customItem = Get(type); + public static bool TryGet(Type type, out CustomItem customItem) + { + customItem = Get(type); + return customItem is not null; + } /// /// Tries to spawn a custom item at the specified position. diff --git a/Exiled.CustomModules/API/Features/CustomItems/Items/ItemBehaviour.cs b/Exiled.CustomModules/API/Features/CustomItems/Items/ItemBehaviour.cs index f51f96fbb3..95f5661bda 100644 --- a/Exiled.CustomModules/API/Features/CustomItems/Items/ItemBehaviour.cs +++ b/Exiled.CustomModules/API/Features/CustomItems/Items/ItemBehaviour.cs @@ -164,6 +164,13 @@ protected override void PostInitialize() AdjustAdditivePipe(); } + /// + protected override void OnBeginPlay() + { + base.OnBeginPlay(); + this.SubscribeEvents(); + } + /// protected override void SubscribeEvents() { diff --git a/Exiled.CustomModules/API/Features/CustomItems/Items/ItemSettings.cs b/Exiled.CustomModules/API/Features/CustomItems/Items/ItemSettings.cs index 88fc18a341..8a8167d1c2 100644 --- a/Exiled.CustomModules/API/Features/CustomItems/Items/ItemSettings.cs +++ b/Exiled.CustomModules/API/Features/CustomItems/Items/ItemSettings.cs @@ -8,7 +8,6 @@ namespace Exiled.CustomModules.API.Features.CustomItems.Items { using Exiled.API.Features; - using Exiled.API.Features.Core; using Exiled.API.Features.Spawn; using UnityEngine; diff --git a/Exiled.CustomModules/API/Features/CustomModule.cs b/Exiled.CustomModules/API/Features/CustomModule.cs index 1bbcab8186..a2444933c5 100644 --- a/Exiled.CustomModules/API/Features/CustomModule.cs +++ b/Exiled.CustomModules/API/Features/CustomModule.cs @@ -24,7 +24,6 @@ namespace Exiled.CustomModules.API.Features using Exiled.CustomModules.API.Enums; using Exiled.CustomModules.API.Features.Attributes; using YamlDotNet.Serialization; - using YamlDotNet.Serialization.NodeDeserializers; /// /// Represents a marker class for custom modules. @@ -54,14 +53,14 @@ public abstract class CustomModule : TypeCastObject, IEquatable [YamlIgnore] [DynamicEventDispatcher] - public static TDynamicEventDispatcher OnEnabled { get; set; } + public static TDynamicEventDispatcher OnEnabled { get; set; } = new(); /// /// Gets or sets the which handles all delegates to be fired when a module gets disabled. /// [YamlIgnore] [DynamicEventDispatcher] - public static TDynamicEventDispatcher OnDisabled { get; set; } + public static TDynamicEventDispatcher OnDisabled { get; set; } = new(); /// /// Gets or sets the 's name. @@ -239,7 +238,6 @@ internal string PointerPath .WithTypeConverter(new EnumClassConverter()) .WithTypeConverter(new PrivateConstructorConverter()) .WithNamingConvention(UnderscoredNamingConvention.Instance) - .WithNodeDeserializer(_ => new CustomModuleDeserializer(), deserializer => deserializer.InsteadOf()) .WithDuplicateKeyChecking() .IgnoreFields() .IgnoreUnmatchedProperties() @@ -329,17 +327,12 @@ UUModuleType FindClosestModuleType(Type t, IEnumerable source) return source.ElementAt(matches.IndexOf(matches.Min())).GetValue(null) as UUModuleType; } - Type runtime_ModuleType = assembly.GetTypes().FirstOrDefault(t => !t.IsAbstract && typeof(UUModuleType).IsAssignableFrom(t)); - if (runtime_ModuleType is null) - { - Log.Debug("No UUModuleType-derived types were found. Custom modules must have an identifier based on UUModuleType."); - return; - } - + Type runtime_ModuleType = assembly.GetTypes().FirstOrDefault(t => !t.IsAbstract && typeof(UUModuleType).IsAssignableFrom(t)) ?? typeof(UUModuleType); IEnumerable moduleTypeValuesInfo = runtime_ModuleType.GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.GetValue(null) is UUModuleType); + foreach (Type type in assembly.GetTypes()) { - if (type.IsAbstract || type.BaseType != typeof(CustomModule) || Loader.Any(m => m.Type == type)) + if (type.IsAbstract || (type.BaseType != typeof(CustomModule) && !type.IsSubclassOf(typeof(CustomModule)))) continue; IEnumerable rhMethods = type.GetMethods(ModuleInfo.SIGNATURE_BINDINGS) @@ -377,6 +370,7 @@ UUModuleType FindClosestModuleType(Type t, IEnumerable source) }; ModuleInfo.AllModules.Add(moduleInfo); + CustomModules.Instance.RegistrationHandler.OnModuleEnabled(moduleInfo); if (!shouldBeEnabled) continue; @@ -564,6 +558,9 @@ private static void AutomaticModulesLoaderState(bool shouldLoad) private void CopyProperties(CustomModule source) { + if (source is null) + throw new NullReferenceException("Source is null. Was the custom module deserialized?"); + foreach (PropertyInfo property in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (property.CanWrite) diff --git a/Exiled.CustomModules/API/Features/CustomRoles/CustomRole.cs b/Exiled.CustomModules/API/Features/CustomRoles/CustomRole.cs index 74d0514900..b68c3e0484 100644 --- a/Exiled.CustomModules/API/Features/CustomRoles/CustomRole.cs +++ b/Exiled.CustomModules/API/Features/CustomRoles/CustomRole.cs @@ -212,6 +212,11 @@ public abstract class CustomRole : CustomModule, IAdditiveBehaviour [YamlIgnore] public int Instances { get; private set; } + /// + /// Gets all the instances of this in the global context. + /// + public int GlobalInstances { get; private set; } + /// /// Gets a value indicating whether the role can spawn given a condition. /// @@ -283,14 +288,14 @@ public virtual bool EvaluateConditions /// /// The specified id. /// The matching the search or if not registered. - public static CustomRole Get(uint id) => IdLookupTable[id]; + public static CustomRole Get(uint id) => IdLookupTable.ContainsKey(id) ? IdLookupTable[id] : null; /// /// Gets a given the specified name. /// /// The specified name. /// The matching the search or if not registered. - public static CustomRole Get(string name) => NameLookupTable[name]; + public static CustomRole Get(string name) => NameLookupTable.ContainsKey(name) ? NameLookupTable[name] : null; /// /// Gets a given the specified . @@ -373,7 +378,11 @@ public static CustomRole Get() /// The id to look for. /// The found , if not registered. /// if a was found; otherwise, . - public static bool TryGet(uint id, out CustomRole customRole) => customRole = Get(id); + public static bool TryGet(uint id, out CustomRole customRole) + { + customRole = Get(id); + return customRole is not null; + } /// /// Tries to get a given a specified name. @@ -381,7 +390,11 @@ public static CustomRole Get() /// The name to look for. /// The found , if not registered. /// if a was found; otherwise, . - public static bool TryGet(string name, out CustomRole customRole) => customRole = Get(name); + public static bool TryGet(string name, out CustomRole customRole) + { + customRole = Get(name); + return customRole is not null; + } /// /// Tries to get the player's current . @@ -389,7 +402,11 @@ public static CustomRole Get() /// The to search on. /// The found , if not registered. /// if a was found; otherwise, . - public static bool TryGet(Pawn player, out CustomRole customRole) => customRole = Get(player); + public static bool TryGet(Pawn player, out CustomRole customRole) + { + customRole = Get(player); + return customRole is not null; + } /// /// Tries to get a given the specified . @@ -397,7 +414,11 @@ public static CustomRole Get() /// The to search for. /// The found , if not registered. /// if a was found; otherwise, . - public static bool TryGet(Type type, out CustomRole customRole) => customRole = Get(type); + public static bool TryGet(Type type, out CustomRole customRole) + { + customRole = Get(type); + return customRole is not null; + } /// /// Attempts to spawn the specified player with the provided custom role. @@ -470,6 +491,7 @@ public static bool TrySpawn(Pawn player, string name) /// A value indicating whether the custom role assignment should maintain the player's current position. /// The to be set. /// The to be set. + /// Indicating whether should override current role. /// /// if the player was spawned with the custom role successfully; otherwise, . /// @@ -477,12 +499,12 @@ public static bool TrySpawn(Pawn player, string name) /// This method forces the specified player to respawn with the given custom role. If the custom role is /// not provided or is invalid, the method returns . /// - public static bool Spawn(Pawn player, CustomRole customRole, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) + public static bool Spawn(Pawn player, CustomRole customRole, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All, bool force = false) { if (!customRole) return false; - customRole.ForceSpawn(player, preservePosition, spawnReason, roleSpawnFlags); + customRole.Spawn(player, preservePosition, spawnReason, roleSpawnFlags, force); return true; } @@ -494,6 +516,7 @@ public static bool Spawn(Pawn player, CustomRole customRole, bool preservePositi /// A value indicating whether the custom role assignment should maintain the player's current position. /// The to be set. /// The to be set. + /// Indicating whether should override the role. /// /// if the player was spawned with the custom role successfully; otherwise, . /// @@ -501,12 +524,12 @@ public static bool Spawn(Pawn player, CustomRole customRole, bool preservePositi /// This method allows the spawning of the specified player with a custom role identified by its type or type name. /// If the custom role type or name is not provided, or if the identification process fails, the method returns . /// - public static bool Spawn(Pawn player, uint id, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) + public static bool Spawn(Pawn player, uint id, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All, bool force = false) { if (!TryGet(id, out CustomRole customRole)) return false; - Spawn(player, customRole, preservePosition, spawnReason, roleSpawnFlags); + Spawn(player, customRole, preservePosition, spawnReason, roleSpawnFlags, force); return true; } @@ -518,6 +541,7 @@ public static bool Spawn(Pawn player, uint id, bool preservePosition = false, Sp /// A value indicating whether the custom role assignment should maintain the player's current position. /// The to be set. /// The to be set. + /// Indicating whether should override the role. /// /// if the player was spawned with the custom role successfully; otherwise, . /// @@ -525,7 +549,7 @@ public static bool Spawn(Pawn player, uint id, bool preservePosition = false, Sp /// This method allows the spawning of the specified player with a custom role identified by its name. /// If the custom role name is not provided, or if the identification process fails, the method returns . /// - public static bool Spawn(Pawn player, string name, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) + public static bool Spawn(Pawn player, string name, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All, bool force = false) { if (!TryGet(name, out CustomRole customRole)) return false; @@ -627,9 +651,10 @@ public static void Remove(IEnumerable players) /// public static void EnableAll(Assembly assembly) { - if (!CustomModules.Instance.Config.Modules.Contains(UUModuleType.CustomRoles.Name)) + if (CustomModules.Instance.Config.Modules is null || !CustomModules.Instance.Config.Modules.Contains("CustomRoles")) throw new Exception("ModuleType::CustomRoles must be enabled in order to load any custom roles"); + Player.DEFAULT_ROLE_BEHAVIOUR = typeof(RoleBehaviour); List customRoles = new(); foreach (Type type in assembly.GetTypes()) { @@ -673,6 +698,7 @@ public static void DisableAll() /// A value indicating whether the assignment should maintain the player's current position. /// The to be set. /// The to be set. + /// indicating whethere the spawn is forced. /// /// if the player was spawned; otherwise, . /// @@ -681,18 +707,14 @@ public static void DisableAll() /// required behavior component. If the player is already alive, the spawn operation /// fails and returns . /// - public bool Spawn(Pawn player, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) + public bool Spawn(Pawn player, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All, bool force = false) { - if (player is null || player.IsAlive) + if (player is null || (player.IsAlive && !force)) return false; ChangingCustomRoleEventArgs changingCustomRole = new(player, Id); ChangingCustomRoleDispatcher.InvokeAll(changingCustomRole); - if (!changingCustomRole.IsAllowed) - return false; - - player = changingCustomRole.Player.Cast(); if (changingCustomRole.Role is RoleTypeId rId) { player.SetRole(rId, preservePosition, spawnReason, roleSpawnFlags); @@ -710,6 +732,7 @@ public bool Spawn(Pawn player, bool preservePosition = false, SpawnReason spawnR PlayersValue.Remove(player); PlayersValue.Add(player, this); Instances += 1; + GlobalInstances += 1; ChangedCustomRoleEventArgs @event = new(player, prevRole); ChangedCustomRoleDispatcher.InvokeAll(@event); @@ -721,145 +744,14 @@ public bool Spawn(Pawn player, bool preservePosition = false, SpawnReason spawnR /// Spawns each player in the specified collection as a specific . /// /// The collection of instances to be spawned. + /// Indicating whether should override role. /// /// This method spawns each player in the provided collection as the current , /// adding the required behavior component. Players that are already alive will not be /// affected by the spawn operation. The method is designed for spawning multiple players /// with a single call. /// - public void Spawn(IEnumerable players) => players.ForEach(player => Spawn(player)); - - /// - /// Force spawns the specified player as a specific . - /// - /// The to be force spawned. - /// - /// This method forcefully spawns the player as the current , regardless of - /// the player's current state. If the player is not alive, it is immediately spawned; - /// otherwise, the player's role is temporarily set to Spectator before being force spawned. - /// - public void ForceSpawn(Pawn player) - { - ChangingCustomRoleEventArgs ev = new(player, Id); - ChangingCustomRoleDispatcher.InvokeAll(ev); - - if (!ev.IsAllowed) - return; - - player = ev.Player.Cast(); - if (ev.Role is RoleTypeId rId) - { - player.SetRole(rId); - return; - } - - if (!TryGet(ev.Role, out CustomRole role)) - return; - - if (role.Id != Id) - { - role.ForceSpawn(player); - return; - } - - object prevRole = player.CustomRole ? player.CustomRole.Id : player.Role.Type; - Remove(player); - PlayersValue.Add(player, this); - - if (!player.IsAlive) - { - ForceSpawn_Internal(player, false); - ChangedCustomRoleEventArgs @event = new(player, prevRole); - ChangedCustomRoleDispatcher.InvokeAll(@event); - return; - } - - player.Role.Set(RoleTypeId.Spectator, SpawnReason.Respawn); - Timing.CallDelayed(0.1f, () => - { - ForceSpawn_Internal(player, false); - ChangedCustomRoleEventArgs @event = new(player, prevRole); - ChangedCustomRoleDispatcher.InvokeAll(@event); - }); - } - - /// - /// Force spawns the specified player as a specific . - /// - /// The to be force spawned. - /// A value indicating whether the assignment should maintain the player's current position. - /// The to be set. - /// The to be set. - /// - /// This method forcefully spawns the player as the current , regardless of - /// the player's current state. If the player is not alive, it is immediately spawned; - /// otherwise, the player's role is temporarily set to Spectator before being force spawned. - /// - public void ForceSpawn(Pawn player, bool preservePosition, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) - { - ChangingCustomRoleEventArgs changingCustomRole = new(player, Id); - ChangingCustomRoleDispatcher.InvokeAll(changingCustomRole); - - if (!changingCustomRole.IsAllowed) - return; - - player = changingCustomRole.Player.Cast(); - - if (!TryGet(changingCustomRole.Role, out CustomRole role)) - return; - - if (role.Id != Id) - { - role.ForceSpawn(player, preservePosition, spawnReason, roleSpawnFlags); - return; - } - - object prevRole = player.CustomRole ? player.CustomRole.Id : player.Role.Type; - PlayersValue.Remove(player); - PlayersValue.Add(player, this); - - if (!player.IsAlive) - { - ForceSpawn_Internal(player, preservePosition, spawnReason, roleSpawnFlags); - ChangedCustomRoleEventArgs @event = new(player, prevRole); - ChangedCustomRoleDispatcher.InvokeAll(@event); - return; - } - - player.Role.Set(RoleTypeId.Spectator, SpawnReason.Respawn); - Timing.CallDelayed(0.1f, () => - { - ForceSpawn_Internal(player, preservePosition, spawnReason, roleSpawnFlags); - ChangedCustomRoleEventArgs @event = new(player, prevRole); - ChangedCustomRoleDispatcher.InvokeAll(@event); - }); - } - - /// - /// Force spawns each player in the specified collection as a specific . - /// - /// The collection of instances to be force spawned. - /// - /// This method forcefully spawns each player in the provided collection as the current CustomRole, - /// regardless of their current state. Players that are not alive will be immediately spawned; - /// otherwise, their roles are temporarily set to Spectator before being force spawned. - /// - public void ForceSpawn(IEnumerable players) => players.ForEach(ForceSpawn); - - /// - /// Force spawns each player in the specified collection as a specific . - /// - /// The collection of instances to be force spawned. - /// A value indicating whether the assignment should maintain the players' current positions. - /// The to be set. - /// The to be set.. - /// - /// This method forcefully spawns each player in the provided collection as the current CustomRole, - /// regardless of their current state. Players that are not alive will be immediately spawned; - /// otherwise, their roles are temporarily set to Spectator before being force spawned. - /// - public void ForceSpawn(IEnumerable players, bool preservePosition = false, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) => - players.ForEach(player => ForceSpawn(player, preservePosition, spawnReason, roleSpawnFlags)); + public void Spawn(IEnumerable players, bool force = false) => players.ForEach(player => Spawn(player, force: force)); /// /// Removes the custom role from the specified player. @@ -972,7 +864,7 @@ protected override bool TryUnregister() private void ForceSpawn_Internal(Pawn player, bool preservePosition, SpawnReason spawnReason = null, RoleSpawnFlags roleSpawnFlags = RoleSpawnFlags.All) { Instances += 1; - RoleBehaviour roleBehaviour = EObject.CreateDefaultSubobject(BehaviourComponent, $"ECS-{Name}"); + RoleBehaviour roleBehaviour = EObject.CreateDefaultSubobject(BehaviourComponent, $"ECS-{Name}").Cast(); roleBehaviour.Settings.PreservePosition = preservePosition; spawnReason ??= SpawnReason.ForceClass; @@ -982,7 +874,7 @@ private void ForceSpawn_Internal(Pawn player, bool preservePosition, SpawnReason if (roleSpawnFlags != roleBehaviour.Settings.SpawnFlags) roleBehaviour.Settings.SpawnFlags = roleSpawnFlags; - player.AddComponent(roleBehaviour); + EActor ea = player.AddComponent(roleBehaviour); } } } diff --git a/Exiled.CustomModules/API/Features/CustomRoles/RoleBehaviour.cs b/Exiled.CustomModules/API/Features/CustomRoles/RoleBehaviour.cs index 83e69ca89a..39a9fbd90f 100644 --- a/Exiled.CustomModules/API/Features/CustomRoles/RoleBehaviour.cs +++ b/Exiled.CustomModules/API/Features/CustomRoles/RoleBehaviour.cs @@ -37,7 +37,7 @@ namespace Exiled.CustomModules.API.Features.CustomRoles /// This class extends and implements . ///
It provides a foundation for creating custom behaviors associated with in-game player roles. /// - public abstract class RoleBehaviour : ModuleBehaviour, IAdditiveSettings + public class RoleBehaviour : ModuleBehaviour, IAdditiveSettings { private Vector3 lastPosition; private RoleTypeId fakeAppearance; @@ -98,6 +98,11 @@ static bool EvalSpawnPoint(IEnumerable spawnpoints, out Vector3 outP } } + /// + /// Gets the . + /// + protected virtual InventoryManager Inventory { get; } + /// /// Gets or sets the of the fake appearance applied by this component. /// @@ -111,11 +116,6 @@ protected virtual RoleTypeId FakeAppearance } } - /// - /// Gets the . - /// - protected virtual InventoryManager Inventory { get; } - /// /// Gets or sets a of which should be permanently given to the player. /// @@ -235,8 +235,8 @@ public virtual void AdjustAdditivePipe() } Owner.UniqueRole = CustomRole.Name; - Owner.TryAddCustomRoleFriendlyFire(Name, Settings.FriendlyFireMultiplier); + // TODO: Owner.TryAddCustomRoleFriendlyFire(Name, Settings.FriendlyFireMultiplier); if (CustomRole.EscapeBehaviourComponent is not null) { Owner.AddComponent(CustomRole.EscapeBehaviourComponent); @@ -316,9 +316,7 @@ protected override void PostInitialize() Owner.CustomInfo += Settings.CustomInfo; if (Settings.HideInfoArea) - { Owner.InfoArea = Owner.InfoArea.RemoveFlags(PlayerInfoArea.UnitName, PlayerInfoArea.Role); - } if (isHuman && !Settings.PreserveInventory) { @@ -357,9 +355,9 @@ protected override void PostInitialize() protected override void OnBeginPlay() { base.OnBeginPlay(); - if (!Owner) { + Log.WarnWithContext("Owner is null"); Destroy(); return; } @@ -371,6 +369,8 @@ protected override void OnBeginPlay() Owner.ChangeAppearance(FakeAppearance, false); PermanentEffects?.ForEach(x => Owner.SyncEffect(x)); + + SubscribeEvents(); } /// @@ -452,9 +452,6 @@ protected override void SubscribeEvents() { base.SubscribeEvents(); - EscapingEventDispatcher.Bind(this, OnEscaping); - EscapedEventDispatcher.Bind(this, OnEscaped); - Exiled.Events.Handlers.Player.ChangingItem += ChangingItemBehaviour; Exiled.Events.Handlers.Player.Destroying += DestroyOnLeave; Exiled.Events.Handlers.Player.ChangingRole += DestroyOnChangingRole; @@ -476,6 +473,8 @@ protected override void SubscribeEvents() Exiled.Events.Handlers.Player.Handcuffing += HandcuffingBehavior; Exiled.Events.Handlers.Map.PlacingBlood += PlacingBloodBehavior; Exiled.Events.Handlers.Player.ChangingNickname += OnInternalChangingNickname; + EscapingEventDispatcher.Bind(this, OnEscaping); + EscapedEventDispatcher.Bind(this, OnEscaped); } /// @@ -650,6 +649,11 @@ protected virtual void OverrideSpawnPoint(SpawningEventArgs ev) /// protected virtual void PickingUpItemBehavior(SearchingPickupEventArgs ev) { + if (ev.Pickup is null) + { + Log.Error("Pickup is null"); + } + if (!Check(ev.Player) || Settings.CanPickupItems) return; diff --git a/Exiled.CustomModules/API/Features/CustomRoles/RoleSettings.cs b/Exiled.CustomModules/API/Features/CustomRoles/RoleSettings.cs index 0ee8bcff1f..24a1c47ea1 100644 --- a/Exiled.CustomModules/API/Features/CustomRoles/RoleSettings.cs +++ b/Exiled.CustomModules/API/Features/CustomRoles/RoleSettings.cs @@ -119,7 +119,7 @@ public class RoleSettings : TypeCastObject, IAdditiveProperty /// /// Gets or sets the . /// - public virtual SpawnReason SpawnReason { get; set; } = SpawnReason.ForceClass; + public virtual RoleChangeReason SpawnReason { get; set; } = RoleChangeReason.RemoteAdmin; /// /// Gets or sets a value indicating whether the assignment should maintain the player's current inventory. diff --git a/Exiled.CustomModules/API/Features/Deserializers/Inheritables/RoleSettingsDeserializer.cs b/Exiled.CustomModules/API/Features/Deserializers/Inheritables/AdditivePropertyDeserializer.cs similarity index 79% rename from Exiled.CustomModules/API/Features/Deserializers/Inheritables/RoleSettingsDeserializer.cs rename to Exiled.CustomModules/API/Features/Deserializers/Inheritables/AdditivePropertyDeserializer.cs index 136e451807..227267f433 100644 --- a/Exiled.CustomModules/API/Features/Deserializers/Inheritables/RoleSettingsDeserializer.cs +++ b/Exiled.CustomModules/API/Features/Deserializers/Inheritables/AdditivePropertyDeserializer.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------- -// +// // Copyright (c) Exiled Team. All rights reserved. // Licensed under the CC BY-SA 3.0 license. // @@ -10,6 +10,7 @@ namespace Exiled.CustomModules.API.Features.Deserializers.Inheritables using System; using System.Reflection; + using Exiled.API.Features.Core.Interfaces; using Exiled.CustomModules.API.Features.CustomRoles; using YamlDotNet.Core; using YamlDotNet.Core.Events; @@ -17,7 +18,7 @@ namespace Exiled.CustomModules.API.Features.Deserializers.Inheritables /// /// The deserializer for Role Settings. /// - public class RoleSettingsDeserializer : ModuleParser + public class AdditivePropertyDeserializer : ModuleParser { /// public override ParserContext.ModuleDelegate Delegate { get; set; } = Deserialize; @@ -30,16 +31,16 @@ public class RoleSettingsDeserializer : ModuleParser /// A bool stating if it was successful or not. public static bool Deserialize(in ParserContext ctx, out object value) { - RoleSettings roleSettings = new RoleSettings(); + IAdditiveProperty additiveProperty = Activator.CreateInstance(ctx.ExpectedType) as IAdditiveProperty; ctx.Parser.Consume(); - while (ctx.Parser.TryConsume(out Scalar scalar)) + while (ctx.Parser.TryConsume(out Scalar scalar)) { PropertyInfo property = typeof(RoleSettings).GetProperty(scalar.Value, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (property != null) { object propertyValue = ctx.NestedObjectDeserializer(ctx.Parser, property.PropertyType); - property.SetValue(roleSettings, propertyValue); + property.SetValue(additiveProperty, propertyValue); } else { @@ -49,7 +50,7 @@ public static bool Deserialize(in ParserContext ctx, out object value) } ctx.Parser.Consume(); - value = roleSettings; + value = additiveProperty; return true; } } diff --git a/Exiled.CustomModules/API/Features/Deserializers/Inheritables/CustomRoleDeserializer.cs b/Exiled.CustomModules/API/Features/Deserializers/Inheritables/CustomRoleDeserializer.cs deleted file mode 100644 index 1e6c4f30af..0000000000 --- a/Exiled.CustomModules/API/Features/Deserializers/Inheritables/CustomRoleDeserializer.cs +++ /dev/null @@ -1,90 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Copyright (c) Exiled Team. All rights reserved. -// Licensed under the CC BY-SA 3.0 license. -// -// ----------------------------------------------------------------------- - -namespace Exiled.CustomModules.API.Features.Deserializers.Inheritables -{ - using System; - using System.Reflection; - - using Exiled.CustomModules.API.Features.CustomRoles; - using YamlDotNet.Core; - using YamlDotNet.Core.Events; - - /// - /// The deserializer for Custom Roles. - /// - public class CustomRoleDeserializer : ModuleParser - { - /// - public override ParserContext.ModuleDelegate Delegate { get; set; } = Deserialize; - - /// - /// The actual deserializer. - /// - /// The context. - /// If valid, returns this. - /// A bool stating if it was successful or not. - public static bool Deserialize(in ParserContext ctx, out object value) - { - if (IsCustomRoleType(ctx.ExpectedType)) - { - value = Activator.CreateInstance(ctx.ExpectedType); - ctx.Parser.Consume(); - - while (ctx.Parser.TryConsume(out Scalar scalar)) - { - PropertyInfo property = ctx.ExpectedType.GetProperty(scalar.Value, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); - if (property != null) - { - object propertyValue = ctx.NestedObjectDeserializer(ctx.Parser, property.PropertyType); - property.SetValue(value, propertyValue); - } - else if (scalar.Value.Equals("settings", StringComparison.OrdinalIgnoreCase)) - { - PropertyInfo settingsProperty = ctx.ExpectedType.GetProperty("Settings", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); - if (settingsProperty != null) - { - object settingsValue = ctx.NestedObjectDeserializer(ctx.Parser, settingsProperty.PropertyType); - settingsProperty.SetValue(value, settingsValue); - } - } - else - { - // Skip unknown properties - ctx.Parser.SkipThisAndNestedEvents(); - } - } - - ctx.Parser.Consume(); - return true; - } - - value = null; - return false; - } - - /// - /// A function that returns whether a type is a custom role. - /// - /// The Type. - /// A bool that says if the type is a custom role. - public static bool IsCustomRoleType(Type type) - { - while (type != null) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(CustomRole<>)) - { - return true; - } - - type = type.BaseType; - } - - return false; - } - } -} \ No newline at end of file diff --git a/Exiled.CustomModules/API/Features/Deserializers/ModuleParser.cs b/Exiled.CustomModules/API/Features/Deserializers/ModuleParser.cs index a93d143bd8..2145e919f8 100644 --- a/Exiled.CustomModules/API/Features/Deserializers/ModuleParser.cs +++ b/Exiled.CustomModules/API/Features/Deserializers/ModuleParser.cs @@ -7,16 +7,13 @@ namespace Exiled.CustomModules.API.Features.Deserializers { - // ReSharper disable VirtualMemberCallInConstructor using System; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Exiled.API.Features; using JetBrains.Annotations; - using YamlDotNet.Core; /// /// An inheritable class that declares an additional deserializer. @@ -28,7 +25,7 @@ public abstract class ModuleParser /// protected ModuleParser() { - ParserContext.Delegates.Add(this.Delegate); + ParserContext.Delegates.Add(Delegate); } /// @@ -42,7 +39,7 @@ protected ModuleParser() /// public static void InstantiateModuleParsers() { - Log.Info("Registering Custom Module Deserializers:"); + Log.Debug("Registering Custom Module Deserializers:"); // Get the current assembly Assembly assembly = typeof(ModuleParser).Assembly; @@ -54,62 +51,9 @@ public static void InstantiateModuleParsers() // Instantiate each type with no parameters foreach (Type type in moduleParserTypes) { - Log.Info(type.Name); + Log.Debug(type.Name); Activator.CreateInstance(type); } } } - - /// - /// A context for deserializer parsing. - /// -#pragma warning disable SA1402 - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Need to be public for deserializer.")] - public class ParserContext -#pragma warning restore SA1402 - { - /// - /// A list of functions that should be checked when deserializing a module. - /// - public static List Delegates = new(); - - /// - /// The Parser. - /// - public readonly IParser Parser; - - /// - /// The expected type. - /// - public readonly Type ExpectedType; - - /// - /// The fallback deserializer. - /// - public readonly Func NestedObjectDeserializer; - - /// - /// Initializes a new instance of the class. - /// - /// The Parser. - /// The type expected. - /// The fallback deserializer. - public ParserContext( - IParser parser, - Type expectedType, - Func nestedObjectDeserializer) - { - this.Parser = parser; - this.ExpectedType = expectedType; - this.NestedObjectDeserializer = nestedObjectDeserializer; - } - - /// - /// A delegate returning bool retaining to a module. - /// - /// The parser context. - /// The output object if successful. - /// A bool stating if parsing was successful or not. - public delegate bool ModuleDelegate(in ParserContext input, out object output); - } } \ No newline at end of file diff --git a/Exiled.CustomModules/API/Features/Deserializers/ParserContext.cs b/Exiled.CustomModules/API/Features/Deserializers/ParserContext.cs new file mode 100644 index 0000000000..da728ccd38 --- /dev/null +++ b/Exiled.CustomModules/API/Features/Deserializers/ParserContext.cs @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.CustomModules.API.Features.Deserializers +{ + using System; + using System.Collections.Generic; + + using YamlDotNet.Core; + +#pragma warning disable SA1401 + + /// + /// A context for deserializer parsing. + /// + public class ParserContext + { + /// + /// A list of functions that should be checked when deserializing a module. + /// + public static readonly List Delegates = new(); + + /// + /// The parser. + /// + public readonly IParser Parser; + + /// + /// The expected type. + /// + public readonly Type ExpectedType; + + /// + /// The fallback deserializer. + /// + public readonly Func NestedObjectDeserializer; + + /// + /// Initializes a new instance of the class. + /// + /// The Parser. + /// The type expected. + /// The fallback deserializer. + public ParserContext( + IParser parser, + Type expectedType, + Func nestedObjectDeserializer) + { + Parser = parser; + ExpectedType = expectedType; + NestedObjectDeserializer = nestedObjectDeserializer; + } + + /// + /// A delegate returning bool retaining to a module. + /// + /// The parser context. + /// The output object if successful. + /// A bool stating if parsing was successful or not. + public delegate bool ModuleDelegate(in ParserContext input, out object output); + } +} \ No newline at end of file diff --git a/Exiled.CustomModules/API/Features/ModuleBehaviour.cs b/Exiled.CustomModules/API/Features/ModuleBehaviour.cs index 03c3368b62..efb7a43f23 100644 --- a/Exiled.CustomModules/API/Features/ModuleBehaviour.cs +++ b/Exiled.CustomModules/API/Features/ModuleBehaviour.cs @@ -12,7 +12,7 @@ namespace Exiled.CustomModules.API.Features using Exiled.API.Features.Core; using Exiled.API.Features.Core.Generic; - using Exiled.CustomModules.API.Features.Attributes; + using UnityEngine; /// /// Represents a marker class for a module's pointer. @@ -25,14 +25,6 @@ namespace Exiled.CustomModules.API.Features public abstract class ModuleBehaviour : EBehaviour where TEntity : GameEntity { - /// - /// Initializes a new instance of the class. - /// - protected ModuleBehaviour() - : base() - { - } - /// /// Gets or sets the behaviour's configs. /// diff --git a/Exiled.CustomModules/API/Features/ModuleInfo.cs b/Exiled.CustomModules/API/Features/ModuleInfo.cs index 76fe2b692c..841011ae41 100644 --- a/Exiled.CustomModules/API/Features/ModuleInfo.cs +++ b/Exiled.CustomModules/API/Features/ModuleInfo.cs @@ -33,7 +33,7 @@ public struct ModuleInfo /// /// The binding flags for identifying the registration handlers. /// - public const BindingFlags SIGNATURE_BINDINGS = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase; + public const BindingFlags SIGNATURE_BINDINGS = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy; #pragma warning restore SA1310 #pragma warning disable SA1202 diff --git a/Exiled.CustomModules/API/Features/Pawn.cs b/Exiled.CustomModules/API/Features/Pawn.cs index 71c3499330..846858c4a2 100644 --- a/Exiled.CustomModules/API/Features/Pawn.cs +++ b/Exiled.CustomModules/API/Features/Pawn.cs @@ -15,7 +15,6 @@ namespace Exiled.CustomModules.API.Features using Exiled.API.Extensions; using Exiled.API.Features; using Exiled.API.Features.Attributes; - using Exiled.API.Features.Core.Generic; using Exiled.API.Features.Items; using Exiled.API.Features.Roles; using Exiled.CustomModules.API.Features.CustomAbilities; @@ -41,7 +40,7 @@ namespace Exiled.CustomModules.API.Features ///
It serves as a comprehensive representation of an in-game entity, encapsulating the associated with an expanded set of features.
/// ///
- [DefaultPlayerClass(enforceAuthority: false)] + [DefaultPlayerClass(enforceAuthority: true)] public class Pawn : Player { private readonly List abilityBehaviours = new(); @@ -83,21 +82,21 @@ public Pawn(GameObject gameObject) /// /// Can be . ///
- public CustomRole CustomRole => roleBehaviour.CustomRole; + public CustomRole CustomRole => roleBehaviour?.CustomRole; /// /// Gets the pawn's . /// /// Can be . /// - public CustomTeam CustomTeam => roleBehaviour.CustomTeam; + public CustomTeam CustomTeam => roleBehaviour?.CustomTeam; /// /// Gets the pawn's . /// /// Can be . /// - public CustomEscape CustomEscape => escapeBehaviour.CustomEscape; + public CustomEscape CustomEscape => escapeBehaviour?.CustomEscape; /// /// Gets the pawn's current . diff --git a/Exiled.CustomModules/API/Features/RoleAssigner.cs b/Exiled.CustomModules/API/Features/RoleAssigner.cs index eb25d2d172..40aa5949eb 100644 --- a/Exiled.CustomModules/API/Features/RoleAssigner.cs +++ b/Exiled.CustomModules/API/Features/RoleAssigner.cs @@ -60,7 +60,7 @@ public class RoleAssigner : StaticActor /// /// Gets or sets the human roles queue. /// - public Team[] HumanRolesQueue { get; protected set; } = { }; + public Team[] HumanRolesQueue { get; protected set; } = { Team.ClassD, Team.Scientists, Team.FoundationForces, Team.ChaosInsurgency, }; /// /// Gets the next human role to spawn from the Queue. @@ -83,9 +83,7 @@ public RoleTypeId NextHumanRoleToSpawn /// /// Gets a filter to retrieve all available human custom roles. /// - public Func FilterHumans => customRole => - customRole.AssignFromRole is RoleTypeId.None && - (HumanRolesQueue.Contains(RoleExtensions.GetTeam(customRole.Role)) || customRole.TeamsOwnership.Any(to => HumanRolesQueue.Contains(to))); + public Func FilterHumans => customRole => HumanRolesQueue.Contains(RoleExtensions.GetTeam(customRole.Role)) || customRole.TeamsOwnership.Any(to => HumanRolesQueue.Contains(to)); /// /// Gets a filter to retrieve all available SCP custom roles. @@ -139,7 +137,7 @@ public virtual void SpawnHumans(Team[] queue, int queueLength) AssigningHumanCustomRolesDispatcher.InvokeAll(ev); EnqueuedHumans = ev.Roles; - DistributeOrEnqueue(hubs.ToList(), EnqueuedHumans.Where(o => o is uint).Cast(), FilterHumans); + DistributeOrEnqueue(hubs.ToList(), spawnable, FilterHumans); EnqueuedHumans.RemoveAll(o => o is not RoleTypeId); for (int j = 0; j < num; j++) @@ -193,7 +191,7 @@ public virtual void SpawnScps(int targetScpNumber) ScpSpawner.AssignScp(chosenPlayers, scp, enqueuedScps); } - DistributeOrEnqueue(chosenPlayers, EnqueuedScps.Where(o => o is uint).Cast(), FilterScps); + DistributeOrEnqueue(chosenPlayers, spawnable, FilterScps); EnqueuedScps.Clear(); } @@ -224,11 +222,11 @@ public virtual void DistributeOrEnqueue(List players, IEnumerable< int spawned = 0; foreach (uint id in roles) { - if (!CustomRole.TryGet(id, out CustomRole role) || (role.Instances >= role.MaxInstances || !role.EvaluateConditions)) + if (!CustomRole.TryGet(id, out CustomRole role) || (role.GlobalInstances >= role.MaxInstances || (!role.EvaluateConditions && role.AssignFromRole is RoleTypeId.None)) || !role.CanSpawnByProbability) continue; ReferenceHub target = players[0]; - Player.Get(target).Cast().SetRole(role); + role.Spawn(Player.Get(target).Cast(), roleSpawnFlags: RoleSpawnFlags.All, force: true); players.RemoveAt(0); ++spawned; } @@ -238,8 +236,10 @@ public virtual void DistributeOrEnqueue(List players, IEnumerable< for (int i = spawned; i == roles.Count(); i++) { ReferenceHub target = players[0]; - Player.Get(target).Cast().SetRole(FindAvailableRole(predicate)); + CustomRole role = FindAvailableRole(predicate); + role.Spawn(Player.Get(target).Cast(), false, force: true); players.RemoveAt(0); + EnqueuedPlayers.Remove(Player.Get(target).Cast()); } } } @@ -260,16 +260,12 @@ public CustomRole FindAvailableRole(Func predicate) => public CustomRole FindAvailableRole(List toEvaluate) { if (toEvaluate.IsEmpty()) - { - throw new Exception( - "Couldn't find any alternative custom role to assign." + - "Common causes may be circular dependencies into conditions, overridden SCPs or a wrong defined amount of maximum allowed instances."); - } + return null; CustomRole role = toEvaluate[0]; - if ((role.IsScp && !role.OverrideScps.IsEmpty()) || role.Instances >= role.MaxInstances || - (role.AssignFromRole is RoleTypeId.None && !role.EvaluateConditions)) + if ((role.IsScp && role.OverrideScps is not null && !role.OverrideScps.IsEmpty()) || role.GlobalInstances >= role.MaxInstances || + (role.AssignFromRole is RoleTypeId.None && !role.EvaluateConditions) || !role.CanSpawnByProbability) { toEvaluate.RemoveAt(0); return FindAvailableRole(toEvaluate); @@ -285,16 +281,19 @@ public CustomRole FindAvailableRole(List toEvaluate) /// All evaluated custom roles. public virtual IEnumerable GetCustomRolesByProbability(IEnumerable customRoles) { + List roles = new(); foreach (CustomRole customRole in customRoles) { - for (int i = 0; i == customRole.MaxInstances; i++) + for (int i = 0; i < customRole.MaxInstances; i++) { if (!customRole.CanSpawnByProbability) continue; - yield return customRole.Id; + roles.Add(customRole.Id); } } + + return roles; } /// @@ -306,7 +305,6 @@ protected override void SubscribeEvents() Exiled.Events.Handlers.Server.PreAssigningScpRoles += OnPreAssigningScpRoles; Exiled.Events.Handlers.Player.ChangingRole += OnChangingRole; - Exiled.Events.Handlers.Player.Spawning += OnSpawning; } /// @@ -318,7 +316,6 @@ protected override void UnsubscribeEvents() Exiled.Events.Handlers.Server.PreAssigningScpRoles -= OnPreAssigningScpRoles; Exiled.Events.Handlers.Player.ChangingRole -= OnChangingRole; - Exiled.Events.Handlers.Player.Spawning -= OnSpawning; } private void OnPreAssigningHumanRoles(PreAssigningHumanRolesEventArgs ev) @@ -341,19 +338,6 @@ private void OnChangingRole(ChangingRoleEventArgs ev) EnqueuedPlayers.Add(ev.Player.Cast()); } - private void OnSpawning(SpawningEventArgs ev) - { - if (!EnqueuedPlayers.Contains(ev.Player) || ev.Player.Cast().HasCustomRole) - return; - - CustomRole customRole = FindAvailableRole(role => role.AssignFromRole == ev.Player.Role); - - if (!customRole) - return; - - customRole.ForceSpawn(ev.Player.Cast(), true); - } - private IHumanSpawnHandler GetHumanSpawnHandler(Team team) { return team switch diff --git a/Exiled.CustomModules/Config.cs b/Exiled.CustomModules/Config.cs index 54cc45a327..6cf3768674 100644 --- a/Exiled.CustomModules/Config.cs +++ b/Exiled.CustomModules/Config.cs @@ -10,7 +10,6 @@ namespace Exiled.CustomModules using System.ComponentModel; using Exiled.API.Interfaces; - using Exiled.CustomModules.API.Enums; /// /// The plugin's config. @@ -48,7 +47,7 @@ public class Config : IConfig /// It negatively affects the performance in case of the presence of a big amount of plugins. /// [Description("Whether the automatic modules loader should be used.")] - public bool UseAutomaticModulesLoader { get; set; } + public bool UseAutomaticModulesLoader { get; set; } = true; /// /// Gets or sets all modules to be loaded. diff --git a/Exiled.CustomModules/Events/EventArgs/CustomAbilities/AddedAbilityEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/CustomAbilities/AddedAbilityEventArgs.cs index 5a155f7b56..9147aadff0 100644 --- a/Exiled.CustomModules/Events/EventArgs/CustomAbilities/AddedAbilityEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/CustomAbilities/AddedAbilityEventArgs.cs @@ -9,7 +9,6 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomAbilities { using Exiled.API.Features.Core; using Exiled.CustomModules.API.Features.CustomAbilities; - using Exiled.Events.EventArgs.Interfaces; /// /// Contains all information after adding an ability. diff --git a/Exiled.CustomModules/Events/EventArgs/CustomAbilities/RemovedAbilityEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/CustomAbilities/RemovedAbilityEventArgs.cs index 743d9b43b5..70881c71c2 100644 --- a/Exiled.CustomModules/Events/EventArgs/CustomAbilities/RemovedAbilityEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/CustomAbilities/RemovedAbilityEventArgs.cs @@ -9,7 +9,6 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomAbilities { using Exiled.API.Features.Core; using Exiled.CustomModules.API.Features.CustomAbilities; - using Exiled.Events.EventArgs.Interfaces; /// /// Contains all information after removing an ability. diff --git a/Exiled.CustomModules/Events/EventArgs/CustomItems/OwnerChangingRoleEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/CustomItems/OwnerChangingRoleEventArgs.cs index 1463032206..17e28cd8c1 100644 --- a/Exiled.CustomModules/Events/EventArgs/CustomItems/OwnerChangingRoleEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/CustomItems/OwnerChangingRoleEventArgs.cs @@ -8,14 +8,10 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomItems { using Exiled.API.Features; - using Exiled.API.Features.Core; using Exiled.API.Features.Items; using Exiled.CustomModules.API.Features.CustomItems; using Exiled.CustomModules.API.Features.CustomItems.Items; using Exiled.Events.EventArgs.Player; - - using InventorySystem.Items; - using PlayerRoles; /// diff --git a/Exiled.CustomModules/Events/EventArgs/CustomRoles/AssigningHumanCustomRolesEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/CustomRoles/AssigningHumanCustomRolesEventArgs.cs index ac5b2ee84d..0b73290866 100644 --- a/Exiled.CustomModules/Events/EventArgs/CustomRoles/AssigningHumanCustomRolesEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/CustomRoles/AssigningHumanCustomRolesEventArgs.cs @@ -9,9 +9,7 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomRoles { using System.Collections.Generic; - using API.Enums; using Exiled.Events.EventArgs.Interfaces; - using PlayerRoles; /// /// Contains all information before assigning human roles. diff --git a/Exiled.CustomModules/Events/EventArgs/CustomRoles/AssigningScpCustomRolesEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/CustomRoles/AssigningScpCustomRolesEventArgs.cs index 6f1ed74caa..a6a9d013fd 100644 --- a/Exiled.CustomModules/Events/EventArgs/CustomRoles/AssigningScpCustomRolesEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/CustomRoles/AssigningScpCustomRolesEventArgs.cs @@ -10,11 +10,8 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomRoles using System.Collections.Generic; using System.Linq; - using API.Enums; - using API.Features; using Exiled.API.Features; using Exiled.Events.EventArgs.Interfaces; - using PlayerRoles; /// /// Contains all information before assigning SCP roles. diff --git a/Exiled.CustomModules/Events/EventArgs/CustomRoles/ChangedCustomRoleEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/CustomRoles/ChangedCustomRoleEventArgs.cs index 3a3e20ac66..115454ce52 100644 --- a/Exiled.CustomModules/Events/EventArgs/CustomRoles/ChangedCustomRoleEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/CustomRoles/ChangedCustomRoleEventArgs.cs @@ -7,13 +7,8 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomRoles { - using System.Collections.Generic; - - using API.Enums; using Exiled.API.Features; - using Exiled.CustomModules.API.Features; using Exiled.Events.EventArgs.Interfaces; - using PlayerRoles; /// /// Contains all information after a player changes role to a custom role. diff --git a/Exiled.CustomModules/Events/EventArgs/CustomRoles/ChangingCustomRoleEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/CustomRoles/ChangingCustomRoleEventArgs.cs index edcfe38ff3..6d0dea1b89 100644 --- a/Exiled.CustomModules/Events/EventArgs/CustomRoles/ChangingCustomRoleEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/CustomRoles/ChangingCustomRoleEventArgs.cs @@ -7,13 +7,8 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomRoles { - using System.Collections.Generic; - - using API.Enums; using Exiled.API.Features; - using Exiled.CustomModules.API.Features; using Exiled.Events.EventArgs.Interfaces; - using PlayerRoles; /// /// Contains all information before a player changes role to a custom role. diff --git a/Exiled.CustomModules/Events/EventArgs/CustomRoles/SelectingCustomTeamRespawnEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/CustomRoles/SelectingCustomTeamRespawnEventArgs.cs index 613976ebc3..0196630602 100644 --- a/Exiled.CustomModules/Events/EventArgs/CustomRoles/SelectingCustomTeamRespawnEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/CustomRoles/SelectingCustomTeamRespawnEventArgs.cs @@ -7,15 +7,8 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomRoles { - using System.Collections.Generic; - using System.Linq; - - using API.Enums; - using API.Features; - using Exiled.API.Features; using Exiled.CustomModules.API.Features.CustomRoles; using Exiled.Events.EventArgs.Interfaces; - using PlayerRoles; using Respawning; /// diff --git a/Exiled.CustomModules/Events/EventArgs/Interfaces/ICustomItemEvent.cs b/Exiled.CustomModules/Events/EventArgs/Interfaces/ICustomItemEvent.cs index a10fca117e..4ca319a7c5 100644 --- a/Exiled.CustomModules/Events/EventArgs/Interfaces/ICustomItemEvent.cs +++ b/Exiled.CustomModules/Events/EventArgs/Interfaces/ICustomItemEvent.cs @@ -7,7 +7,6 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomItems { - using Exiled.API.Features.Items; using Exiled.CustomModules.API.Features.CustomItems; using Exiled.CustomModules.API.Features.CustomItems.Items; using Exiled.Events.EventArgs.Interfaces; diff --git a/Exiled.CustomModules/Events/EventArgs/Interfaces/ICustomPickupEvent.cs b/Exiled.CustomModules/Events/EventArgs/Interfaces/ICustomPickupEvent.cs index 84ab6214ff..edac8a47e7 100644 --- a/Exiled.CustomModules/Events/EventArgs/Interfaces/ICustomPickupEvent.cs +++ b/Exiled.CustomModules/Events/EventArgs/Interfaces/ICustomPickupEvent.cs @@ -7,7 +7,6 @@ namespace Exiled.CustomModules.Events.EventArgs.CustomItems { - using Exiled.API.Features.Items; using Exiled.CustomModules.API.Features.CustomItems; using Exiled.CustomModules.API.Features.CustomItems.Items; using Exiled.Events.EventArgs.Interfaces; diff --git a/Exiled.CustomModules/Events/EventArgs/Tracking/ItemTrackingModifiedEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/Tracking/ItemTrackingModifiedEventArgs.cs index 81e38ad053..0fdb68a706 100644 --- a/Exiled.CustomModules/Events/EventArgs/Tracking/ItemTrackingModifiedEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/Tracking/ItemTrackingModifiedEventArgs.cs @@ -11,8 +11,6 @@ namespace Exiled.CustomModules.Events.EventArgs.Tracking using Exiled.API.Features.Items; using Exiled.API.Features.Pickups; - using Exiled.CustomModules.API.Features; - using Exiled.CustomModules.API.Features.CustomAbilities; using Exiled.CustomModules.API.Interfaces; using Exiled.Events.EventArgs.Interfaces; diff --git a/Exiled.CustomModules/Events/EventArgs/Tracking/PickupTrackingModifiedEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/Tracking/PickupTrackingModifiedEventArgs.cs index 1dbbcc2df3..67a37aed79 100644 --- a/Exiled.CustomModules/Events/EventArgs/Tracking/PickupTrackingModifiedEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/Tracking/PickupTrackingModifiedEventArgs.cs @@ -10,7 +10,6 @@ namespace Exiled.CustomModules.Events.EventArgs.Tracking using System.Collections.Generic; using Exiled.API.Features.Pickups; - using Exiled.CustomModules.API.Features; using Exiled.CustomModules.API.Interfaces; using Exiled.Events.EventArgs.Interfaces; diff --git a/Exiled.CustomModules/Events/EventArgs/Tracking/TrackingModifiedEventArgs.cs b/Exiled.CustomModules/Events/EventArgs/Tracking/TrackingModifiedEventArgs.cs index 0631f83061..d7607bbf55 100644 --- a/Exiled.CustomModules/Events/EventArgs/Tracking/TrackingModifiedEventArgs.cs +++ b/Exiled.CustomModules/Events/EventArgs/Tracking/TrackingModifiedEventArgs.cs @@ -9,7 +9,6 @@ namespace Exiled.CustomModules.Events.EventArgs.Tracking { using System.Collections.Generic; - using Exiled.CustomModules.API.Features; using Exiled.CustomModules.API.Interfaces; using Exiled.Events.EventArgs.Interfaces; diff --git a/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs b/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs new file mode 100644 index 0000000000..cad328ea2e --- /dev/null +++ b/Exiled.Events/EventArgs/Player/PlayingAudioLogEventArgs.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Player +{ + using API.Features; + + using Interfaces; + + /// + /// Contains all information before a player plays the AudioLog. + /// + public class PlayingAudioLogEventArgs : IPlayerEvent, IDeniableEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + public PlayingAudioLogEventArgs(Player player, bool isAllowed = true) + { + Player = player; + IsAllowed = isAllowed; + } + + /// + /// Gets or sets a value indicating whether or not the audio will start. + /// + public bool IsAllowed { get; set; } + + /// + /// Gets the player who started the AudioLog. + /// + public Player Player { get; } + } +} \ No newline at end of file diff --git a/Exiled.Events/Handlers/Player.cs b/Exiled.Events/Handlers/Player.cs index 465f0f3e1a..38a8fa3f27 100644 --- a/Exiled.Events/Handlers/Player.cs +++ b/Exiled.Events/Handlers/Player.cs @@ -218,6 +218,11 @@ public class Player /// public static Event DroppingNothing { get; set; } = new(); + /// + /// Invoked before playing an AudioLog. + /// + public static Event PlayingAudioLog { get; set; } = new(); + /// /// Invoked before picking up an . /// @@ -727,6 +732,12 @@ public class Player /// The instance. public static void OnDroppingNothing(DroppingNothingEventArgs ev) => DroppingNothing.InvokeSafely(ev); + /// + /// Called before a plays an AudioLog. + /// + /// The instance. + public static void OnPlayingAudioLog(PlayingAudioLogEventArgs ev) => PlayingAudioLog.InvokeSafely(ev); + /// /// Called before a picks up an item. /// diff --git a/Exiled.Events/Patches/Events/Player/PlayingAudioLog.cs b/Exiled.Events/Patches/Events/Player/PlayingAudioLog.cs new file mode 100644 index 0000000000..894d99ae9d --- /dev/null +++ b/Exiled.Events/Patches/Events/Player/PlayingAudioLog.cs @@ -0,0 +1,68 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled 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; + using Exiled.API.Features.Core.Generic.Pools; + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Player; + using HarmonyLib; + using MapGeneration.Spawnables; + + using static HarmonyLib.AccessTools; + + /// + /// Patch the . + /// Adds the event. + /// + [EventPatch(typeof(Handlers.Player), nameof(Handlers.Player.PlayingAudioLog))] + [HarmonyPatch(typeof(AudioLog), nameof(AudioLog.ServerInteract))] + internal static class PlayingAudioLog + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + Label retLabel = generator.DefineLabel(); + + newInstructions.InsertRange( + 0, + new CodeInstruction[] + { + // Player player = Player.Get(ReferenceHub); + new(OpCodes.Ldarg_1), + new(OpCodes.Call, Method(typeof(Player), nameof(Player.Get), new[] { typeof(ReferenceHub) })), + + // true + new(OpCodes.Ldc_I4_1), + + // PlayingAudioLogEventArgs ev = new(Player, bool) + new(OpCodes.Newobj, GetDeclaredConstructors(typeof(PlayingAudioLogEventArgs))[0]), + new(OpCodes.Dup), + + // Handlers.Player.OnPlayingAudioLog(ev) + new(OpCodes.Call, Method(typeof(Handlers.Player), nameof(Handlers.Player.OnPlayingAudioLog))), + + // if (!ev.IsAllowed) + // return; + new(OpCodes.Callvirt, PropertyGetter(typeof(PlayingAudioLogEventArgs), nameof(PlayingAudioLogEventArgs.IsAllowed))), + new(OpCodes.Brfalse, retLabel), + }); + + newInstructions[newInstructions.Count - 1].labels.Add(retLabel); + + for (int z = 0; z < newInstructions.Count; z++) + yield return newInstructions[z]; + + ListPool.Pool.Return(newInstructions); + } + } +} \ No newline at end of file diff --git a/Exiled.Events/Patches/Fixes/NWFixDetonationTimer.cs b/Exiled.Events/Patches/Fixes/NWFixDetonationTimer.cs new file mode 100644 index 0000000000..2a6340631d --- /dev/null +++ b/Exiled.Events/Patches/Fixes/NWFixDetonationTimer.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System; + using System.Linq; + + using GameCore; + using HarmonyLib; + + /// + /// Fixes the issue where the game was not selecting the scenario with the nearest value. + /// Bug Report + /// + [HarmonyPatch(typeof(AlphaWarheadController), nameof(AlphaWarheadController.Start))] + internal class NWFixDetonationTimer + { + private static void Postfix() + { + AlphaWarheadSyncInfo networkInfo = default; + networkInfo.ScenarioId = Array.IndexOf(AlphaWarheadController.Singleton._startScenarios, AlphaWarheadController.Singleton._startScenarios.OrderBy(d => Math.Abs(d.TimeToDetonate - ConfigFile.ServerConfig.GetInt("warhead_tminus_start_duration", 90))).First()); + + AlphaWarheadController.Singleton.NetworkInfo = networkInfo; + return; + } + } +} \ No newline at end of file diff --git a/Exiled.Example/Example.cs b/Exiled.Example/Example.cs index 0f75995444..b4fefd668a 100644 --- a/Exiled.Example/Example.cs +++ b/Exiled.Example/Example.cs @@ -7,7 +7,6 @@ namespace Exiled.Example { - using Exiled.API.Enums; using Exiled.API.Features; /// @@ -21,7 +20,10 @@ public class Example : Plugin public static Example Instance { get; private set; } /// - public override PluginPriority Priority { get; } = PluginPriority.Last; + public override string Name { get; } = "Exiled.Example"; + + /// + public override string Author { get; } = "Exiled Team"; /// /// Gets the current instance of the event handler. diff --git a/Exiled.Example/Exiled.Example.csproj b/Exiled.Example/Exiled.Example.csproj index f8a2050b08..f87fc12964 100644 --- a/Exiled.Example/Exiled.Example.csproj +++ b/Exiled.Example/Exiled.Example.csproj @@ -13,6 +13,7 @@ + diff --git a/Exiled.Example/TestRole/CustomRoleType.cs b/Exiled.Example/TestRole/CustomRoleType.cs new file mode 100644 index 0000000000..67fe24e309 --- /dev/null +++ b/Exiled.Example/TestRole/CustomRoleType.cs @@ -0,0 +1,22 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Example.TestRole +{ + using Exiled.CustomModules.API.Enums; + + /// + /// The custom role type. + /// + public class CustomRoleType : UUCustomRoleType + { + /// + /// Initializes a new custom role id. + /// + public static readonly CustomRoleType Scp999 = new(); + } +} \ No newline at end of file diff --git a/Exiled.Example/TestRole/Scp999Behaviour.cs b/Exiled.Example/TestRole/Scp999Behaviour.cs new file mode 100644 index 0000000000..4e5e443ed8 --- /dev/null +++ b/Exiled.Example/TestRole/Scp999Behaviour.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Example.TestRole +{ + using Exiled.CustomModules.API.Features.CustomRoles; + + /// + public class Scp999Behaviour : RoleBehaviour + { + /// + protected override void PostInitialize() + { + base.PostInitialize(); + } + } +} \ No newline at end of file diff --git a/Exiled.Example/TestRole/Scp999Config.cs b/Exiled.Example/TestRole/Scp999Config.cs new file mode 100644 index 0000000000..44cd93c69a --- /dev/null +++ b/Exiled.Example/TestRole/Scp999Config.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Example.TestRole +{ + using Exiled.CustomModules.API.Features.Attributes; + using Exiled.CustomModules.API.Features.CustomRoles; + using Exiled.CustomModules.API.Features.Generic; + + /// + [ModuleIdentifier] + public class Scp999Config : ModulePointer + { + /// + public override uint Id { get; set; } = CustomRoleType.Scp999; + } +} \ No newline at end of file diff --git a/Exiled.Example/TestRole/Scp999Role.cs b/Exiled.Example/TestRole/Scp999Role.cs new file mode 100644 index 0000000000..883110626e --- /dev/null +++ b/Exiled.Example/TestRole/Scp999Role.cs @@ -0,0 +1,79 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Example.TestRole +{ + using Exiled.API.Enums; + using Exiled.CustomModules.API.Features.Attributes; + using Exiled.CustomModules.API.Features.CustomRoles; + using PlayerRoles; + + /// + [ModuleIdentifier] + public class Scp999Role : CustomRole + { + /// + public override string Name { get; set; } = "SCP-999"; + + /// + public override uint Id { get; set; } = CustomRoleType.Scp999; + + /// + public override bool IsEnabled { get; set; } = true; + + /// + public override string Description { get; set; } = "SCP-999"; + + /// + public override RoleTypeId Role { get; set; } = RoleTypeId.ClassD; + + /// + public override int Probability { get; set; } = 100; + + /// + public override int MaxInstances { get; set; } = 1; + + /// + public override Team[] TeamsOwnership { get; set; } = { Team.SCPs }; + + /// + public override RoleTypeId AssignFromRole { get; set; } = RoleTypeId.ClassD; + + /// + public override bool IsScp { get; set; } = true; + + /// + public override RoleSettings Settings { get; set; } = new() + { + UseDefaultRoleOnly = true, + UniqueRole = RoleTypeId.ClassD, + + Health = 300, + MaxHealth = 400, + Scale = 0.90f, + CustomInfo = "SCP-999", + + SpawnedText = new("You've been spawned as SCP-999", 10, channel: TextChannelType.Broadcast), + + PreservePosition = true, + + PreserveInventory = true, + + CanActivateWarhead = true, + + CanBypassCheckpoints = true, + CanActivateGenerators = false, + CanPlaceBlood = false, + CanBeHurtByScps = false, + CanHurtScps = false, + CanBeHandcuffed = false, + + DoesLookingAffectScp096 = false, + DoesLookingAffectScp173 = false, + }; + } +} \ No newline at end of file diff --git a/Exiled.Loader/Loader.cs b/Exiled.Loader/Loader.cs index 2823c415a1..6a653fbc38 100644 --- a/Exiled.Loader/Loader.cs +++ b/Exiled.Loader/Loader.cs @@ -199,19 +199,25 @@ public static IPlugin CreatePlugin(Assembly assembly) continue; } + bool foundAuthority = false; foreach (Type type in assembly.GetTypes()) { if (type.BaseType == typeof(Player) || type.IsSubclassOf(typeof(Player))) - { - Log.ErrorWithContext(type.Name); defaultPlayerClass = type; - } - DefaultPlayerClassAttribute dpc = Player.DEFAULT_PLAYER_CLASS.GetCustomAttribute(); - if (Player.DEFAULT_PLAYER_CLASS != typeof(Player) && !dpc.EnforceAuthority && defaultPlayerClass is not null) + DefaultPlayerClassAttribute dpc = type.GetCustomAttribute(); + + if (Player.DEFAULT_PLAYER_CLASS == typeof(Player) && dpc is not null && defaultPlayerClass is not null) { - if (Player.DEFAULT_PLAYER_CLASS == typeof(Player) && dpc.EnforceAuthority) + if (dpc.EnforceAuthority) + { Player.DEFAULT_PLAYER_CLASS = defaultPlayerClass; + foundAuthority = true; + } + else if (!dpc.EnforceAuthority && !foundAuthority) + { + Player.DEFAULT_PLAYER_CLASS = defaultPlayerClass; + } } } diff --git a/stylecop.json b/stylecop.json index 1388251d0d..b16da3eaf8 100644 --- a/stylecop.json +++ b/stylecop.json @@ -1,10 +1,4 @@ { - // ACTION REQUIRED: This file was automatically added to your project, but it - // will not take effect until additional steps are taken to enable it. See the - // following page for additional information: - // - // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "settings": { "documentationRules": {