using AdminToys; using CommandSystem; using CustomPlayerEffects; using HintServiceMeow.Core.Enum; using HintServiceMeow.Core.Models.Hints; using HintServiceMeow.Core.Utilities; using Interactables.Interobjects.DoorUtils; using InventorySystem.Items.Firearms.Modules; using LabApi.Events.Handlers; using LabApi.Features.Permissions; using LabApi.Features.Wrappers; using MEC; using PlayerRoles; using UnityEngine; using LightSourceToy = LabApi.Features.Wrappers.LightSourceToy; using Logger = LabApi.Features.Console.Logger; using PrimitiveObjectToy = LabApi.Features.Wrappers.PrimitiveObjectToy; using Random = System.Random; namespace CustomClasses; public class SerpentsHandManager { public static bool IsSerpentsHand(Player player) { try { return player.CustomInfo.Contains("SerpentsHand"); } catch { return false; } } private readonly CustomClasses _customClasses; public SerpentsHandManager(CustomClasses customClasses) { _customClasses = customClasses; PlayerEvents.Escaping += ev => { if (!IsSerpentsHand(ev.Player)) return; var state = (SerpentsHandState) _customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)); var hadItem = false; foreach (var playerItem in ev.Player.Items) { switch (playerItem.Type) { case ItemType.SCP018 or ItemType.SCP207 or ItemType.SCP244a or ItemType.SCP244b or ItemType.SCP268 or ItemType.SCP330 or ItemType.SCP500 or ItemType.SCP1344 or ItemType.SCP1576 or ItemType.SCP1853 or ItemType.SCP2176 or ItemType.AntiSCP207: state.Points += 1; hadItem = true; break; case ItemType.GunSCP127: state.Points += 2; hadItem = true; break; } } if (Warhead.IsDetonationInProgress || Warhead.IsDetonated) { hadItem = true; state.Points += 1; } if (!hadItem) return; ev.Player.SendBroadcast("You brought back the SCP items...", 5); ev.Player.SetRole(RoleTypeId.Spectator); Logger.Info($"New SH Points: {state.Points}"); }; ServerEvents.RoundEnding += ev => { var factions = Player.ReadyList.Select(x => { return x.Team switch { Team.ChaosInsurgency or Team.ClassD => Faction.FoundationEnemy, Team.FoundationForces or Team.Scientists => Faction.FoundationStaff, Team.Flamingos => Faction.Flamingos, Team.SCPs => Faction.SCP, Team.OtherAlive when IsSerpentsHand(x) => (Faction)35, _ => Faction.Unclassified }; }).Where(x=>x!=Faction.Unclassified).GroupBy(x=>x).Select(x=>x.Key).ToArray(); if (factions.Length > 1) { ev.IsAllowed = false; return; } var state = (SerpentsHandState) _customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)); if (state.Points >= 10) { ev.LeadingTeam = RoundSummary.LeadingTeam.Flamingos; } }; } public static void PreSpawn(Player player) { player.SetRole(RoleTypeId.Tutorial, RoleChangeReason.RespawnMiniwave, RoleSpawnFlags.None); } public IEnumerator UpdateSerpentsHandHint() { while (true) { yield return Timing.WaitForSeconds(1); if (_customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) continue; foreach (var player in Player.ReadyList) { if (!_customClasses.Hints.ContainsKey(player)) { _customClasses.Hints[player] = new Hint { Text = "", Alignment = HintAlignment.Center, YCoordinate = 900, Hide = true }; var playerDisplay = PlayerDisplay.Get(player); playerDisplay.AddHint(_customClasses.Hints[player]); } _customClasses.Hints[player].Text = $"Serpents Hand Chance: {state.ExtraChance + SerpentsHandConfig.BaseChance:0.00}%"; _customClasses.Hints[player].Hide = state.HasSpawned || player.Role != RoleTypeId.Spectator; } } // ReSharper disable once IteratorNeverReturns } public static void SpawnSerpentWave() { var state = (SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)); var serpentsHandConfig = CustomClasses.Instance.ClassManager.GetConfig(); state.SetSpawned(); state.SetWillSpawn(); var possibleLocations = (Vector3[])serpentsHandConfig.SpawnLocations.Clone(); possibleLocations.ShuffleListSecure(); var spawnLocation = possibleLocations[0]; foreach (var possibleLocation in possibleLocations) { if (Player.ReadyList.Any(p => (p.Position - possibleLocation).SqrMagnitudeIgnoreY() < 400)) { continue; } spawnLocation = possibleLocation; break; } state.SpawnLocation = spawnLocation; var light = LightSourceToy.Create(spawnLocation + new Vector3(0, 2, 0), Quaternion.identity); light.Color = Color.blue; light.Intensity = 3; light.Range = 40; var spaceTimeHole = PrimitiveObjectToy.Create(spawnLocation + new Vector3(0, 2, 0), Quaternion.identity); spaceTimeHole.Color = new Color(44.7f, 73.7f, 83.1f, 0.5f); spaceTimeHole.Flags = PrimitiveFlags.Visible; spaceTimeHole.Scale *= 2; Timing.CallDelayed(20, () => { spaceTimeHole.Destroy(); light.Destroy(); }); } } public sealed record SerpentsHandState: SpawnState { public bool HasSpawned => _hasSpawned || Warhead.IsDetonated; public float ExtraChance; public int Points; public bool WillSpawn => _willSpawn && !Warhead.IsDetonated; private bool _hasSpawned; private bool _willSpawn; public Vector3? SpawnLocation; public override void Reset() { base.Reset(); _hasSpawned = false; ExtraChance = 0f; Points = 0; _willSpawn = false; SpawnLocation = null; } public void SetSpawned() { _hasSpawned = true; } public void SetWillSpawn() { _willSpawn = true; } public void SetWillNotSpawn() { _willSpawn = false; } } public sealed class SerpentsHandConfig : CustomClassConfig { public override double ChancePerPlayer { get; set; } = 1.0; public override int MaxSpawns { get; set; } = int.MaxValue; public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Tutorial; public override ItemType[] Items { get; set; } = [ItemType.Painkillers, ItemType.Medkit, ItemType.ArmorCombat]; public const float BaseChance = 90f; public readonly Vector3[] SpawnLocations = [new(0.22f, 300.96f, -0.31f), new(123.921f, 288.792f, 20.929f)]; public override string Color { get; init; } = "#32CD32"; public override string Name { get; init; } = "Serpents Hand"; } public class SerpentsHandHandler : SimpleAddItemHandler { public override void HandleSpawn(Player player, CustomClassConfig config, Random random) { base.HandleSpawn(player, config, random); var spawnLocation = ((SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState( typeof(SerpentsHandConfig))).SpawnLocation; if (spawnLocation != null) CustomClasses.Instance.ClassManager.TeleportPlayerToAround(player, (Vector3)spawnLocation); ItemType[] guns = [ItemType.GunAK, ItemType.GunE11SR, ItemType.GunCrossvec]; var gun = guns[random.Next(0, guns.Length-1)]; var gunPickup = Pickup.Create(gun, Vector3.one); if (gunPickup is FirearmPickup firearm) { if (firearm.Base.Template.TryGetModule(out MagazineModule magazine)) { magazine.ServerSetInstanceAmmo(firearm.Serial, magazine.AmmoMax); player.SetAmmo(magazine.AmmoType, 120); } else { Logger.Error("Failed to get magazine module for Serpents Hand firearm."); } } else { Logger.Error("Failed to get firearm from pickup for Serpents Hand."); } player.AddItem(gunPickup!); KeycardItem.CreateCustomKeycardTaskForce( player, "Serpent's Hand Keycard", $"SH. {player.Nickname}", new KeycardLevels(3, 3, 2), Color.black, new Color(0.271f, 0.271f, 0.271f), "SH", 3 ); player.EnableEffect(20, 30); player.EnableEffect(1, 20f); } public override void SendSpawnMessage(Player player, CustomClassConfig config) { player.SendBroadcast("You're a Serpent's Hand member!", CustomClasses.BroadcastDuration); } } [CommandHandler(typeof(RemoteAdminCommandHandler))] [CommandHandler(typeof(ClientCommandHandler))] public class SpawnSerpentsCommand : ICommand { public string Command => "spsh"; public string[] Aliases => []; public string Description => "Makes sure serpents hand spawns"; public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) { var executor = Player.Get(sender); if (executor == null) { response = "You must be a player to use this command!"; return false; } if (!executor.HasPermissions("customclasses.setcustomclass")) { response = "You do not have permission to use this command!"; return false; } SerpentsHandManager.SpawnSerpentWave(); response = "success"; return true; } }