├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── Common Utilities.sln ├── Common Utilities.sln.DotSettings.user ├── Common Utilities ├── API.cs ├── Common Utilities.csproj ├── Config.cs ├── ConfigObjects │ ├── ItemChance.cs │ ├── ItemUpgradeChance.cs │ ├── PlayerUpgradeChance.cs │ ├── Scp914EffectChance.cs │ ├── Scp914TeleportChance.cs │ └── StartingAmmo.cs ├── Configs │ └── RoleInventory.cs ├── EventHandlers │ ├── MapHandlers.cs │ ├── PlayerHandlers.cs │ └── ServerHandlers.cs ├── Main.cs └── Patches │ └── ServerGrantLoadoutPatches.cs ├── Directory.Build.props ├── Exiled.ruleset ├── Plugin.props ├── README.md ├── Updating.md └── stylecop.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: needs testing 6 | assignees: joker-119 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Server logs** 24 | Please include a pastebin of your localadmin log file (or both MA_log and SCP_log files if you use MultiAdmin) from the time in which the bug occured 25 | 26 | **Copy of current CU config section** 27 | 28 | 29 | **EXILED Version ("latest" is not a version):** 30 | 31 | 32 | **Results of `show plugins` command in console:** 33 | 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Common Utilities/bin/* 2 | /Common Utilities/obj/* 3 | /Common Utilities.sln.DotSettings.user 4 | /packages 5 | /packages/* 6 | /.idea/* 7 | /.vs/* 8 | /bin/* 9 | /obj/* 10 | -------------------------------------------------------------------------------- /Common Utilities.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common Utilities", "Common Utilities\Common Utilities.csproj", "{23AB2900-9C8B-4F23-A3B6-D92EAEE2F88C}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {23AB2900-9C8B-4F23-A3B6-D92EAEE2F88C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {23AB2900-9C8B-4F23-A3B6-D92EAEE2F88C}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {23AB2900-9C8B-4F23-A3B6-D92EAEE2F88C}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {23AB2900-9C8B-4F23-A3B6-D92EAEE2F88C}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /Common Utilities.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True 12 | True 13 | True 14 | True 15 | True 16 | True 17 | True 18 | True 19 | True 20 | True 21 | True 22 | True 23 | True 24 | True 25 | True 26 | True 27 | True 28 | True 29 | True 30 | True 31 | True 32 | True 33 | True 34 | True 35 | True 36 | True 37 | True 38 | True 39 | True 40 | True 41 | True 42 | True 43 | True 44 | True 45 | True 46 | True 47 | True 48 | True 49 | True 50 | True 51 | True 52 | True 53 | True 54 | True 55 | True 56 | True 57 | True 58 | True 59 | True 60 | True 61 | True 62 | True 63 | True 64 | True 65 | True 66 | True 67 | True 68 | True 69 | True 70 | True 71 | True 72 | True 73 | True 74 | True 75 | True 76 | True 77 | True 78 | True 79 | True 80 | True 81 | True 82 | True 83 | True 84 | True 85 | True 86 | True 87 | True 88 | True 89 | True 90 | True 91 | True 92 | True 93 | True 94 | True 95 | True 96 | True 97 | True 98 | True 99 | True 100 | True 101 | True 102 | True 103 | True 104 | True 105 | True 106 | True 107 | True 108 | True 109 | True 110 | True 111 | True 112 | True 113 | True 114 | True 115 | True 116 | True 117 | True 118 | True 119 | True 120 | True 121 | True 122 | True 123 | True 124 | True 125 | True 126 | True 127 | True 128 | True 129 | True 130 | True 131 | True 132 | True 133 | True 134 | True 135 | True 136 | True 137 | True 138 | True 139 | True 140 | True 141 | True 142 | True 143 | True 144 | True 145 | True 146 | True 147 | True 148 | True 149 | True 150 | True 151 | True 152 | True 153 | True 154 | True 155 | True 156 | True 157 | True 158 | True 159 | True 160 | <AssemblyExplorer> 161 | <Assembly Path="\home\Joker\Projects\SCP Plugins\References\Assembly-CSharp-Publicized.dll" /> 162 | <Assembly Path="\home\Joker\Downloads\SUPPLYDROP.dll" /> 163 | <Assembly Path="\home\Joker\Downloads\UNLIMITEDRADIOBATTERY.dll" /> 164 | <Assembly Path="\home\Joker\Downloads\SCP096INFO.dll" /> 165 | <Assembly Path="\home\Joker\Downloads\PEANUTFUCKINGEXPLODES.dll" /> 166 | <Assembly Path="\home\Joker\Downloads\012TELEPORT.dll" /> 167 | <Assembly Path="C:\Users\Jacob\Downloads\Common-Utils\packages\EXILED.6.0.0-beta.1\lib\net472\Exiled.Events.dll" /> 168 | <Assembly Path="C:\Users\Jacob\Downloads\Common-Utils\packages\EXILED.6.0.0-beta.1\lib\net472\Exiled.API.dll" /> 169 | </AssemblyExplorer> 170 | C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe 171 | 1114112 172 | 173 | -------------------------------------------------------------------------------- /Common Utilities/API.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities 2 | { 3 | using System.Collections.Generic; 4 | 5 | using Exiled.API.Features; 6 | using PlayerRoles; 7 | 8 | public static class API 9 | { 10 | public static List GetStartItems(RoleTypeId role) => Main.Instance.PlayerHandlers.StartItems(role); 11 | 12 | public static List GetStartItems(RoleTypeId role, Player player) => Main.Instance.PlayerHandlers.StartItems(role, player); 13 | 14 | public static float GetHealthOnKill(RoleTypeId role) => Main.Instance.Config.HealthOnKill?.ContainsKey(role) ?? false ? Main.Instance.Config.HealthOnKill[role] : 0f; 15 | } 16 | } -------------------------------------------------------------------------------- /Common Utilities/Common Utilities.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | $(ProjectName) 6 | true 7 | false 8 | 7.1.0 9 | default 10 | 7.0.6 11 | 7.0.6 12 | disable 13 | Always 14 | Exiled-Team 15 | 16 | 17 | 18 | 19 | 20 | 21 | <_Parameter1>$(ProjectName) 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Common Utilities/Config.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities 2 | { 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | 6 | using ConfigObjects; 7 | using Configs; 8 | using Exiled.API.Enums; 9 | using Exiled.API.Features; 10 | using Exiled.API.Interfaces; 11 | using PlayerRoles; 12 | using Scp914; 13 | using UnityEngine; 14 | 15 | public class Config : IConfig 16 | { 17 | [Description("Whether or not debug messages should be shown.")] 18 | public bool Debug { get; set; } = false; 19 | 20 | [Description("Whether or not MTF/CI can 'escape' while disarmed to switch teams.")] 21 | public bool DisarmSwitchTeams { get; set; } = true; 22 | 23 | [Description("Whether or not disarmed people will be prevented from interacting with doors/elevators.")] 24 | public bool RestrictiveDisarming { get; set; } = true; 25 | 26 | [Description("The text displayed at the timed interval specified below.")] 27 | public string TimedBroadcast { get; set; } = "This server is running EXILED Common-Utilities, enjoy your stay!"; 28 | 29 | [Description("The time each timed broadcast will be displayed.")] 30 | public ushort TimedBroadcastDuration { get; set; } = 5; 31 | 32 | [Description("The delay between each timed broadcast. To disable timed broadcasts, set this to 0")] 33 | public float TimedBroadcastDelay { get; set; } = 300f; 34 | 35 | [Description("The message displayed to the player when they first join the server. Setting this to empty will disable these broadcasts.")] 36 | public string JoinMessage { get; set; } = "Welcome %player%! Please read our rules!"; 37 | 38 | [Description("The amount of time (in seconds) the join message is displayed.")] 39 | public ushort JoinMessageDuration { get; set; } = 5; 40 | 41 | [Description("The amount of time (in seconds) after the round starts, before the facilities auto-nuke will start.")] 42 | public float AutonukeTime { get; set; } = 1500f; 43 | 44 | [Description("Wether or not the nuke should be unable to be disabled during the auto-nuke countdown.")] 45 | public bool AutonukeLock { get; set; } = true; 46 | 47 | [Description("The message given to all players when the auto-nuke is triggered. A duration of 2 or more will be a text message on-screen. A duration of 1 makes it a cassie announcement. A duration of 0 disables it.")] 48 | public Broadcast AutonukeBroadcast { get; set; } = new() 49 | { 50 | Content = "The auto nuke has been activated.", 51 | Duration = 10, 52 | Show = true, 53 | Type = global::Broadcast.BroadcastFlags.Normal, 54 | }; 55 | 56 | [Description("Whether or not to show player's health under their name when you look at them.")] 57 | public bool PlayerHealthInfo { get; set; } = true; 58 | 59 | [Description("Whether or not friendly fire should automatically turn on when a round ends (it will turn itself back off before the next round starts).")] 60 | public bool FriendlyFireOnRoundEnd { get; set; } = false; 61 | 62 | [Description("The multiplier applied to radio battery usage. Set to 0 to disable radio battery drain.")] 63 | public float RadioBatteryDrainMultiplier { get; set; } = 1f; 64 | 65 | [Description("The color to use for lights while the warhead is active.")] 66 | public Color WarheadColor { get; set; } = new(1f, 0.2f, 0.2f); 67 | 68 | [Description("The maximum time, in seconds, that a player can be AFK before being kicked. Set to -1 to disable AFK system.")] 69 | public int AfkLimit { get; set; } = 120; 70 | 71 | [Description("The roles that are ignored by the AFK system.")] 72 | public List AfkIgnoredRoles { get; set; } = new() 73 | { 74 | RoleTypeId.Scp079, 75 | RoleTypeId.Spectator, 76 | RoleTypeId.Tutorial, 77 | }; 78 | 79 | [Description("Whether or not probabilities should be additive (50 + 50 = 100) or not (50 + 50 = 2 seperate 50% chances)")] 80 | public bool AdditiveProbabilities { get; set; } = false; 81 | 82 | [Description( 83 | "The list of starting items for roles. ItemName is the item to give them, and Chance is the percent chance of them spawning with it, and Group allows you to restrict the item to only players with certain RA groups (Leave this as 'none' to allow all players to get the item). You can specify the same item multiple times.")] 84 | public Dictionary StartingInventories { get; set; } = new() 85 | { 86 | { 87 | RoleTypeId.ClassD, new RoleInventory 88 | { 89 | Slot1 = new List 90 | { 91 | new() 92 | { 93 | ItemName = ItemType.KeycardJanitor.ToString(), 94 | Chance = 10, 95 | Group = "none", 96 | }, 97 | new() 98 | { 99 | ItemName = ItemType.Coin.ToString(), 100 | Chance = 100, 101 | Group = "none", 102 | }, 103 | }, 104 | Slot2 = new List 105 | { 106 | new() 107 | { 108 | ItemName = ItemType.Flashlight.ToString(), 109 | Chance = 100, 110 | Group = "none", 111 | }, 112 | }, 113 | Ammo = new List 114 | { 115 | new() 116 | { 117 | Type = ItemType.Ammo556x45, 118 | Amount = 200, 119 | Group = "none", 120 | }, 121 | }, 122 | } 123 | }, 124 | }; 125 | 126 | [Description("The list of custom 914 recipies. Original is the item being upgraded, New is the item to upgrade to, and Chance is the percent chance of the upgrade happening. You can specify multiple upgrade choices for the same item.")] 127 | public Dictionary> Scp914ItemChanges { get; set; } = new() 128 | { 129 | { 130 | Scp914KnobSetting.Rough, new List 131 | { 132 | { 133 | new() 134 | { 135 | Original = ItemType.KeycardO5, 136 | New = ItemType.MicroHID, 137 | Chance = 50, 138 | } 139 | }, 140 | } 141 | }, 142 | }; 143 | 144 | [Description("The list of custom 914 recipies for roles. Original is the role to be changed, New is the new role to assign, Chance is the % chance of the upgrade occuring.")] 145 | public Dictionary> Scp914ClassChanges { get; set; } = new() 146 | { 147 | { 148 | Scp914KnobSetting.Rough, new List 149 | { 150 | { 151 | new() 152 | { 153 | Original = RoleTypeId.ClassD, 154 | New = RoleTypeId.Spectator.ToString(), 155 | Chance = 100, 156 | } 157 | }, 158 | } 159 | }, 160 | }; 161 | 162 | [Description("The list of 914 teleport settings. Note that if you set \"zone\" to anything other than Unspecified, it will always select a random room from that zone that isn't in the ignoredRooms list, instead of the room type defined.")] 163 | public Dictionary> Scp914TeleportChances { get; set; } = new() 164 | { 165 | { 166 | Scp914KnobSetting.Rough, new List 167 | { 168 | new() 169 | { 170 | Room = RoomType.LczClassDSpawn, 171 | Chance = 100, 172 | }, 173 | new() 174 | { 175 | Zone = ZoneType.LightContainment, 176 | IgnoredRooms = new() 177 | { 178 | RoomType.Lcz173, 179 | }, 180 | }, 181 | } 182 | }, 183 | }; 184 | 185 | [Description("A dictionary of random effects to apply to players when going through 914 on certain settings.")] 186 | public Dictionary> Scp914EffectChances { get; set; } = new() 187 | { 188 | { 189 | Scp914KnobSetting.Rough, new List 190 | { 191 | new() 192 | { 193 | Effect = EffectType.Bleeding, 194 | Chance = 100, 195 | }, 196 | } 197 | }, 198 | }; 199 | 200 | [Description("Determines if 914 effects are exclusive, meaning only one can be applied each time a player is processed by 914.")] 201 | public bool Scp914EffectsExclusivity { get; set; } = false; 202 | 203 | [Description("Whether or not SCPs are immune to effects gained from 914.")] 204 | public bool ScpsImmuneTo914Effects { get; set; } = false; 205 | 206 | [Description("The frequency (in seconds) between ragdoll cleanups. Set to 0 to disable.")] 207 | public float RagdollCleanupDelay { get; set; } = 0f; 208 | 209 | [Description("If ragdoll cleanup should only happen in the Pocket Dimension or not.")] 210 | public bool RagdollCleanupOnlyPocket { get; set; } = false; 211 | 212 | [Description("The frequency (in seconds) between item cleanups. Set to 0 to disable.")] 213 | public float ItemCleanupDelay { get; set; } = 0f; 214 | 215 | [Description("If item cleanup should only happen in the Pocket Dimension or not.")] 216 | public bool ItemCleanupOnlyPocket { get; set; } = false; 217 | 218 | [Description("A list of all roles and their damage modifiers. The number here is a multiplier, not a raw damage amount. Thus, setting it to 1 = normal damage, 1.5 = 50% more damage, and 0.5 = 50% less damage.")] 219 | public Dictionary RoleDamageMultipliers { get; set; } = new() 220 | { 221 | { 222 | RoleTypeId.Scp173, 1.0f 223 | }, 224 | }; 225 | 226 | [Description("A list of all Weapons and their damage modifiers. The number here is a multiplier, not a raw damage amount. Thus, setting it to 1 = normal damage, 1.5 = 50% more damage, and 0.5 = 50% less damage.")] 227 | public Dictionary DamageMultipliers { get; set; } = new() 228 | { 229 | { 230 | DamageType.E11Sr, 1.0f 231 | }, 232 | }; 233 | 234 | [Description("A list of roles and how much health they should be given when they kill someone.")] 235 | public Dictionary HealthOnKill { get; set; } = new() 236 | { 237 | { 238 | RoleTypeId.Scp173, 0 239 | }, 240 | { 241 | RoleTypeId.Scp939, 10 242 | }, 243 | }; 244 | 245 | [Description("A list of roles and what their default starting health should be.")] 246 | public Dictionary HealthValues { get; set; } = new() 247 | { 248 | { 249 | RoleTypeId.Scp173, 3200 250 | }, 251 | { 252 | RoleTypeId.NtfCaptain, 150 253 | }, 254 | }; 255 | 256 | [Description("If the plugin is enabled or not.")] 257 | public bool IsEnabled { get; set; } = true; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /Common Utilities/ConfigObjects/ItemChance.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.ConfigObjects; 2 | 3 | public class ItemChance 4 | { 5 | public string ItemName { get; set; } = ItemType.None.ToString(); 6 | 7 | public double Chance { get; set; } 8 | 9 | public string Group { get; set; } = "none"; 10 | 11 | public void Deconstruct(out string name, out double i, out string groupKey) 12 | { 13 | name = ItemName; 14 | i = Chance; 15 | groupKey = Group; 16 | } 17 | } -------------------------------------------------------------------------------- /Common Utilities/ConfigObjects/ItemUpgradeChance.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.ConfigObjects 2 | { 3 | public class ItemUpgradeChance 4 | { 5 | public ItemType Original { get; set; } 6 | 7 | public ItemType New { get; set; } 8 | 9 | public double Chance { get; set; } 10 | 11 | public int Count { get; set; } = 1; 12 | 13 | public void Deconstruct(out ItemType itemType, out ItemType itemType1, out double i, out int count) 14 | { 15 | itemType = Original; 16 | itemType1 = New; 17 | i = Chance; 18 | count = Count; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Common Utilities/ConfigObjects/PlayerUpgradeChance.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.ConfigObjects 2 | { 3 | using PlayerRoles; 4 | 5 | public class PlayerUpgradeChance 6 | { 7 | public RoleTypeId Original { get; set; } 8 | 9 | public string New { get; set; } = RoleTypeId.Spectator.ToString(); 10 | 11 | public double Chance { get; set; } 12 | 13 | public bool KeepInventory { get; set; } = true; 14 | 15 | public bool KeepHealth { get; set; } = true; 16 | 17 | public void Deconstruct(out RoleTypeId old, out string newRole, out double i, out bool keepInventory, out bool keepHealth) 18 | { 19 | old = Original; 20 | newRole = New; 21 | i = Chance; 22 | keepInventory = KeepInventory; 23 | keepHealth = KeepHealth; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Common Utilities/ConfigObjects/Scp914EffectChance.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.ConfigObjects 2 | { 3 | using Exiled.API.Enums; 4 | 5 | public class Scp914EffectChance 6 | { 7 | public EffectType Effect { get; set; } 8 | 9 | public double Chance { get; set; } 10 | 11 | public float Duration { get; set; } 12 | 13 | public void Deconstruct(out EffectType effect, out double chance, out float duration) 14 | { 15 | effect = Effect; 16 | chance = Chance; 17 | duration = Duration; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Common Utilities/ConfigObjects/Scp914TeleportChance.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common_Utilities.ConfigObjects 4 | { 5 | using Exiled.API.Enums; 6 | using UnityEngine; 7 | 8 | public class Scp914TeleportChance 9 | { 10 | public ZoneType Zone { get; set; } = ZoneType.Unspecified; 11 | 12 | public List IgnoredRooms { get; set; } 13 | 14 | public RoomType Room { get; set; } 15 | 16 | public Vector3 Offset { get; set; } = Vector3.zero; 17 | 18 | public double Chance { get; set; } 19 | 20 | public float Damage { get; set; } = 0f; 21 | 22 | public void Deconstruct(out RoomType room, out List ignoredRooms, out Vector3 offset, out double chance, out float damage, out ZoneType zone) 23 | { 24 | room = Room; 25 | ignoredRooms = IgnoredRooms; 26 | offset = Offset; 27 | chance = Chance; 28 | damage = Damage; 29 | zone = Zone; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Common Utilities/ConfigObjects/StartingAmmo.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.ConfigObjects 2 | { 3 | public class StartingAmmo 4 | { 5 | public ItemType Type { get; set; } 6 | 7 | public ushort Amount { get; set; } 8 | 9 | public string Group { get; set; } = "none"; 10 | 11 | public void Deconstruct(out ItemType type, out ushort limit, out string group) 12 | { 13 | type = Type; 14 | limit = Amount; 15 | group = Group; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Common Utilities/Configs/RoleInventory.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.Configs 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | using ConfigObjects; 7 | using YamlDotNet.Serialization; 8 | 9 | public class RoleInventory 10 | { 11 | [YamlIgnore] 12 | public int UsedSlots 13 | { 14 | get 15 | { 16 | int i = 0; 17 | if (Slot1 != null && !Slot1.IsEmpty()) 18 | i++; 19 | if (Slot2 != null && !Slot2.IsEmpty()) 20 | i++; 21 | if (Slot3 != null && !Slot3.IsEmpty()) 22 | i++; 23 | if (Slot4 != null && !Slot4.IsEmpty()) 24 | i++; 25 | if (Slot5 != null && !Slot5.IsEmpty()) 26 | i++; 27 | if (Slot6 != null && !Slot6.IsEmpty()) 28 | i++; 29 | if (Slot7 != null && !Slot7.IsEmpty()) 30 | i++; 31 | if (Slot8 != null && !Slot8.IsEmpty()) 32 | i++; 33 | return i; 34 | } 35 | } 36 | 37 | public List Slot1 { get; set; } = new(); 38 | 39 | public List Slot2 { get; set; } = new(); 40 | 41 | public List Slot3 { get; set; } = new(); 42 | 43 | public List Slot4 { get; set; } = new(); 44 | 45 | public List Slot5 { get; set; } = new(); 46 | 47 | public List Slot6 { get; set; } = new(); 48 | 49 | public List Slot7 { get; set; } = new(); 50 | 51 | public List Slot8 { get; set; } = new(); 52 | 53 | public List Ammo { get; set; } = new(); 54 | 55 | public IEnumerable this[int i] => i switch 56 | { 57 | 0 => Slot1, 58 | 1 => Slot2, 59 | 2 => Slot3, 60 | 3 => Slot4, 61 | 4 => Slot5, 62 | 5 => Slot6, 63 | 6 => Slot7, 64 | 7 => Slot8, 65 | _ => throw new ArgumentOutOfRangeException(), 66 | }; 67 | } 68 | } -------------------------------------------------------------------------------- /Common Utilities/EventHandlers/MapHandlers.cs: -------------------------------------------------------------------------------- 1 | using Exiled.API.Extensions; 2 | using Exiled.API.Features.Items; 3 | 4 | namespace Common_Utilities.EventHandlers 5 | { 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | using Common_Utilities.ConfigObjects; 11 | using Exiled.API.Enums; 12 | using Exiled.API.Features; 13 | using Exiled.API.Features.Pickups; 14 | using Exiled.CustomRoles.API.Features; 15 | using Exiled.Events.EventArgs.Scp914; 16 | using MEC; 17 | using PlayerRoles; 18 | using UnityEngine; 19 | 20 | public class MapHandlers 21 | { 22 | private readonly Main plugin; 23 | 24 | public MapHandlers(Main plugin) => this.plugin = plugin; 25 | 26 | public void OnScp914UpgradingItem(UpgradingPickupEventArgs ev) 27 | { 28 | if (plugin.Config.Scp914ItemChanges.ContainsKey(ev.KnobSetting)) 29 | { 30 | IEnumerable itemUpgradeChance = plugin.Config.Scp914ItemChanges[ev.KnobSetting].Where(x => x.Original == ev.Pickup.Type); 31 | 32 | foreach ((ItemType sourceItem, ItemType destinationItem, double chance, int count) in itemUpgradeChance) 33 | { 34 | double r; 35 | if (plugin.Config.AdditiveProbabilities) 36 | r = plugin.Rng.NextDouble() * itemUpgradeChance.Sum(x => x.Chance); 37 | else 38 | r = plugin.Rng.NextDouble() * 100; 39 | 40 | Log.Debug($"{nameof(OnScp914UpgradingItem)}: SCP-914 is trying to upgrade a {ev.Pickup.Type}. {sourceItem} -> {destinationItem} ({chance}). Should process: {r <= chance} ({r})"); 41 | if (r <= chance) 42 | { 43 | UpgradeItem(ev.Pickup, destinationItem, ev.OutputPosition, count); 44 | ev.IsAllowed = false; 45 | break; 46 | } 47 | 48 | r -= chance; 49 | } 50 | } 51 | } 52 | 53 | public void OnScp914UpgradingInventoryItem(UpgradingInventoryItemEventArgs ev) 54 | { 55 | if (plugin.Config.Scp914ItemChanges.ContainsKey(ev.KnobSetting)) 56 | { 57 | IEnumerable itemUpgradeChance = plugin.Config.Scp914ItemChanges[ev.KnobSetting].Where(x => x.Original == ev.Item.Type); 58 | 59 | foreach ((ItemType sourceItem, ItemType destinationItem, double chance, int count) in itemUpgradeChance) 60 | { 61 | double r; 62 | if (plugin.Config.AdditiveProbabilities) 63 | r = plugin.Rng.NextDouble() * itemUpgradeChance.Sum(x => x.Chance); 64 | else 65 | r = plugin.Rng.NextDouble() * 100; 66 | 67 | Log.Debug($"{nameof(OnScp914UpgradingInventoryItem)}: {ev.Player.Nickname} is attempting to upgrade hit {ev.Item.Type}. {sourceItem} -> {destinationItem} ({chance}). Should process: {r <= chance} ({r})"); 68 | if (r <= chance) 69 | { 70 | ev.Player.RemoveItem(ev.Item); 71 | if (destinationItem is not ItemType.None) 72 | { 73 | for (int i = 0; i < count; i++) 74 | { 75 | if (!ev.Player.IsInventoryFull) 76 | ev.Player.AddItem(destinationItem); 77 | else 78 | Pickup.CreateAndSpawn(destinationItem, Scp914.OutputPosition, ev.Player.Rotation, ev.Player); 79 | } 80 | } 81 | 82 | break; 83 | } 84 | 85 | r -= chance; 86 | } 87 | } 88 | } 89 | 90 | public void OnScp914UpgradingPlayer(UpgradingPlayerEventArgs ev) 91 | { 92 | if (plugin.Config.Scp914ClassChanges != null && plugin.Config.Scp914ClassChanges.TryGetValue(ev.KnobSetting, out var change)) 93 | { 94 | IEnumerable playerUpgradeChance = change.Where(x => x.Original == ev.Player.Role); 95 | 96 | foreach ((RoleTypeId sourceRole, string destinationRole, double chance, bool keepInventory, bool keepHealth) in playerUpgradeChance) 97 | { 98 | double r; 99 | if (plugin.Config.AdditiveProbabilities) 100 | r = plugin.Rng.NextDouble() * playerUpgradeChance.Sum(x => x.Chance); 101 | else 102 | r = plugin.Rng.NextDouble() * 100; 103 | 104 | Log.Debug($"{nameof(OnScp914UpgradingPlayer)}: {ev.Player.Nickname} ({ev.Player.Role})is trying to upgrade his class. {sourceRole} -> {destinationRole} ({chance}). Should be processed: {r <= chance} ({r})"); 105 | if (r <= chance) 106 | { 107 | float originalHealth = ev.Player.Health; 108 | var originalItems = ev.Player.Items; 109 | var originalAmmo = ev.Player.Ammo; 110 | 111 | if (Enum.TryParse(destinationRole, true, out RoleTypeId roleType)) 112 | { 113 | ev.Player.Role.Set(roleType, SpawnReason.Respawn, RoleSpawnFlags.None); 114 | } 115 | else if (CustomRole.TryGet(destinationRole, out CustomRole customRole)) 116 | { 117 | if (customRole is not null) 118 | { 119 | customRole.AddRole(ev.Player); 120 | Timing.CallDelayed(0.5f, () => ev.Player.Teleport(ev.OutputPosition)); 121 | } 122 | } 123 | 124 | if (keepHealth) 125 | { 126 | ev.Player.Health = originalHealth; 127 | } 128 | 129 | if (keepInventory) 130 | { 131 | foreach (var item in originalItems) 132 | { 133 | ev.Player.AddItem(item); 134 | } 135 | 136 | foreach (var kvp in originalAmmo) 137 | { 138 | ev.Player.SetAmmo(kvp.Key.GetAmmoType(), kvp.Value); 139 | } 140 | } 141 | 142 | ev.Player.Position = ev.OutputPosition; 143 | break; 144 | } 145 | 146 | r -= chance; 147 | } 148 | } 149 | 150 | if (plugin.Config.Scp914EffectChances != null && plugin.Config.Scp914EffectChances.ContainsKey(ev.KnobSetting) && (ev.Player.Role.Side != Side.Scp || !plugin.Config.ScpsImmuneTo914Effects)) 151 | { 152 | IEnumerable scp914EffectChances = plugin.Config.Scp914EffectChances[ev.KnobSetting]; 153 | 154 | foreach ((EffectType effect, double chance, float duration) in scp914EffectChances) 155 | { 156 | double r; 157 | if (plugin.Config.AdditiveProbabilities) 158 | r = plugin.Rng.NextDouble() * scp914EffectChances.Sum(x => x.Chance); 159 | else 160 | r = plugin.Rng.NextDouble() * 100; 161 | 162 | Log.Debug($"{nameof(OnScp914UpgradingPlayer)}: {ev.Player.Nickname} is trying to gain an effect. {effect} ({chance}). Should be added: {r <= chance} ({r})"); 163 | if (r <= chance) 164 | { 165 | ev.Player.EnableEffect(effect, duration); 166 | if (plugin.Config.Scp914EffectsExclusivity) 167 | break; 168 | } 169 | 170 | r -= chance; 171 | } 172 | } 173 | 174 | if (plugin.Config.Scp914TeleportChances != null && plugin.Config.Scp914TeleportChances.ContainsKey(ev.KnobSetting)) 175 | { 176 | IEnumerable scp914TeleportChances = plugin.Config.Scp914TeleportChances[ev.KnobSetting]; 177 | 178 | foreach ((RoomType roomType, List ignoredRooms, Vector3 offset, double chance, float damage, ZoneType zone) in plugin.Config.Scp914TeleportChances[ev.KnobSetting]) 179 | { 180 | double r; 181 | if (plugin.Config.AdditiveProbabilities) 182 | r = plugin.Rng.NextDouble() * scp914TeleportChances.Sum(x => x.Chance); 183 | else 184 | r = plugin.Rng.NextDouble() * 100; 185 | 186 | Log.Debug($"{nameof(OnScp914UpgradingPlayer)}: {ev.Player.Nickname} is trying to be teleported by 914. {roomType} + {offset} ({chance}). Should be teleported: {r <= chance} ({r})"); 187 | if (r <= chance) 188 | { 189 | if (zone != ZoneType.Unspecified) 190 | { 191 | ev.OutputPosition = Room.List.Where(x => x.Zone == zone && !ignoredRooms.Contains(x.Type)).GetRandomValue().Position + ((Vector3.up * 1.5f) + offset); 192 | if (damage > 0f) 193 | { 194 | float amount = ev.Player.MaxHealth * damage; 195 | if (damage > 1f) 196 | amount = damage; 197 | 198 | Log.Debug($"{nameof(OnScp914UpgradingPlayer)}: {ev.Player.Nickname} is being damaged for {amount}. -- {ev.Player.Health} * {damage}"); 199 | ev.Player.Hurt(amount, "SCP-914 Teleport", "SCP-914"); 200 | } 201 | } 202 | else 203 | { 204 | ev.OutputPosition = Room.Get(roomType).Position + (Vector3.up * 1.5f) + offset; 205 | if (damage > 0f) 206 | { 207 | float amount = ev.Player.MaxHealth * damage; 208 | if (damage > 1f) 209 | amount = damage; 210 | 211 | Log.Debug( 212 | $"{nameof(OnScp914UpgradingPlayer)}: {ev.Player.Nickname} is being damaged for {amount}. -- {ev.Player.Health} * {damage}"); 213 | ev.Player.Hurt(amount, "SCP-914 Teleport", "SCP-914"); 214 | } 215 | } 216 | 217 | break; 218 | } 219 | 220 | r -= chance; 221 | } 222 | } 223 | } 224 | 225 | internal void UpgradeItem(Pickup oldItem, ItemType newItem, Vector3 pos, int count) 226 | { 227 | Quaternion quaternion = oldItem.Rotation; 228 | Player previousOwner = oldItem.PreviousOwner; 229 | oldItem.Destroy(); 230 | if (newItem is not ItemType.None) 231 | { 232 | for (int i = 0; i < count; i++) 233 | { 234 | Pickup.CreateAndSpawn(newItem, pos, quaternion, previousOwner); 235 | } 236 | } 237 | } 238 | } 239 | } -------------------------------------------------------------------------------- /Common Utilities/EventHandlers/PlayerHandlers.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.EventHandlers 2 | { 3 | #pragma warning disable IDE0018 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | using Common_Utilities.ConfigObjects; 9 | using Exiled.API.Enums; 10 | using Exiled.API.Features; 11 | using Exiled.API.Features.Roles; 12 | using Exiled.CustomItems.API.Features; 13 | using Exiled.Events.EventArgs.Interfaces; 14 | using Exiled.Events.EventArgs.Player; 15 | using PlayerRoles; 16 | using UnityEngine; 17 | 18 | using Player = Exiled.API.Features.Player; 19 | 20 | public class PlayerHandlers 21 | { 22 | private readonly Main plugin; 23 | 24 | public PlayerHandlers(Main plugin) => this.plugin = plugin; 25 | 26 | public void OnPlayerVerified(VerifiedEventArgs ev) 27 | { 28 | string message = FormatJoinMessage(ev.Player); 29 | if (!string.IsNullOrEmpty(message)) 30 | ev.Player.Broadcast(plugin.Config.JoinMessageDuration, message); 31 | } 32 | 33 | public void OnChangingRole(ChangingRoleEventArgs ev) 34 | { 35 | if (ev.Player == null) 36 | { 37 | Log.Warn($"{nameof(OnChangingRole)}: Triggering player is null."); 38 | return; 39 | } 40 | 41 | if (plugin.Config.StartingInventories.ContainsKey(ev.NewRole) && !ev.ShouldPreserveInventory) 42 | { 43 | if (ev.Items == null) 44 | { 45 | Log.Warn("items is null"); 46 | return; 47 | } 48 | 49 | ev.Items.Clear(); 50 | ev.Items.AddRange(StartItems(ev.NewRole, ev.Player)); 51 | 52 | if (plugin.Config.StartingInventories[ev.NewRole].Ammo != null && plugin.Config.StartingInventories[ev.NewRole].Ammo.Count > 0) 53 | { 54 | if (plugin.Config.StartingInventories[ev.NewRole].Ammo.Any(s => string.IsNullOrEmpty(s.Group) || s.Group == "none" || (ServerStatic.PermissionsHandler._groups.TryGetValue(s.Group, out UserGroup userGroup) && userGroup == ev.Player.Group))) 55 | { 56 | ev.Ammo.Clear(); 57 | foreach ((ItemType type, ushort amount, string group) in plugin.Config.StartingInventories[ev.NewRole].Ammo) 58 | { 59 | if (string.IsNullOrEmpty(group) || group == "none" || (ServerStatic.PermissionsHandler._groups.TryGetValue(group, out UserGroup userGroup) && userGroup == ev.Player.Group)) 60 | { 61 | ev.Ammo.Add(type, amount); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | public void OnSpawned(SpawnedEventArgs ev) 70 | { 71 | if (ev.Player == null) 72 | { 73 | Log.Warn($"{nameof(OnSpawned)}: Triggering player is null."); 74 | return; 75 | } 76 | 77 | RoleTypeId newRole = ev.Player.Role.Type; 78 | if (plugin.Config.HealthValues != null && plugin.Config.HealthValues.TryGetValue(newRole, out int health)) 79 | { 80 | ev.Player.Health = health; 81 | ev.Player.MaxHealth = health; 82 | } 83 | 84 | if (ev.Player.Role is FpcRole && plugin.Config.PlayerHealthInfo) 85 | { 86 | ev.Player.CustomInfo = $"({ev.Player.Health}/{ev.Player.MaxHealth}) {(!string.IsNullOrEmpty(ev.Player.CustomInfo) ? ev.Player.CustomInfo.Substring(ev.Player.CustomInfo.LastIndexOf(')') + 1) : string.Empty)}"; 87 | } 88 | 89 | if (plugin.Config.AfkIgnoredRoles.Contains(newRole) && plugin.AfkDict.TryGetValue(ev.Player, out Tuple value)) 90 | plugin.AfkDict[ev.Player] = new Tuple(newRole is RoleTypeId.Spectator ? value.Item1 : 0, ev.Player.Position); 91 | } 92 | 93 | public void OnPlayerDied(DiedEventArgs ev) 94 | { 95 | if (ev.Attacker != null && plugin.Config.HealthOnKill.ContainsKey(ev.Attacker.Role)) 96 | { 97 | ev.Attacker.Heal(plugin.Config.HealthOnKill[ev.Attacker.Role]); 98 | } 99 | } 100 | 101 | public List StartItems(RoleTypeId role, Player player = null) 102 | { 103 | List items = new(); 104 | 105 | for (int i = 0; i < plugin.Config.StartingInventories[role].UsedSlots; i++) 106 | { 107 | IEnumerable itemChances = plugin.Config.StartingInventories[role][i].Where(x => player == null || string.IsNullOrEmpty(x.Group) || x.Group == "none" || (ServerStatic.PermissionsHandler._groups.TryGetValue(x.Group, out var group) && group == player.Group)); 108 | double r; 109 | if (plugin.Config.AdditiveProbabilities) 110 | r = plugin.Rng.NextDouble() * itemChances.Sum(val => val.Chance); 111 | else 112 | r = plugin.Rng.NextDouble() * 100; 113 | Log.Debug($"[StartItems] ActualChance ({r})/{itemChances.Sum(val => val.Chance)}"); 114 | foreach ((string item, double chance, string groupKey) in itemChances) 115 | { 116 | Log.Debug($"[StartItems] Probability ({r})/{chance}"); 117 | if (r <= chance) 118 | { 119 | if (Enum.TryParse(item, true, out ItemType type)) 120 | { 121 | items.Add(type); 122 | break; 123 | } 124 | else if (CustomItem.TryGet(item, out CustomItem customItem)) 125 | { 126 | if (player != null) 127 | customItem!.Give(player); 128 | else 129 | Log.Warn($"{nameof(StartItems)}: Tried to give {customItem!.Name} to a null player."); 130 | 131 | break; 132 | } 133 | else 134 | Log.Warn($"{nameof(StartItems)}: {item} is not a valid ItemType or it is a CustomItem that is not installed! It is being skipper in inventory decisions."); 135 | } 136 | 137 | r -= chance; 138 | } 139 | } 140 | 141 | return items; 142 | } 143 | 144 | public string FormatJoinMessage(Player player) => 145 | string.IsNullOrEmpty(plugin.Config.JoinMessage) ? string.Empty : plugin.Config.JoinMessage.Replace("%player%", player.Nickname).Replace("%server%", Server.Name).Replace("%count%", $"{Player.Dictionary.Count}"); 146 | 147 | public void OnPlayerHurting(HurtingEventArgs ev) 148 | { 149 | float damageMultiplier; 150 | if (plugin.Config.RoleDamageMultipliers != null && ev.Attacker != null && plugin.Config.RoleDamageMultipliers.TryGetValue(ev.Attacker.Role, out damageMultiplier)) 151 | ev.Amount *= damageMultiplier; 152 | 153 | if (plugin.Config.DamageMultipliers != null && plugin.Config.DamageMultipliers.TryGetValue(ev.DamageHandler.Type, out damageMultiplier)) 154 | ev.Amount *= damageMultiplier; 155 | 156 | if (plugin.Config.PlayerHealthInfo) 157 | ev.Player.CustomInfo = $"({ev.Player.Health}/{ev.Player.MaxHealth}) {(!string.IsNullOrEmpty(ev.Player.CustomInfo) ? ev.Player.CustomInfo.Substring(ev.Player.CustomInfo.LastIndexOf(')') + 1) : string.Empty)}"; 158 | 159 | if (ev.Attacker is not null && plugin.AfkDict.ContainsKey(ev.Attacker)) 160 | { 161 | Log.Debug($"Resetting {ev.Attacker.Nickname} AFK timer."); 162 | plugin.AfkDict[ev.Attacker] = new Tuple(0, ev.Attacker.Position); 163 | } 164 | } 165 | 166 | public void OnInteractingDoor(InteractingDoorEventArgs ev) 167 | { 168 | if (ev.Player.IsCuffed && plugin.Config.RestrictiveDisarming) 169 | ev.IsAllowed = false; 170 | 171 | if (plugin.AfkDict.ContainsKey(ev.Player)) 172 | { 173 | Log.Debug($"Resetting {ev.Player.Nickname} AFK timer."); 174 | plugin.AfkDict[ev.Player] = new Tuple(0, ev.Player.Position); 175 | } 176 | } 177 | 178 | public void OnInteractingElevator(InteractingElevatorEventArgs ev) 179 | { 180 | if (ev.Player.IsCuffed && plugin.Config.RestrictiveDisarming) 181 | ev.IsAllowed = false; 182 | 183 | if (plugin.AfkDict.ContainsKey(ev.Player)) 184 | { 185 | Log.Debug($"Resetting {ev.Player.Nickname} AFK timer."); 186 | plugin.AfkDict[ev.Player] = new Tuple(0, ev.Player.Position); 187 | } 188 | } 189 | 190 | public void OnEscaping(EscapingEventArgs ev) 191 | { 192 | if (ev.EscapeScenario is EscapeScenario.CustomEscape) 193 | { 194 | ev.NewRole = ev.Player.Role.Type switch 195 | { 196 | RoleTypeId.FacilityGuard or RoleTypeId.NtfPrivate or RoleTypeId.NtfSergeant or RoleTypeId.NtfCaptain or RoleTypeId.NtfSpecialist => RoleTypeId.ChaosConscript, 197 | RoleTypeId.ChaosConscript or RoleTypeId.ChaosMarauder or RoleTypeId.ChaosRepressor or RoleTypeId.ChaosRifleman => RoleTypeId.ChaosConscript, 198 | _ => RoleTypeId.None, 199 | }; 200 | 201 | ev.IsAllowed = ev.NewRole is not RoleTypeId.None; 202 | } 203 | } 204 | 205 | public void OnUsingRadioBattery(UsingRadioBatteryEventArgs ev) 206 | { 207 | ev.Drain *= plugin.Config.RadioBatteryDrainMultiplier; 208 | } 209 | 210 | public void AntiAfkEventHandler(IPlayerEvent ev) 211 | { 212 | if (ev.Player != null && plugin.AfkDict.ContainsKey(ev.Player)) 213 | { 214 | Log.Debug($"Resetting {ev.Player.Nickname} AFK timer."); 215 | plugin.AfkDict[ev.Player] = new Tuple(0, ev.Player.Position); 216 | } 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /Common Utilities/EventHandlers/ServerHandlers.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.EventHandlers 2 | { 3 | #pragma warning disable SA1313 // Parameter names should begin with lower-case letter 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | using Exiled.API.Enums; 10 | using Exiled.API.Features; 11 | using Exiled.API.Features.Pickups; 12 | using Exiled.API.Features.Roles; 13 | using Exiled.Events.EventArgs.Server; 14 | using Exiled.Events.EventArgs.Warhead; 15 | using InventorySystem.Configs; 16 | using MEC; 17 | using PlayerRoles; 18 | using UnityEngine; 19 | 20 | public class ServerHandlers 21 | { 22 | private readonly Main plugin; 23 | 24 | private bool friendlyFireDisable = false; 25 | 26 | public ServerHandlers(Main plugin) => this.plugin = plugin; 27 | 28 | public void OnRoundStarted() 29 | { 30 | if (plugin.Config.AutonukeTime > -1) 31 | plugin.Coroutines.Add(Timing.CallDelayed(plugin.Config.AutonukeTime, AutoNuke)); 32 | 33 | if (plugin.Config.RagdollCleanupDelay > 0) 34 | plugin.Coroutines.Add(Timing.RunCoroutine(RagdollCleanup())); 35 | 36 | if (plugin.Config.ItemCleanupDelay > 0) 37 | plugin.Coroutines.Add(Timing.RunCoroutine(ItemCleanup())); 38 | } 39 | 40 | public void OnWaitingForPlayers() 41 | { 42 | if (plugin.Config.AfkLimit > 0) 43 | { 44 | plugin.AfkDict.Clear(); 45 | plugin.Coroutines.Add(Timing.RunCoroutine(AfkCheck())); 46 | } 47 | 48 | if (friendlyFireDisable) 49 | { 50 | Log.Debug($"{nameof(OnWaitingForPlayers)}: Disabling friendly fire."); 51 | Server.FriendlyFire = false; 52 | friendlyFireDisable = false; 53 | } 54 | 55 | if (plugin.Config.TimedBroadcastDelay > 0) 56 | plugin.Coroutines.Add(Timing.RunCoroutine(ServerBroadcast())); 57 | 58 | // Fix GrandLoadout not able to give this 2 inventory 59 | StartingInventories.DefinedInventories[RoleTypeId.Tutorial] = new(Array.Empty(), new()); 60 | StartingInventories.DefinedInventories[RoleTypeId.ClassD] = new(Array.Empty(), new()); 61 | 62 | Warhead.IsLocked = false; 63 | } 64 | 65 | public void OnRoundEnded(RoundEndedEventArgs ev) 66 | { 67 | if (plugin.Config.FriendlyFireOnRoundEnd && !Server.FriendlyFire) 68 | { 69 | Log.Debug($"{nameof(OnRoundEnded)}: Enabling friendly fire."); 70 | Server.FriendlyFire = true; 71 | friendlyFireDisable = true; 72 | } 73 | 74 | foreach (CoroutineHandle coroutine in plugin.Coroutines) 75 | Timing.KillCoroutines(coroutine); 76 | plugin.Coroutines.Clear(); 77 | } 78 | 79 | public IEnumerator ServerBroadcast() 80 | { 81 | for (; ; ) 82 | { 83 | yield return Timing.WaitForSeconds(plugin.Config.TimedBroadcastDelay); 84 | 85 | Map.Broadcast(plugin.Config.TimedBroadcastDuration, plugin.Config.TimedBroadcast); 86 | } 87 | } 88 | 89 | public IEnumerator ItemCleanup() 90 | { 91 | for (; ; ) 92 | { 93 | yield return Timing.WaitForSeconds(plugin.Config.ItemCleanupDelay); 94 | 95 | foreach (Pickup pickup in Pickup.List.ToList()) 96 | { 97 | if (!plugin.Config.ItemCleanupOnlyPocket || pickup.Position.y < -1500f) 98 | pickup.Destroy(); 99 | } 100 | } 101 | } 102 | 103 | public IEnumerator RagdollCleanup() 104 | { 105 | for (; ; ) 106 | { 107 | yield return Timing.WaitForSeconds(plugin.Config.RagdollCleanupDelay); 108 | 109 | foreach (Ragdoll ragdoll in Ragdoll.List.ToList()) 110 | { 111 | if (!plugin.Config.RagdollCleanupOnlyPocket || ragdoll.Position.y < -1500f) 112 | ragdoll.Destroy(); 113 | } 114 | } 115 | } 116 | 117 | public void AutoNuke() 118 | { 119 | if (!Warhead.IsInProgress) 120 | { 121 | switch (plugin.Config.AutonukeBroadcast.Duration) 122 | { 123 | case 0: 124 | break; 125 | case 1: 126 | Cassie.Message(plugin.Config.AutonukeBroadcast.Content); 127 | break; 128 | default: 129 | Map.Broadcast(plugin.Config.AutonukeBroadcast); 130 | break; 131 | } 132 | 133 | Warhead.Start(); 134 | } 135 | 136 | if (plugin.Config.AutonukeLock) 137 | Warhead.IsLocked = true; 138 | } 139 | 140 | public IEnumerator AfkCheck() 141 | { 142 | for (; ; ) 143 | { 144 | yield return Timing.WaitForSeconds(1f); 145 | 146 | foreach (Player player in Player.List) 147 | { 148 | if (!plugin.AfkDict.ContainsKey(player)) 149 | plugin.AfkDict.Add(player, new Tuple(0, player.Position)); 150 | 151 | if (player.Role.IsDead || player.IsGodModeEnabled || player.IsNoclipPermitted || player.Role is FpcRole { IsGrounded: false } || player.RemoteAdminPermissions.HasFlag(PlayerPermissions.AFKImmunity) || plugin.Config.AfkIgnoredRoles.Contains(player.Role.Type)) 152 | { 153 | #pragma warning disable SA1013 154 | Log.Debug($"Player {player.Nickname} ({player.Role.Type}) is not a checkable player. NoClip: {player.IsNoclipPermitted} GodMode: {player.IsGodModeEnabled} IsNotGrounded: {player.Role is FpcRole { IsGrounded: false }} AFKImunity: {player.RemoteAdminPermissions.HasFlag(PlayerPermissions.AFKImmunity)}"); 155 | continue; 156 | #pragma warning restore SA1013 157 | } 158 | 159 | if ((plugin.AfkDict[player].Item2 - player.Position).sqrMagnitude > 2) 160 | { 161 | Log.Debug($"Player {player.Nickname} has moved, resetting AFK timer."); 162 | plugin.AfkDict[player] = new Tuple(0, player.Position); 163 | } 164 | 165 | if (plugin.AfkDict[player].Item1 >= plugin.Config.AfkLimit) 166 | { 167 | plugin.AfkDict.Remove(player); 168 | Log.Debug($"Kicking {player.Nickname} for being AFK."); 169 | player.Kick("You were kicked by CommonUtilities for being AFK."); 170 | } 171 | else if (plugin.AfkDict[player].Item1 >= (plugin.Config.AfkLimit / 2)) 172 | { 173 | player.Broadcast(2, $"You have been AFK for {plugin.AfkDict[player].Item1} seconds. You will be automatically kicked if you remain AFK for a total of {plugin.Config.AfkLimit} seconds.", shouldClearPrevious: true); 174 | } 175 | 176 | plugin.AfkDict[player] = new Tuple(plugin.AfkDict[player].Item1 + 1, plugin.AfkDict[player].Item2); 177 | } 178 | } 179 | } 180 | 181 | public void OnRestartingRound() 182 | { 183 | foreach (CoroutineHandle coroutine in plugin.Coroutines) 184 | Timing.KillCoroutines(coroutine); 185 | plugin.Coroutines.Clear(); 186 | } 187 | 188 | public void OnWarheadStarting(StartingEventArgs _) 189 | { 190 | foreach (Room room in Room.List) 191 | room.Color = plugin.Config.WarheadColor; 192 | } 193 | 194 | public void OnWarheadStopping(StoppingEventArgs _) 195 | { 196 | if (Warhead.IsLocked) 197 | return; 198 | 199 | foreach (Room room in Room.List) 200 | room.ResetColor(); 201 | } 202 | } 203 | } -------------------------------------------------------------------------------- /Common Utilities/Main.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities 2 | { 3 | #pragma warning disable SA1401 // Fields should be private 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | using ConfigObjects; 8 | using Configs; 9 | using EventHandlers; 10 | using Exiled.API.Enums; 11 | using Exiled.API.Features; 12 | using HarmonyLib; 13 | using MEC; 14 | using PlayerRoles; 15 | using Scp914; 16 | using UnityEngine; 17 | 18 | using Player = Exiled.Events.Handlers.Player; 19 | using Random = System.Random; 20 | using Scp914 = Exiled.Events.Handlers.Scp914; 21 | using Server = Exiled.Events.Handlers.Server; 22 | 23 | public class Main : Plugin 24 | { 25 | public static Main Instance; 26 | public PlayerHandlers PlayerHandlers; 27 | public ServerHandlers ServerHandlers; 28 | public MapHandlers MapHandlers; 29 | public Random Rng = new(); 30 | public Harmony Harmony; 31 | public string HarmonyName; 32 | 33 | public override string Name { get; } = "Common Utilities"; 34 | 35 | public override string Author { get; } = "Exiled-Team"; 36 | 37 | public override Version Version { get; } = new(7, 1, 1); 38 | 39 | public override Version RequiredExiledVersion { get; } = new(8, 5, 0); 40 | 41 | public override string Prefix { get; } = "CommonUtilities"; 42 | 43 | public override PluginPriority Priority => PluginPriority.Higher; 44 | 45 | public List Coroutines { get; } = new(); 46 | 47 | public Dictionary> AfkDict { get; } = new(); 48 | 49 | public override void OnEnabled() 50 | { 51 | if (Config.Debug) 52 | DebugConfig(); 53 | 54 | Instance = this; 55 | 56 | Log.Info($"Instantiating Events.."); 57 | PlayerHandlers = new PlayerHandlers(this); 58 | ServerHandlers = new ServerHandlers(this); 59 | MapHandlers = new MapHandlers(this); 60 | 61 | Log.Info($"Registering EventHandlers.."); 62 | if (Config.HealthOnKill != null) 63 | Player.Died += PlayerHandlers.OnPlayerDied; 64 | Player.Hurting += PlayerHandlers.OnPlayerHurting; 65 | Player.Verified += PlayerHandlers.OnPlayerVerified; 66 | if (Config.StartingInventories != null) 67 | Player.ChangingRole += PlayerHandlers.OnChangingRole; 68 | Player.Spawned += PlayerHandlers.OnSpawned; 69 | Player.InteractingDoor += PlayerHandlers.OnInteractingDoor; 70 | if (Config.RadioBatteryDrainMultiplier is not 1) 71 | Player.UsingRadioBattery += PlayerHandlers.OnUsingRadioBattery; 72 | Player.InteractingElevator += PlayerHandlers.OnInteractingElevator; 73 | if (Config.DisarmSwitchTeams) 74 | Player.Escaping += PlayerHandlers.OnEscaping; 75 | if (Config.AfkLimit > 0) 76 | { 77 | Player.Jumping += PlayerHandlers.AntiAfkEventHandler; 78 | Player.Shooting += PlayerHandlers.AntiAfkEventHandler; 79 | Player.UsingItem += PlayerHandlers.AntiAfkEventHandler; 80 | Player.MakingNoise += PlayerHandlers.AntiAfkEventHandler; 81 | Player.ReloadingWeapon += PlayerHandlers.AntiAfkEventHandler; 82 | Player.ThrownProjectile += PlayerHandlers.AntiAfkEventHandler; 83 | Player.ChangingMoveState += PlayerHandlers.AntiAfkEventHandler; 84 | } 85 | 86 | Server.RoundEnded += ServerHandlers.OnRoundEnded; 87 | Server.RoundStarted += ServerHandlers.OnRoundStarted; 88 | Server.RestartingRound += ServerHandlers.OnRestartingRound; 89 | Server.WaitingForPlayers += ServerHandlers.OnWaitingForPlayers; 90 | 91 | if (Config.Scp914ItemChanges != null) 92 | Scp914.UpgradingPickup += MapHandlers.OnScp914UpgradingItem; 93 | if (Config.Scp914ItemChanges != null) 94 | Scp914.UpgradingInventoryItem += MapHandlers.OnScp914UpgradingInventoryItem; 95 | Scp914.UpgradingPlayer += MapHandlers.OnScp914UpgradingPlayer; 96 | 97 | Exiled.Events.Handlers.Warhead.Starting += ServerHandlers.OnWarheadStarting; 98 | Exiled.Events.Handlers.Warhead.Stopping += ServerHandlers.OnWarheadStopping; 99 | 100 | HarmonyName = $"com-joker.cu-{DateTime.UtcNow.Ticks}"; 101 | Harmony = new Harmony(HarmonyName); 102 | Harmony.PatchAll(); 103 | 104 | base.OnEnabled(); 105 | } 106 | 107 | public override void OnDisabled() 108 | { 109 | Player.Died -= PlayerHandlers.OnPlayerDied; 110 | Player.Jumping -= PlayerHandlers.AntiAfkEventHandler; 111 | Player.Shooting -= PlayerHandlers.AntiAfkEventHandler; 112 | Player.UsingItem -= PlayerHandlers.AntiAfkEventHandler; 113 | Player.Hurting -= PlayerHandlers.OnPlayerHurting; 114 | Player.Verified -= PlayerHandlers.OnPlayerVerified; 115 | Player.MakingNoise -= PlayerHandlers.AntiAfkEventHandler; 116 | Player.ReloadingWeapon -= PlayerHandlers.AntiAfkEventHandler; 117 | Player.ChangingRole -= PlayerHandlers.OnChangingRole; 118 | Player.Spawned -= PlayerHandlers.OnSpawned; 119 | Player.ThrownProjectile -= PlayerHandlers.AntiAfkEventHandler; 120 | Player.InteractingDoor -= PlayerHandlers.OnInteractingDoor; 121 | Player.UsingRadioBattery -= PlayerHandlers.OnUsingRadioBattery; 122 | Player.ChangingMoveState -= PlayerHandlers.AntiAfkEventHandler; 123 | Player.InteractingElevator -= PlayerHandlers.OnInteractingElevator; 124 | Player.Escaping -= PlayerHandlers.OnEscaping; 125 | 126 | Server.RoundEnded -= ServerHandlers.OnRoundEnded; 127 | Server.RoundStarted -= ServerHandlers.OnRoundStarted; 128 | Server.RestartingRound -= ServerHandlers.OnRestartingRound; 129 | Server.WaitingForPlayers -= ServerHandlers.OnWaitingForPlayers; 130 | 131 | Scp914.UpgradingPickup -= MapHandlers.OnScp914UpgradingItem; 132 | Scp914.UpgradingPlayer -= MapHandlers.OnScp914UpgradingPlayer; 133 | Scp914.UpgradingInventoryItem -= MapHandlers.OnScp914UpgradingInventoryItem; 134 | 135 | Exiled.Events.Handlers.Warhead.Starting -= ServerHandlers.OnWarheadStarting; 136 | Exiled.Events.Handlers.Warhead.Stopping -= ServerHandlers.OnWarheadStopping; 137 | 138 | Harmony.UnpatchAll(HarmonyName); 139 | 140 | ServerHandlers = null; 141 | PlayerHandlers = null; 142 | MapHandlers = null; 143 | base.OnDisabled(); 144 | } 145 | 146 | public void DebugConfig() 147 | { 148 | if (Config.StartingInventories != null) 149 | { 150 | Log.Debug($"{Config.StartingInventories.Count}"); 151 | foreach (KeyValuePair inv in Config.StartingInventories) 152 | { 153 | for (int i = 0; i < inv.Value.UsedSlots; i++) 154 | { 155 | foreach (ItemChance chance in inv.Value[i]) 156 | { 157 | Log.Debug($"Inventory Config: {inv.Key} - Slot{i + 1}: {chance.ItemName} ({chance.Chance})"); 158 | } 159 | } 160 | 161 | foreach ((ItemType type, ushort amount, string group) in inv.Value.Ammo) 162 | { 163 | Log.Debug($"Ammo Config: {inv.Key} - {type} {amount} ({group})"); 164 | } 165 | } 166 | } 167 | 168 | if (Config.Scp914ItemChanges != null) 169 | { 170 | Log.Debug($"{Config.Scp914ItemChanges.Count}"); 171 | foreach (KeyValuePair> upgrade in Config.Scp914ItemChanges) 172 | { 173 | foreach ((ItemType oldItem, ItemType newItem, double chance, int count) in upgrade.Value) 174 | Log.Debug($"914 Item Config: {upgrade.Key}: {oldItem} -> {newItem}x({count}) - {chance}"); 175 | } 176 | } 177 | 178 | if (Config.Scp914ClassChanges != null) 179 | { 180 | Log.Debug($"{Config.Scp914ClassChanges.Count}"); 181 | foreach (KeyValuePair> upgrade in Config.Scp914ClassChanges) 182 | { 183 | foreach ((RoleTypeId oldRole, string newRole, double chance, bool keepInventory, bool keepHealth) in upgrade.Value) 184 | Log.Debug($"914 Role Config: {upgrade.Key}: {oldRole} -> {newRole} - {chance} keepInventory: {keepInventory} keepHealth: {keepHealth}"); 185 | } 186 | } 187 | 188 | if (Config.Scp914EffectChances != null) 189 | { 190 | Log.Debug($"{Config.Scp914EffectChances.Count}"); 191 | foreach (KeyValuePair> upgrade in Config.Scp914EffectChances) 192 | { 193 | foreach ((EffectType effect, double chance, float duration) in upgrade.Value) 194 | Log.Debug($"914 Effect Config: {upgrade.Key}: {effect} + {duration} - {chance}"); 195 | } 196 | } 197 | 198 | if (Config.Scp914TeleportChances != null) 199 | { 200 | Log.Debug($"{Config.Scp914TeleportChances.Count}"); 201 | foreach (KeyValuePair> upgrade in Config.Scp914TeleportChances) 202 | { 203 | foreach ((RoomType room, List ignoredRooms, Vector3 offset, double chance, float damage, ZoneType zone) in upgrade.Value) 204 | { 205 | Log.Debug($"914 Teleport Config: {upgrade.Key}: {room}/{zone} + {offset} - {chance} [{damage}]"); 206 | Log.Debug("Ignored rooms:"); 207 | if (ignoredRooms != null) 208 | { 209 | foreach (RoomType roomType in ignoredRooms) 210 | { 211 | Log.Debug(roomType); 212 | } 213 | } 214 | } 215 | } 216 | } 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /Common Utilities/Patches/ServerGrantLoadoutPatches.cs: -------------------------------------------------------------------------------- 1 | namespace Common_Utilities.Patches 2 | { 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | using Common_Utilities.ConfigObjects; 7 | using Common_Utilities.Configs; 8 | using Exiled.API.Features; 9 | using HarmonyLib; 10 | using InventorySystem; 11 | using PlayerRoles; 12 | 13 | [HarmonyPatch(typeof(InventoryItemProvider), nameof(InventoryItemProvider.ServerGrantLoadout))] 14 | public class ServerGrantLoadoutPatches 15 | { 16 | public static bool Prefix(ReferenceHub target, RoleTypeId roleTypeId, bool resetInventory = true) 17 | { 18 | if (Main.Instance.Config.StartingInventories == null || !Main.Instance.Config.StartingInventories.TryGetValue(roleTypeId, out RoleInventory startingInventories) || !Player.TryGet(target, out Player player)) 19 | return true; 20 | 21 | if (resetInventory) 22 | player.ClearInventory(); 23 | 24 | player.AddItem(Main.Instance.PlayerHandlers.StartItems(roleTypeId, player)); 25 | 26 | if (startingInventories.Ammo != null && startingInventories.Ammo.Count > 0) 27 | { 28 | IEnumerable startingAmmo = startingInventories.Ammo.Where(s => string.IsNullOrEmpty(s.Group) || s.Group == "none" || (ServerStatic.PermissionsHandler._groups.TryGetValue(s.Group, out UserGroup userGroup) && userGroup == player.Group)); 29 | if (startingAmmo.Any()) 30 | { 31 | player.Ammo.Clear(); 32 | 33 | foreach ((ItemType type, ushort amount, string group) in startingAmmo) 34 | { 35 | player.Ammo.Add(type, amount); 36 | } 37 | } 38 | } 39 | 40 | return false; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $(MSBuildThisFileDirectory)\Exiled.ruleset 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Exiled.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Plugin.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exiled-Team 6 | 7 | 8 | 9 | net48 10 | 10.0 11 | x64 12 | false 13 | ../bin\$(Configuration)\ 14 | true 15 | false 16 | 17 | 18 | 19 | 20 | 21 | 7.1.0 22 | 23 | false 24 | 25 | 2.2.2 26 | 1.1.118 27 | 1.3.0 28 | 8.0.0-beta.1 29 | 30 | Copyright © $(Authors) 2020 - $([System.DateTime]::Now.ToString("yyyy")) 31 | Git 32 | https://github.com/galaxy119/EXILED 33 | https://github.com/galaxy119/EXILED 34 | CC-BY-SA-3.0 35 | 36 | $(DefineConstants);PUBLIC_BETA 37 | 38 | 39 | 40 | False 41 | Portable 42 | 43 | 44 | 45 | 46 | $(NoWarn);SA0001 47 | $(NoWarn);SA1000 48 | $(NoWarn);SA1520 49 | $(NoWarn);SA1009 50 | $(NoWarn);SA1002 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Common-Utils 2 | Common Utils is a plugin that serves many common utilites in a day to day server life. 3 | 4 | # Main Features 5 | ## 914 Features 6 | - Ability to change 914's class upgrading (ex, DClass goes in; scientist comes out.) 7 | - Ability to add custom 914 recipies. 8 | ## Server Broadcast/Welcoming Features 9 | - Ability to completly configure a welcome message. 10 | - Ability to completly configure a broadcast message, this can appear every 'x' amount seconds. 11 | ## Custom Inventories 12 | - Ability to add custom inventories to all the main classes. 13 | 14 | (These are depricated, if you are using the Exiled 2.0 version of the plugin) 15 | # Default config: 16 | ```yaml 17 | CommonUtilities: 18 | # Whether or not debug messages should be shown. 19 | debug: false 20 | # The SCP Roles able to use V to talk to humans. 21 | scp_speech: 22 | - Scp049 23 | # Whether or not MTF/CI can 'escape' while disarmed to switch teams. 24 | disarm_switch_teams: true 25 | # Whether or not disarmed people will be prevented from interacting with doors/elevators. 26 | restrictive_disarming: true 27 | # The text displayed at the timed interval specified below. 28 | timed_broadcast: 'This server is running EXILED Common-Utilities, enjoy your stay!' 29 | # The time each timed broadcast will be displayed. 30 | timed_broadcast_duration: 5 31 | # The delay between each timed broadcast. To disable timed broadcasts, set this to 0 32 | timed_broadcast_delay: 300 33 | # The message displayed to the player when they first join the server. Setting this to empty will disable these broadcasts. 34 | join_message: 'Welcome %player%! Please read our rules!' 35 | # The amount of time (in seconds) the join message is displayed. 36 | join_message_duration: 5 37 | # The amount of time (in seconds) after the round starts, before the facilities auto-nuke will start. 38 | autonuke_time: 1500 39 | # Wether or not the nuke should be unable to be disabled during the auto-nuke countdown. 40 | autonuke_lock: true 41 | # The message given to all players when the auto-nuke is triggered. A duration of 2 or more will be a text message on-screen. A duration of 1 makes it a cassie announcement. A duration of 0 disables it. 42 | autonuke_broadcast: 43 | # The broadcast content 44 | content: 'The auto nuke has been activated.' 45 | # The broadcast duration 46 | duration: 10 47 | # The broadcast type 48 | type: Normal 49 | # Indicates whether the broadcast should be shown or not 50 | show: true 51 | # Whether or not to show player's health under their name when you look at them. 52 | player_health_info: true 53 | # Whether or not friendly fire should automatically turn on when a round ends (it will turn itself back off before the next round starts). 54 | friendly_fire_on_round_end: false 55 | # The multiplier applied to radio battery usage. Set to 0 to disable radio battery drain. 56 | radio_battery_drain_multiplier: 1 57 | # The color to use for lights while the warhead is active. 58 | warhead_color: 59 | r: 1 60 | g: 0.2 61 | b: 0.2 62 | a: 1 63 | # The maximum time, in seconds, that a player can be AFK before being kicked. Set to -1 to disable AFK system. 64 | afk_limit: 120 65 | # The roles that are ignored by the AFK system. 66 | afk_ignored_roles: 67 | - Scp079 68 | - Spectator 69 | - Tutorial 70 | # Whether or not probabilities should be additive (50 + 50 = 100) or not (50 + 50 = 2 seperate 50% chances) 71 | additive_probabilities: false 72 | # The list of starting items for roles. ItemName is the item to give them, and Chance is the percent chance of them spawning with it, and Group allows you to restrict the item to only players with certain RA groups (Leave this as 'none' to allow all players to get the item). You can specify the same item multiple times. 73 | starting_inventories: 74 | ClassD: 75 | slot1: 76 | - item_name: 'KeycardJanitor' 77 | chance: 10 78 | group: 'none' 79 | - item_name: 'Coin' 80 | chance: 100 81 | group: 'none' 82 | slot2: 83 | - item_name: 'Flashlight' 84 | chance: 100 85 | group: 'none' 86 | slot3: [] 87 | slot4: [] 88 | slot5: [] 89 | slot6: [] 90 | slot7: [] 91 | slot8: [] 92 | ammo: 93 | - type: Ammo556x45 94 | amount: 200 95 | group: 'none' 96 | # The list of custom 914 recipies. Original is the item being upgraded, New is the item to upgrade to, and Chance is the percent chance of the upgrade happening. You can specify multiple upgrade choices for the same item. 97 | scp914_item_changes: 98 | Rough: 99 | - original: KeycardO5 100 | new: MicroHID 101 | chance: 50 102 | # The list of custom 914 recipies for roles. Original is the role to be changed, New is the new role to assign, Chance is the % chance of the upgrade occuring. 103 | scp914_class_changes: 104 | Rough: 105 | - original: ClassD 106 | new: Spectator 107 | chance: 100 108 | spawn_flags: None 109 | # The list of 914 teleport settings. Note that if you set "zone" to anything other than Unspecified, it will always select a random room from that zone, instead of the room type defined. 110 | scp914_teleport_chances: 111 | Rough: 112 | - zone: Unspecified 113 | room: LczClassDSpawn 114 | offset: 115 | x: 0 116 | y: 0 117 | z: 0 118 | chance: 100 119 | damage: 0 120 | # A dictionary of random effects to apply to players when going through 914 on certain settings. 121 | scp914_effect_chances: 122 | Rough: 123 | - effect: Bleeding 124 | chance: 100 125 | duration: 0 126 | # Determines if 914 effects are exclusive, meaning only one can be applied each time a player is processed by 914. 127 | scp914_effects_exclusivity: false 128 | # Whether or not SCPs are immune to effects gained from 914. 129 | scps_immune_to914_effects: false 130 | # The frequency (in seconds) between ragdoll cleanups. Set to 0 to disable. 131 | ragdoll_cleanup_delay: 0 132 | # If ragdoll cleanup should only happen in the Pocket Dimension or not. 133 | ragdoll_cleanup_only_pocket: false 134 | # The frequency (in seconds) between item cleanups. Set to 0 to disable. 135 | item_cleanup_delay: 0 136 | # If item cleanup should only happen in the Pocket Dimension or not. 137 | item_cleanup_only_pocket: false 138 | # A list of all roles and their damage modifiers. The number here is a multiplier, not a raw damage amount. Thus, setting it to 1 = normal damage, 1.5 = 50% more damage, and 0.5 = 50% less damage. 139 | role_damage_multipliers: 140 | Scp173: 1 141 | # A list of all Weapons and their damage modifiers. The number here is a multiplier, not a raw damage amount. Thus, setting it to 1 = normal damage, 1.5 = 50% more damage, and 0.5 = 50% less damage. 142 | damage_multipliers: 143 | E11Sr: 1 144 | # A list of roles and how much health they should be given when they kill someone. 145 | health_on_kill: 146 | Scp173: 0 147 | Scp939: 10 148 | # A list of roles and what their default starting health should be. 149 | health_values: 150 | Scp173: 3200 151 | NtfCaptain: 150 152 | # If the plugin is enabled or not. 153 | is_enabled: true 154 | 155 | ``` 156 | -------------------------------------------------------------------------------- /Updating.md: -------------------------------------------------------------------------------- 1 | ### This guide should help you understand the differences between the old and new configs. 2 | 3 | ## Inventory 4 | #### All inventory configs have been rolled into a single config value `starting_inventories`. This means that you will not need to have 'empty configs' defined for roles you do not wish to change the inventories for. The new configs also allow you specify a usergroup, which will mean only that group can get that item. 5 | starting_inventories is a dictionary, and should be formatted like this: 6 | ```yaml 7 | starting_inventories: 8 | ClassD: 9 | slot1: 10 | - item_name: KeycardJanitor 11 | chance: 10 12 | group: none 13 | ``` 14 | If you wish to add additional slots, or roles, you can do so. 15 | With the new inventory config, if you define a role, it's inventory will be replaced with whatever is listed. If you list a role here, but don't give them any items, it will give them an empty inventory. 16 | If you don't wish to change a particular roles inventory, simply don't include their roletype in this config. 17 | 18 | You can define configs for slots 1 through 8. Any slots that you don't define will be auto-generated as empty, but not defining them will NOT cause the config to break. 19 | 20 | ## SCP-914 Item recipe configs: 21 | #### These configs have been simplified, to allow you to not define keys you will not use, and should look like this: 22 | ```yaml 23 | scp914_item_changes: 24 | Rough: 25 | - original: KeycardO5 26 | new: MicroHID 27 | chance: 50 28 | - original: GunCOM18 29 | new: GunCOM15 30 | chance: 100 31 | ``` 32 | If you wish to add additional knob settings, you can do so, but unused knob setting values will not be auto-generated anymore. 33 | 34 | ## SCP-914 Class recipe configs: 35 | #### Everything from the above Item recipe config applies, except this uses RoleTypes instead of ItemTypes, IE: 36 | ```yaml 37 | scp914_class_changes: 38 | Rough: 39 | - original: ClassD 40 | new: Spectator 41 | chance: 100 42 | OneToOne: 43 | - original: ClassD 44 | new: Scientist 45 | chance: 100 46 | - original: Scientist 47 | new: ClassD 48 | chance: 100 49 | ``` 50 | 51 | 52 | ## SCP Damage Multipliers 53 | #### These have been renamed to `role_damage_multipliers` and will affect the damage of anyone who has that role. 54 | 55 | ## Weapon Damage Multipliers 56 | #### This has changed to use ItemTypes, and will affect any damage dealt by the specified item. Since only guns grenades & micro can deal damage, only those are affected. 57 | 58 | ### Both of the above damage multiplier configs are applied together, meaning if you have NtfCaptain doing double damage, and then have E11 doing double damage, an NtfCaptain will deal 4x damage with an E11 59 | 60 | ## HealthOnKill and HealthValues have both been updated to use RoleTypes directly, instead of a string that's parsed into a RoleType by the plugin. 61 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | // ACTION REQUIRED: This file was automatically added to your project, but it 3 | // will not take effect until additional steps are taken to enable it. See the 4 | // following page for additional information: 5 | // 6 | // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md 7 | 8 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 9 | "settings": { 10 | "documentationRules": { 11 | "companyName": "Chaos Theory", 12 | "copyrightText": "Unauthorized copying of this file, via any medium is strictly prohibited.", 13 | "headerDecoration": "-----------------------------------------------------------------------", 14 | "variables": { 15 | "licenseFile": "LICENSE", 16 | "licenseName": "Proprietary and confidential" 17 | } 18 | }, 19 | "orderingRules": { 20 | "blankLinesBetweenUsingGroups": "require" 21 | } 22 | } 23 | } 24 | --------------------------------------------------------------------------------