diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 4d67329a6..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: "nuget" # See documentation for possible values - directory: "./EXILED" # Location of package manifests - schedule: - interval: "weekly" diff --git a/.github/labeler.yml b/.github/labeler.yml index 6fd09c3a1..bd83bb6c8 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -11,10 +11,10 @@ GitHub: - any-glob-to-any-file: .github/** # Any modifications to github related files Events: # Add the 'Events' label -- changed-files: - - any-glob-to-any-file: EXILED/Exiled.Events/** # Any Modifications to events - - all-globs-to-all-files: '!EXILED/Exiled.Events/Patches/**/*' # Exept modifications to transpiler files - +- all: + - changed-files: + - any-glob-to-any-file: 'EXILED/Exiled.Events/**/*' + - all-globs-to-all-files: '!EXILED/Exiled.Events/Patches/**/*' API: # Add the 'API' label - changed-files: - any-glob-to-any-file: EXILED/Exiled.API/** # Any modifications to the API diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index e8e84f705..16ff4dafa 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -48,7 +48,7 @@ jobs: shell: pwsh run: | ./build.ps1 -BuildNuGet - $File = (Get-ChildItem -Path . -Include 'EXILED-OFFICIAL.*.nupkg' -Recurse).Name + $File = (Get-ChildItem -Path . -Include 'ExMod.Exiled.*.nupkg' -Recurse).Name Out-File -FilePath ${{ github.env }} -InputObject "PackageFile=$File" -Encoding utf-8 -Append - name: Upload nuget package diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9e2dc2994..f4d75022e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,11 +34,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Dotnet Setup - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 8.x - - run: dotnet tool update -g docfx - name: Setup .NET Core SDK diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 929ef4169..3c842e3a3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,7 +46,7 @@ jobs: shell: pwsh run: | ./build.ps1 -BuildNuGet - $File = (Get-ChildItem -Path . -Include 'EXILED-OFFICIAL.*.nupkg' -Recurse).Name + $File = (Get-ChildItem -Path . -Include 'ExMod.Exiled.*.nupkg' -Recurse).Name Out-File -FilePath ${{ github.env }} -InputObject "PackageFile=$File" -Encoding utf-8 -Append - name: Upload artifacts diff --git a/.github/workflows/push_nuget.yml b/.github/workflows/push_nuget.yml index d45971d95..87acc59b8 100644 --- a/.github/workflows/push_nuget.yml +++ b/.github/workflows/push_nuget.yml @@ -41,7 +41,7 @@ jobs: shell: pwsh run: | ./build.ps1 -BuildNuGet - $File = (Get-ChildItem -Path . -Include 'EXILED-OFFICIAL.*.nupkg' -Recurse).Name + $File = (Get-ChildItem -Path . -Include 'ExMod.Exiled.*.nupkg' -Recurse).Name Out-File -FilePath ${{ github.env }} -InputObject "PackageFile=$File" -Encoding utf-8 -Append - name: Push NuGet diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5230704fe..ac0937bd6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: shell: pwsh run: | ./build.ps1 - $File = (Get-ChildItem -Path . -Include 'EXILED-OFFICIAL.*.nupkg' -Recurse).Name + $File = (Get-ChildItem -Path . -Include 'ExMod.Exiled.*.nupkg' -Recurse).Name Out-File -FilePath ${{ github.env }} -InputObject "PackageFile=$File" -Encoding utf-8 -Append - name: Get references diff --git a/EXILED/EXILED.props b/EXILED/EXILED.props index 617f53a2b..9b3f7ecab 100644 --- a/EXILED/EXILED.props +++ b/EXILED/EXILED.props @@ -15,7 +15,7 @@ - 8.12.2 + 8.14.1 false diff --git a/EXILED/EXILED.sln b/EXILED/EXILED.sln index 3614644a8..60b7f5f72 100644 --- a/EXILED/EXILED.sln +++ b/EXILED/EXILED.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exiled.CreditTags", "Exiled EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exiled.CustomRoles", "Exiled.CustomRoles\Exiled.CustomRoles.csproj", "{417C3309-8B93-4218-A1D1-D4BB7B09BE0F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{BE130A80-6819-44C6-AA1B-BF068DEA67FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -74,6 +76,12 @@ Global {417C3309-8B93-4218-A1D1-D4BB7B09BE0F}.Installer|Any CPU.ActiveCfg = Installer|Any CPU {417C3309-8B93-4218-A1D1-D4BB7B09BE0F}.Release|Any CPU.ActiveCfg = Release|Any CPU {417C3309-8B93-4218-A1D1-D4BB7B09BE0F}.Release|Any CPU.Build.0 = Release|Any CPU + {BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Installer|Any CPU.ActiveCfg = Installer|Any CPU + {BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Installer|Any CPU.Build.0 = Installer|Any CPU + {BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE130A80-6819-44C6-AA1B-BF068DEA67FF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/EXILED/Exiled.API/Features/Items/Scp330.cs b/EXILED/Exiled.API/Features/Items/Scp330.cs index 689929f12..d0f902184 100644 --- a/EXILED/Exiled.API/Features/Items/Scp330.cs +++ b/EXILED/Exiled.API/Features/Items/Scp330.cs @@ -87,6 +87,11 @@ internal Scp330() /// public CandyKindID ExposedType { get; set; } = CandyKindID.None; + /// + /// Gets or sets the candy that will be added to the bag. Used for events. + /// + internal CandyKindID CandyToAdd { get; set; } = CandyKindID.None; + /// /// Adds a specific candy to the bag. /// diff --git a/EXILED/Exiled.API/Features/Lockers/Chamber.cs b/EXILED/Exiled.API/Features/Lockers/Chamber.cs index 8209d39a5..72304774f 100644 --- a/EXILED/Exiled.API/Features/Lockers/Chamber.cs +++ b/EXILED/Exiled.API/Features/Lockers/Chamber.cs @@ -62,6 +62,16 @@ public Chamber(LockerChamber chamber, Locker locker) /// public Quaternion Rotation => Base.transform.rotation; + /// + /// Gets the . + /// + public GameObject GameObject => Base.gameObject; + + /// + /// Gets the . + /// + public Transform Transform => Base.transform; + /// /// Gets or sets all pickups that should be spawned when the door is initially opened. /// diff --git a/EXILED/Exiled.API/Features/Lockers/Locker.cs b/EXILED/Exiled.API/Features/Lockers/Locker.cs index 73108359b..509f243ef 100644 --- a/EXILED/Exiled.API/Features/Lockers/Locker.cs +++ b/EXILED/Exiled.API/Features/Lockers/Locker.cs @@ -60,6 +60,11 @@ public Locker(BaseLocker locker) /// public LockerType Type { get; } + /// + /// Gets the . + /// + public GameObject GameObject => Base.gameObject; + /// /// Gets the . /// diff --git a/EXILED/Exiled.API/Features/Npc.cs b/EXILED/Exiled.API/Features/Npc.cs index 82a6c7374..e79038065 100644 --- a/EXILED/Exiled.API/Features/Npc.cs +++ b/EXILED/Exiled.API/Features/Npc.cs @@ -16,7 +16,6 @@ namespace Exiled.API.Features using CentralAuth; using CommandSystem; using Exiled.API.Enums; - using Exiled.API.Extensions; using Exiled.API.Features.Components; using Exiled.API.Features.Roles; using Footprinting; @@ -142,6 +141,7 @@ public override Vector3 Position /// The userID of the NPC. /// The position to spawn the NPC. /// The spawned. + [Obsolete("This metod is marked as obsolet due to a bug that make player have the same id. Use Npc.Spawn(string) instead")] public static Npc Spawn(string name, RoleTypeId role, int id = 0, string userId = PlayerAuthenticationManager.DedicatedId, Vector3? position = null) { GameObject newObject = UnityEngine.Object.Instantiate(Mirror.NetworkManager.singleton.playerPrefab); @@ -208,18 +208,118 @@ public static Npc Spawn(string name, RoleTypeId role, int id = 0, string userId return npc; } + /// + /// Spawns an NPC based on the given parameters. + /// + /// The name of the NPC. + /// The RoleTypeId of the NPC, defaulting to None. + /// Whether the NPC should be ignored by round ending checks. + /// The userID of the NPC for authentication. Defaults to the Dedicated ID. + /// The position where the NPC should spawn. If null, the default spawn location is used. + /// The spawned. + public static Npc Spawn(string name, RoleTypeId role = RoleTypeId.None, bool ignored = false, string userId = PlayerAuthenticationManager.DedicatedId, Vector3? position = null) + { + GameObject newObject = UnityEngine.Object.Instantiate(Mirror.NetworkManager.singleton.playerPrefab); + + Npc npc = new(newObject) + { + IsNPC = true, + }; + + FakeConnection fakeConnection = new(npc.Id); + + try + { + if (userId == PlayerAuthenticationManager.DedicatedId) + { + npc.ReferenceHub.authManager.SyncedUserId = userId; + try + { + npc.ReferenceHub.authManager.InstanceMode = ClientInstanceMode.DedicatedServer; + } + catch (Exception e) + { + Log.Debug($"Ignore: {e.Message}"); + } + } + else + { + npc.ReferenceHub.authManager.InstanceMode = ClientInstanceMode.Unverified; + npc.ReferenceHub.authManager._privUserId = userId == string.Empty ? $"Dummy-{npc.Id}@localhost" : userId; + } + } + catch (Exception e) + { + Log.Debug($"Ignore: {e.Message}"); + } + + try + { + npc.ReferenceHub.roleManager.InitializeNewRole(RoleTypeId.None, RoleChangeReason.None); + } + catch (Exception e) + { + Log.Debug($"Ignore: {e.Message}"); + } + + NetworkServer.AddPlayerForConnection(fakeConnection, newObject); + + npc.ReferenceHub.nicknameSync.Network_myNickSync = name; + Dictionary.Add(newObject, npc); + + Timing.CallDelayed(0.5f, () => + { + npc.Role.Set(role, SpawnReason.RoundStart, position is null ? RoleSpawnFlags.All : RoleSpawnFlags.AssignInventory); + + if (position is not null) + npc.Position = position.Value; + }); + + if (ignored) + Round.IgnoredPlayers.Add(npc.ReferenceHub); + + return npc; + } + + /// + /// Destroys all NPCs currently spawned. + /// + public static void DestroyAll() + { + foreach (Npc npc in List) + npc.Destroy(); + } + /// /// Destroys the NPC. /// public void Destroy() { - NetworkConnectionToClient conn = ReferenceHub.connectionToClient; - if (ReferenceHub._playerId.Value <= RecyclablePlayerId._autoIncrement) - ReferenceHub._playerId.Destroy(); - ReferenceHub.OnDestroy(); - CustomNetworkManager.TypedSingleton.OnServerDisconnect(conn); - Dictionary.Remove(GameObject); - Object.Destroy(GameObject); + try + { + Round.IgnoredPlayers.Remove(ReferenceHub); + NetworkConnectionToClient conn = ReferenceHub.connectionToClient; + ReferenceHub.OnDestroy(); + CustomNetworkManager.TypedSingleton.OnServerDisconnect(conn); + Dictionary.Remove(GameObject); + Object.Destroy(GameObject); + } + catch (Exception e) + { + Log.Error($"Error while destroying a NPC: {e.Message}"); + } + } + + /// + /// Schedules the destruction of the NPC after a delay. + /// + /// The delay in seconds before the NPC is destroyed. + public void LateDestroy(float time) + { + Timing.CallDelayed(time, () => + { + this?.Destroy(); + }); } } } diff --git a/EXILED/Exiled.API/Features/Player.cs b/EXILED/Exiled.API/Features/Player.cs index 9e55712ee..d5e3d3eae 100644 --- a/EXILED/Exiled.API/Features/Player.cs +++ b/EXILED/Exiled.API/Features/Player.cs @@ -715,23 +715,7 @@ public ScpSpawnPreferences.SpawnPreferences ScpPreferences public Vector3 Scale { get => ReferenceHub.transform.localScale; - set - { - if (value == Scale) - return; - - try - { - ReferenceHub.transform.localScale = value; - - foreach (Player target in List) - Server.SendSpawnMessage?.Invoke(null, new object[] { NetworkIdentity, target.Connection }); - } - catch (Exception exception) - { - Log.Error($"{nameof(Scale)} error: {exception}"); - } - } + set => SetScale(value, List); } /// @@ -2070,6 +2054,56 @@ public void Disconnect(string reason = null) => /// public void ResetStamina() => Stamina = StaminaStat.MaxValue; + /// + /// Sets the scale of a player on the server side. + /// + /// The scale to set. + /// Who should see the updated scale. + public void SetScale(Vector3 scale, IEnumerable viewers) + { + if (scale == Scale) + return; + + try + { + ReferenceHub.transform.localScale = scale; + + foreach (Player target in viewers) + Server.SendSpawnMessage?.Invoke(null, new object[] { NetworkIdentity, target.Connection }); + } + catch (Exception exception) + { + Log.Error($"{nameof(SetScale)} error: {exception}"); + } + } + + /// + /// Sets the scale of the player for other players. + /// + /// The scale to set to. + /// Who should see the fake scale. + public void SetFakeScale(Vector3 fakeScale, IEnumerable viewers) + { + Vector3 currentScale = Scale; + + if (fakeScale == currentScale) + return; + + try + { + ReferenceHub.transform.localScale = fakeScale; + + foreach (Player target in viewers) + Server.SendSpawnMessage.Invoke(null, new object[] { NetworkIdentity, target.Connection }); + + ReferenceHub.transform.localScale = currentScale; + } + catch (Exception ex) + { + Log.Error($"{nameof(SetFakeScale)}: {ex}"); + } + } + /// /// Gets a user's SCP preference. /// diff --git a/EXILED/Exiled.API/Features/Round.cs b/EXILED/Exiled.API/Features/Round.cs index ed334f519..fb06a1b86 100644 --- a/EXILED/Exiled.API/Features/Round.cs +++ b/EXILED/Exiled.API/Features/Round.cs @@ -61,6 +61,12 @@ public static class Round /// public static bool IsLobby => !(IsEnded || IsStarted); + /// + /// Gets a value indicating the last ClassList. + /// + /// this value is only updated when Round is and not . + public static RoundSummary.SumInfo_ClassList LastClassList { get; internal set; } + /// /// Gets or sets a value indicating the amount of Chaos Targets remaining. /// @@ -121,6 +127,7 @@ public static int Kills public static int SurvivingSCPs { get => RoundSummary.SurvivingSCPs; + [Obsolete("This value is rewritten by NW every time it's used", true)] set => RoundSummary.SurvivingSCPs = value; } diff --git a/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs b/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs index 92e24e712..7336fd083 100644 --- a/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs +++ b/EXILED/Exiled.API/Features/Spawn/RoomSpawnPoint.cs @@ -48,6 +48,9 @@ public override Vector3 Position { Room roomInstance = Features.Room.Get(Room) ?? throw new InvalidOperationException("The room instance could not be found."); + if (roomInstance.Type == RoomType.Surface) + return Offset != Vector3.zero ? Offset : roomInstance.Position; + return Offset != Vector3.zero ? roomInstance.transform.TransformPoint(Offset) : roomInstance.Position; } set => throw new InvalidOperationException("The position of this type of SpawnPoint cannot be changed."); diff --git a/EXILED/Exiled.CustomItems/CustomItems.cs b/EXILED/Exiled.CustomItems/CustomItems.cs index 855186c00..207f55b6f 100644 --- a/EXILED/Exiled.CustomItems/CustomItems.cs +++ b/EXILED/Exiled.CustomItems/CustomItems.cs @@ -35,7 +35,7 @@ public override void OnEnabled() roundHandler = new MapHandler(); playerHandler = new PlayerHandler(); - Exiled.Events.Handlers.Map.Generated += roundHandler.OnMapGenerated; + Exiled.Events.Handlers.Server.WaitingForPlayers += roundHandler.OnWaitingForPlayers; Exiled.Events.Handlers.Player.ChangingItem += playerHandler.OnChangingItem; @@ -50,7 +50,7 @@ public override void OnEnabled() /// public override void OnDisabled() { - Exiled.Events.Handlers.Map.Generated -= roundHandler!.OnMapGenerated; + Exiled.Events.Handlers.Server.WaitingForPlayers -= roundHandler!.OnWaitingForPlayers; Exiled.Events.Handlers.Player.ChangingItem -= playerHandler!.OnChangingItem; diff --git a/EXILED/Exiled.CustomItems/Events/MapHandler.cs b/EXILED/Exiled.CustomItems/Events/MapHandler.cs index 4e0c2442f..61993e03c 100644 --- a/EXILED/Exiled.CustomItems/Events/MapHandler.cs +++ b/EXILED/Exiled.CustomItems/Events/MapHandler.cs @@ -7,6 +7,7 @@ namespace Exiled.CustomItems.Events { + using Exiled.API.Features; using Exiled.CustomItems.API.Features; using MEC; @@ -15,13 +16,22 @@ namespace Exiled.CustomItems.Events /// internal sealed class MapHandler { - /// - public void OnMapGenerated() + /// + public void OnWaitingForPlayers() { - Timing.CallDelayed(0.5f, () => // Delay its necessary for the spawnpoints of lockers and rooms to be generated. + Timing.CallDelayed(2, () => // The delay is necessary because the generation of the lockers takes time, due to the way they are made in the base game. { foreach (CustomItem customItem in CustomItem.Registered) - customItem?.SpawnAll(); + { + try + { + customItem?.SpawnAll(); + } + catch (System.Exception e) + { + Log.Error($"There was an error while spawning the custom item '{customItem?.Name}' ({customItem?.Id}) | {e.Message}"); + } + } }); } } diff --git a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs index 29151b1f4..43c1b5172 100644 --- a/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs +++ b/EXILED/Exiled.CustomRoles/API/Features/CustomRole.cs @@ -879,7 +879,7 @@ protected virtual void UnsubscribeEvents() Exiled.Events.Handlers.Player.ChangingRole -= OnInternalChangingRole; Exiled.Events.Handlers.Player.Spawning -= OnInternalSpawning; Exiled.Events.Handlers.Player.SpawningRagdoll -= OnSpawningRagdoll; - Exiled.Events.Handlers.Player.Destroying += OnDestroying; + Exiled.Events.Handlers.Player.Destroying -= OnDestroying; } /// diff --git a/EXILED/Exiled.Events/Commands/Hub/Install.cs b/EXILED/Exiled.Events/Commands/Hub/Install.cs index bdcd7e18f..b1d7e7e10 100644 --- a/EXILED/Exiled.Events/Commands/Hub/Install.cs +++ b/EXILED/Exiled.Events/Commands/Hub/Install.cs @@ -89,9 +89,11 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s releaseToDownload = foundRelease; } - Log.Info($"Downloading release \"{releaseToDownload.TagName}\". Found {releaseToDownload.Assets.Length} asset(s) to download."); + ReleaseAsset[] releaseAssets = releaseToDownload.Assets.Where(x => x.Name.IndexOf("nwapi", StringComparison.OrdinalIgnoreCase) == -1).ToArray(); - foreach (ReleaseAsset asset in releaseToDownload.Assets) + Log.Info($"Downloading release \"{releaseToDownload.TagName}\". Found {releaseAssets.Length} asset(s) to download."); + + foreach (ReleaseAsset asset in releaseAssets) { Log.Info($"Downloading asset {asset.Name}. Asset size: {Math.Round(asset.Size / 1000f, 2)} KB."); using HttpResponseMessage assetResponse = client.GetAsync(asset.BrowserDownloadUrl).ConfigureAwait(false).GetAwaiter().GetResult(); diff --git a/EXILED/Exiled.Events/EventArgs/Map/PlacingPickupIntoPocketDimensionEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Map/PlacingPickupIntoPocketDimensionEventArgs.cs new file mode 100644 index 000000000..dfe70c3ed --- /dev/null +++ b/EXILED/Exiled.Events/EventArgs/Map/PlacingPickupIntoPocketDimensionEventArgs.cs @@ -0,0 +1,83 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.EventArgs.Map +{ + using Exiled.API.Features.Pickups; + using Exiled.Events.EventArgs.Interfaces; + using InventorySystem.Items.Pickups; + + using UnityEngine; + + using static PlayerRoles.PlayableScps.Scp106.Scp106PocketItemManager; + + /// + /// Contains information about items in the pocket dimension. + /// + public class PlacingPickupIntoPocketDimensionEventArgs : IDeniableEvent, IPickupEvent + { + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public PlacingPickupIntoPocketDimensionEventArgs(ItemPickupBase pickupBase, PocketItem pocketItem, bool isAllowed) + { + Pickup = Pickup.Get(pickupBase); + PocketItem = pocketItem; + IsAllowed = isAllowed; + } + + /// + public Pickup Pickup { get; } + + /// + /// Gets the value of the PocketItem. + /// + public PocketItem PocketItem { get; } + + /// + /// Gets or sets a value indicating when the Pickup will be dropped onto the map. + /// + public double DropTime + { + get => PocketItem.TriggerTime; + set => PocketItem.TriggerTime = value; + } + + /// + /// Gets or sets a value indicating whether the pickup should be removed from the Pocket Dimension. + /// + public bool ShouldRemove + { + get => PocketItem.Remove; + set => PocketItem.Remove = value; + } + + /// + /// Gets or sets a value indicating whether the warning sound for the pickup should be sent. + /// + public bool ShouldWarn + { + get => !PocketItem.WarningSent; + set => PocketItem.WarningSent = !value; + } + + /// + /// Gets or sets the location where the pickup will drop onto the map. + /// + public Vector3 Position + { + get => PocketItem.DropPosition.Position; + set => PocketItem.DropPosition = new(value); + } + + /// + public bool IsAllowed { get; set; } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Player/InteractingDoorEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Player/InteractingDoorEventArgs.cs index d56c6c682..d8ab13538 100644 --- a/EXILED/Exiled.Events/EventArgs/Player/InteractingDoorEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Player/InteractingDoorEventArgs.cs @@ -29,16 +29,25 @@ public class InteractingDoorEventArgs : IPlayerEvent, IDoorEvent, IDeniableEvent /// /// /// - public InteractingDoorEventArgs(Player player, DoorVariant door, bool isAllowed = true) + /// + /// + /// + public InteractingDoorEventArgs(Player player, DoorVariant door, bool isAllowed = true, bool canInteract = true) { Player = player; Door = Door.Get(door); IsAllowed = isAllowed; + CanInteract = canInteract; } /// /// Gets or sets a value indicating whether or not the player can interact with the door. /// + public bool CanInteract { get; set; } + + /// + /// Gets or sets a value indicating whether or not the player can access the door. + /// public bool IsAllowed { get; set; } /// diff --git a/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs b/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs index 45a587755..cd4d4922f 100644 --- a/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Scp330/InteractingScp330EventArgs.cs @@ -31,12 +31,23 @@ public class InteractingScp330EventArgs : IPlayerEvent, IScp330Event, IDeniableE public InteractingScp330EventArgs(Player player, int usage) { Player = player; - Scp330 = Scp330Bag.TryGetBag(player.ReferenceHub, out Scp330Bag scp330Bag) ? (Scp330)Item.Get(scp330Bag) : null; - Candy = Scp330Candies.GetRandom(); UsageCount = usage; ShouldSever = usage >= 2; ShouldPlaySound = true; IsAllowed = Player.IsHuman; + + if (Scp330Bag.TryGetBag(player.ReferenceHub, out Scp330Bag scp330Bag)) + { + Scp330 = (Scp330)Item.Get(scp330Bag); + } + else + { + Scp330 = (Scp330)Item.Create(ItemType.SCP330, player); + Scp330.RemoveAllCandy(); + player.AddItem(Scp330); + } + + Scp330.CandyToAdd = Scp330Candies.GetRandom(); } /// @@ -47,7 +58,11 @@ public InteractingScp330EventArgs(Player player, int usage) /// /// Gets or sets a value indicating the type of candy that will be received from this interaction. /// - public CandyKindID Candy { get; set; } + public CandyKindID Candy + { + get => Scp330.CandyToAdd; + set => Scp330.CandyToAdd = value; + } /// /// Gets or sets a value indicating whether the player's hands should get severed. @@ -71,11 +86,9 @@ public InteractingScp330EventArgs(Player player, int usage) public Player Player { get; } /// - /// This value can be null. public Scp330 Scp330 { get; } /// - /// This value can be null. public Item Item => Scp330; } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Server/RoundEndedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Server/RoundEndedEventArgs.cs index 6536962f8..c48103683 100644 --- a/EXILED/Exiled.Events/EventArgs/Server/RoundEndedEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Server/RoundEndedEventArgs.cs @@ -49,5 +49,10 @@ public RoundEndedEventArgs(LeadingTeam leadingTeam, RoundSummary.SumInfo_ClassLi /// Gets or sets the time to restart the next round. /// public int TimeToRestart { get; set; } + + /// + /// Gets or sets a value indicating whether Round Summary will be sent to all players. + /// + public bool ShowRoundSummary { get; set; } = true; } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/EventArgs/Server/UnbannedEventArgs.cs b/EXILED/Exiled.Events/EventArgs/Server/UnbannedEventArgs.cs index a88a5f3a4..595d0551b 100644 --- a/EXILED/Exiled.Events/EventArgs/Server/UnbannedEventArgs.cs +++ b/EXILED/Exiled.Events/EventArgs/Server/UnbannedEventArgs.cs @@ -7,10 +7,12 @@ namespace Exiled.Events.EventArgs.Server { + using Exiled.Events.EventArgs.Interfaces; + /// /// Contains all information after a player gets unbanned. /// - public class UnbannedEventArgs + public class UnbannedEventArgs : IExiledEvent { /// /// Initializes a new instance of the class. diff --git a/EXILED/Exiled.Events/Handlers/Map.cs b/EXILED/Exiled.Events/Handlers/Map.cs index 561a2f9b8..41f1978ae 100644 --- a/EXILED/Exiled.Events/Handlers/Map.cs +++ b/EXILED/Exiled.Events/Handlers/Map.cs @@ -110,6 +110,11 @@ public static class Map /// public static Event Scp244Spawning { get; set; } = new(); + /// + /// Invoked before an item is placed in the pocket dimension. + /// + public static Event PlacingPickupIntoPocketDimension { get; set; } = new(); + /// /// Called before placing a decal. /// @@ -216,5 +221,11 @@ public static class Map /// /// The instance. public static void OnScp244Spawning(Scp244SpawningEventArgs ev) => Scp244Spawning.InvokeSafely(ev); + + /// + /// Called before an item is dropped in the pocket dimension. + /// + /// The instnace. + public static void OnPlacingPickupIntoPocketDimension(PlacingPickupIntoPocketDimensionEventArgs ev) => PlacingPickupIntoPocketDimension.InvokeSafely(ev); } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Map/PlacingPickupIntoPocketDimension.cs b/EXILED/Exiled.Events/Patches/Events/Map/PlacingPickupIntoPocketDimension.cs new file mode 100644 index 000000000..26afed44b --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Events/Map/PlacingPickupIntoPocketDimension.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Events.Map +{ + using System.Collections.Generic; + + using Exiled.Events.Attributes; + using Exiled.Events.EventArgs.Map; + using Handlers; + using HarmonyLib; + using InventorySystem.Items.Pickups; + using Mirror; + using PlayerRoles.PlayableScps.Scp106; + + using static PlayerRoles.PlayableScps.Scp106.Scp106PocketItemManager; + + /// + /// Patches + /// Adds the event. + /// + [EventPatch(typeof(Map), nameof(Map.PlacingPickupIntoPocketDimension))] + [HarmonyPatch(typeof(Scp106PocketItemManager), nameof(Scp106PocketItemManager.OnAdded))] + internal static class PlacingPickupIntoPocketDimension + { + private static void Postfix(ItemPickupBase ipb) + { + if (TrackedItems.TryGetValue(ipb, out PocketItem pocketItem)) + { + PlacingPickupIntoPocketDimensionEventArgs ev = new(ipb, pocketItem, true); + Map.OnPlacingPickupIntoPocketDimension(ev); + + if (!ev.IsAllowed) + { + TrackedItems.Remove(ipb); + } + } + } + } +} \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Player/ChangingRoleAndSpawned.cs b/EXILED/Exiled.Events/Patches/Events/Player/ChangingRoleAndSpawned.cs index 8de757847..c7c8d4176 100644 --- a/EXILED/Exiled.Events/Patches/Events/Player/ChangingRoleAndSpawned.cs +++ b/EXILED/Exiled.Events/Patches/Events/Player/ChangingRoleAndSpawned.cs @@ -153,10 +153,16 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable /// Patches the method to add the - /// event. + /// event. /// - [EventPatch(typeof(Scp330), nameof(Scp330.InteractingScp330))] + [EventPatch(typeof(Handlers.Scp330), nameof(Handlers.Scp330.InteractingScp330))] [HarmonyPatch(typeof(Scp330Interobject), nameof(Scp330Interobject.ServerInteract))] public static class InteractingScp330 { @@ -66,7 +67,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable.Pool.Return(newInstructions); } } + + /// + /// Replaces with . + /// + [EventPatch(typeof(Handlers.Scp330), nameof(Handlers.Scp330.InteractingScp330))] + [HarmonyPatch(typeof(Scp330Bag), nameof(Scp330Bag.TryAddSpecific))] + internal static class ReplaceCandy + { + private static void Prefix(Scp330Bag __instance, ref CandyKindID kind) + { + Scp330 scp330 = Item.Get(__instance); + + if (scp330.CandyToAdd != CandyKindID.None) + kind = scp330.CandyToAdd; + } + } } \ No newline at end of file diff --git a/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs b/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs index d672d1831..587af6055 100644 --- a/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs +++ b/EXILED/Exiled.Events/Patches/Events/Server/Reporting.cs @@ -84,7 +84,7 @@ private static IEnumerable Transpiler(IEnumerable instruction.StoresField(Field(typeof(CheaterReport), nameof(CheaterReport._lastReport)))) + offset; diff --git a/EXILED/Exiled.Events/Patches/Events/Server/RoundEnd.cs b/EXILED/Exiled.Events/Patches/Events/Server/RoundEnd.cs index fff92653a..53b8d446b 100644 --- a/EXILED/Exiled.Events/Patches/Events/Server/RoundEnd.cs +++ b/EXILED/Exiled.Events/Patches/Events/Server/RoundEnd.cs @@ -20,6 +20,7 @@ namespace Exiled.Events.Patches.Events.Server using PlayerRoles; using static HarmonyLib.AccessTools; + using static RoundSummary; /// /// Patches . @@ -131,6 +132,18 @@ private static IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Stfld && x.operand == (object)Field(typeof(SumInfo_ClassList), nameof(SumInfo_ClassList.warhead_kills))) + offset; + newInstructions.InsertRange(index, new CodeInstruction[] + { + new(OpCodes.Ldarg_0), + new(OpCodes.Ldfld, Field(PrivateType, NewList)), + new(OpCodes.Call, PropertySetter(typeof(Round), nameof(Round.LastClassList))), + }); + + Label skip = generator.DefineLabel(); + offset = 7; index = newInstructions.FindLastIndex(x => x.opcode == OpCodes.Ldstr && x.operand == (object)"auto_round_restart_time") + offset; @@ -153,6 +166,7 @@ private static IEnumerable Transpiler(IEnumerable Transpiler(IEnumerable x.opcode == OpCodes.Call && x.operand == (object)Method(typeof(RoundSummary), nameof(RoundSummary.RpcShowRoundSummary))) + offset; + newInstructions[index].labels.Add(skip); + for (int z = 0; z < newInstructions.Count; z++) yield return newInstructions[z]; diff --git a/EXILED/Exiled.Events/Patches/Fixes/JailbirdHitRegFix.cs b/EXILED/Exiled.Events/Patches/Fixes/JailbirdHitRegFix.cs new file mode 100644 index 000000000..fa848d09f --- /dev/null +++ b/EXILED/Exiled.Events/Patches/Fixes/JailbirdHitRegFix.cs @@ -0,0 +1,61 @@ +// ----------------------------------------------------------------------- +// +// Copyright (c) Exiled Team. All rights reserved. +// Licensed under the CC BY-SA 3.0 license. +// +// ----------------------------------------------------------------------- + +namespace Exiled.Events.Patches.Fixes +{ + using System.Collections.Generic; + using System.Reflection.Emit; + + using API.Features.Pools; + + using HarmonyLib; + using InventorySystem.Items.Jailbird; + using Mirror; + using Utils.Networking; + + using static HarmonyLib.AccessTools; + + /// + /// Patches . + /// + [HarmonyPatch(typeof(JailbirdHitreg), nameof(JailbirdHitreg.ServerAttack))] + internal static class JailbirdHitRegFix + { + private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) + { + List newInstructions = ListPool.Pool.Get(instructions); + + int index = newInstructions.FindIndex(i => i.Calls(Method(typeof(ReferenceHubReaderWriter), nameof(ReferenceHubReaderWriter.TryReadReferenceHub)))) - 2; + + int breakIndex = newInstructions.FindIndex(i => i.Calls(Method(typeof(JailbirdHitreg), nameof(JailbirdHitreg.DetectDestructibles)))) - 1; + Label breakLabel = generator.DefineLabel(); + newInstructions[breakIndex].WithLabels(breakLabel); + + List