├── .gitattributes ├── Types ├── DrinkSound.cs ├── Config │ ├── DrinkEffect.cs │ ├── SpawningConfig.cs │ ├── SpawnTransform.cs │ ├── CustomDrink.cs │ └── DrinkCallbacks.cs └── DrinkInfo.cs ├── Handlers ├── serverHandler.cs └── playerHandler.cs ├── Utils └── RoomHandler.cs ├── Properties └── AssemblyInfo.cs ├── Classes ├── DrinkManager.cs ├── SoundHandler.cs ├── SCP294Object.cs └── OpusComponent.cs ├── SCP294.cs ├── README.md ├── Patches └── OnEffectsApplied.cs ├── Config.cs ├── SCP294.csproj └── Commands ├── SCP294AdminCommand.cs └── SCP294Command.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Types/DrinkSound.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SCP294.Types 8 | { 9 | public enum DrinkSound 10 | { 11 | Normal = 0, 12 | Unstable = 1 13 | } 14 | public class DrinkSoundFiles { 15 | public List List = new List() { 16 | "294dispense.ogg", 17 | "294explode.ogg" 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Types/Config/DrinkEffect.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SCP294.Types.Config 9 | { 10 | public sealed class DrinkEffect 11 | { 12 | public EffectType EffectType { get; set; } 13 | 14 | public bool ShouldAddIfPresent { get; set; } 15 | 16 | public float Time { get; set; } 17 | 18 | public byte EffectAmount { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Handlers/serverHandler.cs: -------------------------------------------------------------------------------- 1 | using MapEditorReborn.API.Features.Objects; 2 | using SCP294.Classes; 3 | using SCP294.Types; 4 | using System.Collections.Generic; 5 | using VoiceChat.Codec; 6 | 7 | namespace SCP294.handlers 8 | { 9 | public class serverHandler 10 | { 11 | public void WaitingForPlayers() { 12 | SCP294.Instance.SpawnedSCP294s = new Dictionary(); 13 | SCP294.Instance.PlayersNear294 = new List(); 14 | SCP294.Instance.CustomDrinkItems = new Dictionary(); 15 | SCP294.Instance.PlayerVoicePitch = new Dictionary(); 16 | SCP294.Instance.Encoders = new Dictionary(); 17 | SCP294Object.SpawnSCP294(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Types/Config/SpawningConfig.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using UnityEngine; 8 | 9 | namespace SCP294.Types 10 | { 11 | public class SpawningConfig 12 | { 13 | /// 14 | /// Amount of SCP-294s that will spawn in the given round 15 | /// 16 | public int SpawnAmount { get; set; } = 1; 17 | /// 18 | /// Dictionary of rooms SCP-294 can spawn in, Key is Exiled RoomType, Value is a List of possible Transforms, Relative to the Room's Rotation 19 | /// 20 | public Dictionary> SpawnRooms { get; set; } = new Dictionary>(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Types/Config/SpawnTransform.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using UnityEngine; 8 | 9 | namespace SCP294.Types 10 | { 11 | public class SpawnTransform 12 | { 13 | /// 14 | /// The position for SCP-294 to spawn at, relative to room's transform 15 | /// 16 | public Vector3 Position { get; set; } = new Vector3(0, 0, 0); 17 | /// 18 | /// The rotation for SCP-294 to spawn at, relative to room's transform 19 | /// 20 | public Vector3 Rotation { get; set; } = new Vector3(0, 0, 0); 21 | /// 22 | /// The scale for SCP-294's schematic, default Vector3.one 23 | /// 24 | public Vector3 Scale { get; set; } = Vector3.one; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Utils/RoomHandler.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Enums; 2 | using Exiled.API.Features; 3 | using Mirror; 4 | using Mono.Cecil; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using UnityEngine; 11 | 12 | namespace SCP294.Utils 13 | { 14 | public class RoomHandler 15 | { 16 | public static Room GetRandomRoom(RoomType _roomType) 17 | { 18 | IEnumerable _roomList = Room.Get((Room room) => room.Type == _roomType); 19 | return _roomList.ElementAtOrDefault(UnityEngine.Random.Range(0, _roomList.Count())); 20 | } 21 | public static Room GetRandomRoom(List _roomType) 22 | { 23 | IEnumerable _roomList = Room.Get((Room room) => _roomType.Contains(room.Type)); 24 | return _roomList.ElementAtOrDefault(UnityEngine.Random.Range(0, _roomList.Count())); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Ultimate294")] 9 | [assembly: AssemblyDescription("Adds in SCP-294, a vending machine capable of dispensing custom drinks. Contains every Containment Breach Drink")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("creepycats")] 12 | [assembly: AssemblyProduct("Ultimate294")] 13 | [assembly: AssemblyCopyright("Copyright © 2023")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("69c9f4e8-2660-4df9-98a1-c7fb1efab522")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.1.1.0")] 36 | [assembly: AssemblyFileVersion("1.1.1.0")] 37 | -------------------------------------------------------------------------------- /Types/DrinkInfo.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Features; 2 | using Exiled.API.Features.Items; 3 | using InventorySystem.Items; 4 | using SCP294.Types.Config; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using UnityEngine; 11 | 12 | namespace SCP294.Types 13 | { 14 | public class DrinkInfo 15 | { 16 | /// 17 | /// The Item Serial ID this Info is Attached To 18 | /// 19 | public ushort ItemSerial { get; set; } = 0; 20 | /// 21 | /// The Reference to the Item Object 22 | /// 23 | public Item ItemObject { get; set; } = null; 24 | /// 25 | /// The Drink's Name which was put into 294 26 | /// 27 | public string DrinkName { get; set; } = ""; 28 | /// 29 | /// The message to display when the player drinks the drink 30 | /// 31 | public string DrinkMessage { get; set; } = ""; 32 | /// 33 | /// Kill the player when ingested? 34 | /// 35 | public bool KillPlayer { get; set; } = false; 36 | /// 37 | /// Death Reason for Kill on ingestion 38 | /// 39 | public string KillPlayerString { get; set; } = ""; 40 | /// 41 | /// How much HP the player drinking gets healed 42 | /// 43 | public float HealAmount { get; set; } = 0; 44 | /// 45 | /// Should the Drink act like SCP-500 46 | /// 47 | public bool HealStatusEffects { get; set; } = false; 48 | /// 49 | /// Should using the Drink force a tantrum puddle? 50 | /// 51 | public bool Tantrum { get; set; } = false; 52 | /// 53 | /// List of effects to apply when Drinking the Custom Drink 54 | /// 55 | public List DrinkEffects { get; set; } = new List() 56 | { 57 | 58 | }; 59 | public Action DrinkCallback = null; 60 | 61 | /// 62 | /// Check if a Drink is a Custom Drink from SCP-294 63 | /// 64 | /// 65 | /// 66 | public static bool IsCustomDrink(ItemBase itembase) { 67 | return SCP294.Instance.CustomDrinkItems.Keys.Contains(itembase.ItemSerial); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Types/Config/CustomDrink.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Features; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SCP294.Types.Config 9 | { 10 | public class CustomDrink 11 | { 12 | /// 13 | /// Accepted list of Drink Names 14 | /// 15 | public List DrinkNames { get; set; } = new List(){ 16 | "" 17 | }; 18 | /// 19 | /// Should the Drink use the AntiCola model instead of the normal Cola Model 20 | /// 21 | public bool AntiColaModel { get; set; } = false; 22 | /// 23 | /// Kill the player when ingested? 24 | /// 25 | public bool KillPlayer { get; set; } = false; 26 | /// 27 | /// Death Reason for Kill on ingestion 28 | /// 29 | public string KillPlayerString { get; set; } = ""; 30 | /// 31 | /// How much HP the player drinking gets healed 32 | /// 33 | public float HealAmount { get; set; } = 0; 34 | /// 35 | /// Should the Drink act like SCP-500 36 | /// 37 | public bool HealStatusEffects { get; set; } = false; 38 | /// 39 | /// Should using the Drink force a tantrum puddle? 40 | /// 41 | public bool Tantrum { get; set; } = false; 42 | /// 43 | /// Should the Drink Explode when Purchased 44 | /// 45 | public bool Explode { get; set; } = false; 46 | /// 47 | /// Should the Drink Explode when Backfired 48 | /// 49 | public bool ExplodeOnBackfire { get; set; } = false; 50 | /// 51 | /// Float representing the chance out of 1.0f that the drink will instead yield the player a cup of themselves 52 | /// 53 | public float BackfireChance { get; set; } = 0f; 54 | /// 55 | /// The Message that will appear when you Drink the Custom Drink 56 | /// 57 | public string DrinkMessage { get; set; } = "You drank the placeholder drink. Very cool"; 58 | /// 59 | /// List of effects to apply when Drinking the Custom Drink 60 | /// 61 | public List DrinkEffects { get; set; } = new List() 62 | { 63 | 64 | }; 65 | public Action DrinkCallback { get; set; } = null; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Classes/DrinkManager.cs: -------------------------------------------------------------------------------- 1 | using SCP294.Types.Config; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using MEC; 8 | using MapEditorReborn.API.Features.Objects; 9 | using Exiled.API.Features; 10 | 11 | namespace SCP294.Classes 12 | { 13 | public class DrinkManager 14 | { 15 | public List LoadedDrinks = new List() { }; 16 | 17 | public void UnloadAllDrinks() 18 | { 19 | LoadedDrinks = new List() { }; 20 | } 21 | 22 | public void LoadBaseDrinks() 23 | { 24 | LoadedDrinks = LoadedDrinks.Concat(DrinkList.DefaultDrinks).ToList(); 25 | if (SCP294.Instance.Config.EnableCommunityDrinks) LoadedDrinks = LoadedDrinks.Concat(DrinkList.CommunityDrinks).ToList(); 26 | } 27 | 28 | public void UnloadBaseDrinks() 29 | { 30 | LoadedDrinks = LoadedDrinks.Except(DrinkList.DefaultDrinks).ToList(); 31 | if (SCP294.Instance.Config.EnableCommunityDrinks) LoadedDrinks = LoadedDrinks.Except(DrinkList.CommunityDrinks).ToList(); 32 | } 33 | 34 | public static bool IsDrinkManagerLoaded() { return SCP294.Instance.DrinkManager.LoadedDrinks != null; } 35 | 36 | public static void RegisterDrink(CustomDrink newDrink) 37 | { 38 | RegisterDrink(new CustomDrink[] { newDrink }); 39 | } 40 | public static void RegisterDrink(CustomDrink[] newDrink) 41 | { 42 | Timing.RunCoroutine(AwaitAddDrink(newDrink)); 43 | } 44 | 45 | public static IEnumerator AwaitAddDrink(CustomDrink[] newDrink) 46 | { 47 | yield return Timing.WaitUntilTrue(IsDrinkManagerLoaded); 48 | SCP294.Instance.DrinkManager.LoadedDrinks = SCP294.Instance.DrinkManager.LoadedDrinks.Concat(newDrink.ToList()).ToList(); 49 | } 50 | 51 | public static void UnloadDrink(CustomDrink newDrink) 52 | { 53 | UnloadDrink(new CustomDrink[] { newDrink }); 54 | } 55 | public static void UnloadDrink(CustomDrink[] newDrink) 56 | { 57 | Timing.RunCoroutine(AwaitRemoveDrink(newDrink)); 58 | } 59 | 60 | public static IEnumerator AwaitRemoveDrink(CustomDrink[] newDrink) 61 | { 62 | while (SCP294.Instance.DrinkManager == null) 63 | { 64 | yield return Timing.WaitForSeconds(1f); 65 | } 66 | 67 | SCP294.Instance.DrinkManager.LoadedDrinks = SCP294.Instance.DrinkManager.LoadedDrinks.Except(newDrink.ToList()).ToList(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SCP294.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Features; 2 | using Player = Exiled.Events.Handlers.Player; 3 | using Server = Exiled.Events.Handlers.Server; 4 | using System; 5 | using System.Collections.Generic; 6 | using MEC; 7 | using MapEditorReborn.API.Features.Objects; 8 | using SCP294.Classes; 9 | using SCP294.Types; 10 | using HarmonyLib; 11 | using Exiled.API.Enums; 12 | using VoiceChat.Codec; 13 | 14 | namespace SCP294 15 | { 16 | public class SCP294 : Plugin 17 | { 18 | public override string Name => "Ultimate294"; 19 | public override string Author => "creepycats"; 20 | public override Version Version => new Version(1, 1, 1); 21 | 22 | public override PluginPriority Priority => PluginPriority.Highest; 23 | 24 | public static SCP294 Instance { get; set; } 25 | 26 | public Dictionary SpawnedSCP294s { get; set; } = new Dictionary(); 27 | public Dictionary SCP294UsesLeft { get; set; } = new Dictionary(); 28 | public Dictionary SCP294LightSources { get; set; } = new Dictionary(); 29 | public List PlayersNear294 { get; set; } = new List(); 30 | public Dictionary CustomDrinkItems = new Dictionary(); 31 | public DrinkManager DrinkManager = new DrinkManager(); 32 | public Dictionary PlayerVoicePitch = new Dictionary(); 33 | 34 | private Harmony _harmony; 35 | 36 | private CoroutineHandle hintCoroutine; 37 | 38 | public Dictionary Encoders = new Dictionary(); 39 | 40 | public override void OnEnabled() 41 | { 42 | Instance = this; 43 | Log.Info($"{Name} v{Version} - made by creepycats"); 44 | if (Config.Debug) 45 | Log.Info("Registering events..."); 46 | RegisterEvents(); 47 | 48 | DrinkManager.LoadBaseDrinks(); 49 | 50 | hintCoroutine = Timing.RunCoroutine(SCP294Object.Handle294Hint()); 51 | 52 | _harmony = new Harmony("SCP294"); 53 | _harmony.PatchAll(); 54 | 55 | Log.Info("Plugin Enabled!"); 56 | } 57 | public override void OnDisabled() 58 | { 59 | if (Config.Debug) 60 | Log.Info("Unregistering events..."); 61 | UnregisterEvents(); 62 | 63 | DrinkManager.UnloadAllDrinks(); 64 | 65 | Timing.KillCoroutines(hintCoroutine); 66 | 67 | _harmony.UnpatchAll(); 68 | _harmony = null; 69 | 70 | Log.Info("Disabled Plugin Successfully"); 71 | } 72 | 73 | // NotesToSelf 74 | // OBJECT.EVENT += FUNCTION > Add Function to Callback 75 | // OBJECT.EVENT -= FUNCTION > Remove Function from Callback 76 | 77 | private handlers.serverHandler ServerHandler; 78 | private handlers.playerHandler PlayerHandler; 79 | 80 | public void RegisterEvents() 81 | { 82 | ServerHandler = new handlers.serverHandler(); 83 | PlayerHandler = new handlers.playerHandler(); 84 | 85 | Server.RoundStarted += ServerHandler.WaitingForPlayers; 86 | 87 | Player.ChangingItem += PlayerHandler.ChangingItem; 88 | Player.UsedItem += PlayerHandler.UsedItem; 89 | Player.Joined += PlayerHandler.Joined; 90 | } 91 | public void UnregisterEvents() 92 | { 93 | Server.RoundStarted -= ServerHandler.WaitingForPlayers; 94 | 95 | Player.ChangingItem -= PlayerHandler.ChangingItem; 96 | Player.UsedItem -= PlayerHandler.UsedItem; 97 | Player.Joined -= PlayerHandler.Joined; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Github All Releases](https://img.shields.io/github/downloads/creepycats/Ultimate294/total.svg)](https://github.com/creepycats/Ultimate294/releases) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/creepycats/Ultimate294/graphs/commit-activity) [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/creepycats/Ultimate294/blob/main/LICENSE) 2 | Releases 3 | Support 4 | 5 | # Ultimate294 - The True SCP-294 Experience 6 | Ultimate294 is a plugin that adds a randomly spawning Anomalous Drinks Vending Machine to SCP: Secret Laboratory. 7 | 8 | 9 | This Recreation Features: 10 | - SCP-294 Model Recreation 11 | - Controllable Spawn Locations 12 | - Usage Limit Control 13 | - *Sound and Animation* for Dispensing Drinks 14 | - [**100+ Unique Drinks (Double that for the different Names) ported from Containment Breach, including custom drinks from the Banana's Bungalow community**](https://github.com/creepycats/Ultimate294/blob/main/Types/Config/DrinkList.cs) 15 | - Silly Secret Commands :3 16 | 17 | 18 | Made for `v13.3.1` of SCP:SL and `v8.4.2` of Exiled and onward by creepycats. 19 | 20 | 21 | ## How It Works 22 | At the beginning of the round, SCP-294 machine(s) will spawn randomly around the map, defaulting to certain rooms of Entrance Zone. 23 | 24 | 25 | When a player approaches the machine, they will see a pop-up Hint, informing them to hold out a coin and run the `.scp294` command. 26 | 27 | 28 | If a player holds a coin next to SCP-294, they will be able to run the command `.scp294 `, which will cause the drink to dispense from the machine. `` is replaced with the name for whatever drink the player wishes to order. 29 | 30 | 31 | Unique drink names can be entered, resulting in various effects, events, and modifiers being applied. 32 | 33 | 34 | Drinks have a set chance to backfire, which will result in the player's drink pouring out their blood, giving them Cardiac Arrest for a few seconds, generally non-fatal, but very harsh. 35 | Better drinks are more likely to result in backfires. 36 | 37 | 38 | Players can also get a Cup of another Player's blood, if they use the command `.scp294 player `, replacing `` with the aforementioned player's name. 39 | This can be used to kill camping players in the event that nobody can reach them, provided the players find coins and know the player's name. 40 | 41 | 42 | Some drinks have been given SCP:SL Twists in comparison to their Containment Breach counterparts. For example, order something gross, and you might end up pouring the drink on the floor (Spawning a Tantrum puddle) 43 | 44 | 45 | ## Admin Commands 46 | Administrators with the `SCP294.admin` permission have access to the RA command `scp294`: 47 | 48 | -`scp294 create/spawn` > Spawns a new SCP-294 machine at the player's coordinates. 49 | 50 | -`scp294 remove/delete` > Deletes the closest SCP-294 machine to the player. 51 | 52 | -`scp294 setuses <#>` > Sets the number of uses left on the SCP-294 Machine instance nearest to the player. Set to -1 for infinite uses, or 0 for disabled. 53 | 54 | -`scp294 givedrink ` > Gives the player the drink that would have been given to them from SCP-294 for free into their inventory. 55 | 56 | ## Installation 57 | **This Plugin Requires MapEditorReborn, [you can download it here](https://github.com/Michal78900/MapEditorReborn/releases)** 58 | 59 | **This Plugin Requires SCPSLAudioApi, [you can download it here](https://github.com/CedModV2/SCPSLAudioApi/releases)** 60 | 61 | After you've installed MapEditorReborn and SCPSLAudioApi, go to [Releases](https://github.com/creepycats/Ultimate294/releases) and download the latest ZIP file. 62 | 63 | Extract the Zip, put `Ultimate294.dll` in your Plugins folder, and Paste the `Config` folder into your `EXILED` folder (which installs my custom Schematic and the sound files) 64 | 65 | ## Permissions 66 | `SCP294.admin` 67 | -------------------------------------------------------------------------------- /Handlers/playerHandler.cs: -------------------------------------------------------------------------------- 1 | using CustomPlayerEffects; 2 | using Exiled.API.Enums; 3 | using Exiled.Events.EventArgs.Player; 4 | using Hazards; 5 | using MEC; 6 | using Mirror; 7 | using PlayerRoles; 8 | using PlayerRoles.PlayableScps.Scp173; 9 | using RelativePositioning; 10 | using SCP294.Types; 11 | using SCP294.Types.Config; 12 | using System; 13 | using UnityEngine; 14 | 15 | namespace SCP294.handlers 16 | { 17 | public class playerHandler 18 | { 19 | public void Joined(JoinedEventArgs args) 20 | { 21 | 22 | } 23 | 24 | public void ChangingItem(ChangingItemEventArgs args) { 25 | if (args.Player == null) return; 26 | if (args.Item == null) return; 27 | if (args.Player.IsNPC) return; 28 | 29 | if (SCP294.Instance.CustomDrinkItems.TryGetValue(args.Item.Serial, out DrinkInfo drinkInfo)) 30 | { 31 | args.Player.ShowHint($"You pulled out the Drink of {drinkInfo.DrinkName}", 3); 32 | } 33 | } 34 | 35 | public void UsedItem(UsedItemEventArgs args) 36 | { 37 | if (args.Player == null) return; 38 | if (args.Item == null) return; 39 | if (args.Player.IsNPC) return; 40 | 41 | if (SCP294.Instance.CustomDrinkItems.TryGetValue(args.Item.Serial, out DrinkInfo drinkInfo)) 42 | { 43 | if (args.Player.IsScp) 44 | { 45 | Timing.CallDelayed(0.01f, () => 46 | { 47 | PlayerEffectsController controller = args.Player.ReferenceHub.playerEffectsController; 48 | if (args.Player.TryGetEffect(args.Item.Type == ItemType.AntiSCP207 ? EffectType.AntiScp207 : EffectType.Scp207, out StatusEffectBase statusEffect)) 49 | { 50 | byte newValue = (byte)Mathf.Min(255, statusEffect.Intensity - 1); 51 | controller.ChangeState(statusEffect.GetType().Name, newValue, 0, false); 52 | } 53 | }); 54 | } 55 | 56 | // Run any Callbacks 57 | if (drinkInfo.DrinkCallback != null) drinkInfo.DrinkCallback(args.Player); 58 | 59 | // Show the Message for Drinking 60 | args.Player.ShowHint(drinkInfo.DrinkMessage, 5); 61 | 62 | args.Player.CurrentItem = null; 63 | 64 | if (drinkInfo.KillPlayer) 65 | { 66 | DrinkCallbacks.DrinkKill(args.Player, drinkInfo); 67 | } else 68 | { 69 | args.Player.Heal(drinkInfo.HealAmount); 70 | // Give player effects from drink 71 | PlayerEffectsController controller = args.Player.ReferenceHub.playerEffectsController; 72 | if (drinkInfo.HealStatusEffects) 73 | { 74 | foreach (EffectType effect in Enum.GetValues(typeof(EffectType))) 75 | { 76 | if (args.Player.TryGetEffect(effect, out StatusEffectBase statusEffect)) 77 | { 78 | byte newValue = (byte)Mathf.Min(255, 0); 79 | controller.ChangeState(statusEffect.GetType().Name, newValue, 0, false); 80 | } 81 | } 82 | } 83 | foreach (DrinkEffect effect in drinkInfo.DrinkEffects) 84 | { 85 | if (args.Player.TryGetEffect(effect.EffectType, out StatusEffectBase statusEffect)) 86 | { 87 | byte newValue = (byte)Mathf.Min(255, statusEffect.Intensity + effect.EffectAmount); 88 | controller.ChangeState(statusEffect.GetType().Name, newValue, effect.Time, effect.ShouldAddIfPresent); 89 | } 90 | } 91 | } 92 | 93 | // Spawn Tantrum when player Drink Funny 94 | if (drinkInfo.Tantrum) 95 | { 96 | if (PlayerRoleLoader.TryGetRoleTemplate(RoleTypeId.Scp173, out PlayerRoleBase result)) 97 | { 98 | Scp173TantrumAbility ability = ((Scp173Role)result).GetComponentInChildren(); 99 | 100 | if (Physics.Raycast(args.Player.Position, Vector3.down, out RaycastHit hitInfo, 3f, ability._tantrumMask)) 101 | { 102 | TantrumEnvironmentalHazard tantrumEnvironmentalHazard = UnityEngine.Object.Instantiate(ability._tantrumPrefab); 103 | Vector3 targetPos = hitInfo.point + (Vector3.up * 1.25f); 104 | tantrumEnvironmentalHazard.SynchronizedPosition = new RelativePosition(targetPos); 105 | NetworkServer.Spawn(tantrumEnvironmentalHazard.gameObject); 106 | foreach (TeslaGate teslaGate in TeslaGateController.Singleton.TeslaGates) 107 | { 108 | if (teslaGate.IsInIdleRange(args.Player.Position)) 109 | { 110 | teslaGate.TantrumsToBeDestroyed.Add(tantrumEnvironmentalHazard); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | SCP294.Instance.CustomDrinkItems.Remove(args.Item.Serial); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Patches/OnEffectsApplied.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Features; 2 | using HarmonyLib; 3 | using InventorySystem.Items; 4 | using InventorySystem.Items.Usables; 5 | using Mirror; 6 | using NorthwoodLib.Pools; 7 | using PlayerRoles.Voice; 8 | using SCP294.Classes; 9 | using SCP294.Types; 10 | using System.Collections.Generic; 11 | using System.Reflection.Emit; 12 | using VoiceChat; 13 | using VoiceChat.Codec; 14 | using VoiceChat.Networking; 15 | 16 | namespace SCP294.Patches 17 | { 18 | 19 | [HarmonyPatch(typeof(Scp207), nameof(Scp207.OnEffectsActivated))] 20 | internal static class SCP207OnEffectsActivated 21 | { 22 | static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) 23 | { 24 | List newInstructions = ListPool.Shared.Rent(instructions); 25 | 26 | Label skip = generator.DefineLabel(); 27 | 28 | // Insert instructions to skip when NPC to the skip label 29 | newInstructions.Add(new CodeInstruction(OpCodes.Ret)); 30 | newInstructions[newInstructions.Count - 1].labels.Add(skip); 31 | 32 | newInstructions.InsertRange(0, new List() 33 | { 34 | new CodeInstruction(OpCodes.Ldarg_0), 35 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(DrinkInfo), nameof(DrinkInfo.IsCustomDrink), new[] { typeof(ItemBase) })), 36 | new CodeInstruction(OpCodes.Brtrue_S, skip), 37 | }); 38 | 39 | foreach (CodeInstruction instruction in newInstructions) 40 | yield return instruction; 41 | 42 | ListPool.Shared.Return(newInstructions); 43 | } 44 | } 45 | 46 | [HarmonyPatch(typeof(AntiScp207), nameof(AntiScp207.OnEffectsActivated))] 47 | internal static class AntiSCP207OnEffectsActivated 48 | { 49 | static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) 50 | { 51 | List newInstructions = ListPool.Shared.Rent(instructions); 52 | 53 | Label skip = generator.DefineLabel(); 54 | 55 | // Insert instructions to skip when NPC to the skip label 56 | newInstructions.Add(new CodeInstruction(OpCodes.Ret)); 57 | newInstructions[newInstructions.Count - 1].labels.Add(skip); 58 | 59 | newInstructions.InsertRange(0, new List() 60 | { 61 | new CodeInstruction(OpCodes.Ldarg_0), 62 | new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(DrinkInfo), nameof(DrinkInfo.IsCustomDrink), new[] { typeof(ItemBase) })), 63 | new CodeInstruction(OpCodes.Brtrue_S, skip), 64 | }); 65 | 66 | foreach (CodeInstruction instruction in newInstructions) 67 | yield return instruction; 68 | 69 | ListPool.Shared.Return(newInstructions); 70 | } 71 | } 72 | 73 | [HarmonyPatch(typeof(VoiceTransceiver), nameof(VoiceTransceiver.ServerReceiveMessage))] 74 | internal static class ServerReceiveMessage 75 | { 76 | [HarmonyPrefix] 77 | private static bool Prefix(NetworkConnection conn, VoiceMessage msg) 78 | { 79 | if (!SCP294.Instance.Config.EnableVoiceEffects) return true; 80 | 81 | if (msg.SpeakerNull || msg.Speaker.netId != conn.identity.netId) 82 | { 83 | return false; 84 | } 85 | IVoiceRole voiceRole = msg.Speaker.roleManager.CurrentRole as IVoiceRole; 86 | if (voiceRole == null) 87 | { 88 | return false; 89 | } 90 | if (!voiceRole.VoiceModule.CheckRateLimit()) 91 | { 92 | return false; 93 | } 94 | if (VoiceChatMutes.IsMuted(msg.Speaker, false)) 95 | { 96 | return false; 97 | } 98 | VoiceChatChannel voiceChatChannel = voiceRole.VoiceModule.ValidateSend(msg.Channel); 99 | if (voiceChatChannel == VoiceChatChannel.None) 100 | { 101 | return false; 102 | } 103 | voiceRole.VoiceModule.CurrentChannel = voiceChatChannel; 104 | 105 | Player plr = Player.Get(msg.Speaker); 106 | if (SCP294.Instance.PlayerVoicePitch.TryGetValue(plr.UserId, out float pitchShift) && pitchShift != 1f) 107 | { 108 | float[] message = new float[48000]; 109 | OpusComponent comp = OpusComponent.Get(plr.ReferenceHub); 110 | comp.Decoder.Decode(msg.Data, msg.DataLength, message); 111 | 112 | comp.PitchShift(pitchShift, (long)480, 48000, message); 113 | 114 | msg.DataLength = comp.Encoder.Encode(message, msg.Data, 480); 115 | } 116 | 117 | foreach (ReferenceHub referenceHub in ReferenceHub.AllHubs) 118 | { 119 | IVoiceRole voiceRole2 = referenceHub.roleManager.CurrentRole as IVoiceRole; 120 | if (voiceRole2 != null) 121 | { 122 | VoiceChatChannel voiceChatChannel2 = voiceRole2.VoiceModule.ValidateReceive(msg.Speaker, voiceChatChannel); 123 | if (voiceChatChannel2 != VoiceChatChannel.None) 124 | { 125 | msg.Channel = voiceChatChannel2; 126 | referenceHub.connectionToClient.Send(msg, 0); 127 | } 128 | } 129 | } 130 | return false; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Config.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Enums; 2 | using Exiled.API.Interfaces; 3 | using SCP294.Types; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using UnityEngine; 8 | 9 | namespace SCP294.Config 10 | { 11 | public class Config : IConfig 12 | { 13 | 14 | // Required Config 15 | /// 16 | /// Will the plugin run? 17 | /// 18 | [Description("Will the plugin run?")] 19 | public bool IsEnabled { get; set; } = true; 20 | /// 21 | /// Will the plugin print Debug Text? 22 | /// 23 | [Description("Will the plugin print Debug Text?")] 24 | public bool Debug { get; set; } = false; 25 | /// 26 | /// Configure the Spawning Locations of SCP-294 27 | /// 28 | [Description("Configure the Spawning Locations of SCP-294")] 29 | public SpawningConfig SpawningLocations { get; set; } = new SpawningConfig() { 30 | SpawnAmount = 1, 31 | SpawnRooms = new Dictionary>() { 32 | [RoomType.EzUpstairsPcs] = new List(){ 33 | new SpawnTransform() { 34 | Position = new Vector3(-5.15f, 0f, 2f), 35 | Rotation = new Vector3(0f, -90f, 0f), 36 | Scale = Vector3.one 37 | } 38 | }, 39 | [RoomType.EzPcs] = new List(){ 40 | new SpawnTransform() { 41 | Position = new Vector3(-7f, 0f, -1.75f), 42 | Rotation = new Vector3(0f, -90f, 0f), 43 | Scale = Vector3.one 44 | }, 45 | new SpawnTransform() { 46 | Position = new Vector3(2.5f, 0f, 6.8f), 47 | Rotation = new Vector3(0f, 0f, 0f), 48 | Scale = Vector3.one 49 | } 50 | }, 51 | [RoomType.EzDownstairsPcs] = new List(){ 52 | new SpawnTransform() { 53 | Position = new Vector3(7f, -1.5f, -5.8f), 54 | Rotation = new Vector3(0f, 90f, 0f), 55 | Scale = Vector3.one 56 | }, 57 | new SpawnTransform() { 58 | Position = new Vector3(7f, -1.5f, 5.8f), 59 | Rotation = new Vector3(0f, 90f, 0f), 60 | Scale = Vector3.one 61 | } 62 | } 63 | } 64 | }; 65 | /// 66 | /// Enable Voice Effects? Disable this if performance is subpar and you give out more than about 8 drinks per round. 67 | /// 68 | [Description("Enable Voice Effects? Disable this if performance is subpar and you give out more than about 8 drinks per round.")] 69 | public bool EnableVoiceEffects { get; set; } = true; 70 | /// 71 | /// Should players be forced to get a random drink? (Player drinks will still be requestable) 72 | /// 73 | [Description("Should players be forced to get a random drink? (Player drinks will still be requestable)")] 74 | public bool ForceRandom { get; set; } = false; 75 | /// 76 | /// How close to the machine does the player have to be? 77 | /// 78 | [Description("How close to the machine does the player have to be?")] 79 | public float UseDistance { get; set; } = 2.5f; 80 | /// 81 | /// Should the Cola be dispensed into the machine's output? Set to False to put it in the player's inventory. 82 | /// 83 | [Description("Should the Cola be dispensed into the machine's output? Set to False to put it in the player's inventory.")] 84 | public bool SpawnInOutput { get; set; } = true; 85 | /// 86 | /// How long should it take from command execution to dispense the drink? 87 | /// 88 | [Description("How long should it take from command execution to dispense the drink?")] 89 | public float DispenseDelay { get; set; } = 5.5f; 90 | /// 91 | /// Cooldown after a player uses the machine. Starts exactly as the coin is inserted 92 | /// 93 | [Description("Cooldown after a player uses the machine. Starts exactly as the coin is inserted")] 94 | public float CooldownTime { get; set; } = 10f; 95 | /// 96 | /// Enable to use the Community Made Drinks 97 | /// 98 | [Description("Enable to use the Community Made Drinks")] 99 | public bool EnableCommunityDrinks { get; set; } = true; 100 | /// 101 | /// The maximum uses of a SCP-294 machine before it deactivates. Set to -1 for infinite uses 102 | /// 103 | [Description("The maximum uses of a SCP-294 machine before it deactivates. Set to -1 for infinite uses")] 104 | public int MaxUsesPerMachine { get; set; } = 3; 105 | /// 106 | /// The maximum size a player can grow to from a drink. 107 | /// 108 | [Description("The maximum size a player can grow to from a drink.")] 109 | public Vector3 MaxSizeFromDrink { get; set; } = new Vector3(1.3f,1.3f,1.3f); 110 | /// 111 | /// The minimum size a player can shrink to from a drink. 112 | /// 113 | [Description("The minimum size a player can shrink to from a drink.")] 114 | public Vector3 MinSizeFromDrink { get; set; } = new Vector3(0.7f,0.7f,0.7f); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Classes/SoundHandler.cs: -------------------------------------------------------------------------------- 1 | using CentralAuth; 2 | using Exiled.API.Enums; 3 | using Exiled.API.Extensions; 4 | using Exiled.API.Features; 5 | using Exiled.API.Features.Components; 6 | using MEC; 7 | using Mirror; 8 | using PlayerRoles; 9 | using PlayerStatsSystem; 10 | using SCPSLAudioApi.AudioCore; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.IO; 14 | using UnityEngine; 15 | using VoiceChat; 16 | using VoiceChat.Codec; 17 | using YamlDotNet.Core.Tokens; 18 | 19 | namespace SCP294 20 | { 21 | public class SoundHandler 22 | { 23 | public OpusEncoder Encoder = new OpusEncoder(VoiceChat.Codec.Enums.OpusApplicationType.Voip); 24 | 25 | // Borrowed from AutoEvents <3 26 | public static List AudioPlayers = new List(); 27 | public static void PlayAudio(string audioFile, byte volume, bool loop, string soundName, Vector3 position, float dur = 0) 28 | { 29 | try 30 | { 31 | //var newPlayer = UnityEngine.Object.Instantiate(NetworkManager.singleton.playerPrefab); 32 | int id = 9999 + AudioPlayers.Count; 33 | //var fakeConnection = new FakeConnection(id++); 34 | //var hubPlayer = newPlayer.GetComponent(); 35 | //AudioPlayers.Add(hubPlayer); 36 | //NetworkServer.AddPlayerForConnection(fakeConnection, newPlayer); 37 | Npc audioNpc = SpawnFix(soundName, position != Vector3.zero ? RoleTypeId.Tutorial : RoleTypeId.Spectator, id, ""); 38 | audioNpc.Health = 9999f; 39 | //audioNpc.IsGodModeEnabled = true; 40 | audioNpc.ReferenceHub.characterClassManager._godMode = true; 41 | audioNpc.ReferenceHub.playerStats.GetModule().SetFlag(AdminFlags.GodMode, true); 42 | var hubPlayer = audioNpc.ReferenceHub; 43 | AudioPlayers.Add(hubPlayer); 44 | 45 | hubPlayer.authManager.InstanceMode = ClientInstanceMode.Unverified; 46 | 47 | try 48 | { 49 | hubPlayer.nicknameSync.SetNick(soundName); 50 | } 51 | catch (Exception) { } 52 | 53 | var audioPlayer = AudioPlayerBase.Get(hubPlayer); 54 | 55 | var path = Path.Combine(Path.Combine(Paths.Configs, "SCP294"), audioFile); 56 | 57 | audioPlayer.Enqueue(path, -1); 58 | audioPlayer.LogDebug = false; 59 | audioPlayer.BroadcastChannel = VoiceChatChannel.Intercom; 60 | if (position != Vector3.zero) 61 | { 62 | audioPlayer.BroadcastChannel = VoiceChatChannel.Proximity; 63 | try 64 | { 65 | hubPlayer.roleManager.ServerSetRole(RoleTypeId.Tutorial, RoleChangeReason.None, RoleSpawnFlags.None); 66 | hubPlayer.gameObject.transform.position = position; 67 | 68 | hubPlayer.gameObject.transform.localScale = Vector3.zero; 69 | foreach (Player item in Player.List) 70 | { 71 | Server.SendSpawnMessage?.Invoke(null, new object[2] { hubPlayer.networkIdentity, item.Connection }); 72 | } 73 | } 74 | catch (Exception) { } 75 | } 76 | audioPlayer.Volume = volume; 77 | audioPlayer.Loop = loop; 78 | audioPlayer.Play(0); 79 | 80 | if (dur != 0) 81 | { 82 | Timing.CallDelayed(dur, delegate 83 | { 84 | hubPlayer.transform.position = new Vector3(-99999,-99999,-99999); 85 | AudioPlayers.Remove(hubPlayer); 86 | if (audioPlayer.CurrentPlay != null) 87 | { 88 | audioPlayer.Stoptrack(true); 89 | audioPlayer.OnDestroy(); 90 | } 91 | 92 | hubPlayer.gameObject.transform.position = new Vector3(-9999f, -9999f, -9999f); 93 | Timing.CallDelayed(0.5f, () => 94 | { 95 | NetworkServer.Destroy(hubPlayer.gameObject); 96 | }); 97 | //NetworkConnectionToClient conn = hubPlayer.connectionToClient; 98 | //hubPlayer.OnDestroy(); 99 | //CustomNetworkManager.TypedSingleton.OnServerDisconnect(conn); 100 | //UnityEngine.Object.Destroy(hubPlayer.gameObject); 101 | }); 102 | } 103 | 104 | Log.Debug($"Playing sound {path}"); 105 | } 106 | catch (Exception e) 107 | { 108 | Log.Error($"Error on: {e.Data} -- {e.StackTrace}"); 109 | } 110 | } 111 | public static void StopAudio() 112 | { 113 | foreach (var player in AudioPlayers) 114 | { 115 | if (!player) continue; 116 | var audioPlayer = AudioPlayerBase.Get(player); 117 | if (!audioPlayer) continue; 118 | 119 | if (audioPlayer.CurrentPlay != null) 120 | { 121 | audioPlayer.Stoptrack(true); 122 | audioPlayer.OnDestroy(); 123 | } 124 | 125 | player.gameObject.transform.position = new Vector3(-9999f, -9999f, -9999f); 126 | Timing.CallDelayed(0.5f, () => 127 | { 128 | NetworkServer.Destroy(player.gameObject); 129 | }); 130 | //NetworkConnectionToClient conn = player.connectionToClient; 131 | //player.OnDestroy(); 132 | //CustomNetworkManager.TypedSingleton.OnServerDisconnect(conn); 133 | //NetworkServer.Destroy(player.gameObject); 134 | } 135 | AudioPlayers.Clear(); 136 | } 137 | public static Npc SpawnFix(string name, RoleTypeId role, int id = 0, string userId = "", Vector3? position = null) 138 | { 139 | GameObject gameObject = UnityEngine.Object.Instantiate(Mirror.NetworkManager.singleton.playerPrefab); 140 | Npc npc = new Npc(gameObject) 141 | { 142 | IsNPC = true 143 | }; 144 | try 145 | { 146 | npc.ReferenceHub.roleManager.InitializeNewRole(RoleTypeId.None, RoleChangeReason.None, RoleSpawnFlags.None); 147 | } 148 | catch (Exception arg) 149 | { 150 | Log.Debug($"Ignore: {arg}"); 151 | } 152 | 153 | if (RecyclablePlayerId.FreeIds.Contains(id)) 154 | { 155 | RecyclablePlayerId.FreeIds.RemoveFromQueue(id); 156 | } 157 | else if (RecyclablePlayerId._autoIncrement >= id) 158 | { 159 | id = ++RecyclablePlayerId._autoIncrement; 160 | } 161 | 162 | NetworkServer.AddPlayerForConnection(new FakeConnection(id), gameObject); 163 | try 164 | { 165 | npc.ReferenceHub.authManager.NetworkSyncedUserId = null; 166 | } 167 | catch (Exception arg2) 168 | { 169 | Log.Debug($"Ignore: {arg2}"); 170 | } 171 | 172 | npc.ReferenceHub.nicknameSync.Network_myNickSync = name; 173 | Player.Dictionary.Add(gameObject, npc); 174 | Timing.CallDelayed(0.3f, delegate 175 | { 176 | npc.Role.Set(role, SpawnReason.RoundStart, RoleSpawnFlags.None); 177 | }); 178 | if (position.HasValue) 179 | { 180 | Timing.CallDelayed(0.5f, delegate 181 | { 182 | npc.Position = position.Value; 183 | }); 184 | } 185 | 186 | return npc; 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /SCP294.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5DBBB54A-9D5F-4E56-A5D8-F62A871B93EE} 8 | Library 9 | Properties 10 | SCP294 11 | Ultimate294 12 | v4.8 13 | 9 14 | latest 15 | 512 16 | true 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | False 63 | ..\NPC Extentions\bin\Release\net4.8\0Harmony.dll 64 | 65 | 66 | False 67 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp-firstpass.dll 68 | 69 | 70 | False 71 | ..\Assembly-CSharp-Publicized.dll 72 | 73 | 74 | False 75 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\CommandSystem.Core.dll 76 | 77 | 78 | False 79 | ..\..\..\..\AppData\Roaming\EXILED\Plugins\dependencies\Exiled.API.dll 80 | 81 | 82 | False 83 | ..\..\..\..\AppData\Roaming\EXILED\Plugins\Exiled.CreditTags.dll 84 | 85 | 86 | False 87 | ..\..\..\..\AppData\Roaming\EXILED\Plugins\Exiled.CustomItems.dll 88 | 89 | 90 | False 91 | ..\..\..\..\AppData\Roaming\EXILED\Plugins\Exiled.CustomRoles.dll 92 | 93 | 94 | False 95 | ..\..\..\..\AppData\Roaming\EXILED\Plugins\Exiled.Events.dll 96 | 97 | 98 | False 99 | ..\..\..\..\AppData\Roaming\EXILED\Plugins\Exiled.Permissions.dll 100 | 101 | 102 | False 103 | ..\..\..\..\AppData\Roaming\EXILED\Plugins\MapEditorReborn.dll 104 | 105 | 106 | False 107 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll 108 | 109 | 110 | False 111 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\NorthwoodLib.dll 112 | 113 | 114 | False 115 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\PluginAPI.dll 116 | 117 | 118 | False 119 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Pooling.dll 120 | 121 | 122 | False 123 | ..\SCPSLAudioApi.dll 124 | 125 | 126 | 127 | 128 | 129 | False 130 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll 131 | 132 | 133 | False 134 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll 135 | 136 | 137 | False 138 | ..\..\..\..\..\..\SteamLibrary\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\YamlDotNet.dll 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /Classes/SCP294Object.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Enums; 2 | using Exiled.API.Features; 3 | using MapEditorReborn.API.Features; 4 | using MapEditorReborn.API.Features.Objects; 5 | using MapEditorReborn.API.Features.Serializable; 6 | using MapEditorReborn.Commands.ModifyingCommands.Scale; 7 | using SCP294.Types; 8 | using SCP294.Utils; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | using MEC; 15 | using UnityEngine; 16 | using Exiled.API.Features.Pickups; 17 | using Exiled.API.Features.Toys; 18 | using InventorySystem.Items.Pickups; 19 | using InventorySystem.Items; 20 | using Exiled.API.Features.Items; 21 | using MapEditorReborn.Commands.ModifyingCommands.Position; 22 | using MapEditorReborn.Commands.ModifyingCommands.Rotation; 23 | 24 | namespace SCP294.Classes 25 | { 26 | public class SCP294Object 27 | { 28 | /// 29 | /// Coroutine that handles the hint upon approaching SCP-294 30 | /// 31 | /// 32 | public static IEnumerator Handle294Hint() 33 | { 34 | for (; ; ) 35 | { 36 | yield return Timing.WaitForSeconds(0.1f); 37 | 38 | foreach (Player player in Player.List) 39 | { 40 | if (player == null) continue; 41 | if (player.IsNPC) continue; 42 | if (CanPlayerUse294(player)) 43 | { 44 | if (!SCP294.Instance.PlayersNear294.Contains(player.UserId)) { 45 | SchematicObject scp294 = GetClosest294(player); 46 | if (SCP294.Instance.SCP294UsesLeft.Keys.Contains(scp294) && SCP294.Instance.SCP294UsesLeft[scp294] == 0) { 47 | player.ShowHint("\n\nYou Approach SCP-294.\nIt seems to have lost all power, rendering it unusable for now...", 3); 48 | } 49 | else 50 | { 51 | player.ShowHint("\n\nYou Approach SCP-294.\nIf you had a coin, you could buy a drink...\n(Hold a Coin, Open Console, Use the command '.scp294 ' to dispense your drink of choice)", 3); 52 | } 53 | SCP294.Instance.PlayersNear294.Add(player.UserId); 54 | } 55 | } 56 | else 57 | { 58 | if (SCP294.Instance.PlayersNear294.Contains(player.UserId)) 59 | { 60 | SCP294.Instance.PlayersNear294.Remove(player.UserId); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | /// 68 | /// Naturally Spawns Instances of SCP-294 based on Config Values 69 | /// 70 | public static void SpawnSCP294() 71 | { 72 | SCP294.Instance.SpawnedSCP294s = new Dictionary(); 73 | 74 | // Get Room and Visual 75 | for (int i = 0; i < SCP294.Instance.Config.SpawningLocations.SpawnAmount; i++) 76 | { 77 | Room SpawnRoom = RoomHandler.GetRandomRoom(SCP294.Instance.Config.SpawningLocations.SpawnRooms.Keys.ToList()); 78 | if (!SpawnRoom) continue; 79 | SpawnTransform relativeOffsetTransform = SCP294.Instance.Config.SpawningLocations.SpawnRooms[SpawnRoom.Type].RandomItem(); 80 | 81 | CreateSCP294(SpawnRoom.Position + (SpawnRoom.Rotation * relativeOffsetTransform.Position), Quaternion.Euler(SpawnRoom.Rotation.eulerAngles + Quaternion.Euler(relativeOffsetTransform.Rotation.x, relativeOffsetTransform.Rotation.y, relativeOffsetTransform.Rotation.z).eulerAngles), relativeOffsetTransform.Scale); 82 | } 83 | } 84 | 85 | /// 86 | /// Spawns an instance of SCP-294 at the given location 87 | /// 88 | public static void CreateSCP294(Vector3 Position, Quaternion Rotation, Vector3 Scale) 89 | { 90 | SchematicObject scp294 = ObjectSpawner.SpawnSchematic("scp294", Vector3.zero, Quaternion.identity, Vector3.one); 91 | scp294.Position = Position; 92 | scp294.Rotation = Rotation; 93 | scp294.Scale = Scale; 94 | 95 | // Add Illumination to Front 96 | Vector3 lightPos = scp294.Position; 97 | lightPos += scp294.Rotation * new Vector3(0f, 1.25f, -1.25f); 98 | SCP294.Instance.SCP294LightSources.Add(scp294,ObjectSpawner.SpawnLightSource(new LightSourceSerializable() 99 | { 100 | Color = "#FFF", 101 | Intensity = 0.25f, 102 | Shadows = true, 103 | Range = 1 104 | }, lightPos)); 105 | 106 | // Add to 294 List 107 | SCP294.Instance.SpawnedSCP294s.Add(scp294, false); 108 | SCP294.Instance.SCP294UsesLeft.Add(scp294, SCP294.Instance.Config.MaxUsesPerMachine); 109 | } 110 | 111 | /// 112 | /// Destroys the nearest SCP-294 to the player 113 | /// 114 | /// Instance of SCP-294 to change 115 | public static void RemoveSCP294(SchematicObject scp294) 116 | { 117 | if (scp294 != null && SCP294.Instance.SCP294UsesLeft.Keys.Contains(scp294)) 118 | { 119 | SCP294.Instance.SpawnedSCP294s.Remove(scp294); 120 | SCP294.Instance.SCP294UsesLeft.Remove(scp294); 121 | try 122 | { 123 | if (SCP294.Instance.SCP294LightSources.Keys.Contains(scp294)) 124 | { 125 | SCP294.Instance.SCP294LightSources[scp294].Destroy(); 126 | SCP294.Instance.SCP294LightSources.Remove(scp294); 127 | } 128 | } catch (Exception err) { } 129 | scp294.Destroy(); 130 | } 131 | } 132 | 133 | /// 134 | /// Set the uses the SCP-294 instance has left 135 | /// 136 | /// Instance of SCP-294 to change 137 | /// Amount of Uses 138 | public static void SetSCP294Uses(SchematicObject scp294, int useCount) 139 | { 140 | if (scp294 != null && SCP294.Instance.SCP294UsesLeft.Keys.Contains(scp294)) 141 | { 142 | SCP294.Instance.SCP294UsesLeft[scp294] = useCount; 143 | // Disable and Enable 144 | SCP294.Instance.SCP294LightSources[scp294].Light.Range = useCount == 0 ? 0 : 1; 145 | SCP294.Instance.SCP294LightSources[scp294].Light.Intensity = useCount == 0 ? 0 : 0.25f; 146 | } 147 | } 148 | 149 | /// 150 | /// Plays the dispensing sound effect at the SCP-294 nearest to the player 151 | /// 152 | /// The Player who Triggered the Sound Effect. Used to get the SCP-294 closest to them 153 | /// The Sound Type to play, either normal or unstable 154 | public static void PlayDispensingSound(Player player, DrinkSound soundType) { 155 | SchematicObject scp294 = GetClosest294(player); 156 | 157 | SoundHandler.PlayAudio(new DrinkSoundFiles().List[(int)soundType], 50, false, "SCP-294", scp294.Position + new Vector3(0,1,0), 6f); 158 | } 159 | 160 | /// 161 | /// Check if the player is nearby a SCP-294 Instance within using distance 162 | /// 163 | /// 164 | /// 165 | public static bool CanPlayerUse294(Player player) { 166 | foreach (SchematicObject scp294 in SCP294.Instance.SpawnedSCP294s.Keys) { 167 | if (!scp294) continue; 168 | if (Vector3.Distance(player.Position, scp294.Position) < SCP294.Instance.Config.UseDistance) return true; 169 | } 170 | return false; 171 | } 172 | 173 | /// 174 | /// Gets the closest SCP-294 to the player 175 | /// 176 | /// 177 | /// SchematicObject reference of the SCP-294 closest to the player 178 | public static SchematicObject GetClosest294(Player player) 179 | { 180 | float closestDistance = 99999f; 181 | SchematicObject closestObject = null; 182 | foreach (SchematicObject scp294 in SCP294.Instance.SpawnedSCP294s.Keys) 183 | { 184 | if (!scp294) continue; 185 | if (Vector3.Distance(player.Position, scp294.Position) < closestDistance) { 186 | closestDistance = Vector3.Distance(player.Position, scp294.Position); 187 | closestObject = scp294; 188 | }; 189 | } 190 | return closestObject; 191 | } 192 | 193 | /// 194 | /// A Patched version that spawns a new Drink Pickup using the created Item's Serial ID instead of giving it a new one. Important for drinks to function 195 | /// 196 | /// 197 | /// 198 | /// 199 | /// 200 | /// 201 | public static Pickup CreateDrinkPickup(Item item, Vector3 position, Quaternion rotation = default(Quaternion), bool spawn = true) 202 | { 203 | ItemPickupBase itemPickupBase = UnityEngine.Object.Instantiate(item.Base.PickupDropModel, position, rotation); 204 | itemPickupBase.Info = new PickupSyncInfo(item.Type, item.Weight, item.Serial); 205 | itemPickupBase.gameObject.transform.localScale = item.Scale; 206 | Pickup pickup = Pickup.Get(itemPickupBase); 207 | if (spawn) 208 | { 209 | pickup.Spawn(); 210 | } 211 | 212 | return pickup; 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Commands/SCP294AdminCommand.cs: -------------------------------------------------------------------------------- 1 | using CommandSystem; 2 | using CustomPlayerEffects; 3 | using Exiled.API.Features; 4 | using Exiled.CreditTags.Features; 5 | using Exiled.Permissions.Extensions; 6 | using MEC; 7 | using RemoteAdmin; 8 | using SCP294.Classes; 9 | using SCP294.Types.Config; 10 | using SCP294.Types; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using UnityEngine; 15 | using Exiled.API.Features.Items; 16 | using Exiled.API.Enums; 17 | 18 | namespace SCP294.Commands 19 | { 20 | [CommandHandler(typeof(RemoteAdminCommandHandler))] 21 | public class SCP294AdminCommand : ICommand, IUsageProvider 22 | { 23 | public string Command { get; } = "scp294"; 24 | 25 | public string[] Aliases { get; } = { }; 26 | 27 | public string Description { get; } = "SCP294 Admin Command Base"; 28 | 29 | public string[] Usage { get; } = new string[1] { "create/spawn/remove/delete/setuses/givedrink" }; 30 | 31 | public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) 32 | { 33 | if (!(sender is PlayerCommandSender)) 34 | { 35 | response = "This command can only be ran by a player!"; 36 | return true; 37 | } 38 | 39 | var player = Player.Get(((PlayerCommandSender)sender).ReferenceHub); 40 | if (!sender.CheckPermission("SCP294.admin")) 41 | { 42 | response = "You do not have access to the Commands for SCProphunt Admin Command Base"; 43 | return false; 44 | } 45 | 46 | if (arguments.Count < 1) 47 | { 48 | response = "Usage: scp294 create/spawn/remove/delete"; 49 | return true; 50 | } 51 | 52 | if (arguments.At(0) == "create" || arguments.At(0) == "spawn") 53 | { 54 | SCP294Object.CreateSCP294(player.Position - new Vector3(0,1,0), Quaternion.Euler(new Vector3(0, player.Rotation.y + 180, 0)), Vector3.one); 55 | response = $"Created a SCP-294 at your Position and Rotation"; 56 | return true; 57 | } 58 | else if (arguments.At(0) == "remove" || arguments.At(0) == "delete") 59 | { 60 | SCP294Object.RemoveSCP294(SCP294Object.GetClosest294(player)); 61 | response = $"Removed the nearest SCP-294 instance"; 62 | return true; 63 | } 64 | else if (arguments.At(0) == "setuses") 65 | { 66 | int useAmount = 0; 67 | if (int.TryParse(arguments.At(1), out useAmount)) 68 | { 69 | SCP294Object.SetSCP294Uses(SCP294Object.GetClosest294(player),useAmount); 70 | response = $"Set the nearest SCP-294 instance's uses to {useAmount}"; 71 | return true; 72 | } else 73 | { 74 | response = $"Parameter is not of type Int"; 75 | return false; 76 | } 77 | } 78 | else if (arguments.At(0) == "givedrink") 79 | { 80 | if (arguments.At(1).ToLower() == "player") 81 | { 82 | // Player Cup 83 | // Try and Get player 84 | Player targetPlayer = Player.Get(String.Join(" ", arguments.Skip(2).ToArray())); 85 | if (targetPlayer != null) 86 | { 87 | List stealEffects = new List() { 88 | new DrinkEffect () 89 | { 90 | EffectType = EffectType.CardiacArrest, 91 | Time = 8, 92 | EffectAmount = 1, 93 | ShouldAddIfPresent = true 94 | } 95 | }; 96 | PlayerEffectsController controller = targetPlayer.ReferenceHub.playerEffectsController; 97 | foreach (DrinkEffect effect in stealEffects) 98 | { 99 | if (targetPlayer.TryGetEffect(effect.EffectType, out StatusEffectBase statusEffect)) 100 | { 101 | byte newValue = (byte)Mathf.Min(255, statusEffect.Intensity + effect.EffectAmount); 102 | controller.ChangeState(statusEffect.GetType().Name, newValue, effect.Time, effect.ShouldAddIfPresent); 103 | } 104 | } 105 | targetPlayer.ShowHint($"You feel queasy, as if you're missing some of your body's contents...\n({player.Nickname} ordered a Cup of You from SCP-294)", 5); 106 | 107 | // Found Drink 108 | Item drinkItem = Item.Create(ItemType.SCP207); 109 | drinkItem.Scale = new Vector3(1f, 1f, 0.8f); 110 | player.AddItem(drinkItem); 111 | 112 | SCP294.Instance.CustomDrinkItems.Add(drinkItem.Serial, new DrinkInfo() 113 | { 114 | ItemSerial = drinkItem.Serial, 115 | ItemObject = drinkItem, 116 | DrinkEffects = new List() { }, 117 | DrinkMessage = "The drink tastes like blood. It's still warm.", 118 | DrinkName = targetPlayer.Nickname, 119 | KillPlayer = false, 120 | KillPlayerString = "", 121 | HealAmount = 0, 122 | HealStatusEffects = false, 123 | Tantrum = false 124 | }); 125 | 126 | response = $"Gave yourself a Drink of {targetPlayer.Nickname}."; 127 | return true; 128 | } 129 | response = "Couldn't Find a Drink with this Name"; 130 | return false; 131 | } 132 | else if (arguments.At(1).ToLower() == "playercum") 133 | { 134 | // Player Cup 135 | // Try and Get player 136 | Player targetPlayer = Player.Get(String.Join(" ", arguments.Skip(2).ToArray())); 137 | if (targetPlayer != null) 138 | { 139 | targetPlayer.ShowHint($"You feel funny, almost excited in a way...\n({player.Nickname} ordered a Cup of You from SCP-294)", 5); 140 | 141 | // Found Drink 142 | Item drinkItem = Item.Create(ItemType.SCP207); 143 | drinkItem.Scale = new Vector3(1f, 1f, 0.8f); 144 | player.AddItem(drinkItem); 145 | 146 | SCP294.Instance.CustomDrinkItems.Add(drinkItem.Serial, new DrinkInfo() 147 | { 148 | ItemSerial = drinkItem.Serial, 149 | ItemObject = drinkItem, 150 | DrinkEffects = new List() { }, 151 | DrinkMessage = "Kind of salty. Tastes good though. Feels nice and warm.", 152 | DrinkName = $"{targetPlayer.Nickname}'s Cum", 153 | KillPlayer = false, 154 | KillPlayerString = "", 155 | HealAmount = 0, 156 | HealStatusEffects = false, 157 | Tantrum = false 158 | }); 159 | 160 | response = $"Gave yourself a Drink of {targetPlayer.Nickname}'s Cum."; 161 | return true; 162 | } 163 | response = "Couldn't Find a Drink with this Name"; 164 | return false; 165 | } 166 | else 167 | { 168 | // Other Drinks 169 | foreach (CustomDrink customDrink in SCP294.Instance.DrinkManager.LoadedDrinks) 170 | { 171 | foreach (string drinkName in customDrink.DrinkNames) 172 | { 173 | if (drinkName.ToLower() == String.Join(" ", arguments.Skip(1).Where(s => !String.IsNullOrEmpty(s))).ToLower()) 174 | { 175 | // Found Drink 176 | Item drinkItem = Item.Create(ItemType.SCP207); 177 | drinkItem.Scale = new Vector3(1f, 1f, 0.8f); 178 | player.AddItem(drinkItem); 179 | SCP294.Instance.CustomDrinkItems.Add(drinkItem.Serial, new DrinkInfo() 180 | { 181 | ItemSerial = drinkItem.Serial, 182 | ItemObject = drinkItem, 183 | DrinkEffects = customDrink.DrinkEffects, 184 | DrinkMessage = customDrink.DrinkMessage, 185 | DrinkName = drinkName, 186 | KillPlayer = customDrink.KillPlayer, 187 | KillPlayerString = customDrink.KillPlayerString, 188 | HealAmount = customDrink.HealAmount, 189 | HealStatusEffects = customDrink.HealStatusEffects, 190 | Tantrum = customDrink.Tantrum, 191 | DrinkCallback = customDrink.DrinkCallback 192 | }); 193 | 194 | response = $"Gave yourself a Drink of {drinkName}."; 195 | return true; 196 | } 197 | } 198 | } 199 | response = "Couldn't Find a Drink with this Name"; 200 | return false; 201 | } 202 | } 203 | else 204 | { 205 | response = "Invalid Subcommand."; 206 | return false; 207 | } 208 | 209 | response = "Something is very wrong. Let me know if you somehow get this result."; 210 | return false; 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Classes/OpusComponent.cs: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * 3 | * NAME: PitchShift.cs 4 | * VERSION: 1.2 5 | * HOME URL: http://www.dspdimension.com 6 | * KNOWN BUGS: none 7 | * 8 | * SYNOPSIS: Routine for doing pitch shifting while maintaining 9 | * duration using the Short Time Fourier Transform. 10 | * 11 | * DESCRIPTION: The routine takes a pitchShift factor value which is between 0.5 12 | * (one octave down) and 2. (one octave up). A value of exactly 1 does not change 13 | * the pitch. numSampsToProcess tells the routine how many samples in indata[0... 14 | * numSampsToProcess-1] should be pitch shifted and moved to outdata[0 ... 15 | * numSampsToProcess-1]. The two buffers can be identical (ie. it can process the 16 | * data in-place). fftFrameSize defines the FFT frame size used for the 17 | * processing. Typical values are 1024, 2048 and 4096. It may be any value <= 18 | * MAX_FRAME_LENGTH but it MUST be a power of 2. osamp is the STFT 19 | * oversampling factor which also determines the overlap between adjacent STFT 20 | * frames. It should at least be 4 for moderate scaling ratios. A value of 32 is 21 | * recommended for best quality. sampleRate takes the sample rate for the signal 22 | * in unit Hz, ie. 44100 for 44.1 kHz audio. The data passed to the routine in 23 | * indata[] should be in the range [-1.0, 1.0), which is also the output range 24 | * for the data, make sure you scale the data accordingly (for 16bit signed integers 25 | * you would have to divide (and multiply) by 32768). 26 | * 27 | * COPYRIGHT 1999-2006 Stephan M. Bernsee 28 | * 29 | * The Wide Open License (WOL) 30 | * 31 | * Permission to use, copy, modify, distribute and sell this software and its 32 | * documentation for any purpose is hereby granted without fee, provided that 33 | * the above copyright notice and this license appear in all source copies. 34 | * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF 35 | * ANY KIND. See http://www.dspguru.com/wol.htm for more information. 36 | * 37 | *****************************************************************************/ 38 | 39 | /**************************************************************************** 40 | * 41 | * This code was converted to C# by Michael Knight 42 | * madmik3 at gmail dot com. 43 | * http://sites.google.com/site/mikescoderama/ 44 | * 45 | *****************************************************************************/ 46 | 47 | using SCPSLAudioApi.AudioCore; 48 | using System; 49 | using System.Collections.Generic; 50 | using System.Linq; 51 | using System.Text; 52 | using System.Threading.Tasks; 53 | using UnityEngine; 54 | using VoiceChat.Codec; 55 | 56 | namespace SCP294.Classes 57 | { 58 | public class OpusComponent : MonoBehaviour 59 | { 60 | /// 61 | /// The ReferenceHub instance that this player sends as. 62 | /// 63 | public ReferenceHub Owner { get; set; } 64 | 65 | public OpusEncoder Encoder { get; } = new OpusEncoder(VoiceChat.Codec.Enums.OpusApplicationType.Voip); 66 | public OpusDecoder Decoder { get; } = new OpusDecoder(); 67 | 68 | /// 69 | /// Add or retrieve the OpusComponent instance based on a ReferenceHub instance. 70 | /// 71 | /// The ReferenceHub instance that this OpusComponent belongs to 72 | /// 73 | public static OpusComponent Get(ReferenceHub hub) 74 | { 75 | if (SCP294.Instance.Encoders.TryGetValue(hub, out OpusComponent player)) 76 | { 77 | return player; 78 | } 79 | 80 | player = hub.gameObject.AddComponent(); 81 | player.Owner = hub; 82 | 83 | SCP294.Instance.Encoders.Add(hub, player); 84 | return player; 85 | } 86 | 87 | #region Private Static Memebers 88 | private static int MAX_FRAME_LENGTH = 16000; 89 | private float[] gInFIFO = new float[MAX_FRAME_LENGTH]; 90 | private float[] gOutFIFO = new float[MAX_FRAME_LENGTH]; 91 | private float[] gFFTworksp = new float[2 * MAX_FRAME_LENGTH]; 92 | private float[] gLastPhase = new float[MAX_FRAME_LENGTH / 2 + 1]; 93 | private float[] gSumPhase = new float[MAX_FRAME_LENGTH / 2 + 1]; 94 | private float[] gOutputAccum = new float[2 * MAX_FRAME_LENGTH]; 95 | private float[] gAnaFreq = new float[MAX_FRAME_LENGTH]; 96 | private float[] gAnaMagn = new float[MAX_FRAME_LENGTH]; 97 | private float[] gSynFreq = new float[MAX_FRAME_LENGTH]; 98 | private float[] gSynMagn = new float[MAX_FRAME_LENGTH]; 99 | private long gRover, gInit; 100 | #endregion 101 | 102 | #region Public Static Methods 103 | public void PitchShift(float pitchShift, long numSampsToProcess, 104 | float sampleRate, float[] indata) 105 | { 106 | PitchShift(pitchShift, numSampsToProcess, (long)2048, (long)10, sampleRate, indata); 107 | } 108 | public void PitchShift(float pitchShift, long numSampsToProcess, long fftFrameSize, 109 | long osamp, float sampleRate, float[] indata) 110 | { 111 | double magn, phase, tmp, window, real, imag; 112 | double freqPerBin, expct; 113 | long i, k, qpd, index, inFifoLatency, stepSize, fftFrameSize2; 114 | 115 | 116 | float[] outdata = indata; 117 | /* set up some handy variables */ 118 | fftFrameSize2 = fftFrameSize / 2; 119 | stepSize = fftFrameSize / osamp; 120 | freqPerBin = sampleRate / (double)fftFrameSize; 121 | expct = 2.0 * Math.PI * (double)stepSize / (double)fftFrameSize; 122 | inFifoLatency = fftFrameSize - stepSize; 123 | if (gRover == 0) gRover = inFifoLatency; 124 | 125 | 126 | /* main processing loop */ 127 | for (i = 0; i < numSampsToProcess; i++) 128 | { 129 | 130 | /* As long as we have not yet collected enough data just read in */ 131 | gInFIFO[gRover] = indata[i]; 132 | outdata[i] = gOutFIFO[gRover - inFifoLatency]; 133 | gRover++; 134 | 135 | /* now we have enough data for processing */ 136 | if (gRover >= fftFrameSize) 137 | { 138 | gRover = inFifoLatency; 139 | 140 | /* do windowing and re,im interleave */ 141 | for (k = 0; k < fftFrameSize; k++) 142 | { 143 | window = -.5 * Math.Cos(2.0 * Math.PI * (double)k / (double)fftFrameSize) + .5; 144 | gFFTworksp[2 * k] = (float)(gInFIFO[k] * window); 145 | gFFTworksp[2 * k + 1] = 0.0F; 146 | } 147 | 148 | 149 | /* ***************** ANALYSIS ******************* */ 150 | /* do transform */ 151 | ShortTimeFourierTransform(gFFTworksp, fftFrameSize, -1); 152 | 153 | /* this is the analysis step */ 154 | for (k = 0; k <= fftFrameSize2; k++) 155 | { 156 | 157 | /* de-interlace FFT buffer */ 158 | real = gFFTworksp[2 * k]; 159 | imag = gFFTworksp[2 * k + 1]; 160 | 161 | /* compute magnitude and phase */ 162 | magn = 2.0 * Math.Sqrt(real * real + imag * imag); 163 | phase = Math.Atan2(imag, real); 164 | 165 | /* compute phase difference */ 166 | tmp = phase - gLastPhase[k]; 167 | gLastPhase[k] = (float)phase; 168 | 169 | /* subtract expected phase difference */ 170 | tmp -= (double)k * expct; 171 | 172 | /* map delta phase into +/- Pi interval */ 173 | qpd = (long)(tmp / Math.PI); 174 | if (qpd >= 0) qpd += qpd & 1; 175 | else qpd -= qpd & 1; 176 | tmp -= Math.PI * (double)qpd; 177 | 178 | /* get deviation from bin frequency from the +/- Pi interval */ 179 | tmp = osamp * tmp / (2.0 * Math.PI); 180 | 181 | /* compute the k-th partials' true frequency */ 182 | tmp = (double)k * freqPerBin + tmp * freqPerBin; 183 | 184 | /* store magnitude and true frequency in analysis arrays */ 185 | gAnaMagn[k] = (float)magn; 186 | gAnaFreq[k] = (float)tmp; 187 | 188 | } 189 | 190 | /* ***************** PROCESSING ******************* */ 191 | /* this does the actual pitch shifting */ 192 | for (int zero = 0; zero < fftFrameSize; zero++) 193 | { 194 | gSynMagn[zero] = 0; 195 | gSynFreq[zero] = 0; 196 | } 197 | 198 | for (k = 0; k <= fftFrameSize2; k++) 199 | { 200 | index = (long)(k * pitchShift); 201 | if (index <= fftFrameSize2) 202 | { 203 | gSynMagn[index] += gAnaMagn[k]; 204 | gSynFreq[index] = gAnaFreq[k] * pitchShift; 205 | } 206 | } 207 | 208 | /* ***************** SYNTHESIS ******************* */ 209 | /* this is the synthesis step */ 210 | for (k = 0; k <= fftFrameSize2; k++) 211 | { 212 | 213 | /* get magnitude and true frequency from synthesis arrays */ 214 | magn = gSynMagn[k]; 215 | tmp = gSynFreq[k]; 216 | 217 | /* subtract bin mid frequency */ 218 | tmp -= (double)k * freqPerBin; 219 | 220 | /* get bin deviation from freq deviation */ 221 | tmp /= freqPerBin; 222 | 223 | /* take osamp into account */ 224 | tmp = 2.0 * Math.PI * tmp / osamp; 225 | 226 | /* add the overlap phase advance back in */ 227 | tmp += (double)k * expct; 228 | 229 | /* accumulate delta phase to get bin phase */ 230 | gSumPhase[k] += (float)tmp; 231 | phase = gSumPhase[k]; 232 | 233 | /* get real and imag part and re-interleave */ 234 | gFFTworksp[2 * k] = (float)(magn * Math.Cos(phase)); 235 | gFFTworksp[2 * k + 1] = (float)(magn * Math.Sin(phase)); 236 | } 237 | 238 | /* zero negative frequencies */ 239 | for (k = fftFrameSize + 2; k < 2 * fftFrameSize; k++) gFFTworksp[k] = 0.0F; 240 | 241 | /* do inverse transform */ 242 | ShortTimeFourierTransform(gFFTworksp, fftFrameSize, 1); 243 | 244 | /* do windowing and add to output accumulator */ 245 | for (k = 0; k < fftFrameSize; k++) 246 | { 247 | window = -.5 * Math.Cos(2.0 * Math.PI * (double)k / (double)fftFrameSize) + .5; 248 | gOutputAccum[k] += (float)(2.0 * window * gFFTworksp[2 * k] / (fftFrameSize2 * osamp)); 249 | } 250 | for (k = 0; k < stepSize; k++) gOutFIFO[k] = gOutputAccum[k]; 251 | 252 | /* shift accumulator */ 253 | //memmove(gOutputAccum, gOutputAccum + stepSize, fftFrameSize * sizeof(float)); 254 | for (k = 0; k < fftFrameSize; k++) 255 | { 256 | gOutputAccum[k] = gOutputAccum[k + stepSize]; 257 | } 258 | 259 | /* move input FIFO */ 260 | for (k = 0; k < inFifoLatency; k++) gInFIFO[k] = gInFIFO[k + stepSize]; 261 | } 262 | } 263 | } 264 | #endregion 265 | 266 | #region Private Static Methods 267 | public static void ShortTimeFourierTransform(float[] fftBuffer, long fftFrameSize, long sign) 268 | { 269 | float wr, wi, arg, temp; 270 | float tr, ti, ur, ui; 271 | long i, bitm, j, le, le2, k; 272 | 273 | for (i = 2; i < 2 * fftFrameSize - 2; i += 2) 274 | { 275 | for (bitm = 2, j = 0; bitm < 2 * fftFrameSize; bitm <<= 1) 276 | { 277 | if ((i & bitm) != 0) j++; 278 | j <<= 1; 279 | } 280 | if (i < j) 281 | { 282 | temp = fftBuffer[i]; 283 | fftBuffer[i] = fftBuffer[j]; 284 | fftBuffer[j] = temp; 285 | temp = fftBuffer[i + 1]; 286 | fftBuffer[i + 1] = fftBuffer[j + 1]; 287 | fftBuffer[j + 1] = temp; 288 | } 289 | } 290 | long max = (long)(Math.Log(fftFrameSize) / Math.Log(2.0) + .5); 291 | for (k = 0, le = 2; k < max; k++) 292 | { 293 | le <<= 1; 294 | le2 = le >> 1; 295 | ur = 1.0F; 296 | ui = 0.0F; 297 | arg = (float)Math.PI / (le2 >> 1); 298 | wr = (float)Math.Cos(arg); 299 | wi = (float)(sign * Math.Sin(arg)); 300 | for (j = 0; j < le2; j += 2) 301 | { 302 | 303 | for (i = j; i < 2 * fftFrameSize; i += le) 304 | { 305 | tr = fftBuffer[i + le2] * ur - fftBuffer[i + le2 + 1] * ui; 306 | ti = fftBuffer[i + le2] * ui + fftBuffer[i + le2 + 1] * ur; 307 | fftBuffer[i + le2] = fftBuffer[i] - tr; 308 | fftBuffer[i + le2 + 1] = fftBuffer[i + 1] - ti; 309 | fftBuffer[i] += tr; 310 | fftBuffer[i + 1] += ti; 311 | 312 | } 313 | tr = ur * wr - ui * wi; 314 | ui = ur * wi + ui * wr; 315 | ur = tr; 316 | } 317 | } 318 | } 319 | #endregion 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /Commands/SCP294Command.cs: -------------------------------------------------------------------------------- 1 | using CommandSystem; 2 | using CustomPlayerEffects; 3 | using Exiled.API.Enums; 4 | using Exiled.API.Features; 5 | using Exiled.API.Features.Items; 6 | using Exiled.API.Features.Pickups; 7 | using Exiled.API.Features.Roles; 8 | using Exiled.Permissions.Extensions; 9 | using MapEditorReborn.API.Features.Objects; 10 | using MapEditorReborn.Commands.ModifyingCommands.Position; 11 | using MapEditorReborn.Commands.ModifyingCommands.Rotation; 12 | using MEC; 13 | using Mirror; 14 | using PlayerRoles; 15 | using RemoteAdmin; 16 | using SCP294; 17 | using SCP294.Classes; 18 | using SCP294.Types; 19 | using SCP294.Types.Config; 20 | using System; 21 | using System.Collections.Generic; 22 | using System.IO; 23 | using System.Linq; 24 | using UnityEngine; 25 | 26 | namespace SCP294.Commands 27 | { 28 | [CommandHandler(typeof(ClientCommandHandler))] 29 | public class SCP294Command : ICommand 30 | { 31 | public string Command { get; } = "scp294"; 32 | 33 | public string[] Aliases { get; } = { "294" }; 34 | 35 | public string Description { get; } = "Look at and stand close to SCP-294. Usage: .scp294 OR .scp294 player "; 36 | 37 | private System.Random rand = new System.Random(); 38 | 39 | public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) 40 | { 41 | // Cannot be Server 42 | if (!(sender is PlayerCommandSender)) 43 | { 44 | response = "This command can only be ran by a player!"; 45 | return false; 46 | } 47 | 48 | // Player MUST Be Human 49 | Player player = Player.Get(((PlayerCommandSender)sender).ReferenceHub); 50 | if (player.Role.Team == Team.Dead) 51 | { 52 | response = "Please wait until you spawn in as a normal class."; 53 | return false; 54 | } 55 | if (player.Role.Team == Team.SCPs) 56 | { 57 | response = "SCPs cannot interact with SCP-294."; 58 | return false; 59 | } 60 | 61 | // Only Continue if Player is close enough to use 294 62 | if (SCP294Object.CanPlayerUse294(player)) 63 | { 64 | SchematicObject scp294 = SCP294Object.GetClosest294(player); 65 | if (SCP294.Instance.SpawnedSCP294s[scp294]) 66 | { 67 | response = "This SCP-294 is on cooldown!"; 68 | return false; 69 | } 70 | if (SCP294.Instance.SCP294UsesLeft[scp294] == 0) 71 | { 72 | response = "This SCP-294 has been deactivated..."; 73 | return false; 74 | } 75 | if (arguments.Count < 1 && !SCP294.Instance.Config.ForceRandom) 76 | { 77 | response = "Requires you hold a coin to work | Usage: .scp294 OR .scp294 player "; 78 | return false; 79 | } 80 | if (player.CurrentItem == null || player.CurrentItem.Type != ItemType.Coin) 81 | { 82 | response = "You aren't holding a coin to buy a drink with."; 83 | return false; 84 | } 85 | if (arguments.Count > 0 && arguments.At(0).ToLower() == "player") { 86 | // Player Cup 87 | // Try and Get player 88 | Player targetPlayer = Player.Get(String.Join(" ", arguments.Skip(1).ToArray())); 89 | if (targetPlayer != null) 90 | { 91 | List stealEffects = new List() { 92 | new DrinkEffect () 93 | { 94 | EffectType = EffectType.CardiacArrest, 95 | Time = 8, 96 | EffectAmount = 1, 97 | ShouldAddIfPresent = true 98 | } 99 | }; 100 | PlayerEffectsController controller = targetPlayer.ReferenceHub.playerEffectsController; 101 | foreach (DrinkEffect effect in stealEffects) 102 | { 103 | if (targetPlayer.TryGetEffect(effect.EffectType, out StatusEffectBase statusEffect)) 104 | { 105 | byte newValue = (byte)Mathf.Min(255, statusEffect.Intensity + effect.EffectAmount); 106 | controller.ChangeState(statusEffect.GetType().Name, newValue, effect.Time, effect.ShouldAddIfPresent); 107 | } 108 | } 109 | targetPlayer.ShowHint($"You feel queasy, as if you're missing some of your body's contents...\n({player.Nickname} ordered a Cup of You from SCP-294)", 5); 110 | 111 | // Found Drink 112 | player.RemoveItem(player.CurrentItem); 113 | SCP294Object.PlayDispensingSound(player, DrinkSound.Normal); 114 | Timing.CallDelayed(SCP294.Instance.Config.DispenseDelay, () => 115 | { 116 | Item drinkItem = Item.Create(ItemType.AntiSCP207); 117 | drinkItem.Scale = new Vector3(1f, 1f, 0.8f); 118 | if (SCP294.Instance.Config.SpawnInOutput) 119 | { 120 | Vector3 spawnPos = scp294.Position; 121 | spawnPos += scp294.Rotation * new Vector3(-0.375f, 1f, -0.425f); 122 | 123 | Pickup drinkPickup = SCP294Object.CreateDrinkPickup(drinkItem, spawnPos, Quaternion.Euler(-90, 0, 0)); 124 | } 125 | else 126 | { 127 | player.AddItem(drinkItem); 128 | } 129 | 130 | SCP294.Instance.CustomDrinkItems.Add(drinkItem.Serial, new DrinkInfo() 131 | { 132 | ItemSerial = drinkItem.Serial, 133 | ItemObject = drinkItem, 134 | DrinkEffects = new List() { }, 135 | DrinkMessage = "The drink tastes like blood. It's still warm.", 136 | DrinkName = targetPlayer.Nickname, 137 | KillPlayer = false, 138 | KillPlayerString = "", 139 | HealAmount = 0, 140 | HealStatusEffects = false, 141 | Tantrum = false 142 | }); 143 | }); 144 | 145 | // Cooldown 146 | SCP294.Instance.SpawnedSCP294s[scp294] = true; 147 | Timing.CallDelayed(SCP294.Instance.Config.CooldownTime, () => 148 | { 149 | SCP294.Instance.SpawnedSCP294s[scp294] = false; 150 | }); 151 | 152 | SCP294Object.SetSCP294Uses(scp294, SCP294.Instance.SCP294UsesLeft[scp294] - 1); 153 | response = $"SCP-294 Started Dispensing a Drink of {targetPlayer.Nickname}."; 154 | return true; 155 | } 156 | response = "SCP-294 couldn't determine your drink, and refunded you your coin."; 157 | return false; 158 | } 159 | else if (arguments.Count > 0 && arguments.At(0).ToLower() == "playercum") 160 | { 161 | // Player Cup 162 | // Try and Get player 163 | Player targetPlayer = Player.Get(String.Join(" ", arguments.Skip(1).ToArray())); 164 | if (targetPlayer != null) 165 | { 166 | targetPlayer.ShowHint($"You feel funny, almost excited in a way...\n({player.Nickname} ordered a Cup of You from SCP-294)", 5); 167 | 168 | // Found Drink 169 | player.RemoveItem(player.CurrentItem); 170 | SCP294Object.PlayDispensingSound(player, DrinkSound.Normal); 171 | Timing.CallDelayed(SCP294.Instance.Config.DispenseDelay, () => 172 | { 173 | Item drinkItem = Item.Create(ItemType.SCP207); 174 | drinkItem.Scale = new Vector3(1f, 1f, 0.8f); 175 | if (SCP294.Instance.Config.SpawnInOutput) 176 | { 177 | Vector3 spawnPos = scp294.Position; 178 | spawnPos += scp294.Rotation * new Vector3(-0.375f, 1f, -0.425f); 179 | 180 | Pickup drinkPickup = SCP294Object.CreateDrinkPickup(drinkItem, spawnPos, Quaternion.Euler(-90, 0, 0)); 181 | } 182 | else 183 | { 184 | player.AddItem(drinkItem); 185 | } 186 | 187 | SCP294.Instance.CustomDrinkItems.Add(drinkItem.Serial, new DrinkInfo() 188 | { 189 | ItemSerial = drinkItem.Serial, 190 | ItemObject = drinkItem, 191 | DrinkEffects = new List() { }, 192 | DrinkMessage = "Kind of salty. Tastes good though. Feels nice and warm.", 193 | DrinkName = $"{targetPlayer.Nickname}'s Cum", 194 | KillPlayer = false, 195 | KillPlayerString = "", 196 | HealAmount = 0, 197 | HealStatusEffects = false, 198 | Tantrum = false 199 | }); 200 | }); 201 | 202 | // Cooldown 203 | SCP294.Instance.SpawnedSCP294s[scp294] = true; 204 | Timing.CallDelayed(SCP294.Instance.Config.CooldownTime, () => 205 | { 206 | SCP294.Instance.SpawnedSCP294s[scp294] = false; 207 | }); 208 | 209 | SCP294Object.SetSCP294Uses(scp294, SCP294.Instance.SCP294UsesLeft[scp294] - 1); 210 | response = $"SCP-294 Started Dispensing a Drink of{targetPlayer.Nickname}'s Cum."; 211 | return true; 212 | } 213 | response = "SCP-294 couldn't determine your drink, and refunded you your coin."; 214 | return false; 215 | } 216 | else 217 | { 218 | if (SCP294.Instance.Config.ForceRandom || arguments.At(0).ToLower() == "random") 219 | { 220 | arguments = new ArraySegment(SCP294.Instance.DrinkManager.LoadedDrinks.RandomItem().DrinkNames.RandomItem().Split()); 221 | } 222 | 223 | // Other Drinks 224 | foreach (CustomDrink customDrink in SCP294.Instance.DrinkManager.LoadedDrinks) 225 | { 226 | foreach (string drinkName in customDrink.DrinkNames) 227 | { 228 | if (drinkName.ToLower() == String.Join(" ", arguments.Where(s => !String.IsNullOrEmpty(s))).ToLower()) { 229 | float failNumber = (float)rand.NextDouble(); 230 | if (customDrink.BackfireChance > failNumber) 231 | { 232 | // BACKFIRED!!! 233 | if (customDrink.ExplodeOnBackfire) { 234 | List stealEffects = new List() { 235 | new DrinkEffect () 236 | { 237 | EffectType = EffectType.Ensnared, 238 | Time = SCP294.Instance.Config.DispenseDelay, 239 | EffectAmount = 1, 240 | ShouldAddIfPresent = true 241 | } 242 | }; 243 | PlayerEffectsController controller = player.ReferenceHub.playerEffectsController; 244 | foreach (DrinkEffect effect in stealEffects) 245 | { 246 | if (player.TryGetEffect(effect.EffectType, out StatusEffectBase statusEffect)) 247 | { 248 | byte newValue = (byte)Mathf.Min(255, statusEffect.Intensity + effect.EffectAmount); 249 | controller.ChangeState(statusEffect.GetType().Name, newValue, effect.Time, effect.ShouldAddIfPresent); 250 | } 251 | } 252 | 253 | player.RemoveItem(player.CurrentItem); 254 | SCP294Object.PlayDispensingSound(player, DrinkSound.Unstable); 255 | Timing.CallDelayed(SCP294.Instance.Config.DispenseDelay, () => 256 | { 257 | ExplosiveGrenade grenade = (ExplosiveGrenade)Item.Create(ItemType.GrenadeHE); 258 | grenade.FuseTime = 0.1f; 259 | grenade.SpawnActive(player.Position, player); 260 | player.Kill($"Ordered a backfired {drinkName} from SCP-294"); 261 | }); 262 | 263 | // Cooldown 264 | SCP294.Instance.SpawnedSCP294s[scp294] = true; 265 | Timing.CallDelayed(SCP294.Instance.Config.CooldownTime, () => 266 | { 267 | SCP294.Instance.SpawnedSCP294s[scp294] = false; 268 | }); 269 | 270 | response = $"SCP-294 Started Dispensing a Drink of {drinkName}. {(SCP294.Instance.Config.ForceRandom ? "(Server Forced Random Drink)" : "")}"; 271 | SCP294Object.SetSCP294Uses(scp294, SCP294.Instance.SCP294UsesLeft[scp294] - 1); 272 | return true; 273 | } else 274 | { 275 | List stealEffects = new List() { 276 | new DrinkEffect () 277 | { 278 | EffectType = EffectType.CardiacArrest, 279 | Time = 8, 280 | EffectAmount = 1, 281 | ShouldAddIfPresent = true 282 | } 283 | }; 284 | PlayerEffectsController controller = player.ReferenceHub.playerEffectsController; 285 | foreach (DrinkEffect effect in stealEffects) 286 | { 287 | if (player.TryGetEffect(effect.EffectType, out StatusEffectBase statusEffect)) 288 | { 289 | byte newValue = (byte)Mathf.Min(255, statusEffect.Intensity + effect.EffectAmount); 290 | controller.ChangeState(statusEffect.GetType().Name, newValue, effect.Time, effect.ShouldAddIfPresent); 291 | } 292 | } 293 | player.ShowHint($"You feel queasy, as if you're missing some of your body's contents...\n(SCP-294 Backfired, Dispensing a Cup of {player.Nickname})", 5); 294 | 295 | // Found Drink 296 | player.RemoveItem(player.CurrentItem); 297 | SCP294Object.PlayDispensingSound(player, DrinkSound.Normal); 298 | Timing.CallDelayed(SCP294.Instance.Config.DispenseDelay, () => 299 | { 300 | Item drinkItem = Item.Create(ItemType.AntiSCP207); 301 | drinkItem.Scale = new Vector3(1f, 1f, 0.8f); 302 | if (SCP294.Instance.Config.SpawnInOutput) 303 | { 304 | Vector3 spawnPos = scp294.Position; 305 | spawnPos += scp294.Rotation * new Vector3(-0.375f, 1f, -0.425f); 306 | 307 | Pickup drinkPickup = SCP294Object.CreateDrinkPickup(drinkItem, spawnPos, Quaternion.Euler(-90, 0, 0)); 308 | } 309 | else 310 | { 311 | player.AddItem(drinkItem); 312 | } 313 | 314 | SCP294.Instance.CustomDrinkItems.Add(drinkItem.Serial, new DrinkInfo() 315 | { 316 | ItemSerial = drinkItem.Serial, 317 | ItemObject = drinkItem, 318 | DrinkEffects = new List() { }, 319 | DrinkMessage = "The drink tastes like blood. It's still warm.", 320 | DrinkName = player.Nickname, 321 | KillPlayer = false, 322 | KillPlayerString = "", 323 | HealAmount = 0, 324 | HealStatusEffects = false, 325 | Tantrum = false 326 | }); 327 | }); 328 | 329 | // Cooldown 330 | SCP294.Instance.SpawnedSCP294s[scp294] = true; 331 | Timing.CallDelayed(SCP294.Instance.Config.CooldownTime, () => 332 | { 333 | SCP294.Instance.SpawnedSCP294s[scp294] = false; 334 | }); 335 | 336 | response = $"SCP-294 Backfired!!! It Started Dispensing a Drink of {player.Nickname}"; 337 | SCP294Object.SetSCP294Uses(scp294, SCP294.Instance.SCP294UsesLeft[scp294] - 1); 338 | return true; 339 | } 340 | } else 341 | { 342 | // Found Drink 343 | player.RemoveItem(player.CurrentItem); 344 | if (customDrink.Explode) 345 | { 346 | // BACKFIRED!!! 347 | List stealEffects = new List() { 348 | new DrinkEffect () 349 | { 350 | EffectType = EffectType.Ensnared, 351 | Time = SCP294.Instance.Config.DispenseDelay, 352 | EffectAmount = 1, 353 | ShouldAddIfPresent = true 354 | } 355 | }; 356 | PlayerEffectsController controller = player.ReferenceHub.playerEffectsController; 357 | foreach (DrinkEffect effect in stealEffects) 358 | { 359 | if (player.TryGetEffect(effect.EffectType, out StatusEffectBase statusEffect)) 360 | { 361 | byte newValue = (byte)Mathf.Min(255, statusEffect.Intensity + effect.EffectAmount); 362 | controller.ChangeState(statusEffect.GetType().Name, newValue, effect.Time, effect.ShouldAddIfPresent); 363 | } 364 | } 365 | } 366 | SCP294Object.PlayDispensingSound(player, customDrink.Explode ? DrinkSound.Unstable : DrinkSound.Normal); 367 | Timing.CallDelayed(SCP294.Instance.Config.DispenseDelay, () => 368 | { 369 | if (customDrink.Explode) 370 | { 371 | ExplosiveGrenade grenade = (ExplosiveGrenade)Item.Create(ItemType.GrenadeHE); 372 | grenade.FuseTime = 0.1f; 373 | grenade.SpawnActive(player.Position, player); 374 | player.Kill($"Ordered a {drinkName} from SCP-294"); 375 | } else 376 | { 377 | Item drinkItem = Item.Create(customDrink.AntiColaModel ? ItemType.AntiSCP207 : ItemType.SCP207); 378 | drinkItem.Scale = new Vector3(1f, 1f, 0.8f); 379 | if (SCP294.Instance.Config.SpawnInOutput) 380 | { 381 | Vector3 spawnPos = scp294.Position; 382 | spawnPos += scp294.Rotation * new Vector3(-0.375f, 1f, -0.425f); 383 | 384 | Pickup drinkPickup = SCP294Object.CreateDrinkPickup(drinkItem, spawnPos, Quaternion.Euler(-90, 0, 0)); 385 | } 386 | else 387 | { 388 | player.AddItem(drinkItem); 389 | } 390 | 391 | SCP294.Instance.CustomDrinkItems.Add(drinkItem.Serial, new DrinkInfo() 392 | { 393 | ItemSerial = drinkItem.Serial, 394 | ItemObject = drinkItem, 395 | DrinkEffects = customDrink.DrinkEffects, 396 | DrinkMessage = customDrink.DrinkMessage, 397 | DrinkName = drinkName, 398 | KillPlayer = customDrink.KillPlayer, 399 | KillPlayerString = customDrink.KillPlayerString, 400 | HealAmount = customDrink.HealAmount, 401 | HealStatusEffects = customDrink.HealStatusEffects, 402 | Tantrum = customDrink.Tantrum, 403 | DrinkCallback = customDrink.DrinkCallback 404 | }); 405 | } 406 | }); 407 | 408 | // Cooldown 409 | SCP294.Instance.SpawnedSCP294s[scp294] = true; 410 | Timing.CallDelayed(SCP294.Instance.Config.CooldownTime, () => 411 | { 412 | SCP294.Instance.SpawnedSCP294s[scp294] = false; 413 | }); 414 | 415 | response = $"SCP-294 Started Dispensing a Drink of {drinkName}. {(SCP294.Instance.Config.ForceRandom ? "(Server Forced Random Drink)" : "")}"; 416 | SCP294Object.SetSCP294Uses(scp294, SCP294.Instance.SCP294UsesLeft[scp294] - 1); 417 | return true; 418 | } 419 | } 420 | } 421 | } 422 | response = "SCP-294 couldn't determine your drink, and refunded you your coin."; 423 | return false; 424 | } 425 | } 426 | 427 | response = "You aren't close enough to SCP-294!"; 428 | return false; 429 | } 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /Types/Config/DrinkCallbacks.cs: -------------------------------------------------------------------------------- 1 | using CustomPlayerEffects; 2 | using Exiled.API.Enums; 3 | using Exiled.API.Features; 4 | using Exiled.API.Features.Pickups.Projectiles; 5 | using MEC; 6 | using SCP294.handlers; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using UnityEngine; 14 | 15 | namespace SCP294.Types.Config 16 | { 17 | public class DrinkCallbacks 18 | { 19 | // DRINK CALLBACKS 20 | public static void DrinkKill(Player player, DrinkInfo drinkInfo) 21 | { 22 | if (player.IsScp) 23 | { 24 | player.Health -= 250; 25 | PlayerEffectsController controller = player.ReferenceHub.playerEffectsController; 26 | if (player.TryGetEffect(EffectType.CardiacArrest, out StatusEffectBase statusEffect)) 27 | { 28 | byte newValue = (byte)Mathf.Min(255, 1); 29 | controller.ChangeState(statusEffect.GetType().Name, newValue, 30f, false); 30 | } 31 | if (player.Health < 1) 32 | { 33 | player.Kill(drinkInfo.KillPlayerString); 34 | } 35 | } 36 | else 37 | { 38 | player.Kill(drinkInfo.KillPlayerString); 39 | } 40 | } 41 | public static void AlmostDeadJuice(Player player) // Almost Dead Juice 42 | { 43 | if (player.IsScp) 44 | { 45 | PlayerEffectsController controller = player.ReferenceHub.playerEffectsController; 46 | if (player.TryGetEffect(EffectType.CardiacArrest, out StatusEffectBase statusEffect)) 47 | { 48 | byte newValue = (byte)Mathf.Min(255, 1); 49 | controller.ChangeState(statusEffect.GetType().Name, newValue, 60f, false); 50 | } 51 | } else 52 | { 53 | player.Health = 1; 54 | } 55 | } 56 | public static void Tryhard(Player player) // Tryhard 57 | { 58 | Timing.CallDelayed(7.5f, () => { 59 | player.ShowHint("I don't feel so hot...", 3); 60 | }); 61 | Timing.CallDelayed(15f, () => { 62 | DrinkKill(player, new DrinkInfo() { KillPlayer = true, KillPlayerString = "Tryharded so much that they suffered a stroke and died" }); 63 | }); 64 | } 65 | public static void BallSpam(Player player) // SCP-018 66 | { 67 | Projectile.CreateAndSpawn(ItemType.SCP018, player.Position, default, player).GameObject.GetComponent().AddForce(new Vector3(500, 500, 500)); 68 | Projectile.CreateAndSpawn(ItemType.SCP018, player.Position, default, player).GameObject.GetComponent().AddForce(new Vector3(-500, 500, 500)); 69 | Projectile.CreateAndSpawn(ItemType.SCP018, player.Position, default, player).GameObject.GetComponent().AddForce(new Vector3(500, 500, -500)); 70 | Projectile.CreateAndSpawn(ItemType.SCP018, player.Position, default, player).GameObject.GetComponent().AddForce(new Vector3(-500, 500, -500)); 71 | } 72 | 73 | public static void Shrink(Player player) 74 | { 75 | ChangeSizeX(player, -0.15f); 76 | ChangeSizeY(player, -0.15f); 77 | ChangeSizeZ(player, -0.15f); 78 | LimitSizeOnDrinks(player); 79 | } 80 | public static void Grow(Player player) 81 | { 82 | ChangeSizeX(player, 0.15f); 83 | ChangeSizeY(player, 0.15f); 84 | ChangeSizeZ(player, 0.15f); 85 | LimitSizeOnDrinks(player); 86 | } 87 | 88 | public static void Wide(Player player) 89 | { 90 | ChangeSizeX(player, 0.15f); 91 | ChangeSizeZ(player, 0.15f); 92 | LimitSizeOnDrinks(player); 93 | } 94 | public static void Thin(Player player) 95 | { 96 | ChangeSizeX(player, -0.15f); 97 | ChangeSizeZ(player, -0.15f); 98 | LimitSizeOnDrinks(player); 99 | } 100 | 101 | public static void Boykisser(Player player) 102 | { 103 | Timing.CallDelayed(0.1f, () => 104 | { 105 | player.ShowHint("████████████████████████████████████████████████████████████████████████\n████████████████████████████████████████████████████████████████████\n██████████████████████████████████████████████████████\n██████████████████████████████████████████████████\n█████████████████████████████████\n█████████████████████████████\n███████████████████████████████████\n██████████████████████████████████████\n██████████████████████████████████████████████\n███████████████████████████████████████████████████████\n██████████████████████████████████████████████████████████\n█████████████████████████████████████████████████████████████████\n███████████████████████████████████████████████████████████████████\n█████████████████████████████████████████████████████████████████\n███████████████████████████████████████████████\n███████████████████████████████████████\n████████████████████████████████████\n████████████████████████████████████\n██████████████████████████████████████\n█████████████████████████████████\n██████████████████████\n███████████████████████\n██████████████████████████████\n█████████████████████████████████\n███████████████████████████████\n███████████████████████████████████\n██████████████████████████████████████████████████\n████████████████████████████████████████████████████\n███████████████████████████████████\n██████████████████████████████████████████\n███████████████████████████████████████████████████████\n██████████████████████████████████████████████████████████\n████████████████████████████████████████████████████\n██████████████████████████████████████████████████████████████████\n███████████████████████████████████████████████████████████████████\n███████████████████████████████████████████████████████████████\n███████████████████████████████████████████████████████████████████████\n█████████████████████████████████████████████████████████████\n██████████████████████████████████████████████████████████████\n████████████████████████████████████████████████████████████████████\n██████████████████████████████████████████████████████████████████\n███████████████████████████████████████████████████████████████████████\nYou like kissing boys, dont you! Youre a silly little boykisser! OwO", 5f); 106 | }); 107 | } 108 | public static void RipSoup(Player player) 109 | { 110 | Timing.CallDelayed(0.1f, () => 111 | { 112 | player.ShowHint("██████████████████████████████████████████████████████████████████████████████\n█████████████████████████████████████████████████████████████████████████\n██████████████████████████████████████████████████████████████\n██████████████████████████████████████████\n█████████████████████████████████████████\n█████████████████████████████████████████\n█████████████████████████████████\n████████████████████████████\n████████████████\n████████████████████\n██████████████████\n██████████████████\n██████████████████\n█████████████████\n████████████████\n████████████████████\n██████████████████████\n███████████████████\n█████████\n██████████████\n████████████\n██████████████████\n██████████████████████████\n████████████████████████\n█████████████████████████████\n████████████████████████████████\n██████████████████████████████\n█████████████████████████████\n███████████████████████████████████\n████████████████████████████████████████████\n███████████████████████████████████████████████\n███████████████████████████████████████████████\n███████████████████████████████████████████\n████████████████████████████████████\n████████████████████████████████████\n████████████████████████████████████\n███████████████████████████████████\n███████████████████████████████████\n████████████████████████████████\n███████████████████████████████\nRIP Soup, Fly High, Friend...", 5f); 113 | }); 114 | } 115 | 116 | // SIZE MANAGING FOR CALLBACKS 117 | public static void ChangeSizeX(Player player, float amount) 118 | { 119 | player.Scale = new Vector3(player.Scale.x + amount, player.Scale.y, player.Scale.z); 120 | } 121 | public static void ChangeSizeY(Player player, float amount) 122 | { 123 | player.Scale = new Vector3(player.Scale.x, player.Scale.y + amount, player.Scale.z); 124 | } 125 | public static void ChangeSizeZ(Player player, float amount) 126 | { 127 | player.Scale = new Vector3(player.Scale.x, player.Scale.y, player.Scale.z + amount); 128 | } 129 | public static void LimitSizeOnDrinks(Player player) 130 | { 131 | // MAX 132 | if (player.Scale.x > SCP294.Instance.Config.MaxSizeFromDrink.x) 133 | { 134 | player.Scale = new Vector3(SCP294.Instance.Config.MaxSizeFromDrink.x, player.Scale.y, player.Scale.z); 135 | } 136 | if (player.Scale.y > SCP294.Instance.Config.MaxSizeFromDrink.y) 137 | { 138 | player.Scale = new Vector3(player.Scale.x, SCP294.Instance.Config.MaxSizeFromDrink.y, player.Scale.z); 139 | } 140 | if (player.Scale.z > SCP294.Instance.Config.MaxSizeFromDrink.z) 141 | { 142 | player.Scale = new Vector3(player.Scale.x, player.Scale.y, SCP294.Instance.Config.MaxSizeFromDrink.z); 143 | } 144 | 145 | // MIN 146 | if (player.Scale.x < SCP294.Instance.Config.MinSizeFromDrink.x) 147 | { 148 | player.Scale = new Vector3(SCP294.Instance.Config.MinSizeFromDrink.x, player.Scale.y, player.Scale.z); 149 | } 150 | if (player.Scale.y < SCP294.Instance.Config.MinSizeFromDrink.y) 151 | { 152 | player.Scale = new Vector3(player.Scale.x, SCP294.Instance.Config.MinSizeFromDrink.y, player.Scale.z); 153 | } 154 | if (player.Scale.z < SCP294.Instance.Config.MinSizeFromDrink.z) 155 | { 156 | player.Scale = new Vector3(player.Scale.x, player.Scale.y, SCP294.Instance.Config.MinSizeFromDrink.z); 157 | } 158 | 159 | // VOICE 160 | SCP294.Instance.PlayerVoicePitch[player.UserId] = Mathf.Clamp((1-player.Scale.y) + 1f,0.1f,2f); 161 | } 162 | 163 | public static void Helium(Player player) 164 | { 165 | SCP294.Instance.PlayerVoicePitch[player.UserId] = 1.6f; 166 | Timing.CallDelayed(90f, () => 167 | { 168 | // VOICE 169 | SCP294.Instance.PlayerVoicePitch[player.UserId] = Mathf.Clamp((1 - player.Scale.y) + 1f, 0.1f, 2f); 170 | }); 171 | } 172 | 173 | public static void Sulfur(Player player) 174 | { 175 | SCP294.Instance.PlayerVoicePitch[player.UserId] = 0.4f; 176 | Timing.CallDelayed(90f, () => 177 | { 178 | // VOICE 179 | SCP294.Instance.PlayerVoicePitch[player.UserId] = Mathf.Clamp((1 - player.Scale.y) + 1f, 0.1f, 2f); 180 | }); 181 | } 182 | } 183 | } 184 | --------------------------------------------------------------------------------