├── .gitignore ├── BetterCoinflips ├── Commands │ ├── CoinUses │ │ ├── CoinUses.cs │ │ ├── Get │ │ │ ├── Get.cs │ │ │ ├── Serial.cs │ │ │ └── Player.cs │ │ └── Set │ │ │ ├── Set.cs │ │ │ ├── Serial.cs │ │ │ └── Player.cs │ └── GetSerial.cs ├── Properties │ └── AssemblyInfo.cs ├── Plugin.cs ├── BetterCoinflips.csproj ├── Configs │ ├── Translations.cs │ └── Config.cs ├── EventHandlers.cs └── Types │ └── CoinEffect.cs ├── BetterCoinflips.sln └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .idea/ 4 | .vs/ 5 | *.sln.DotSettings.* 6 | -------------------------------------------------------------------------------- /BetterCoinflips/Commands/CoinUses/CoinUses.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandSystem; 3 | 4 | namespace BetterCoinflips.Commands.CoinUses 5 | { 6 | [CommandHandler(typeof(RemoteAdminCommandHandler))] 7 | public class CoinUses : ParentCommand 8 | { 9 | public CoinUses() => LoadGeneratedCommands(); 10 | 11 | public override string Command { get; } = "coinuses"; 12 | public override string[] Aliases { get; } = { }; 13 | public override string Description { get; } = "Gets or sets the uses of a coin."; 14 | 15 | public override void LoadGeneratedCommands() 16 | { 17 | RegisterCommand(new Set.Set()); 18 | RegisterCommand(new Get.Get()); 19 | } 20 | 21 | protected override bool ExecuteParent(ArraySegment arguments, ICommandSender sender, out string response) 22 | { 23 | response = "Invalid subcommand. Available ones: set, get"; 24 | return false; 25 | } 26 | 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /BetterCoinflips.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.32002.261 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterCoinflips", "BetterCoinflips\BetterCoinflips.csproj", "{950DD506-45E1-41EC-A95D-A9BBA76E3117}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {950DD506-45E1-41EC-A95D-A9BBA76E3117}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {950DD506-45E1-41EC-A95D-A9BBA76E3117}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {950DD506-45E1-41EC-A95D-A9BBA76E3117}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {950DD506-45E1-41EC-A95D-A9BBA76E3117}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {B8D280C9-BC11-4FBB-A359-CFCE0AF690AB} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /BetterCoinflips/Commands/CoinUses/Get/Get.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandSystem; 3 | using Exiled.Permissions.Extensions; 4 | 5 | namespace BetterCoinflips.Commands.CoinUses.Get 6 | { 7 | public class Get : ParentCommand 8 | { 9 | public Get() => LoadGeneratedCommands(); 10 | 11 | public override string Command { get; } = "get"; 12 | public override string[] Aliases { get; } = { }; 13 | public override string Description { get; } = "Gets the uses of a coin held by the specified player or by serial."; 14 | 15 | public override void LoadGeneratedCommands() 16 | { 17 | RegisterCommand(new Player()); 18 | RegisterCommand(new Serial()); 19 | } 20 | 21 | protected override bool ExecuteParent(ArraySegment arguments, ICommandSender sender, out string response) 22 | { 23 | if (!((CommandSender)sender).CheckPermission("bc.coinuses.get")) 24 | { 25 | response = "You do not have permission to use this command"; 26 | return false; 27 | } 28 | 29 | response = "Invalid subcommand. Available ones: player, serial"; 30 | return false; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /BetterCoinflips/Commands/CoinUses/Set/Set.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandSystem; 3 | using Exiled.Permissions.Extensions; 4 | 5 | namespace BetterCoinflips.Commands.CoinUses.Set 6 | { 7 | public class Set : ParentCommand 8 | { 9 | public Set() => LoadGeneratedCommands(); 10 | 11 | public override string Command { get; } = "set"; 12 | public override string[] Aliases { get; } = { }; 13 | public override string Description { get; } = "Sets the amount of uses of a coin held by the specified player or by serial."; 14 | 15 | public override void LoadGeneratedCommands() 16 | { 17 | RegisterCommand(new Player()); 18 | RegisterCommand(new Serial()); 19 | } 20 | 21 | protected override bool ExecuteParent(ArraySegment arguments, ICommandSender sender, out string response) 22 | { 23 | if (!((CommandSender)sender).CheckPermission("bc.coinuses.set")) 24 | { 25 | response = "You do not have permission to use this command"; 26 | return false; 27 | } 28 | 29 | response = "Invalid subcommand. Available ones: player, serial"; 30 | return false; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /BetterCoinflips/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("BetterCoinflips")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("BetterCoinflips")] 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("112aafac-29cd-4adf-8469-d3771e980e02")] 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 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /BetterCoinflips/Plugin.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Features; 2 | using System; 3 | using BetterCoinflips.Configs; 4 | using Map = Exiled.Events.Handlers.Map; 5 | using Player = Exiled.Events.Handlers.Player; 6 | 7 | namespace BetterCoinflips 8 | { 9 | public class Plugin : Plugin 10 | { 11 | public override Version RequiredExiledVersion => new(9, 1, 1); 12 | public override Version Version => new(5, 0, 0); 13 | public override string Author => "Miki_hero"; 14 | public override string Name => "BetterCoinflips"; 15 | 16 | public static Plugin Instance; 17 | private EventHandlers _eventHandler; 18 | 19 | public override void OnEnabled() 20 | { 21 | Instance = this; 22 | RegisterEvents(); 23 | base.OnEnabled(); 24 | } 25 | 26 | public override void OnDisabled() 27 | { 28 | UnRegisterEvents(); 29 | Instance = null; 30 | base.OnDisabled(); 31 | } 32 | 33 | private void RegisterEvents() 34 | { 35 | _eventHandler = new EventHandlers(); 36 | Player.FlippingCoin += _eventHandler.OnCoinFlip; 37 | Map.SpawningItem += _eventHandler.OnSpawningItem; 38 | Map.FillingLocker += _eventHandler.OnFillingLocker; 39 | } 40 | 41 | private void UnRegisterEvents() 42 | { 43 | Player.FlippingCoin -= _eventHandler.OnCoinFlip; 44 | Map.SpawningItem -= _eventHandler.OnSpawningItem; 45 | Map.FillingLocker -= _eventHandler.OnFillingLocker; 46 | _eventHandler = null; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /BetterCoinflips/Commands/CoinUses/Get/Serial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandSystem; 4 | using Exiled.Permissions.Extensions; 5 | 6 | namespace BetterCoinflips.Commands.CoinUses.Get 7 | { 8 | public class Serial : ICommand 9 | { 10 | public string Command { get; } = "serial"; 11 | public string[] Aliases { get; } = { }; 12 | public string Description { get; } = "Gets the uses of a coin specified via serial number."; 13 | 14 | public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) 15 | { 16 | if (!((CommandSender)sender).CheckPermission("bc.coinuses.get")) 17 | { 18 | response = "You do not have permission to use this command"; 19 | return false; 20 | } 21 | 22 | if (arguments.Count != 1) 23 | { 24 | response = "Usage: coinuses get serial [serial]"; 25 | return false; 26 | } 27 | bool flag1 = ushort.TryParse(arguments.ElementAt(0), out ushort serial); 28 | if (!flag1) 29 | { 30 | response = $"Couldn't parse {arguments.ElementAt(0)} as serial."; 31 | return false; 32 | } 33 | 34 | if (!EventHandlers.CoinUses.ContainsKey(serial)) 35 | { 36 | response = $"Couldn't find a registered coin with the serial {serial}."; 37 | return false; 38 | } 39 | 40 | response = $"Coin with the serial number {serial} has {EventHandlers.CoinUses[serial]} uses left."; 41 | return true; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /BetterCoinflips/BetterCoinflips.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net48 4 | Library 5 | 9 6 | false 7 | 8 | 9 | true 10 | 11 | 12 | false 13 | true 14 | 15 | 16 | 17 | $(EXILED_REFERENCES)\Assembly-CSharp-firstpass.dll 18 | 19 | 20 | $(EXILED_REFERENCES)\Mirror.dll 21 | 22 | 23 | $(EXILED_REFERENCES)\UnityEngine.dll 24 | 25 | 26 | $(EXILED_REFERENCES)\UnityEngine.CoreModule.dll 27 | 28 | 29 | $(EXILED_REFERENCES)\UnityEngine.PhysicsModule.dll 30 | 31 | 32 | 33 | 34 | 35 | 2.2.2 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /BetterCoinflips/Commands/GetSerial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandSystem; 4 | using Exiled.API.Features; 5 | using Exiled.API.Features.Items; 6 | using Exiled.Permissions.Extensions; 7 | 8 | namespace BetterCoinflips.Commands 9 | { 10 | [CommandHandler(typeof(RemoteAdminCommandHandler))] 11 | public class GetSerial : ICommand, IUsageProvider 12 | { 13 | public string Command { get; } = "getserial"; 14 | public string[] Aliases { get; } = { }; 15 | public string Description { get; } = "Gets the serial number of an item."; 16 | public string[] Usage { get; } = { "id/name (Optional)"}; 17 | 18 | public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) 19 | { 20 | if (!((CommandSender)sender).CheckPermission("bc.getserial")) 21 | { 22 | response = "You do not have permission to use this command"; 23 | return false; 24 | } 25 | if (arguments.Count == 0) 26 | { 27 | Item item = Player.Get(sender).CurrentItem; 28 | if (item == null) 29 | { 30 | response = "You're not holding any items."; 31 | return false; 32 | } 33 | 34 | response = $"Item: {item.Base.name}, Serial: {item.Serial}"; 35 | return true; 36 | } 37 | 38 | if (arguments.Count == 1) 39 | { 40 | Player player = Player.Get(arguments.ElementAt(0)); 41 | if (player == null) 42 | { 43 | response = $"Player {arguments.ElementAt(0)} not found."; 44 | return false; 45 | } 46 | 47 | Item item = player.CurrentItem; 48 | if (item == null) 49 | { 50 | response = "The specified player is not holding any items."; 51 | return false; 52 | } 53 | 54 | response = $"Item: {item.Base.name}, Serial: {item.Serial}"; 55 | return true; 56 | } 57 | 58 | response = "Incorrect usage."; 59 | return false; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /BetterCoinflips/Commands/CoinUses/Set/Serial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandSystem; 4 | using Exiled.API.Features; 5 | using Exiled.Permissions.Extensions; 6 | 7 | namespace BetterCoinflips.Commands.CoinUses.Set 8 | { 9 | public class Serial : ICommand 10 | { 11 | public string Command { get; } = "serial"; 12 | public string[] Aliases { get; } = { }; 13 | public string Description { get; } = "Sets the uses of a coin specified by its serial number."; 14 | 15 | public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) 16 | { 17 | Exiled.API.Features.Player player = Exiled.API.Features.Player.Get(sender); 18 | if (!player.CheckPermission("bc.coinuses.set")) 19 | { 20 | response = "You do not have permission to use this command"; 21 | return false; 22 | } 23 | 24 | if (arguments.Count != 2) 25 | { 26 | response = "Usage: coinuses set serial [serial] [amount]"; 27 | return false; 28 | } 29 | 30 | bool flag1 = ushort.TryParse(arguments.ElementAt(0), out ushort serial); 31 | if (!flag1) 32 | { 33 | response = $"Couldn't parse {arguments.ElementAt(0)} as serial."; 34 | return false; 35 | } 36 | 37 | if (!EventHandlers.CoinUses.ContainsKey(serial)) 38 | { 39 | response = $"Couldn't find a coin with the serial {serial}."; 40 | return false; 41 | } 42 | 43 | bool flag2 = int.TryParse(arguments.ElementAt(1), out int amount); 44 | if (!flag2) 45 | { 46 | response = $"Couldn't parse {arguments.ElementAt(1)} as amount."; 47 | return false; 48 | } 49 | 50 | EventHandlers.CoinUses[serial] = amount; 51 | string message = player.DoNotTrack ? $"{player.Nickname}({player.RawUserId})" : $"{player.Nickname}"; 52 | Log.Debug($"{message} just set the uses of the coin # {serial}, to {amount}."); 53 | response = $"Successfully set the coins uses to {amount}."; 54 | return true; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /BetterCoinflips/Commands/CoinUses/Get/Player.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandSystem; 4 | using Exiled.Permissions.Extensions; 5 | 6 | namespace BetterCoinflips.Commands.CoinUses.Get 7 | { 8 | public class Player : ICommand 9 | { 10 | public string Command { get; } = "player"; 11 | public string[] Aliases { get; } = { }; 12 | public string Description { get; } = "Gets the uses of a coin held by the specified player."; 13 | 14 | public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) 15 | { 16 | if (!((CommandSender)sender).CheckPermission("bc.coinuses.get")) 17 | { 18 | response = "You do not have permission to use this command"; 19 | return false; 20 | } 21 | 22 | if (arguments.Count == 0) 23 | { 24 | Exiled.API.Features.Player player = Exiled.API.Features.Player.Get(sender); 25 | if (player.CurrentItem == null || player.CurrentItem.Type != ItemType.Coin) 26 | { 27 | response = "You are not holding a coin right now."; 28 | return false; 29 | } 30 | 31 | if (!EventHandlers.CoinUses.ContainsKey(player.CurrentItem.Serial)) 32 | { 33 | response = $"Your held coin isn't registered because it wasn't used yet."; 34 | return false; 35 | } 36 | 37 | response = $"Your held coin has {EventHandlers.CoinUses[player.CurrentItem.Serial]} uses left."; 38 | return true; 39 | } 40 | 41 | if (arguments.Count == 1) 42 | { 43 | Exiled.API.Features.Player player = Exiled.API.Features.Player.Get(arguments.ElementAt(0)); 44 | if (player == null) 45 | { 46 | response = $"Couldn't parse {arguments.ElementAt(0)} as a valid target."; 47 | return false; 48 | } 49 | if (player.CurrentItem == null || player.CurrentItem.Type != ItemType.Coin) 50 | { 51 | response = $"{player.Nickname} is not holding a coin right now."; 52 | return false; 53 | } 54 | 55 | if (!EventHandlers.CoinUses.ContainsKey(player.CurrentItem.Serial)) 56 | { 57 | response = $"{player.Nickname}'s held coin isn't registered because it wasn't used yet."; 58 | return false; 59 | } 60 | 61 | response = $"{player.Nickname}'s held coin has {EventHandlers.CoinUses[player.CurrentItem.Serial]} uses left."; 62 | return true; 63 | } 64 | 65 | response = "Usage: coinuses get player [id/name]"; 66 | return false; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /BetterCoinflips/Commands/CoinUses/Set/Player.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandSystem; 4 | using Exiled.API.Features; 5 | using Exiled.API.Features.Items; 6 | using Exiled.Permissions.Extensions; 7 | 8 | namespace BetterCoinflips.Commands.CoinUses.Set 9 | { 10 | public class Player : ICommand 11 | { 12 | public string Command { get; } = "player"; 13 | public string[] Aliases { get; } = { }; 14 | public string Description { get; } = "Sets the uses of a coin held by the specified player."; 15 | 16 | public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) 17 | { 18 | Exiled.API.Features.Player player = Exiled.API.Features.Player.Get(sender); 19 | if (!sender.CheckPermission("bc.coinuses.set")) 20 | { 21 | response = "You do not have permission to use this command"; 22 | return false; 23 | } 24 | 25 | if (arguments.Count == 1) 26 | { 27 | Item coin = GetCoinByPlayer(player); 28 | if (coin == null) 29 | { 30 | response = "You are not holding a coin."; 31 | return false; 32 | } 33 | 34 | bool flag1 = int.TryParse(arguments.ElementAt(0), out int amount); 35 | 36 | if (!flag1) 37 | { 38 | response = $"Couldn't parse {arguments.ElementAt(0)} as amount."; 39 | return false; 40 | } 41 | 42 | EventHandlers.CoinUses[coin.Serial] = amount; 43 | response = $"Successfully set the coins uses to {amount}."; 44 | return true; 45 | } 46 | 47 | if (arguments.Count == 2) 48 | { 49 | Exiled.API.Features.Player target = Exiled.API.Features.Player.Get(arguments.ElementAt(0)); 50 | if (target == null) 51 | { 52 | response = $"Couldn't parse {arguments.ElementAt(0)} as a valid target."; 53 | return false; 54 | } 55 | 56 | Item coin = GetCoinByPlayer(target); 57 | if (coin == null) 58 | { 59 | response = $"{target.Nickname} is not holding a coin."; 60 | return false; 61 | } 62 | 63 | bool flag1 = int.TryParse(arguments.ElementAt(1), out int amount); 64 | 65 | if (!flag1) 66 | { 67 | response = $"Couldn't parse {arguments.ElementAt(1)} as amount."; 68 | return false; 69 | } 70 | 71 | EventHandlers.CoinUses[coin.Serial] = amount; 72 | string message = player.DoNotTrack ? $"{player.Nickname}({player.RawUserId})" : $"{player.Nickname}"; 73 | Log.Debug($"{message} just set the uses of the coin # {coin.Serial}, to {amount}."); 74 | response = $"Successfully set the coins uses to {amount}."; 75 | return true; 76 | } 77 | 78 | response = "\nUsage: coinuses set player [id/name] [amount]\nOR: coinuses set player [amount]"; 79 | return false; 80 | } 81 | 82 | private Item GetCoinByPlayer(Exiled.API.Features.Player pl) 83 | { 84 | return pl.CurrentItem is { Type: ItemType.Coin } ? pl.CurrentItem : null; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /BetterCoinflips/Configs/Translations.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using Exiled.API.Interfaces; 4 | 5 | namespace BetterCoinflips.Configs 6 | { 7 | public class Translations : ITranslation 8 | { 9 | [Description("This is added to the effect message if the coin breaks.")] 10 | public string CoinBreaksMessage { get; set; } = "\nAlso your coin was used too much and it broke down."; 11 | 12 | [Description("The broadcast message when a coin is registered with no uses.")] 13 | public string CoinNoUsesMessage { get; set; } = "Your coin had no uses to begin with!"; 14 | 15 | public List HintMessages { get; set; } = new() 16 | { 17 | "Your coin landed on tails.", 18 | "Your coin landed on heads." 19 | }; 20 | 21 | [Description("Here you can set the message for each of these good coin effects.")] 22 | public string TossOnCooldownMessage { get; set; } = "You can't throw the coin yet."; 23 | public string RedCardMessage { get; set; } = "You acquired a Facility Manager keycard!"; 24 | public string ContainmentEngineerCardMessage { get; set; } = "You acquired a Containment Engineer keycard!"; 25 | public string MediKitMessage { get; set; } = "You received a Medical Kit!"; 26 | public string TpToEscapeMessage { get; set; } = "You can now escape! That's what you wanted right?"; 27 | public string MagicHealMessage { get; set; } = "You've been magically healed!"; 28 | public string HealthIncreaseMessage { get; set; } = "You received 10% more hp!"; 29 | public string NeatHatMessage { get; set; } = "You got a neat hat!"; 30 | public string RandomGoodEffectMessage { get; set; } = "You got a random effect."; 31 | public string OneAmmoLogicerMessage { get; set; } = "You got gun."; 32 | public string LightbulbMessage { get; set; } = "You got a shiny lightbulb!"; 33 | public string PinkCandyMessage { get; set; } = "You got a pretty candy!"; 34 | public string BadRevoMessage { get; set; } = "What is this abomination!?"; 35 | public string EmptyHidMessage { get; set; } = "DID YOU JUST GET A MICRO HID!?"; 36 | public string ForceRespawnMessage { get; set; } = "Someone respawned... probably."; 37 | public string SizeChangeMessage { get; set; } = "You got gnomed."; 38 | public string RandomItemMessage { get; set; } = "You got a random item!"; 39 | 40 | 41 | 42 | [Description("Here you can set the message for each of these bad coin effects.")] 43 | public string HpReductionMessage { get; set; } = "Your hp got reduced by 30%."; 44 | public string TpToClassDCellsMessage { get; set; } = "You got teleported to Class D cells."; 45 | public string TpToClassDCellsAfterWarheadMessage { get; set; } = "You were teleported into a radioactive zone."; 46 | public string RandomBadEffectMessage { get; set; } = "You got a random effect."; 47 | public string WarheadStopMessage { get; set; } = "The warhead has been stopped."; 48 | public string WarheadStartMessage { get; set; } = "The warhead has been started."; 49 | public string LightsOutMessage { get; set; } = "Lights out."; 50 | public string LiveGrenadeMessage { get; set; } = "Watch your head!"; 51 | public string TrollFlashMessage { get; set; } = "You heard something?"; 52 | public string TpToRandomScpMessage { get; set; } = "You were teleported to an SCP."; 53 | public string SmallDamageMessage { get; set; } = "You've lost 15hp."; 54 | public string HugeDamageMessage { get; set; } = "You've lost a lot of hp"; 55 | public string PrimedVaseMessage { get; set; } = "Your grandma paid you a visit!"; 56 | public string ShitPantsMessage { get; set; } = "You just shit your pants."; 57 | public string FakeScpKillMessage { get; set; } = "Did you just kill an SCP?!"; 58 | public string TurnIntoScpMessage { get; set; } = "Get SCP-fied LOL!"; 59 | public string InventoryResetMessage { get; set; } = "You lost your stuff."; 60 | public string ClassSwapMessage { get; set; } = "That's what I call an UNO reverse card!"; 61 | public string InstantExplosionMessage { get; set; } = "You got smoked."; 62 | public string PlayerSwapMessage { get; set; } = "Your inventory was swapped with a random player."; 63 | public string PlayerSwapIfOneAliveMessage { get; set; } = "You were supposed to switch places with someone but no one else is alive!"; 64 | public string KickMessage { get; set; } = "Bye!"; 65 | public string SpectSwapPlayerMessage { get; set; } = "You just made someone's round better!"; 66 | public string SpectSwapSpectMessage { get; set; } = "You were chosen as a random spectator to replace this player!"; 67 | public string SpectSwapNoSpectsMessage { get; set; } = "You got lucky cause there are no spectators to take your place."; 68 | public string TeslaTpMessage { get; set; } = "So you're a fan of electricity?"; 69 | public string TeslaTpAfterWarheadMessage { get; set; } = "You were teleported into a radioactive zone."; 70 | 71 | [Description("This message will be broadcast to both players.")] 72 | public string InventorySwapMessage { get; set; } = "Your inventory was swapped with a random player."; 73 | public string InventorySwapOnePlayerMessage { get; set; } = "You can't swap with anyone so you're losing health instead."; 74 | public string RandomTeleportMessage { get; set; } = "You were randomly teleported."; 75 | public string RandomTeleportWarheadDetonatedMessage { get; set; } = "Warhead is detonated so you only got a candy."; 76 | public string HandcuffMessage { get; set; } = "You were arrested for uhh commiting war crimes... or something."; 77 | } 78 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BetterCoinflips 2 | Downloads 3 | 4 | SCP:SL plugin that adds a Risk-Reward mechanic to the in-game coin. Whenever you flip a coin a 'random' effect will happen depending on the coinflips outcome. 5 | 6 | # Features: 7 | 8 | - Whenever a player flips a coin and it lands on heads one of the following will happen: 9 | 1. They will receive a Containment Engineer/Facility Manager keycard. 10 | 2. They will recevive a 'medical kit' consisting of a medkit and painkillers. 11 | 3. They will be teleported to the escape zone doors. 12 | 4. They will be healed by 25 health. 13 | 5. Their hp will be increased by 10%. 14 | 6. They will get an SCP-268. 15 | 7. They will receive a random good effect for 5 seconds. 16 | 8. They will get a Logicer with 1 ammo. 17 | 9. They will receive an SCP-2176. 18 | 10. They will receive a pink candy. 19 | 11. They will receive a revolver with the worst attachments possible. 20 | 12. They will get an empty micro hid. 21 | 13. MTF/CI will respawn immediatly. 22 | 14. Their scale will be set to 1.3/0.5/1.3. 23 | 15. They will receive a random item. 24 | 25 | - Whenever someone flips a coin and it lands on tails one of the following will happen: 26 | 1. Their hp will be reduced by 30%. 27 | 2. They will be teleported to Class D cells. 28 | 3. They will get a random bad effect for 5 seconds. 29 | 4. The Alpha Warhead will be enabled or disabled depending on it's current state. 30 | 5. Lights all across the map will be turned off for 10 seconds. 31 | 6. A live grenade will appear on their head. 32 | 7. A live flash grenade will spawn on their head. 33 | 8. They will be teleported to an SCP if there are any alive, otherwise they'll lose 15 hp. 34 | 9. They will lose all but 1 hp. 35 | 10. Thye will receive a primed SCP-244. 36 | 11. They receive an SCP-173 tantrum. 37 | 12. A fake CASSIE is sent saying that SCP-173 was killed by a Tesla gate. 38 | 13. They will be forceclassed to a random SCP. 39 | 14. Their inventory will be reset. 40 | 15. Their role will be changed to the opposite one (class d - scientist, mtf - ci etc.) 41 | 16. An instantly exploding grenade will spawn on their head. 42 | 17. They will swap places with another player. 43 | 18. They will be kicked. 44 | 19. They will be replaced by a random spectator. 45 | 20. They will be teleported to a random tesla. 46 | 21. Their inventory will be swapped with another player's inventory. 47 | 22. They will be teleported to a random room. 48 | 23. They will be handcuffed and lose their items. 49 | 50 | - The plugin will prevent the spawns of a specified amount of coins around the map. 51 | - The plugin will replace a specified amount of the chosen item (by default SCP-500) with a coin in the SCP pedestals. 52 | - The plugin will assign a random amount of uses to every thrown coin. This amount can be read or set with a command. If a coin runs out of uses it breaks. 53 | 54 | # Commands 55 | 56 | - GetSerial - gets the serial number of an item held by you or another player. 57 | - CoinUses - gets or sets the number of uses a specific coin has. Example usage: `coinuses get player 5`, `coin uses set player 4`, `coinuses set serial 10` 58 | 59 | # Permissions 60 | 61 | - bc.coinuses.set - grants access to the CoinUses Set command 62 | - bc.coinuses.get - grants access to the CoinUses Get command 63 | 64 | # Default config 65 | 66 | ```yaml 67 | better_cf: 68 | # Whether or not the plugin should be enabled. Default: true 69 | is_enabled: true 70 | # Whether or not debug logs should be shown. Default: false 71 | debug: false 72 | # The amount of base game spawned coins that should be removed. Default: 4 73 | default_coins_amount: 4 74 | # The ItemType of the item to be replaced with a coin and the amount to be replaced, the item is supposed to be something found in SCP pedestals. 75 | item_to_replace: 76 | SCP500: 2 77 | # The boundaries of the random range of throws each coin will have before it breaks. The upper bound is exclusive. 78 | min_max_default_coins: 79 | - 1 80 | - 4 81 | # Time in seconds between coin toses. 82 | coin_cooldown: 5 83 | # The duration of the broadcast informing you about your 'reward'. Default: 3 84 | broadcast_time: 3 85 | # The duration of the map blackout. Default: 10 86 | map_blackout_time: 10 87 | # The fuse time of the grenade falling on your head. Default: 3.25 88 | live_grenade_fuse_time: 3.25 89 | # List of bad effects that can be applied to the players. List available at: https://exiled-team.github.io/EXILED/api/Exiled.API.Enums.EffectType.html 90 | bad_effects: 91 | - Asphyxiated 92 | - Bleeding 93 | - Blinded 94 | - Burned 95 | - Concussed 96 | - Corroding 97 | - CardiacArrest 98 | - Deafened 99 | - Decontaminating 100 | - Disabled 101 | - Ensnared 102 | - Exhausted 103 | - Flashed 104 | - Hemorrhage 105 | - Hypothermia 106 | - InsufficientLighting 107 | - Poisoned 108 | - PocketCorroding 109 | - SeveredHands 110 | - SinkHole 111 | - Stained 112 | - Traumatized 113 | # List of good effects that can be applied to the players. List available at: https://exiled-team.github.io/EXILED/api/Exiled.API.Enums.EffectType.html 114 | good_effects: 115 | - BodyshotReduction 116 | - DamageReduction 117 | - Invigorated 118 | - Invisible 119 | - MovementBoost 120 | - RainbowTaste 121 | - Scp1853 122 | - Scp207 123 | - Vitality 124 | # The % chance of receiving a Facility Manager keycard instead of a Containment Engineer one. 125 | red_card_chance: 15 126 | # The kick reason. 127 | kick_reason: 'The coin kicked your ass.' 128 | # The chance of these good effects happening. It's a proportional chance not a % chance. 129 | keycard_chance: 20 130 | medical_kit_chance: 35 131 | tp_to_escape_chance: 5 132 | heal_chance: 10 133 | more_hp_chance: 10 134 | hat_chance: 10 135 | random_good_effect_chance: 30 136 | one_ammo_logicer_chance: 1 137 | lightbulb_chance: 15 138 | pink_candy_chance: 10 139 | bad_revo_chance: 5 140 | empty_hid_chance: 5 141 | force_respawn_chance: 15 142 | size_change_chance: 20 143 | # The chance of these bad effects happening. It's a proportional chance not a % chance. 144 | hp_reduction_chance: 20 145 | tp_to_class_d_cells_chance: 5 146 | random_bad_effect_chance: 20 147 | warhead_chance: 10 148 | lights_out_chance: 20 149 | live_he_chance: 30 150 | troll_gun_chance: 50 151 | troll_flash_chance: 50 152 | scp_tp_chance: 20 153 | one_hp_left_chance: 15 154 | primed_vase_chance: 20 155 | shit_pants_chance: 40 156 | fake_cassie_chance: 50 157 | turn_into_scp_chance: 30 158 | inventory_reset_chance: 20 159 | class_swap_chance: 10 160 | instant_explosion_chance: 10 161 | player_swap_chance: 20 162 | kick_chance: 5 163 | spect_swap_chance: 10 164 | tesla_tp_chance: 15 165 | inventory_swap_chance: 20 166 | handcuff_chance: 10 167 | random_teleport_chance: 15 168 | ``` -------------------------------------------------------------------------------- /BetterCoinflips/EventHandlers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Exiled.API.Features; 4 | using System.Linq; 5 | using BetterCoinflips.Configs; 6 | using BetterCoinflips.Types; 7 | using Exiled.API.Features.Pickups; 8 | using Exiled.Events.EventArgs.Map; 9 | using Exiled.Events.EventArgs.Player; 10 | using UnityEngine; 11 | 12 | namespace BetterCoinflips 13 | { 14 | public class EventHandlers 15 | { 16 | private static Config Config => Plugin.Instance.Config; 17 | private static Configs.Translations Translations => Plugin.Instance.Translation; 18 | private readonly System.Random _rd = new(); 19 | 20 | // ReSharper disable once FieldCanBeMadeReadOnly.Global 21 | public static Dictionary CoinUses = new(); 22 | 23 | //Dict of all good coin effect chances with an index 24 | private readonly Dictionary _goodEffectChances = new() 25 | { 26 | { 0, Config.KeycardChance }, 27 | { 1, Config.MedicalKitChance }, 28 | { 2, Config.TpToEscapeChance }, 29 | { 3, Config.HealChance }, 30 | { 4, Config.MoreHpChance }, 31 | { 5, Config.HatChance }, 32 | { 6, Config.RandomGoodEffectChance }, 33 | { 7, Config.OneAmmoLogicerChance }, 34 | { 8, Config.LightbulbChance }, 35 | { 9, Config.PinkCandyChance }, 36 | { 10, Config.BadRevoChance }, 37 | { 11, Config.EmptyHidChance }, 38 | { 12, Config.ForceRespawnChance }, 39 | { 13, Config.SizeChangeChance }, 40 | { 14, Config.RandomItemChance } 41 | }; 42 | 43 | //Dict of all bad coin effect chances with an index 44 | private readonly Dictionary _badEffectChances = new() 45 | { 46 | { 0, Config.HpReductionChance }, 47 | { 1, Config.TpToClassDCellsChance }, 48 | { 2, Config.RandomBadEffectChance }, 49 | { 3, Config.WarheadChance }, 50 | { 4, Config.LightsOutChance }, 51 | { 5, Config.LiveHeChance }, 52 | { 6, Config.TrollFlashChance }, 53 | { 7, Config.ScpTpChance }, 54 | { 8, Config.OneHpLeftChance }, 55 | { 9, Config.PrimedVaseChance }, 56 | { 10, Config.ShitPantsChance }, 57 | { 11, Config.FakeCassieChance }, 58 | { 12, Config.TurnIntoScpChance }, 59 | { 13, Config.InventoryResetChance }, 60 | { 14, Config.ClassSwapChance }, 61 | { 15, Config.InstantExplosionChance }, 62 | { 16, Config.PlayerSwapChance }, 63 | { 17, Config.KickChance }, 64 | { 18, Config.SpectSwapChance }, 65 | { 19, Config.TeslaTpChance }, 66 | { 20, Config.InventorySwapChance }, 67 | { 21, Config.RandomTeleportChance }, 68 | { 22, Config.HandcuffChance }, 69 | }; 70 | 71 | private readonly Dictionary _cooldownDict = new(); 72 | 73 | //helper method 74 | public static void SendBroadcast(Player pl, string message, bool showHint = false, bool isTails = false) 75 | { 76 | pl.Broadcast(new Exiled.API.Features.Broadcast(message, Config.BroadcastTime), true); 77 | 78 | if (showHint && Config.HintDuration > 0) 79 | { 80 | pl.ShowHint(isTails ? Translations.HintMessages.First() : Translations.HintMessages.ElementAt(1), Config.HintDuration); 81 | } 82 | } 83 | 84 | //main plugin logic 85 | public void OnCoinFlip(FlippingCoinEventArgs ev) 86 | { 87 | //broadcast message 88 | string message = ""; 89 | //used to remove the coin if uses run out, since they are checked before executing the effect 90 | bool helper = false; 91 | //check if player is on cooldown 92 | bool flag = _cooldownDict.ContainsKey(ev.Player.RawUserId) 93 | && (DateTime.UtcNow - _cooldownDict[ev.Player.RawUserId]).TotalSeconds < Config.CoinCooldown; 94 | if (flag) 95 | { 96 | ev.IsAllowed = false; 97 | SendBroadcast(ev.Player, Translations.TossOnCooldownMessage); 98 | Log.Debug($"{ev.Player.Nickname} tried to throw a coin on cooldown."); 99 | return; 100 | } 101 | 102 | //set cooldown for player 103 | _cooldownDict[ev.Player.RawUserId] = DateTime.UtcNow; 104 | 105 | //check if coin has registered uses 106 | if (!CoinUses.ContainsKey(ev.Player.CurrentItem.Serial)) 107 | { 108 | CoinUses.Add(ev.Player.CurrentItem.Serial, _rd.Next(Config.MinMaxDefaultCoins[0], Config.MinMaxDefaultCoins[1])); 109 | Log.Debug($"Registered a coin, Uses Left: {CoinUses[ev.Player.CurrentItem.Serial]}"); 110 | //check if the newly registered coin has no uses 111 | if (CoinUses[ev.Player.CurrentItem.Serial] < 1) 112 | { 113 | //remove the coin from the uses list 114 | CoinUses.Remove(ev.Player.CurrentItem.Serial); 115 | Log.Debug("Removed the coin"); 116 | if (ev.Player.CurrentItem != null) 117 | { 118 | ev.Player.RemoveHeldItem(); 119 | } 120 | SendBroadcast(ev.Player, Translations.CoinNoUsesMessage); 121 | return; 122 | } 123 | } 124 | 125 | //decrement coin uses 126 | CoinUses[ev.Player.CurrentItem.Serial]--; 127 | Log.Debug($"Uses Left: {CoinUses[ev.Player.CurrentItem.Serial]}"); 128 | 129 | //check if uses that were already registered have been set to 0 to remove the coin after executing the effect 130 | if (CoinUses[ev.Player.CurrentItem.Serial] < 1) 131 | { 132 | helper = true; 133 | } 134 | 135 | Log.Debug($"Is tails: {ev.IsTails}"); 136 | 137 | if (!ev.IsTails) 138 | { 139 | int totalChance = _goodEffectChances.Values.Sum(); 140 | int randomNum = _rd.Next(1, totalChance + 1); 141 | // Set a default value for headsEvent 142 | int headsEvent = 2; 143 | 144 | //magic loop to determine headsevent, takes into account chances for each item in the enumerated Dict 145 | foreach (KeyValuePair kvp in _goodEffectChances) 146 | { 147 | if (randomNum <= kvp.Value) 148 | { 149 | headsEvent = kvp.Key; 150 | break; 151 | } 152 | 153 | randomNum -= kvp.Value; 154 | } 155 | 156 | Log.Debug($"headsEvent = {headsEvent}"); 157 | 158 | //use headsevent to choose the effect and execute it 159 | var effect = CoinFlipEffect.GoodEffects[headsEvent]; 160 | effect.Execute(ev.Player); 161 | message = effect.Message; 162 | } 163 | if (ev.IsTails) 164 | { 165 | int totalChance = _badEffectChances.Values.Sum(); 166 | int randomNum = _rd.Next(1, totalChance + 1); 167 | // Set a default value for headsEvent 168 | int tailsEvent = 13; 169 | 170 | //magic loop to determine headsevent, takes into account chances for each item in the enumerated Dict 171 | foreach (KeyValuePair kvp in _badEffectChances) 172 | { 173 | if (randomNum <= kvp.Value) 174 | { 175 | tailsEvent = kvp.Key; 176 | break; 177 | } 178 | 179 | randomNum -= kvp.Value; 180 | } 181 | 182 | Log.Debug($"tailsEvent = {tailsEvent}"); 183 | 184 | //use tailsevent to choose the effect and execute it 185 | var effect = CoinFlipEffect.BadEffects[tailsEvent]; 186 | effect.Execute(ev.Player); 187 | message = effect.Message; 188 | } 189 | 190 | //if the coin has 0 uses remove it 191 | if (helper) 192 | { 193 | if (ev.Player.CurrentItem != null) 194 | { 195 | ev.Player.RemoveHeldItem(); 196 | } 197 | message += Translations.CoinBreaksMessage; 198 | } 199 | 200 | if (message != null) 201 | { 202 | SendBroadcast(ev.Player, message, true, ev.IsTails); 203 | } 204 | } 205 | 206 | //removing default coins 207 | public void OnSpawningItem(SpawningItemEventArgs ev) 208 | { 209 | if (Config.DefaultCoinsAmount != 0 && ev.Pickup.Type == ItemType.Coin) 210 | { 211 | Log.Debug($"Removed a coin, coins left to remove {Config.DefaultCoinsAmount}"); 212 | ev.IsAllowed = false; 213 | Config.DefaultCoinsAmount--; 214 | } 215 | } 216 | 217 | //removing locker spawning coins and replacing the chosen item in SCP pedestals 218 | public void OnFillingLocker(FillingLockerEventArgs ev) 219 | { 220 | if (ev.Pickup.Type == ItemType.Coin && Config.DefaultCoinsAmount != 0) 221 | { 222 | Log.Debug($"Removed a locker coin, coins left to remove {Config.DefaultCoinsAmount}"); 223 | ev.IsAllowed = false; 224 | Config.DefaultCoinsAmount--; 225 | } 226 | else if (ev.Pickup.Type == Config.ItemToReplace.ElementAt(0).Key 227 | && Config.ItemToReplace.ElementAt(0).Value != 0) 228 | { 229 | Log.Debug($"Placed a coin, coins left to place: {Config.ItemToReplace.ElementAt(0).Value}. Replaced item: {ev.Pickup.Type}"); 230 | ev.IsAllowed = false; 231 | Pickup.CreateAndSpawn(ItemType.Coin, ev.Pickup.Position, new Quaternion()); 232 | Config.ItemToReplace[Config.ItemToReplace.ElementAt(0).Key]--; 233 | } 234 | } 235 | } 236 | } -------------------------------------------------------------------------------- /BetterCoinflips/Configs/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using Exiled.API.Enums; 4 | using Exiled.API.Interfaces; 5 | using PlayerRoles; 6 | 7 | namespace BetterCoinflips.Configs 8 | { 9 | public class Config : IConfig 10 | { 11 | [Description("Whether or not the plugin should be enabled. Default: true")] 12 | public bool IsEnabled { get; set; } = true; 13 | 14 | [Description("Whether or not debug logs should be shown. Default: false")] 15 | public bool Debug { get; set; } = false; 16 | 17 | [Description("The amount of base game spawned coins that should be removed. Default: 4")] 18 | public int DefaultCoinsAmount { get; set; } = 4; 19 | 20 | [Description("The ItemType of the item to be replaced with a coin and the amount to be replaced, the item is supposed to be something found in SCP pedestals.")] 21 | public Dictionary ItemToReplace { get; set; } = new() 22 | { 23 | { ItemType.SCP500, 2 } 24 | }; 25 | 26 | [Description("The boundaries of the random range of throws each coin will have before it breaks. The upper bound is exclusive.")] 27 | public List MinMaxDefaultCoins { get; set; } = new() 28 | { 29 | 1, 30 | 4 31 | }; 32 | 33 | [Description("Time in seconds between coin toses.")] 34 | public double CoinCooldown { get; set; } = 5; 35 | 36 | [Description("The duration of the broadcast informing you about your 'reward'. Default: 3")] 37 | public ushort BroadcastTime { get; set; } = 3; 38 | 39 | [Description("The duration of the hint telling you if you got heads or tails. Set to 0 or less to disable.")] 40 | public float HintDuration { get; set; } = 3; 41 | 42 | [Description("The duration of the map blackout. Default: 10")] 43 | public float MapBlackoutTime { get; set; } = 10; 44 | 45 | [Description("The fuse time of the grenade falling on your head. Default: 3.25")] 46 | public double LiveGrenadeFuseTime { get; set; } = 3.25; 47 | 48 | [Description("List of bad effects that can be applied to the players. List available at: https://exiled-team.github.io/EXILED/api/Exiled.API.Enums.EffectType.html")] 49 | public HashSet BadEffects { get; set; } = new() 50 | { 51 | EffectType.Asphyxiated, 52 | EffectType.Bleeding, 53 | EffectType.Blinded, 54 | EffectType.Burned, 55 | EffectType.Concussed, 56 | EffectType.Corroding, 57 | EffectType.CardiacArrest, 58 | EffectType.Deafened, 59 | EffectType.Decontaminating, 60 | EffectType.Disabled, 61 | EffectType.Ensnared, 62 | EffectType.Exhausted, 63 | EffectType.Flashed, 64 | EffectType.Hemorrhage, 65 | EffectType.Hypothermia, 66 | EffectType.InsufficientLighting, 67 | EffectType.Poisoned, 68 | EffectType.PocketCorroding, 69 | EffectType.SeveredHands, 70 | EffectType.SinkHole, 71 | EffectType.Stained, 72 | EffectType.Traumatized 73 | }; 74 | 75 | [Description("List of good effects that can be applied to the players. List available at: https://exiled-team.github.io/EXILED/api/Exiled.API.Enums.EffectType.html")] 76 | public HashSet GoodEffects { get; set; } = new() 77 | { 78 | EffectType.BodyshotReduction, 79 | EffectType.DamageReduction, 80 | EffectType.Invigorated, 81 | EffectType.Invisible, 82 | EffectType.MovementBoost, 83 | EffectType.RainbowTaste, 84 | EffectType.Scp1853, 85 | EffectType.Scp207, 86 | EffectType.Vitality 87 | }; 88 | 89 | [Description("The % chance of receiving a Facility Manager keycard instead of a Containment Engineer one.")] 90 | public int RedCardChance { get; set; } = 15; 91 | 92 | [Description("The kick reason.")] 93 | public string KickReason { get; set; } = "The coin kicked your ass."; 94 | 95 | [Description("The list of SCP's that you can turn into by using the coin.")] 96 | public HashSet ValidScps { get; set; } = new() 97 | { 98 | RoleTypeId.Scp049, 99 | RoleTypeId.Scp096, 100 | RoleTypeId.Scp106, 101 | RoleTypeId.Scp173, 102 | RoleTypeId.Scp0492, 103 | RoleTypeId.Scp939, 104 | }; 105 | 106 | [Description("List of ignored roles for the PlayerSwap effect (#17)")] 107 | public HashSet PlayerSwapIgnoredRoles { get; set; } = new() 108 | { 109 | RoleTypeId.Spectator, 110 | RoleTypeId.Filmmaker, 111 | RoleTypeId.Overwatch, 112 | RoleTypeId.Scp079, 113 | RoleTypeId.Tutorial, 114 | }; 115 | 116 | [Description("List of ignored roles for the InventorySwap effect (#17)")] 117 | public HashSet InventorySwapIgnoredRoles { get; set; } = new() 118 | { 119 | RoleTypeId.Spectator, 120 | RoleTypeId.Filmmaker, 121 | RoleTypeId.Overwatch, 122 | RoleTypeId.Scp079, 123 | RoleTypeId.Tutorial, 124 | RoleTypeId.Scp049, 125 | RoleTypeId.Scp079, 126 | RoleTypeId.Scp096, 127 | RoleTypeId.Scp106, 128 | RoleTypeId.Scp173, 129 | RoleTypeId.Scp0492, 130 | RoleTypeId.Scp939, 131 | RoleTypeId.Scp3114, 132 | }; 133 | 134 | public HashSet ItemsToGive { get; set; } = new() 135 | { 136 | ItemType.Adrenaline, 137 | ItemType.Coin, 138 | ItemType.Flashlight, 139 | ItemType.Jailbird, 140 | ItemType.Medkit, 141 | ItemType.Painkillers, 142 | ItemType.Radio, 143 | ItemType.ArmorCombat, 144 | ItemType.ArmorHeavy, 145 | ItemType.ArmorLight, 146 | ItemType.GrenadeFlash, 147 | ItemType.GrenadeHE, 148 | ItemType.GunA7, 149 | ItemType.GunCom45, 150 | ItemType.GunCrossvec, 151 | ItemType.GunLogicer, 152 | ItemType.GunRevolver, 153 | ItemType.GunShotgun, 154 | ItemType.GunAK, 155 | ItemType.GunCOM15, 156 | ItemType.GunCOM18, 157 | ItemType.GunE11SR, 158 | ItemType.GunFSP9, 159 | ItemType.GunFRMG0, 160 | }; 161 | 162 | public HashSet RoomsToTeleport { get; set; } = new() 163 | { 164 | RoomType.EzCafeteria, 165 | RoomType.EzCheckpointHallwayA, 166 | RoomType.EzCheckpointHallwayB, 167 | RoomType.EzCollapsedTunnel, 168 | RoomType.EzConference, 169 | RoomType.EzCrossing, 170 | RoomType.EzCurve, 171 | RoomType.EzDownstairsPcs, 172 | RoomType.EzGateA, 173 | RoomType.EzGateB, 174 | RoomType.EzIntercom, 175 | RoomType.EzPcs, 176 | RoomType.EzStraight, 177 | RoomType.EzTCross, 178 | RoomType.EzUpstairsPcs, 179 | RoomType.EzVent, 180 | RoomType.Hcz049, 181 | RoomType.Hcz079, 182 | RoomType.Hcz096, 183 | RoomType.Hcz106, 184 | RoomType.Hcz939, 185 | RoomType.HczArmory, 186 | RoomType.HczCrossing, 187 | RoomType.HczCurve, 188 | RoomType.HczElevatorA, 189 | RoomType.HczElevatorB, 190 | RoomType.HczEzCheckpointA, 191 | RoomType.HczEzCheckpointB, 192 | RoomType.HczHid, 193 | RoomType.HczNuke, 194 | RoomType.HczStraight, 195 | RoomType.HczTesla, 196 | RoomType.HczTestRoom, 197 | RoomType.Lcz173, 198 | RoomType.Lcz330, 199 | RoomType.Lcz914, 200 | RoomType.LczAirlock, 201 | RoomType.LczArmory, 202 | RoomType.LczCafe, 203 | RoomType.LczCheckpointA, 204 | RoomType.LczCheckpointB, 205 | RoomType.LczClassDSpawn, 206 | RoomType.LczCrossing, 207 | RoomType.LczCurve, 208 | RoomType.LczGlassBox, 209 | RoomType.LczPlants, 210 | RoomType.LczStraight, 211 | RoomType.LczTCross, 212 | RoomType.LczToilets, 213 | RoomType.Surface, 214 | }; 215 | 216 | [Description("The chance of these good effects happening. It's a proportional chance not a % chance.")] 217 | public int KeycardChance { get; set; } = 20; 218 | public int MedicalKitChance { get; set; } = 35; 219 | public int TpToEscapeChance { get; set; } = 5; 220 | public int HealChance { get; set; } = 10; 221 | public int MoreHpChance { get; set; } = 10; 222 | public int HatChance { get; set; } = 10; 223 | public int RandomGoodEffectChance { get; set; } = 30; 224 | public int OneAmmoLogicerChance { get; set; } = 1; 225 | public int LightbulbChance { get; set; } = 15; 226 | public int PinkCandyChance { get; set; } = 10; 227 | public int BadRevoChance { get; set; } = 5; 228 | public int EmptyHidChance { get; set; } = 5; 229 | public int ForceRespawnChance { get; set; } = 15; 230 | public int SizeChangeChance { get; set; } = 20; 231 | public int RandomItemChance { get; set; } = 35; 232 | 233 | [Description("The chance of these bad effects happening. It's a proportional chance not a % chance.")] 234 | public int HpReductionChance { get; set; } = 20; 235 | public int TpToClassDCellsChance { get; set; } = 5; 236 | public int RandomBadEffectChance { get; set; } = 20; 237 | public int WarheadChance { get; set; } = 10; 238 | public int LightsOutChance { get; set; } = 20; 239 | public int LiveHeChance { get; set; } = 30; 240 | public int TrollFlashChance { get; set; } = 50; 241 | public int ScpTpChance { get; set; } = 20; 242 | public int OneHpLeftChance { get; set; } = 15; 243 | public int PrimedVaseChance { get; set; } = 20; 244 | public int ShitPantsChance { get; set; } = 40; 245 | public int FakeCassieChance { get; set; } = 50; 246 | public int TurnIntoScpChance { get; set; } = 30; 247 | public int InventoryResetChance { get; set; } = 20; 248 | public int ClassSwapChance { get; set; } = 10; 249 | public int InstantExplosionChance { get; set; } = 10; 250 | public int PlayerSwapChance { get; set; } = 20; 251 | public int KickChance { get; set; } = 5; 252 | public int SpectSwapChance { get; set; } = 10; 253 | public int TeslaTpChance { get; set; } = 15; 254 | public int InventorySwapChance { get; set; } = 20; 255 | public int HandcuffChance { get; set; } = 10; 256 | public int RandomTeleportChance { get; set; } = 15; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /BetterCoinflips/Types/CoinEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using BetterCoinflips.Configs; 5 | using Exiled.API.Enums; 6 | using Exiled.API.Extensions; 7 | using Exiled.API.Features; 8 | using Exiled.API.Features.Doors; 9 | using Exiled.API.Features.Items; 10 | using Exiled.API.Features.Pickups; 11 | using Exiled.API.Features.Waves; 12 | using InventorySystem.Items.Firearms.Attachments; 13 | using MEC; 14 | using PlayerRoles; 15 | using Respawning; 16 | using Respawning.Waves; 17 | using UnityEngine; 18 | using Player = Exiled.API.Features.Player; 19 | 20 | namespace BetterCoinflips.Types 21 | { 22 | public class CoinFlipEffect 23 | { 24 | private static Config Config => Plugin.Instance.Config; 25 | private static Configs.Translations Translations => Plugin.Instance.Translation; 26 | private static readonly System.Random Rd = new(); 27 | 28 | public Action Execute { get; set; } 29 | public string Message { get; set; } 30 | 31 | public CoinFlipEffect(string message, Action execute) 32 | { 33 | Execute = execute; 34 | Message = message; 35 | } 36 | 37 | private static readonly Dictionary _scpNames = new() 38 | { 39 | { "1 7 3", "SCP-173"}, 40 | { "9 3 9", "SCP-939"}, 41 | { "0 9 6", "SCP-096"}, 42 | { "0 7 9", "SCP-079"}, 43 | { "0 4 9", "SCP-049"}, 44 | { "1 0 6", "SCP-106"} 45 | }; 46 | 47 | private static bool flag1 = Config.RedCardChance > Rd.Next(1, 101); 48 | 49 | // GoodEffects list 50 | public static List GoodEffects = new() 51 | { 52 | // 0: Spawns a red keycard or containment enginner 53 | new CoinFlipEffect(flag1 ? Translations.RedCardMessage : Translations.ContainmentEngineerCardMessage, player => 54 | { 55 | Pickup.CreateAndSpawn(flag1 ? ItemType.KeycardFacilityManager : ItemType.KeycardContainmentEngineer, player.Position, new Quaternion()); 56 | }), 57 | 58 | // 1: Spawns a medkit and painkillers for the player. 59 | new CoinFlipEffect(Translations.MediKitMessage, player => 60 | { 61 | Pickup.CreateAndSpawn(ItemType.Medkit, player.Position, new Quaternion()); 62 | Pickup.CreateAndSpawn(ItemType.Painkillers, player.Position, new Quaternion()); 63 | }), 64 | 65 | // 2: Teleports the player to the escape secondary door. 66 | new CoinFlipEffect(Translations.TpToEscapeMessage, player => 67 | { 68 | player.Teleport(Door.Get(DoorType.EscapeSecondary)); 69 | }), 70 | 71 | // 3: Heals the player by 25 health points. 72 | new CoinFlipEffect(Translations.MagicHealMessage, player => 73 | { 74 | player.Heal(25); 75 | }), 76 | 77 | // 4: Increases the player's health by 10%. 78 | new CoinFlipEffect(Translations.HealthIncreaseMessage, player => 79 | { 80 | player.Health *= 1.1f; 81 | }), 82 | 83 | // 5: Spawns SCP-268 (Neat Hat) for the player. 84 | new CoinFlipEffect(Translations.NeatHatMessage, player => 85 | { 86 | Pickup.CreateAndSpawn(ItemType.SCP268, player.Position, new Quaternion()); 87 | }), 88 | 89 | // 6: Applies a random good effect to the player. 90 | new CoinFlipEffect(Translations.RandomGoodEffectMessage, player => 91 | { 92 | var effect = Config.GoodEffects.ToList().RandomItem(); 93 | player.EnableEffect(effect, 5, true); 94 | Log.Debug($"Chosen random effect: {effect}"); 95 | }), 96 | 97 | // 7: Spawns a Logicer with one ammo for the player. 98 | new CoinFlipEffect(Translations.OneAmmoLogicerMessage, player => 99 | { 100 | Firearm gun = (Firearm)Item.Create(ItemType.GunLogicer); 101 | gun.BarrelAmmo = 1; 102 | gun.CreatePickup(player.Position); 103 | }), 104 | 105 | // 8: Spawns SCP-2176 (lightbulb) for the player. 106 | new CoinFlipEffect(Translations.LightbulbMessage, player => 107 | { 108 | Pickup.CreateAndSpawn(ItemType.SCP2176, player.Position, new Quaternion()); 109 | }), 110 | 111 | // 9: Spawns pink candy (SCP-330) for the player. 112 | new CoinFlipEffect(Translations.PinkCandyMessage, player => 113 | { 114 | Scp330 candy = (Scp330)Item.Create(ItemType.SCP330); 115 | candy.AddCandy(InventorySystem.Items.Usables.Scp330.CandyKindID.Pink); 116 | candy.CreatePickup(player.Position); 117 | }), 118 | 119 | // 10: Spawns a customized revolver with attachments for the player. 120 | new CoinFlipEffect(Translations.BadRevoMessage, player => 121 | { 122 | Firearm revo = (Firearm)Item.Create(ItemType.GunRevolver); 123 | revo.AddAttachment(new[] 124 | {AttachmentName.CylinderMag7, AttachmentName.ShortBarrel, AttachmentName.ScopeSight}); 125 | revo.CreatePickup(player.Position); 126 | }), 127 | 128 | // 11: Spawns a MicroHID with no energy for the player. 129 | new CoinFlipEffect(Translations.EmptyHidMessage, player => 130 | { 131 | MicroHIDPickup item = (MicroHIDPickup)Pickup.Create(ItemType.MicroHID); 132 | item.Position = player.Position; 133 | item.Spawn(); 134 | item.Energy = 0; 135 | }), 136 | 137 | // 12: Forces a respawn wave of the team that has more ticketes 138 | new CoinFlipEffect(Translations.ForceRespawnMessage, player => 139 | { 140 | Respawn.ForceWave(WaveManager.Waves.RandomItem()); 141 | }), 142 | 143 | // 13: Changes the player's size 144 | new CoinFlipEffect(Translations.SizeChangeMessage, player => 145 | { 146 | player.Scale = new Vector3(1.13f, 0.5f, 1.13f); 147 | }), 148 | 149 | // 14: Spawns a random item for the player. 150 | new CoinFlipEffect(Translations.RandomItemMessage, player => 151 | { 152 | Item.Create(Config.ItemsToGive.ToList().RandomItem()).CreatePickup(player.Position); 153 | }), 154 | }; 155 | 156 | 157 | // BadEffects list 158 | public static List BadEffects = new() 159 | { 160 | // 0: Reduces player's health by 30% 161 | new CoinFlipEffect(Translations.HpReductionMessage, player => 162 | { 163 | if ((int) player.Health == 1) 164 | player.Kill(DamageType.CardiacArrest); 165 | else 166 | player.Health *= 0.7f; 167 | }), 168 | 169 | // 1: Teleports the player to the class D cells. 170 | new CoinFlipEffect(Warhead.IsDetonated ? Translations.TpToClassDCellsAfterWarheadMessage : Translations.TpToClassDCellsMessage, player => 171 | { 172 | player.DropHeldItem(); 173 | player.Teleport(Door.Get(DoorType.PrisonDoor)); 174 | 175 | if (Warhead.IsDetonated) 176 | { 177 | player.Kill(DamageType.Decontamination); 178 | } 179 | }), 180 | 181 | // 2: Applies a random bad effect to the player. 182 | new CoinFlipEffect(Translations.RandomBadEffectMessage, player => 183 | { 184 | var effect = Config.BadEffects.ToList().RandomItem(); 185 | 186 | //prevents players from staying in PD infinitely 187 | if (effect == EffectType.PocketCorroding) 188 | player.EnableEffect(EffectType.PocketCorroding); 189 | else 190 | player.EnableEffect(effect, 5, true); 191 | 192 | Log.Debug($"Chosen random effect: {effect}"); 193 | }), 194 | 195 | // 3: Starts or stops the warhead based on its state. 196 | new CoinFlipEffect(Warhead.IsDetonated || !Warhead.IsInProgress ? Translations.WarheadStartMessage : Translations.WarheadStopMessage, player => 197 | { 198 | if (Warhead.IsDetonated || !Warhead.IsInProgress) 199 | Warhead.Start(); 200 | else 201 | Warhead.Stop(); 202 | }), 203 | 204 | // 4: Turns off all lights 205 | new CoinFlipEffect(Translations.LightsOutMessage, player => 206 | { 207 | Map.TurnOffAllLights(Config.MapBlackoutTime); 208 | }), 209 | 210 | // 5: Spawns a live HE grenade 211 | new CoinFlipEffect(Translations.LiveGrenadeMessage, player => 212 | { 213 | ExplosiveGrenade grenade = (ExplosiveGrenade) Item.Create(ItemType.GrenadeHE); 214 | grenade.FuseTime = (float) Config.LiveGrenadeFuseTime; 215 | grenade.SpawnActive(player.Position + Vector3.up, player); 216 | }), 217 | 218 | // 6: Spawns a flash grenade with a short fuse time, sets the flash owner to the player so that it hopefully blinds people 219 | new CoinFlipEffect(Translations.TrollFlashMessage, player => 220 | { 221 | FlashGrenade flash = (FlashGrenade) Item.Create(ItemType.GrenadeFlash, player); 222 | flash.FuseTime = 1f; 223 | flash.SpawnActive(player.Position); 224 | }), 225 | 226 | // 7: Teleports the player to a random SCP or inflicts damage if no SCPs exist. 227 | new CoinFlipEffect(Player.Get(Side.Scp).Any(x => x.Role.Type != RoleTypeId.Scp079) ? Translations.TpToRandomScpMessage : Translations.SmallDamageMessage, player => 228 | { 229 | if (Player.Get(Side.Scp).Any(x => x.Role.Type != RoleTypeId.Scp079)) 230 | { 231 | Player scpPlayer = Player.Get(Side.Scp).Where(x => x.Role.Type != RoleTypeId.Scp079).ToList().RandomItem(); 232 | player.Position = scpPlayer.Position; 233 | return; 234 | } 235 | player.Hurt(15); 236 | }), 237 | 238 | // 8: Sets player hp to 1 or kills if it was already 1 239 | new CoinFlipEffect(Translations.HugeDamageMessage, player => 240 | { 241 | if ((int) player.Health == 1) 242 | player.Kill(DamageType.CardiacArrest); 243 | else 244 | player.Health = 1; 245 | }), 246 | 247 | // 9: Spawns a primed SCP-244 vase for the player. 248 | new CoinFlipEffect(Translations.PrimedVaseMessage, player => 249 | { 250 | Scp244 vase = (Scp244)Item.Create(ItemType.SCP244a); 251 | vase.Primed = true; 252 | vase.CreatePickup(player.Position); 253 | }), 254 | 255 | // 10: Spawns a tantrum on the player Keywords: shit spawn create 256 | new CoinFlipEffect(Translations.ShitPantsMessage, player => 257 | { 258 | player.PlaceTantrum(); 259 | }), 260 | 261 | // 11: Broadcasts a fake SCP termination message. 262 | new CoinFlipEffect(Translations.FakeScpKillMessage, player => 263 | { 264 | var scpName = _scpNames.ToList().RandomItem(); 265 | 266 | Cassie.MessageTranslated($"scp {scpName.Key} successfully terminated by automatic security system", 267 | $"{scpName.Value} successfully terminated by Automatic Security System."); 268 | }), 269 | 270 | // 12: Forceclass the player to a random scp from the list Keywords: scp fc forceclass 271 | new CoinFlipEffect(Translations.TurnIntoScpMessage, player => 272 | { 273 | player.DropItems(); 274 | player.Scale = new Vector3(1, 1, 1); 275 | 276 | var randomScp = Config.ValidScps.ToList().RandomItem(); 277 | player.Role.Set(randomScp, RoleSpawnFlags.AssignInventory); 278 | 279 | //prevents the player from staying in PD forever 280 | if (player.CurrentRoom.Type == RoomType.Pocket) 281 | player.EnableEffect(EffectType.PocketCorroding); 282 | }), 283 | 284 | // 13: Resets player's inventory 285 | new CoinFlipEffect(Translations.InventoryResetMessage, player => 286 | { 287 | player.DropHeldItem(); 288 | player.ClearInventory(); 289 | }), 290 | 291 | // 14: Flips the players role to the opposite 292 | new CoinFlipEffect(Translations.ClassSwapMessage, player => 293 | { 294 | player.DropItems(); 295 | switch (player.Role.Type) 296 | { 297 | case RoleTypeId.Scientist: 298 | player.Role.Set(RoleTypeId.ClassD, RoleSpawnFlags.AssignInventory); 299 | break; 300 | case RoleTypeId.ClassD: 301 | player.Role.Set(RoleTypeId.Scientist, RoleSpawnFlags.AssignInventory); 302 | break; 303 | case RoleTypeId.ChaosConscript: 304 | case RoleTypeId.ChaosRifleman: 305 | player.Role.Set(RoleTypeId.NtfSergeant, RoleSpawnFlags.AssignInventory); 306 | break; 307 | case RoleTypeId.ChaosMarauder: 308 | case RoleTypeId.ChaosRepressor: 309 | player.Role.Set(RoleTypeId.NtfCaptain, RoleSpawnFlags.AssignInventory); 310 | break; 311 | case RoleTypeId.FacilityGuard: 312 | player.Role.Set(RoleTypeId.ChaosRifleman, RoleSpawnFlags.AssignInventory); 313 | break; 314 | case RoleTypeId.NtfPrivate: 315 | case RoleTypeId.NtfSergeant: 316 | case RoleTypeId.NtfSpecialist: 317 | player.Role.Set(RoleTypeId.ChaosRifleman, RoleSpawnFlags.AssignInventory); 318 | break; 319 | case RoleTypeId.NtfCaptain: 320 | List roles = new List 321 | { 322 | RoleTypeId.ChaosMarauder, 323 | RoleTypeId.ChaosRepressor 324 | }; 325 | player.Role.Set(roles.RandomItem(), RoleSpawnFlags.AssignInventory); 326 | break; 327 | } 328 | 329 | //prevents the player from staying in PD forever 330 | if (player.CurrentRoom.Type == RoomType.Pocket) 331 | { 332 | player.EnableEffect(EffectType.PocketCorroding); 333 | } 334 | }), 335 | 336 | // 15: Spawns an HE grenade with a very short fuse time 337 | new CoinFlipEffect(Translations.InstantExplosionMessage, player => 338 | { 339 | ExplosiveGrenade instaBoom = (ExplosiveGrenade) Item.Create(ItemType.GrenadeHE); 340 | instaBoom.FuseTime = 0.1f; 341 | instaBoom.SpawnActive(player.Position, player); 342 | }), 343 | 344 | // 16: Swaps positions with another random player 345 | new CoinFlipEffect(Player.List.Count(x => x.IsAlive && !Config.PlayerSwapIgnoredRoles.Contains(x.Role.Type)) <= 1 ? Translations.PlayerSwapIfOneAliveMessage : Translations.PlayerSwapMessage, player => 346 | { 347 | var playerList = Player.List.Where(x => x.IsAlive && !Config.PlayerSwapIgnoredRoles.Contains(x.Role.Type)).ToList(); 348 | playerList.Remove(player); 349 | 350 | if (playerList.IsEmpty()) 351 | { 352 | return; 353 | } 354 | 355 | var targetPlayer = playerList.RandomItem(); 356 | var pos = targetPlayer.Position; 357 | 358 | targetPlayer.Teleport(player.Position); 359 | player.Teleport(pos); 360 | 361 | EventHandlers.SendBroadcast(targetPlayer, Translations.PlayerSwapMessage); 362 | }), 363 | 364 | // 17: kicks the player 365 | new CoinFlipEffect(Translations.KickMessage, player => 366 | { 367 | //delay so the broadcast can be sent to the player and doesn't throw NRE 368 | Timing.CallDelayed(1f, () => player.Kick(Config.KickReason)); 369 | }), 370 | 371 | // 18: swap with a spectator 372 | new CoinFlipEffect(Player.List.Where(x => x.Role.Type == RoleTypeId.Spectator).IsEmpty() ? Translations.SpectSwapNoSpectsMessage : Translations.SpectSwapPlayerMessage, player => 373 | { 374 | var spectList = Player.List.Where(x => x.Role.Type == RoleTypeId.Spectator).ToList(); 375 | 376 | if (spectList.IsEmpty()) 377 | { 378 | return; 379 | } 380 | 381 | var spect = spectList.RandomItem(); 382 | 383 | spect.Role.Set(player.Role.Type, RoleSpawnFlags.None); 384 | spect.Teleport(player); 385 | spect.Health = player.Health; 386 | 387 | List playerItems = player.Items.Select(item => item.Type).ToList(); 388 | 389 | foreach (var item in playerItems) 390 | { 391 | spect.AddItem(item); 392 | } 393 | 394 | 395 | //give spect the players ammo, has to be done before ClearInventory() or else ammo will fall on the floor 396 | for (int i = 0; i < player.Ammo.Count; i++) 397 | { 398 | spect.AddAmmo(player.Ammo.ElementAt(i).Key.GetAmmoType(), player.Ammo.ElementAt(i).Value); 399 | player.SetAmmo(player.Ammo.ElementAt(i).Key.GetAmmoType(), 0); 400 | } 401 | 402 | player.ClearInventory(); 403 | player.Role.Set(RoleTypeId.Spectator); 404 | 405 | EventHandlers.SendBroadcast(spect, Translations.SpectSwapSpectMessage); 406 | }), 407 | 408 | // 19: Teleports to a random Tesla gate if warhead is not detonated 409 | new CoinFlipEffect(Warhead.IsDetonated ? Translations.TeslaTpAfterWarheadMessage : Translations.TeslaTpMessage, player => 410 | { 411 | player.DropHeldItem(); 412 | 413 | player.Teleport(Exiled.API.Features.TeslaGate.List.ToList().RandomItem()); 414 | 415 | if (Warhead.IsDetonated) 416 | { 417 | player.Kill(DamageType.Decontamination); 418 | } 419 | }), 420 | 421 | // 20: Swaps inventory and ammo with another random player 422 | new CoinFlipEffect(Player.List.Where(x => !Config.InventorySwapIgnoredRoles.Contains(x.Role.Type)).Count(x => x.IsAlive) <= 1 ? Translations.InventorySwapOnePlayerMessage : Translations.InventorySwapMessage, player => 423 | { 424 | List playerList = Player.List.Where(x => x != player && !Config.InventorySwapIgnoredRoles.Contains(x.Role.Type)).ToList(); 425 | 426 | if (playerList.Count(x => x.IsAlive) <= 1) 427 | { 428 | player.Hurt(50); 429 | return; 430 | } 431 | 432 | var target = playerList.Where(x => x != player).ToList().RandomItem(); 433 | 434 | // Saving items 435 | List items1 = player.Items.Select(item => item.Type).ToList(); 436 | List items2 = target.Items.Select(item => item.Type).ToList(); 437 | 438 | // Saving and removing ammo 439 | Dictionary ammo1 = new(); 440 | Dictionary ammo2 = new(); 441 | for (int i = 0; i < player.Ammo.Count; i++) 442 | { 443 | ammo1.Add(player.Ammo.ElementAt(i).Key.GetAmmoType(), player.Ammo.ElementAt(i).Value); 444 | player.SetAmmo(ammo1.ElementAt(i).Key, 0); 445 | } 446 | for (int i = 0; i < target.Ammo.Count; i++) 447 | { 448 | ammo2.Add(target.Ammo.ElementAt(i).Key.GetAmmoType(), target.Ammo.ElementAt(i).Value); 449 | target.SetAmmo(ammo2.ElementAt(i).Key, 0); 450 | } 451 | 452 | // setting items 453 | target.ResetInventory(items1); 454 | player.ResetInventory(items2); 455 | 456 | // setting ammo 457 | foreach (var ammo in ammo2) 458 | { 459 | player.SetAmmo(ammo.Key, ammo.Value); 460 | } 461 | foreach (var ammo in ammo1) 462 | { 463 | target.SetAmmo(ammo.Key, ammo.Value); 464 | } 465 | 466 | EventHandlers.SendBroadcast(target, Translations.InventorySwapMessage); 467 | }), 468 | 469 | // 21: Spawns a red candy or teleports the player to a random room based on warhead state. 470 | new CoinFlipEffect(Warhead.IsDetonated ? Translations.RandomTeleportWarheadDetonatedMessage : Translations.RandomTeleportMessage, player => 471 | { 472 | if (Warhead.IsDetonated) 473 | { 474 | Scp330 candy = (Scp330)Item.Create(ItemType.SCP330); 475 | candy.AddCandy(InventorySystem.Items.Usables.Scp330.CandyKindID.Red); 476 | candy.CreatePickup(player.Position); 477 | return; 478 | } 479 | 480 | player.Teleport(Room.Get(Config.RoomsToTeleport.GetRandomValue())); 481 | }), 482 | 483 | // 22: Handcuffs the player and drops their items 484 | new CoinFlipEffect(Translations.HandcuffMessage, player => 485 | { 486 | player.Handcuff(); 487 | player.DropItems(); 488 | }), 489 | }; 490 | } 491 | } 492 | --------------------------------------------------------------------------------