diff --git a/AfkSwap/AfkSwap.cs b/AfkSwap/AfkSwap.cs index 6653c82..062ea30 100644 --- a/AfkSwap/AfkSwap.cs +++ b/AfkSwap/AfkSwap.cs @@ -98,7 +98,7 @@ public class AfkSwap : Plugin continue; } - if (!_playerPositions[playerTime.Key].Equals(playerTime.Key.Position)) + if ((_playerPositions[playerTime.Key] - playerTime.Key.Position).sqrMagnitude > 2) { _playerSpawnTimes.Remove(playerTime.Key); _playerPositions.Remove(playerTime.Key); diff --git a/CustomClasses/CustomClasses.cs b/CustomClasses/CustomClasses.cs index 82958a1..f05d67c 100644 --- a/CustomClasses/CustomClasses.cs +++ b/CustomClasses/CustomClasses.cs @@ -11,11 +11,13 @@ using LabApi.Events.Arguments.Scp914Events; using LabApi.Events.Arguments.ServerEvents; using LabApi.Events.Handlers; using LabApi.Features; +using LabApi.Features.Enums; using LabApi.Features.Wrappers; using LabApi.Loader.Features.Plugins; using MapGeneration; using MEC; using PlayerRoles; +using PlayerRoles.PlayableScps.Scp106; using Scp914.Processors; using UnityEngine; using Logger = LabApi.Features.Console.Logger; @@ -32,6 +34,7 @@ public sealed class CustomClasses : Plugin { public readonly CustomClassManager ClassManager = new(); public SerpentsHandManager SerpentsHandManager; + public NegromancerManager NegromancerManager; /// public override string Name => "CustomClasses"; @@ -77,6 +80,8 @@ public sealed class CustomClasses : Plugin public ExplosiveMasterConfig ExplosiveMasterConfig { get; private set; } = new(); public FlashMasterConfig FlashMasterConfig { get; private set; } = new(); public SerpentsHandConfig SerpentsHandConfig { get; private set; } = new(); + public NegromancerConfig NegromancerConfig { get; private set; } = new(); + public NegromancerShadowConfig NegromancerShadowConfig { get; private set; } = new(); internal readonly Dictionary Hints = new(); @@ -142,6 +147,7 @@ public sealed class CustomClasses : Plugin } } + NegromancerManager = new NegromancerManager(this); Instance = this; } @@ -330,6 +336,8 @@ public class CustomClassManager RegisterHandler(new ExplosiveMasterHandler()); RegisterHandler(new FlashMasterHandler()); RegisterHandler(new SerpentsHandHandler(), new SerpentsHandState()); + RegisterHandler(new NegromancerHandler()); + RegisterHandler(new NegromancerShadowHandler()); } public SpawnState GetSpawnState(Type configType) @@ -420,6 +428,29 @@ public class CustomClassManager } } +public class NegromancerShadowHandler : CustomClassHandler +{ + public override void HandleSpawn(Player player, CustomClassConfig config, Random random) + { + player.MaxHealth = 1000; + player.MaxHumeShield = 0; + player.HumeShield = 0; + + player.EnableEffect(10, float.PositiveInfinity); + + const string customInfo = "Shadow"; + if (!Player.ValidateCustomInfo(customInfo, out var reason)) + { + Logger.Error($"Invalid custom info for Shadow: {reason}"); + } + else + { + player.CustomInfo = customInfo; + player.InfoArea |= PlayerInfoArea.CustomInfo; + } + } +} + /// /// Interface for custom class spawn handlers. /// @@ -444,6 +475,15 @@ public abstract class CustomClassHandler: ICustomClassHandler } } +public enum JanitorSpawn +{ + Lcz173, + Lcz914, + LczGr18, + Lcz330 +} + + /// /// Handler for the Janitor custom class. /// @@ -451,13 +491,51 @@ public class JanitorHandler(CustomClassManager manager) : CustomClassHandler { public override void HandleSpawn(Player player, CustomClassConfig config, Random random) { - var scp914 = Map.Rooms.FirstOrDefault(r => r.Name == RoomName.Lcz914); - if (scp914 == null) + var spawnLocation = (JanitorSpawn)random.Next(0, 4); + + switch (spawnLocation) { - Logger.Error("LCZ 914 room not found for Janitor spawn."); - return; + case JanitorSpawn.Lcz914: + var scp914 = Map.Rooms.FirstOrDefault(r => r.Name == RoomName.Lcz914); + if (scp914 == null) + { + Logger.Error("LCZ 914 room not found for Janitor spawn."); + return; + } + manager.TeleportPlayerToAround(player, scp914.Position); + break; + + case JanitorSpawn.Lcz173: + var lcz173Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.Lcz173Connector); + if (lcz173Door == null) + { + Logger.Error("LCZ 173 connector door not found for Janitor spawn."); + return; + } + manager.TeleportPlayerToAround(player, lcz173Door.Position); + break; + case JanitorSpawn.LczGr18: + var lczGr18Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.LczGr18Inner); + if (lczGr18Door == null) + { + Logger.Error("LCZ Gr18 door not found for Janitor spawn."); + return; + } + manager.TeleportPlayerToAround(player, lczGr18Door.Position); + break; + case JanitorSpawn.Lcz330: + var lcz330Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.Lcz330); + if (lcz330Door == null) + { + Logger.Error("LCZ 330 door not found for Janitor spawn."); + return; + } + manager.TeleportPlayerToAround(player, lcz330Door.Position); + break; + default: + throw new ArgumentOutOfRangeException(); } - manager.TeleportPlayerToAround(player, scp914.Position); + foreach (var spawnItem in config.Items) { player.AddItem(spawnItem, ItemAddReason.StartingItem); @@ -865,6 +943,22 @@ public sealed class FlashMasterConfig : CustomClassConfig public override ItemType[] Items { get; set; } = [ItemType.GrenadeFlash]; } +public sealed class NegromancerConfig : CustomClassConfig +{ + public override double ChancePerPlayer { get; set; } = 0.0; + public override int MaxSpawns { get; set; } = 1; + public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp049; + public override ItemType[] Items { get; set; } = []; +} + +public sealed class NegromancerShadowConfig : CustomClassConfig +{ + public override double ChancePerPlayer { get; set; } = 0.0; + public override int MaxSpawns { get; set; } = int.MaxValue; + public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp106; + public override ItemType[] Items { get; set; } = []; +} + /// /// Tracks the spawn state for a custom class. /// diff --git a/CustomClasses/NegromancerHandler.cs b/CustomClasses/NegromancerHandler.cs new file mode 100644 index 0000000..f10d23c --- /dev/null +++ b/CustomClasses/NegromancerHandler.cs @@ -0,0 +1,164 @@ +using System.Net; +using CustomPlayerEffects; +using LabApi.Events.Arguments.Scp049Events; +using LabApi.Events.Handlers; +using LabApi.Features.Console; +using LabApi.Features.Wrappers; +using MEC; +using PlayerRoles; +using PlayerRoles.PlayableScps.Scp049; +using PlayerRoles.PlayableScps.Scp106; + +namespace CustomClasses; + +public class NegromancerHandler : CustomClassHandler +{ + public override void HandleSpawn(Player player, CustomClassConfig config, Random random) + { + player.SendBroadcast("You are the Negromancer! Revived players become your Shadow.", CustomClasses.BroadcastDuration); + const string customInfo = "Shadowmancer"; + if (!Player.ValidateCustomInfo(customInfo, out var reason)) + { + Logger.Error($"Invalid custom info for Negromancer: {reason}"); + } + else + { + player.CustomInfo = customInfo; + player.InfoArea |= PlayerInfoArea.CustomInfo; + } + } +} + +public class NegromancerManager +{ + private readonly CustomClasses _plugin; + + public static bool IsNegromancer(Player player) => player.CustomInfo.Contains("Shadowmancer"); + public static bool IsShadow(Player player) => player.CustomInfo.Contains("Shadow") && !IsNegromancer(player); + + + public NegromancerManager(CustomClasses plugin) + { + _plugin = plugin; + Scp049Events.ResurrectedBody += OnScp049ResurrectedBody; + + Timing.RunCoroutine(HealNearbyShadows()); + + Scp106Events.TeleportingPlayer += ev => + { + if (!IsShadow(ev.Player)) return; + ev.IsAllowed = false; + ev.Target.EnableEffect(1, float.PositiveInfinity); + ev.Target.Damage(40f, ev.Player); + ev.Player.SendHitMarker(); + }; + + Scp106Events.UsingHunterAtlas += ev => + { + if (!IsShadow(ev.Player)) return; + ev.IsAllowed = false; + + var position = ev.DestinationPosition; + var room = Room.GetRoomAtPosition(position); + + if (room?.LightController == null) + { + return; + } + + var stat = ev.Player.GetStatModule(); + + stat.CurValue = 0; + + room.LightController.FlickerLights(3); + }; + + Scp106Events.ChangingSubmersionStatus += ev => + { + if (!IsShadow(ev.Player)) return; + ev.IsAllowed = false; + + var stat = ev.Player.GetStatModule(); + + ev.Player.DisableEffect(); + + var scaled = stat.CurValue * 40f; + var scaledByte = (byte)scaled; + + if(scaledByte < 15) scaledByte = 15; + + Logger.Debug($"Scaled {stat.CurValue} to {scaledByte}"); + + + ev.Player.EnableEffect(scaledByte, 20); + stat.CurValue = 0; + Timing.CallDelayed(10, () => + { + ev.Player.DisableEffect(); + ev.Player.EnableEffect(10, float.PositiveInfinity); + }); + }; + + Scp106Events.ChangingVigor += ev => + { + if (!IsShadow(ev.Player)) return; + var delta = ev.Value - ev.OldValue; + delta *= 0.5f; + ev.Value = ev.OldValue + delta; + }; + + } + + private static IEnumerator HealNearbyShadows() + { + while (true) + { + yield return Timing.WaitForSeconds(1); + try + { + Player.ReadyList.Where(IsNegromancer).Where(x => + { + var scp = x.RoleBase as Scp049Role; + if (scp == null) + { + Logger.Error("Negromancer has no Scp049Role"); + return false; + } + + scp.SubroutineModule.TryGetSubroutine(out Scp049CallAbility ability); + + if (ability) return ability.IsMarkerShown; + + Logger.Error("Negromancer has no Scp049CallAbility"); + return false; + + }).ToList().ForEach(player => + { + Player.ReadyList.Where(IsShadow).Where(x => + { + var distance = (x.Position - player.Position).SqrMagnitudeIgnoreY(); + return distance < 64; + } + ).ToList().ForEach(x => x.Heal(10)); + }); + } + catch (Exception e) + { + // ignored + } + } + // ReSharper disable once IteratorNeverReturns + } + + private void OnScp049ResurrectedBody(Scp049ResurrectedBodyEventArgs ev) + { + var classManager = _plugin.ClassManager; + // Check if the reviver is a Negromancer + if (classManager == null || + !IsNegromancer(ev.Player)) return; + + ev.Target.SetRole(RoleTypeId.Scp106, RoleChangeReason.Respawn, RoleSpawnFlags.None); + classManager.ForceSpawn(ev.Target, _plugin.NegromancerShadowConfig, typeof(NegromancerShadowConfig), null); + + } +} \ No newline at end of file diff --git a/CustomClasses/SerpentsHandManager.cs b/CustomClasses/SerpentsHandManager.cs index db8d841..be3151a 100644 --- a/CustomClasses/SerpentsHandManager.cs +++ b/CustomClasses/SerpentsHandManager.cs @@ -47,6 +47,12 @@ public class SerpentsHandManager } } + if (Warhead.IsDetonationInProgress || Warhead.IsDetonated) + { + hadItem = true; + state.Points += 1; + } + if (!hadItem) return; ev.Player.SendBroadcast("You brought back the SCP items...", 5); @@ -107,6 +113,8 @@ public class SerpentsHandManager while (true) { yield return Timing.WaitForSeconds(1); + + RoundSummary.singleton.ExtraTargets = Player.ReadyList.Count(IsSerpentsHand); if (_customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) continue; @@ -134,10 +142,10 @@ public class SerpentsHandManager public sealed record SerpentsHandState: SpawnState { - public bool HasSpawned => _hasSpawned || PanicDisable; + public bool HasSpawned => _hasSpawned || PanicDisable || Warhead.IsDetonated; public float ExtraChance; public int Points; - public bool WillSpawn => _willSpawn && !PanicDisable; + public bool WillSpawn => _willSpawn && !PanicDisable && !Warhead.IsDetonated; private bool _hasSpawned; private bool _willSpawn; diff --git a/CustomClasses/SetCClassCommand.cs b/CustomClasses/SetCClassCommand.cs index d1e39de..f071e37 100644 --- a/CustomClasses/SetCClassCommand.cs +++ b/CustomClasses/SetCClassCommand.cs @@ -1,9 +1,11 @@ using CommandSystem; +using LabApi.Features.Permissions; using LabApi.Features.Wrappers; namespace CustomClasses; [CommandHandler(typeof(RemoteAdminCommandHandler))] +[CommandHandler(typeof(ClientCommandHandler))] public class SetCClassCommand : ICommand { public string Command => "setcclass"; @@ -11,6 +13,19 @@ public class SetCClassCommand : ICommand public string Description => "Forces a player to become a specific custom class"; 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; + } + var args = arguments.Array!; if (arguments.Count < 2) { @@ -51,6 +66,7 @@ public class SetCClassCommand : ICommand { SerpentsHandManager.PreSpawn(player); }), + "negromancer" => manager.ForceSpawn(player, customClasses.NegromancerConfig, typeof(NegromancerConfig), null), _ => false }; diff --git a/ScpSwap/ScpSwap.cs b/ScpSwap/ScpSwap.cs index 5839ecc..516c7c6 100644 --- a/ScpSwap/ScpSwap.cs +++ b/ScpSwap/ScpSwap.cs @@ -46,6 +46,11 @@ public class ScpSwap : Plugin return; } + if (ev.Role.RoleTypeId == RoleTypeId.Scp0492) + { + return; + } + ev.Player.SendBroadcast("Willst du dein SCP wechseln? Drücke Ö und gebe .scpswap ein.", 10); }; } diff --git a/ScpSwap/SwapCommand.cs b/ScpSwap/SwapCommand.cs index 9b0b47a..a075b46 100644 --- a/ScpSwap/SwapCommand.cs +++ b/ScpSwap/SwapCommand.cs @@ -33,6 +33,12 @@ public class SwapCommand : ICommand return false; } + if (player.Role == RoleTypeId.Scp0492) + { + response = "You can't swap SCPs while you're a zombie!"; + return false; + } + List validScp = [ "049", diff --git a/VIPTreatment/BullshitDetectedCommand.cs b/VIPTreatment/BullshitDetectedCommand.cs new file mode 100644 index 0000000..162596a --- /dev/null +++ b/VIPTreatment/BullshitDetectedCommand.cs @@ -0,0 +1,42 @@ +using CommandSystem; +using LabApi.Features.Permissions; +using LabApi.Features.Wrappers; + +namespace VIPTreatment; + +[CommandHandler(typeof(RemoteAdminCommandHandler))] +[CommandHandler(typeof(ClientCommandHandler))] +public class BullshitDetectedCommand : ICommand +{ + public string Command => "bullshitdetected"; + + public string[] Aliases => []; + + public string Description => "Broadcasts a message to all players"; + + public bool Execute(ArraySegment arguments, ICommandSender sender, out string response) + { + if (!Player.TryGet(sender, out var player)) + { + response = "You must be a player to use this command!"; + return false; + } + + if (!player.HasPermissions("viptreatment.wiki")) + { + response = "You must have the permission to use this command!"; + return false; + } + + foreach (var target in Player.ReadyList) + { + target.SendBroadcast("⚠️ BULLSHIT DETECTED ⚠️", 5); + target.SendBroadcast("Tipp: Das Wiki hat immer die aktuellsten Informationen!", 5); + } + + Cassie.Message("false information yield_0.8 detected yield_01 see V yield_0.2 KEY", true, false, false); + + response = "Broadcast message sent to all ready players."; + return true; + } +} \ No newline at end of file diff --git a/VisibleSpectators/PlayerDisplayUtil.cs b/VisibleSpectators/PlayerDisplayUtil.cs index 1621a95..e068de4 100644 --- a/VisibleSpectators/PlayerDisplayUtil.cs +++ b/VisibleSpectators/PlayerDisplayUtil.cs @@ -1,5 +1,4 @@ using PlayerRoles; -using LabApi.Features; using LabApi.Features.Wrappers; namespace VisibleSpectators; @@ -42,6 +41,12 @@ public static class PlayerDisplayUtil { "TEAL", "008080" }, { "GOLD", "EFC01A" } }; + + private static readonly Dictionary PlayerSpecificDisplays = new() + { + { "76561198372750067@steam", ("DEAFCC", "1+1=10") }, + { "76561198372587687@steam", ("9933FF", "HoherGeist") } + }; /// /// Returns a formatted display string for a player, with color. @@ -49,6 +54,12 @@ public static class PlayerDisplayUtil public static string PlayerToDisplay(Player player) { if (player is not { IsReady: true }) return string.Empty; + + if (PlayerSpecificDisplays.TryGetValue(player.UserId, out var specificDisplay)) + { + return $"{specificDisplay.Name}"; + } + const string defaultColor = "FFFFFF"; try { diff --git a/build.sh b/build.sh index 80140b2..b92f334 100755 --- a/build.sh +++ b/build.sh @@ -12,7 +12,7 @@ excluded_projects=("TemplateProject" "RangeBan" "LobbyGame" "RangeBan.Tests" "St # Find project directories (containing .csproj files) echo "Copying DLLs to output folder..." -for proj in $(find . -name "*.csproj"); do +find . -path "./fuchsbau" -prune -o -path "./testbau" -prune -o -name "*.csproj" -print0 | while IFS= read -r -d '' proj; do # Extract project name from .csproj file proj_name=$(basename "$proj" .csproj)