diff --git a/ShootingInteractions/EventsHandler.cs b/ShootingInteractions/EventsHandler.cs index d3e3b8b..0603af7 100644 --- a/ShootingInteractions/EventsHandler.cs +++ b/ShootingInteractions/EventsHandler.cs @@ -1,333 +1,333 @@ -using Exiled.API.Features; -using Exiled.API.Features.Doors; -using Exiled.API.Features.Items; -using Exiled.API.Features.Pickups; -using Exiled.Events.EventArgs.Player; -using Interactables.Interobjects.DoorUtils; -using Interactables.Interobjects; -using InventorySystem.Items.ThrowableProjectiles; -using MapGeneration.Distributors; -using MEC; -using ShootingInteractions.Configs; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using DoorBeepType = Exiled.API.Enums.DoorBeepType; -using DoorLockType = Exiled.API.Enums.DoorLockType; -using BasicDoor = Exiled.API.Features.Doors.BasicDoor; -using CheckpointDoor = Exiled.API.Features.Doors.CheckpointDoor; -using ElevatorDoor = Interactables.Interobjects.ElevatorDoor; -using Scp2176Projectile = InventorySystem.Items.ThrowableProjectiles.Scp2176Projectile; -using Exiled.CustomItems.API.Features; -using InventorySystem.Items.Pickups; -using InventorySystem.Items; -using InventorySystem; -using Mirror; - -namespace ShootingInteractions -{ - internal sealed class EventsHandler - { - /// - /// The plugin's config - /// - private static Config Config => Plugin.Instance.Config; - - /// - /// A list of gameobjects that cannot be interacted with. - /// - public static List BlacklistedObjects = new(); - - public void OnShooting(ShootingEventArgs args) - { - if (Config.AccurateBullets) - return; - - // Check what's the player shooting at with a raycast, and return if the raycast doesn't hit something within 70 distance (maximum realistic distance) - if (!Physics.Raycast(args.Player.CameraTransform.position, args.Player.CameraTransform.forward, out RaycastHit raycastHit, 70f, ~(1 << 1 | 1 << 13 | 1 << 16 | 1 << 28))) - return; - - // Interact and check the interaction with the player that's shooting, and the GameObject associated to the raycast - if (Interact(args.Player, raycastHit.transform.gameObject)) - { - // Add the GameObject in the blacklist for a server tick - BlacklistedObjects.Add(raycastHit.transform.gameObject); - Timing.CallDelayed(0.02f, () => BlacklistedObjects.Remove(raycastHit.transform.gameObject)); - } - } - - /// - /// The shot event. Used for accurate shooting interaction. - /// - /// The . - public void OnShot(ShotEventArgs args) - { - if (!Config.AccurateBullets) - return; - - // Check what's the player shooting at with a raycast, and return if the raycast doesn't hit something within 70 distance (maximum realistic distance) - if (!Physics.Raycast(args.Player.CameraTransform.position, (args.RaycastHit.point - args.Player.CameraTransform.position).normalized, out RaycastHit raycastHit, 70f, ~(1 << 1 | 1 << 13 | 1 << 16 | 1 << 28))) - return; - - // Interact and check the interaction with the player that's shooting, and the GameObject associated to the raycast - if (Interact(args.Player, raycastHit.transform.gameObject)) - { - // Add the GameObject in the blacklist for a server tick - BlacklistedObjects.Add(raycastHit.transform.gameObject); - Timing.CallDelayed(0.02f, () => BlacklistedObjects.Remove(raycastHit.transform.gameObject)); - } - } - - /// - /// Interact with the game object. - /// - /// The that's doing the interaction - /// The - /// If the GameObject was an interactable object. - public static bool Interact(Player player, GameObject gameObject) - { - BlacklistedObjects ??= new(); - - // Return if the GameObject is in the blacklist - if (BlacklistedObjects.Contains(gameObject)) - { - Log.Debug("Player shot on a blacklisted GameObject: " + gameObject.name); - return false; - } - - // Doors - if (gameObject.GetComponentInParent() is RegularDoorButton button) - { - // Get the door associated to the button - Door door = Door.Get(button.GetComponentInParent()); - - // Return if there's no door associated to the button, the door is moving, if it's locked and the player doesn't have bypass mode, or if it's an open checkpoint - if (door is null || door.IsMoving || (door.IsLocked && !player.IsBypassModeEnabled) || (door.IsCheckpoint && door.IsOpen)) - return true; - - // Set the cooldown and door interaction variables according to the type of door - float cooldown = 0f; - DoorInteraction doorInteraction = Config.Doors; - - if (door is CheckpointDoor checkpoint) - { - cooldown = checkpoint.Base._openingTime + checkpoint.WaitTime + checkpoint.WarningTime; - doorInteraction = Config.Checkpoints; - } - else if (door is BasicDoor basicDoor) - { - // Return if the door is doing its animation - if (basicDoor.RemainingCooldown >= 0.1f) - return true; - - cooldown = basicDoor.Cooldown - 0.35f; - - if (door.IsGate) { - - // A gate takes less time to open than close - if (!door.IsOpen) - cooldown -= 0.35f; - - doorInteraction = Config.Gates; - } - } - - // Return if the interaction isn't enabled - if (!doorInteraction.IsEnabled) - return true; - - // Should the buttons break ? (Generate number from 1 to 100 then check if lesser than interaction percentage) - bool doorBreak = Random.Range(1, 101) <= doorInteraction.ButtonsBreakChance; - - // Lock the door if the door isn't locked and the buttons should break BEFORE moving, then unlock after the time indicated in the config, if it's greater than 0 - if (!door.IsLocked && doorBreak && !doorInteraction.MoveBeforeBreaking) - { - door.ChangeLock(DoorLockType.SpecialDoorFeature); - - if (doorInteraction.ButtonsBreakTime > 0) - Timing.CallDelayed(doorInteraction.ButtonsBreakTime, () => door.ChangeLock(DoorLockType.None)); - - // Don't interact with the door if the player doesn't have bypass mode enabled - if (!player.IsBypassModeEnabled) - return true; - } - - // Deny access if the door is a keycard door, the player doesn't have bypass mode enabled, and either remote keycard is disabled or the player doesn't have a valid keycard in their inventory - if (door.IsKeycardDoor && !player.IsBypassModeEnabled && (!doorInteraction.RemoteKeycard || !player.Items.Any(item => item is Keycard keycard && (keycard.Base.Permissions & door.RequiredPermissions.RequiredPermissions) != 0))) - { - door.PlaySound(DoorBeepType.PermissionDenied); - return true; - } - - // Open or close the door - door.IsOpen = !door.IsOpen; - - // Lock the door if the door isn't locked and the buttons should break AFTER moving, then unlock after the time indicated in the config, if it's greater than 0 - if (!door.IsLocked && doorBreak && doorInteraction.MoveBeforeBreaking) - Timing.CallDelayed(cooldown, () => - { - - door.ChangeLock(DoorLockType.SpecialDoorFeature); - - if (doorInteraction.ButtonsBreakTime > 0) - Timing.CallDelayed(doorInteraction.ButtonsBreakTime, () => door.ChangeLock(DoorLockType.None)); - }); - } - - // Lockers - else if (gameObject.GetComponentInParent() is Locker locker && gameObject.GetComponentInParent() is LockerChamber chamber) - { - // The right remote keycard interaction config according to the locker type - bool remoteKeycard; - - // Bulletproof locker - if (((gameObject.name == "Collider Keypad") || (gameObject.name == "Collider Door" && !Config.BulletproofLockers.OnlyKeypad)) && Config.BulletproofLockers.IsEnabled) - remoteKeycard = Config.BulletproofLockers.RemoteKeycard; - - // Weapon locker - else if (gameObject.name == "Door" && Config.WeaponLockers.IsEnabled) - remoteKeycard = Config.WeaponLockers.RemoteKeycard; - - // Else, the specific locker interaction isn't enabled, return - else - return true; - - // Return if the locker doesn't allow interaction - if (!chamber.CanInteract) - return true; - - // Deny access if the player doesn't have bypass mode enabled, and either remote keycard is disabled or the player doesn't have the right keycard in their inventory - if (!player.IsBypassModeEnabled && (!remoteKeycard || !player.Items.Any(item => item is Keycard keycard && keycard.Base.Permissions.HasFlag(chamber.RequiredPermissions)))) - { - locker.RpcPlayDenied((byte) locker.Chambers.ToList().IndexOf(chamber)); - return true; - } - - // Open the locker - chamber.SetDoor(!chamber.IsOpen, locker._grantedBeep); - locker.RefreshOpenedSyncvar(); - } - - // Elevators - else if (gameObject.GetComponentInParent() is ElevatorPanel panel && Config.Elevators.IsEnabled) - { - // Get the elevator associated to the button - Lift elevator = Lift.Get(panel.AssignedChamber); - - // Return if there's no elevator associated to the button, it's moving, it's locked and the player doesn't have bypass mode enabled, or it can't get its doors - if (elevator is null || elevator.IsMoving || !elevator.IsOperative || (elevator.IsLocked && !player.IsBypassModeEnabled) || !ElevatorDoor.AllElevatorDoors.TryGetValue(panel.AssignedChamber.AssignedGroup, out List list)) - return true; - - // Should the buttons break ? (Generate a number from 1 to 100 then check if it's lesser than config percentage) - bool elevatorBreak = Random.Range(1, 101) <= Config.Elevators.ButtonsBreakChance; - - // Lock the elevator if the door isn't locked and the buttons should break BEFORE moving, then unlock after the time indicated in the config if it's greater than 0 - if (!elevator.IsLocked && elevatorBreak && !Config.Elevators.MoveBeforeBreaking) - { - foreach (ElevatorDoor door in list) - { - door.ServerChangeLock(DoorLockReason.SpecialDoorFeature, true); - elevator.Base.RefreshLocks(elevator.Group, door); - } - - if (Config.Elevators.ButtonsBreakTime > 0) - Timing.CallDelayed(Config.Elevators.ButtonsBreakTime, () => elevator.ChangeLock(DoorLockReason.None)); - - // Don't interact with the elevator if the player doesn't have bypass mode enabled - if (!player.IsBypassModeEnabled) - return true; - } - - // Move the elevator to the next level - int nextLevel = panel.AssignedChamber.CurrentLevel + 1; - if (nextLevel >= list.Count) - nextLevel = 0; - - elevator.TryStart(nextLevel); - - // Lock the door if the door isn't locked and the buttons should break AFTER moving, then unlock after the time indicated in the config, if it's greater than 0 - if (!elevator.IsLocked && elevatorBreak && Config.Elevators.MoveBeforeBreaking) - { - foreach (ElevatorDoor door in list) - { - door.ServerChangeLock(DoorLockReason.SpecialDoorFeature, true); - elevator.Base.RefreshLocks(elevator.Group, door); - } - - if (Config.Elevators.ButtonsBreakTime > 0) - Timing.CallDelayed(Config.Elevators.ButtonsBreakTime + elevator.MoveTime, () => elevator.ChangeLock(DoorLockReason.None)); - } - } - - // Grenades - else if (gameObject.GetComponentInParent() is TimedGrenadePickup grenadePickup) - { - // Custom grenades - if (CustomItem.TryGet(Pickup.Get(grenadePickup), out CustomItem customItem)) - { - // Return if custom grenades aren't enabled - if (!Config.CustomGrenades.IsEnabled) - return true; - - // Set the attacker to the player shooting and explode the custom grenade - grenadePickup._attacker = player.Footprint; - grenadePickup._replaceNextFrame = true; - } - else - { - TimedProjectileInteraction grenadeInteraction = grenadePickup.Info.ItemId switch - { - ItemType.GrenadeHE => Config.FragGrenades, - ItemType.GrenadeFlash => Config.Flashbangs, - _ => new TimedProjectileInteraction() { IsEnabled = false } - }; - - // Return if the interaction isn't enabled, it can't get the grenade base, or it can't get the throwable - if (!grenadeInteraction.IsEnabled || !InventoryItemLoader.AvailableItems.TryGetValue(grenadePickup.Info.ItemId, out ItemBase grenadeBase) || (grenadeBase is not ThrowableItem grenadeThrowable)) - return true; - - // Instantiate the projectile - ThrownProjectile grenadeProjectile = Object.Instantiate(grenadeThrowable.Projectile); - - // Set the physics of the projectile - PickupStandardPhysics grenadeProjectilePhysics = grenadeProjectile.PhysicsModule as PickupStandardPhysics; - PickupStandardPhysics grenadePickupPhysics = grenadePickup.PhysicsModule as PickupStandardPhysics; - if (grenadeProjectilePhysics is not null && grenadePickupPhysics is not null) - { - Rigidbody grenadeProjectileRigidbody = grenadeProjectilePhysics.Rb; - Rigidbody grenadePickupRigidbody = grenadePickupPhysics.Rb; - grenadeProjectileRigidbody.position = grenadePickupRigidbody.position; - grenadeProjectileRigidbody.rotation = grenadePickupRigidbody.rotation; - grenadeProjectileRigidbody.velocity = grenadePickupRigidbody.velocity + (player.CameraTransform.forward * (grenadeInteraction.AdditionalVelocity ? grenadeInteraction.VelocityForce : 0)); - } - - // Lock the grenade pickup - grenadePickup.Info.Locked = true; - - // Set the network info and owner of the projectile - grenadeProjectile.NetworkInfo = grenadePickup.Info; - grenadePickup._attacker = player.Footprint; - grenadeProjectile.PreviousOwner = player.Footprint; - - // Spawn the grenade projectile - NetworkServer.Spawn(grenadeProjectile.gameObject); - - // Should the grenade have a malfunction ? (Generate number from 1 to 100 then check if lesser than interaction percentage) - // Set the fuse time of the grenade projectile to the interaction fuse time if it should malfunction - if (Random.Range(1, 101) <= grenadeInteraction.MalfunctionChance) - (grenadeProjectile as TimeGrenade)._fuseTime = grenadeInteraction.MalfunctionFuseTime; - - // Activate the projectile and destroy the pickup - grenadeProjectile.ServerActivate(); - grenadePickup.DestroySelf(); - } - } - - // SCP-2176 - else if (gameObject.GetComponentInParent() is Scp2176Projectile projectile && Config.Scp2176.IsEnabled) - projectile.ServerImmediatelyShatter(); - - return false; - } - } -} +using Exiled.API.Features; +using Exiled.API.Features.Doors; +using Exiled.API.Features.Items; +using Exiled.API.Features.Pickups; +using Exiled.Events.EventArgs.Player; +using Interactables.Interobjects.DoorUtils; +using Interactables.Interobjects; +using InventorySystem.Items.ThrowableProjectiles; +using MapGeneration.Distributors; +using MEC; +using ShootingInteractions.Configs; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using DoorBeepType = Exiled.API.Enums.DoorBeepType; +using DoorLockType = Exiled.API.Enums.DoorLockType; +using BasicDoor = Exiled.API.Features.Doors.BasicDoor; +using CheckpointDoor = Exiled.API.Features.Doors.CheckpointDoor; +using ElevatorDoor = Interactables.Interobjects.ElevatorDoor; +using Scp2176Projectile = InventorySystem.Items.ThrowableProjectiles.Scp2176Projectile; +using InventorySystem.Items.Pickups; +using InventorySystem.Items; +using InventorySystem; +using Mirror; +using Exiled.CustomModules.API.Features.CustomItems; + +namespace ShootingInteractions +{ + internal sealed class EventsHandler + { + /// + /// The plugin's config + /// + private static Config Config => Plugin.Instance.Config; + + /// + /// A list of gameobjects that cannot be interacted with. + /// + public static List BlacklistedObjects = new(); + + public void OnShooting(ShootingEventArgs args) + { + if (Config.AccurateBullets) + return; + + // Check what's the player shooting at with a raycast, and return if the raycast doesn't hit something within 70 distance (maximum realistic distance) + if (!Physics.Raycast(args.Player.CameraTransform.position, args.Player.CameraTransform.forward, out RaycastHit raycastHit, 70f, ~(1 << 1 | 1 << 13 | 1 << 16 | 1 << 28))) + return; + + // Interact and check the interaction with the player that's shooting, and the GameObject associated to the raycast + if (Interact(args.Player, raycastHit.transform.gameObject)) + { + // Add the GameObject in the blacklist for a server tick + BlacklistedObjects.Add(raycastHit.transform.gameObject); + Timing.CallDelayed(0.02f, () => BlacklistedObjects.Remove(raycastHit.transform.gameObject)); + } + } + + /// + /// The shot event. Used for accurate shooting interaction. + /// + /// The . + public void OnShot(ShotEventArgs args) + { + if (!Config.AccurateBullets) + return; + + // Check what's the player shooting at with a raycast, and return if the raycast doesn't hit something within 70 distance (maximum realistic distance) + if (!Physics.Raycast(args.Player.CameraTransform.position, (args.RaycastHit.point - args.Player.CameraTransform.position).normalized, out RaycastHit raycastHit, 70f, ~(1 << 1 | 1 << 13 | 1 << 16 | 1 << 28))) + return; + + // Interact and check the interaction with the player that's shooting, and the GameObject associated to the raycast + if (Interact(args.Player, raycastHit.transform.gameObject)) + { + // Add the GameObject in the blacklist for a server tick + BlacklistedObjects.Add(raycastHit.transform.gameObject); + Timing.CallDelayed(0.02f, () => BlacklistedObjects.Remove(raycastHit.transform.gameObject)); + } + } + + /// + /// Interact with the game object. + /// + /// The that's doing the interaction + /// The + /// If the GameObject was an interactable object. + public static bool Interact(Player player, GameObject gameObject) + { + BlacklistedObjects ??= new(); + + // Return if the GameObject is in the blacklist + if (BlacklistedObjects.Contains(gameObject)) + { + Log.Debug("Player shot on a blacklisted GameObject: " + gameObject.name); + return false; + } + + // Doors + if (gameObject.GetComponentInParent() is RegularDoorButton button) + { + // Get the door associated to the button + Door door = Door.Get(button.GetComponentInParent()); + + // Return if there's no door associated to the button, the door is moving, if it's locked and the player doesn't have bypass mode, or if it's an open checkpoint + if (door is null || door.IsMoving || (door.IsLocked && !player.IsBypassModeEnabled) || (door.IsCheckpoint && door.IsOpen)) + return true; + + // Set the cooldown and door interaction variables according to the type of door + float cooldown = 0f; + DoorInteraction doorInteraction = Config.Doors; + + if (door is CheckpointDoor checkpoint) + { + cooldown = checkpoint.Base._openingTime + checkpoint.WaitTime + checkpoint.WarningTime; + doorInteraction = Config.Checkpoints; + } + else if (door is BasicDoor basicDoor) + { + // Return if the door is doing its animation + if (basicDoor.RemainingCooldown >= 0.1f) + return true; + + cooldown = basicDoor.Cooldown - 0.35f; + + if (door.IsGate) { + + // A gate takes less time to open than close + if (!door.IsOpen) + cooldown -= 0.35f; + + doorInteraction = Config.Gates; + } + } + + // Return if the interaction isn't enabled + if (!doorInteraction.IsEnabled) + return true; + + // Should the buttons break ? (Generate number from 1 to 100 then check if lesser than interaction percentage) + bool doorBreak = Random.Range(1, 101) <= doorInteraction.ButtonsBreakChance; + + // Lock the door if the door isn't locked and the buttons should break BEFORE moving, then unlock after the time indicated in the config, if it's greater than 0 + if (!door.IsLocked && doorBreak && !doorInteraction.MoveBeforeBreaking) + { + door.ChangeLock(DoorLockType.SpecialDoorFeature); + + if (doorInteraction.ButtonsBreakTime > 0) + Timing.CallDelayed(doorInteraction.ButtonsBreakTime, () => door.ChangeLock(DoorLockType.None)); + + // Don't interact with the door if the player doesn't have bypass mode enabled + if (!player.IsBypassModeEnabled) + return true; + } + + // Deny access if the door is a keycard door, the player doesn't have bypass mode enabled, and either remote keycard is disabled or the player doesn't have a valid keycard in their inventory + if (door.IsKeycardDoor && !player.IsBypassModeEnabled && (!doorInteraction.RemoteKeycard || !player.Items.Any(item => item is Keycard keycard && (keycard.Base.Permissions & door.RequiredPermissions.RequiredPermissions) != 0))) + { + door.PlaySound(DoorBeepType.PermissionDenied); + return true; + } + + // Open or close the door + door.IsOpen = !door.IsOpen; + + // Lock the door if the door isn't locked and the buttons should break AFTER moving, then unlock after the time indicated in the config, if it's greater than 0 + if (!door.IsLocked && doorBreak && doorInteraction.MoveBeforeBreaking) + Timing.CallDelayed(cooldown, () => + { + + door.ChangeLock(DoorLockType.SpecialDoorFeature); + + if (doorInteraction.ButtonsBreakTime > 0) + Timing.CallDelayed(doorInteraction.ButtonsBreakTime, () => door.ChangeLock(DoorLockType.None)); + }); + } + + // Lockers + else if (gameObject.GetComponentInParent() is Locker locker && gameObject.GetComponentInParent() is LockerChamber chamber) + { + // The right remote keycard interaction config according to the locker type + bool remoteKeycard; + + // Bulletproof locker + if (((gameObject.name == "Collider Keypad") || (gameObject.name == "Collider Door" && !Config.BulletproofLockers.OnlyKeypad)) && Config.BulletproofLockers.IsEnabled) + remoteKeycard = Config.BulletproofLockers.RemoteKeycard; + + // Weapon locker + else if (gameObject.name == "Door" && Config.WeaponLockers.IsEnabled) + remoteKeycard = Config.WeaponLockers.RemoteKeycard; + + // Else, the specific locker interaction isn't enabled, return + else + return true; + + // Return if the locker doesn't allow interaction + if (!chamber.CanInteract) + return true; + + // Deny access if the player doesn't have bypass mode enabled, and either remote keycard is disabled or the player doesn't have the right keycard in their inventory + if (!player.IsBypassModeEnabled && (!remoteKeycard || !player.Items.Any(item => item is Keycard keycard && keycard.Base.Permissions.HasFlag(chamber.RequiredPermissions)))) + { + locker.RpcPlayDenied((byte) locker.Chambers.ToList().IndexOf(chamber)); + return true; + } + + // Open the locker + chamber.SetDoor(!chamber.IsOpen, locker._grantedBeep); + locker.RefreshOpenedSyncvar(); + } + + // Elevators + else if (gameObject.GetComponentInParent() is ElevatorPanel panel && Config.Elevators.IsEnabled) + { + // Get the elevator associated to the button + Lift elevator = Lift.Get(panel.AssignedChamber); + + // Return if there's no elevator associated to the button, it's moving, it's locked and the player doesn't have bypass mode enabled, or it can't get its doors + if (elevator is null || elevator.IsMoving || !elevator.IsOperative || (elevator.IsLocked && !player.IsBypassModeEnabled) || !ElevatorDoor.AllElevatorDoors.TryGetValue(panel.AssignedChamber.AssignedGroup, out List list)) + return true; + + // Should the buttons break ? (Generate a number from 1 to 100 then check if it's lesser than config percentage) + bool elevatorBreak = Random.Range(1, 101) <= Config.Elevators.ButtonsBreakChance; + + // Lock the elevator if the door isn't locked and the buttons should break BEFORE moving, then unlock after the time indicated in the config if it's greater than 0 + if (!elevator.IsLocked && elevatorBreak && !Config.Elevators.MoveBeforeBreaking) + { + foreach (ElevatorDoor door in list) + { + door.ServerChangeLock(DoorLockReason.SpecialDoorFeature, true); + elevator.Base.RefreshLocks(elevator.Group, door); + } + + if (Config.Elevators.ButtonsBreakTime > 0) + Timing.CallDelayed(Config.Elevators.ButtonsBreakTime, () => elevator.ChangeLock(DoorLockType.SpecialDoorFeature)); + + // Don't interact with the elevator if the player doesn't have bypass mode enabled + if (!player.IsBypassModeEnabled) + return true; + } + + // Move the elevator to the next level + int nextLevel = panel.AssignedChamber.CurrentLevel + 1; + if (nextLevel >= list.Count) + nextLevel = 0; + + elevator.TryStart(nextLevel); + + // Lock the door if the door isn't locked and the buttons should break AFTER moving, then unlock after the time indicated in the config, if it's greater than 0 + if (!elevator.IsLocked && elevatorBreak && Config.Elevators.MoveBeforeBreaking) + { + foreach (ElevatorDoor door in list) + { + door.ServerChangeLock(DoorLockReason.SpecialDoorFeature, true); + elevator.Base.RefreshLocks(elevator.Group, door); + } + + if (Config.Elevators.ButtonsBreakTime > 0) + Timing.CallDelayed(Config.Elevators.ButtonsBreakTime + elevator.MoveTime, () => elevator.ChangeLock(DoorLockType.SpecialDoorFeature)); + } + } + + // Grenades + else if (gameObject.GetComponentInParent() is TimedGrenadePickup grenadePickup) + { + // Custom grenades + if (CustomItem.TryGet(Pickup.Get(grenadePickup), out CustomItem customItem)) + { + // Return if custom grenades aren't enabled + if (!Config.CustomGrenades.IsEnabled) + return true; + + // Set the attacker to the player shooting and explode the custom grenade + grenadePickup._attacker = player.Footprint; + grenadePickup._replaceNextFrame = true; + } + else + { + TimedProjectileInteraction grenadeInteraction = grenadePickup.Info.ItemId switch + { + ItemType.GrenadeHE => Config.FragGrenades, + ItemType.GrenadeFlash => Config.Flashbangs, + _ => new TimedProjectileInteraction() { IsEnabled = false } + }; + + // Return if the interaction isn't enabled, it can't get the grenade base, or it can't get the throwable + if (!grenadeInteraction.IsEnabled || !InventoryItemLoader.AvailableItems.TryGetValue(grenadePickup.Info.ItemId, out ItemBase grenadeBase) || (grenadeBase is not ThrowableItem grenadeThrowable)) + return true; + + // Instantiate the projectile + ThrownProjectile grenadeProjectile = Object.Instantiate(grenadeThrowable.Projectile); + + // Set the physics of the projectile + PickupStandardPhysics grenadeProjectilePhysics = grenadeProjectile.PhysicsModule as PickupStandardPhysics; + PickupStandardPhysics grenadePickupPhysics = grenadePickup.PhysicsModule as PickupStandardPhysics; + if (grenadeProjectilePhysics is not null && grenadePickupPhysics is not null) + { + Rigidbody grenadeProjectileRigidbody = grenadeProjectilePhysics.Rb; + Rigidbody grenadePickupRigidbody = grenadePickupPhysics.Rb; + grenadeProjectileRigidbody.position = grenadePickupRigidbody.position; + grenadeProjectileRigidbody.rotation = grenadePickupRigidbody.rotation; + grenadeProjectileRigidbody.velocity = grenadePickupRigidbody.velocity + (player.CameraTransform.forward * (grenadeInteraction.AdditionalVelocity ? grenadeInteraction.VelocityForce : 0)); + } + + // Lock the grenade pickup + grenadePickup.Info.Locked = true; + + // Set the network info and owner of the projectile + grenadeProjectile.NetworkInfo = grenadePickup.Info; + grenadePickup._attacker = player.Footprint; + grenadeProjectile.PreviousOwner = player.Footprint; + + // Spawn the grenade projectile + NetworkServer.Spawn(grenadeProjectile.gameObject); + + // Should the grenade have a malfunction ? (Generate number from 1 to 100 then check if lesser than interaction percentage) + // Set the fuse time of the grenade projectile to the interaction fuse time if it should malfunction + if (Random.Range(1, 101) <= grenadeInteraction.MalfunctionChance) + (grenadeProjectile as TimeGrenade)._fuseTime = grenadeInteraction.MalfunctionFuseTime; + + // Activate the projectile and destroy the pickup + grenadeProjectile.ServerActivate(); + grenadePickup.DestroySelf(); + } + } + + // SCP-2176 + else if (gameObject.GetComponentInParent() is Scp2176Projectile projectile && Config.Scp2176.IsEnabled) + projectile.ServerImmediatelyShatter(); + + return false; + } + } +} diff --git a/ShootingInteractions/Plugin.cs b/ShootingInteractions/Plugin.cs index a88a752..4950ecf 100644 --- a/ShootingInteractions/Plugin.cs +++ b/ShootingInteractions/Plugin.cs @@ -1,48 +1,48 @@ -using Exiled.API.Features; -using ShootingInteractions.Configs; -using System; -using PlayerEvent = Exiled.Events.Handlers.Player; - -namespace ShootingInteractions { - public class Plugin : Plugin { - private static readonly Plugin Singleton = new(); - - public override string Name => "ShootingInteractions"; - - public override string Author => "Ika"; - - public override Version RequiredExiledVersion => new(8, 9, 4); - - public override Version Version => new(2, 3, 4); - - private EventsHandler eventsHandler; - - private Plugin() { } - - public static Plugin Instance => Singleton; - - public override void OnEnabled() { - RegisterEvents(); - base.OnEnabled(); - } - - public override void OnDisabled() { - UnregisterEvents(); - base.OnDisabled(); - } - - public void RegisterEvents() { - eventsHandler = new EventsHandler(); - - PlayerEvent.Shooting += eventsHandler.OnShooting; - PlayerEvent.Shot += eventsHandler.OnShot; - } - - public void UnregisterEvents() { - PlayerEvent.Shooting -= eventsHandler.OnShooting; - PlayerEvent.Shot -= eventsHandler.OnShot; - - eventsHandler = null; - } - } -} +using Exiled.API.Features; +using ShootingInteractions.Configs; +using System; +using PlayerEvent = Exiled.Events.Handlers.Player; + +namespace ShootingInteractions { + public class Plugin : Plugin { + private static readonly Plugin Singleton = new(); + + public override string Name => "ShootingInteractions"; + + public override string Author => "Ika"; + + public override Version RequiredExiledVersion => new(9, 0, 0); + + public override Version Version => new(2, 3, 6); + + private EventsHandler eventsHandler; + + private Plugin() { } + + public static Plugin Instance => Singleton; + + public override void OnEnabled() { + RegisterEvents(); + base.OnEnabled(); + } + + public override void OnDisabled() { + UnregisterEvents(); + base.OnDisabled(); + } + + public void RegisterEvents() { + eventsHandler = new EventsHandler(); + + PlayerEvent.Shooting += eventsHandler.OnShooting; + PlayerEvent.Shot += eventsHandler.OnShot; + } + + public void UnregisterEvents() { + PlayerEvent.Shooting -= eventsHandler.OnShooting; + PlayerEvent.Shot -= eventsHandler.OnShot; + + eventsHandler = null; + } + } +} diff --git a/ShootingInteractions/ShootingInteractions.csproj b/ShootingInteractions/ShootingInteractions.csproj index d51ab31..9e6e97a 100644 --- a/ShootingInteractions/ShootingInteractions.csproj +++ b/ShootingInteractions/ShootingInteractions.csproj @@ -1,85 +1,85 @@ - - - - - Debug - AnyCPU - {CAB84CFC-2008-4CDB-84AB-2446E1E5CF81} - Library - Properties - ShootingInteractions - ShootingInteractions - 9.0 - v4.8 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - - - - False - ..\..\..\..\Packages\Exiled-8.9.4\Assembly-CSharp-firstpass.dll - - - False - ..\..\..\..\Packages\Exiled-8.9.4\Mirror.dll - - - - - - - - - - - False - ..\..\..\..\Packages\Exiled-8.9.4\UnityEngine.AudioModule.dll - - - False - ..\..\..\..\Packages\Exiled-8.9.4\UnityEngine.CoreModule.dll - - - False - ..\..\..\..\Packages\Exiled-8.9.4\UnityEngine.PhysicsModule.dll - - - - - - - - - - - - - - - - - 8.9.4 - - - + + + + + Debug + AnyCPU + {CAB84CFC-2008-4CDB-84AB-2446E1E5CF81} + Library + Properties + ShootingInteractions + ShootingInteractions + 9.0 + v4.8 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + + + + False + ..\..\..\..\..\Packages\SCP SL 13.5\Assembly-CSharp-firstpass.dll + + + False + ..\..\..\..\..\Packages\SCP SL 13.5\Mirror.dll + + + + + + + + + + + False + ..\..\..\..\..\Packages\SCP SL 13.5\UnityEngine.AudioModule.dll + + + False + ..\..\..\..\..\Packages\SCP SL 13.5\UnityEngine.CoreModule.dll + + + False + ..\..\..\..\..\Packages\SCP SL 13.5\UnityEngine.PhysicsModule.dll + + + + + + + + + + + + + + + + + 9.0.0-alpha.7 + + + \ No newline at end of file