commit 840ddbcd47aa238f976fce714f2b8b3f52128dbd Author: code002lover Date: Thu May 22 18:51:47 2025 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ead0428 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +.idea/ +obj/ +*.user +*.dll diff --git a/GamblingCoin/.config/dotnet-tools.json b/GamblingCoin/.config/dotnet-tools.json new file mode 100644 index 0000000..b9f6941 --- /dev/null +++ b/GamblingCoin/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpier": { + "version": "1.0.1", + "commands": [ + "csharpier" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/GamblingCoin/GamblingCoin.cs b/GamblingCoin/GamblingCoin.cs new file mode 100644 index 0000000..03636a8 --- /dev/null +++ b/GamblingCoin/GamblingCoin.cs @@ -0,0 +1,48 @@ +using LabApi.Events.Handlers; +using LabApi.Features; +using LabApi.Features.Console; +using LabApi.Loader; + +namespace GamblingCoin +{ + public class Plugin : LabApi.Loader.Features.Plugins.Plugin + { + public override string Name => "GamblingCoin"; + public override string Author => "Code002Lover"; + public override Version Version { get; } = new(1, 0, 0); + public override string Description => "Gamble your life away"; + public override Version RequiredApiVersion { get; } = new (LabApiProperties.CompiledVersion); + + public GamblingCoinGameplayConfig ConfigGameplay; + public GamblingCoinMessages ConfigMessages; + public GamblingCoinChancesConfig ConfigChances; + + public override void LoadConfigs() + { + base.LoadConfigs(); + + ConfigGameplay = this.LoadConfig< GamblingCoinGameplayConfig > ("gameplay.yml"); + ConfigMessages = this.LoadConfig< GamblingCoinMessages > ("messages.yml"); + ConfigChances = this.LoadConfig< GamblingCoinChancesConfig > ("chances.yml"); + } + + + public static Plugin Singleton; + private GamblingCoinEventHandler _eventHandler; + + public override void Enable() + { + Logger.Debug("starting..."); + Singleton = this; + _eventHandler = new GamblingCoinEventHandler(); + PlayerEvents.FlippedCoin += _eventHandler.OnFlippedCoin; + } + + public override void Disable() + { + Logger.Debug("unloading..."); + Singleton = null; + PlayerEvents.FlippedCoin -= _eventHandler.OnFlippedCoin; + } + } +} \ No newline at end of file diff --git a/GamblingCoin/GamblingCoin.csproj b/GamblingCoin/GamblingCoin.csproj new file mode 100644 index 0000000..41fbf5a --- /dev/null +++ b/GamblingCoin/GamblingCoin.csproj @@ -0,0 +1,37 @@ + + + + net48 + enable + disable + 10 + + + + true + true + full + + + + true + false + none + + + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll + + + + + + + diff --git a/GamblingCoin/GamblingCoinConfig.cs b/GamblingCoin/GamblingCoinConfig.cs new file mode 100644 index 0000000..18d09ea --- /dev/null +++ b/GamblingCoin/GamblingCoinConfig.cs @@ -0,0 +1,182 @@ +using CustomPlayerEffects; + +namespace GamblingCoin +{ + + public class GamblingCoinChancesConfig + { + public int NukeChance { get; set; } = 20; + public int SpawnWaveChance { get; set; } = 150; + public int CommonItemChance { get; set; } = 600; + public int UncommonItemChance { get; set; } = 400; + public int RareItemChance { get; set; } = 250; + public int EpicItemChance { get; set; } = 100; + public int LegendaryItemChance { get; set; } = 30; + public int RandomTeleportChance { get; set; } = 200; + public int StealItemChance { get; set; } = 100; + public int ExplosionChance { get; set; } = 15; + public int AntiMicroChance { get; set; } = 10; + public int GrenadeChance { get; set; } = 50; + public int PocketDimensionChance { get; set; } = 75; + public int SwitchInventoryChance { get; set; } = 150; + public int PositiveEffectChance { get; set; } = 300; + public int NegativeEffectChance { get; set; } = 250; + public int AdvancedPositiveEffectChance { get; set; } = 150; + public int AdvancedNegativeEffectChance { get; set; } = 250; + } + + public class GamblingCoinMessages + { + public String SpawnWaveMessage { get; set; } = "Did someone just enter the Site...?"; + public string ItemSpawnMessage { get; set; } = "*plop*"; + public string UncommonItemSpawnMessage { get; set; } = "*bump*"; + public string RareItemSpawnMessage { get; set; } = "*badunk*"; + public string EpicItemSpawnMessage { get; set; } = "*katong*"; + public string LegendaryItemSpawnMessage { get; set; } = "*sounds of monetary loss*"; + public string RandomTeleportMessage { get; set; } = "Where did you go?"; + public string StealItemMessage { get; set; } = "Be careful, the coin is slippery!"; + public string ExplosionMessage { get; set; } = "How did you even die '-'"; + public string AntiMicroMessage { get; set; } = "Where did all the micros go..."; + public string GrenadeMessage { get; set; } = "Watch out!"; + public string PocketDimensionMessage { get; set; } = "I hear he likes to see people suffer..."; + public string PositiveEffectMessage { get; set; } = "You feel slightly better"; + public string AdvancedPositiveEffectMessage { get; set; } = "You feel better"; + public string NegativeEffectMessage { get; set; } = "You feel worse"; + public string AdvancedNegativeEffectMessage { get; set; } = "You feel like you could die any second"; + public string SwitchInventoryMessage { get; set; } = "Whoops... looks like something happened to your items!"; + } + + public class GamblingCoinGameplayConfig + { + public float WarheadTimeIncrease { get; set; } = 20f; + public ushort BroadcastDuration { get; set; } = 3; + public float TeleportHeightOffset { get; set; } = 1f; + public int MaxMicrosToRemove { get; set; } = 8; + + public ItemPoolConfig Items { get; set; } = new(); + public EffectConfig Effects { get; set; } = new(); + } + + public class ItemPoolConfig + { + public ItemType[] CommonItems { get; set; } = { + ItemType.KeycardJanitor, + ItemType.KeycardScientist, + ItemType.Medkit, + ItemType.Painkillers, + ItemType.Radio, + ItemType.Flashlight + }; + + public ItemType[] UncommonItems { get; set; } = { + ItemType.KeycardZoneManager, + ItemType.KeycardGuard, + ItemType.KeycardResearchCoordinator, + ItemType.Adrenaline, + ItemType.ArmorLight, + ItemType.GrenadeFlash + }; + + public ItemType[] RareItems { get; set; } = { + ItemType.KeycardMTFPrivate, + ItemType.KeycardContainmentEngineer, + ItemType.KeycardMTFOperative, + ItemType.ArmorCombat, + ItemType.ArmorHeavy, + ItemType.SCP330, + ItemType.Lantern, + ItemType.GrenadeHE + }; + + public ItemType[] EpicItems { get; set; } = { + ItemType.KeycardFacilityManager, + ItemType.KeycardChaosInsurgency, + ItemType.KeycardMTFCaptain, + ItemType.SCP500 + }; + + public ItemType[] LegendaryItems { get; set; } = { + ItemType.KeycardO5, + ItemType.MicroHID, + ItemType.Jailbird, + ItemType.ParticleDisruptor, + ItemType.GunCom45, + ItemType.Coin + }; + } + + public class EffectConfig + { + public AdvancedEffectSettings AdvancedPositive { get; set; } = new() + { + Effects = new[] { nameof(Invisible), nameof(DamageReduction), nameof(MovementBoost) }, + Settings = new Dictionary + { + { nameof(Invisible), new EffectSettings(1, 5f, true) }, + { nameof(DamageReduction), new EffectSettings(100, 8f, false) }, + { nameof(MovementBoost), new EffectSettings(40, 2f, false) } + } + }; + + public AdvancedEffectSettings Positive { get; set; } = new() + { + Effects = new[] { nameof(Invigorated), nameof(RainbowTaste), nameof(Vitality), nameof(BodyshotReduction) }, + Settings = new Dictionary + { + { nameof(Invigorated), new EffectSettings(1, 5f, true) }, + { nameof(RainbowTaste), new EffectSettings(2, 10f, true) }, + { nameof(Vitality), new EffectSettings(1, 10f, true) }, + { nameof(BodyshotReduction), new EffectSettings(4, 5f, true) } + } + }; + + public AdvancedEffectSettings Negative { get; set; } = new() + { + Effects = new[] { nameof(Asphyxiated), nameof(AmnesiaVision), nameof(Bleeding), nameof(Blurred), + nameof(Concussed), nameof(Deafened), nameof(Disabled) }, + Settings = new Dictionary + { + { nameof(Asphyxiated), new EffectSettings(1, 10f, true) }, + { nameof(AmnesiaVision), new EffectSettings(1, 5f, true) }, + { nameof(Bleeding), new EffectSettings(1, 5f, true) }, + { nameof(Blurred), new EffectSettings(1, 5f, true) }, + { nameof(Concussed), new EffectSettings(1, 5f, true) }, + { nameof(Deafened), new EffectSettings(1, 5f, true) }, + { nameof(Disabled), new EffectSettings(1, 5f, true) } + } + }; + + public AdvancedEffectSettings AdvancedNegative { get; set; } = new() + { + Effects = new[] { nameof(InsufficientLighting) }, + Settings = new Dictionary + { + { nameof(InsufficientLighting), new EffectSettings(1, 20f, true) } + } + }; + } + + public class AdvancedEffectSettings + { + public String[] Effects { get; set; } + public Dictionary Settings { get; set; } + + public AdvancedEffectSettings(){} + } + + public class EffectSettings + { + public byte Intensity { get; set; } + public float Duration { get; set; } + public bool AddDuration { get; set; } + + public EffectSettings(byte intensity, float duration, bool addDuration) + { + Intensity = intensity; + Duration = duration; + AddDuration = addDuration; + } + + public EffectSettings(){} + } +} \ No newline at end of file diff --git a/GamblingCoin/GamblingCoinEventHandler.cs b/GamblingCoin/GamblingCoinEventHandler.cs new file mode 100644 index 0000000..1c81ae0 --- /dev/null +++ b/GamblingCoin/GamblingCoinEventHandler.cs @@ -0,0 +1,196 @@ +using System.Numerics; +using LabApi.Events.Arguments.PlayerEvents; +using LabApi.Features.Wrappers; +using MapGeneration; +using Mirror; +using PlayerRoles; +using Respawning.Waves; +using Utils; +using Logger = LabApi.Features.Console.Logger; +using Random = UnityEngine.Random; + +namespace GamblingCoin +{ + public class GamblingCoinEventHandler + { + private readonly WeightedRandomExecutor _executor; + + public GamblingCoinEventHandler() + { + var configMessages = Plugin.Singleton.ConfigMessages; + var configGameplay = Plugin.Singleton.ConfigGameplay; + var configChances = Plugin.Singleton.ConfigChances; + + _executor = new WeightedRandomExecutor(); + + _executor + .AddAction(_ => + { + Warhead.DetonationTime += configGameplay.WarheadTimeIncrease; + Warhead.Start(suppressSubtitles: true); + }, configChances.NukeChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.SpawnWaveMessage, configGameplay.BroadcastDuration); + + if(GetPlayers().Any(player=>player.Role == RoleTypeId.Spectator)) { + Respawning.WaveManager.InitiateRespawn(Respawning.WaveManager.Waves[Random.Range(0, Respawning.WaveManager.Waves.Count)]); + } + }, configChances.SpawnWaveChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.ItemSpawnMessage, configGameplay.BroadcastDuration); + SpawnRandomItemAtPlayer(x.Player, configGameplay.Items.CommonItems); + }, configChances.CommonItemChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.UncommonItemSpawnMessage, configGameplay.BroadcastDuration); + SpawnRandomItemAtPlayer(x.Player, configGameplay.Items.UncommonItems); + }, configChances.UncommonItemChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.RareItemSpawnMessage, configGameplay.BroadcastDuration); + SpawnRandomItemAtPlayer(x.Player, configGameplay.Items.RareItems); + }, configChances.RareItemChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.EpicItemSpawnMessage, configGameplay.BroadcastDuration); + SpawnRandomItemAtPlayer(x.Player, configGameplay.Items.EpicItems); + }, configChances.EpicItemChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.LegendaryItemSpawnMessage, configGameplay.BroadcastDuration); + SpawnRandomItemAtPlayer(x.Player, configGameplay.Items.LegendaryItems); + }, configChances.LegendaryItemChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.RandomTeleportMessage, configGameplay.BroadcastDuration); + + var randomRoom = Map.GetRandomRoom(); + if (randomRoom == null) return; + + var newPos = randomRoom.Position; + + x.Player.Position = newPos + new UnityEngine.Vector3(0, configGameplay.TeleportHeightOffset, 0);; + }, configChances.RandomTeleportChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.StealItemMessage, configGameplay.BroadcastDuration); + + if (x.Player.CurrentItem != null) x.Player.DropItem(x.Player.CurrentItem); + }, configChances.StealItemChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.ExplosionMessage, configGameplay.BroadcastDuration); + + x.Player.ClearInventory(); + + ExplosionUtils.ServerExplode(x.Player.ReferenceHub, ExplosionType.Custom); + }, configChances.ExplosionChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.AntiMicroMessage, configGameplay.BroadcastDuration); + GetPlayers().ForEach(p => { p.RemoveItem(ItemType.MicroHID, configGameplay.MaxMicrosToRemove); }); + //TODO: remove *all* micros + }, configChances.AntiMicroChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.GrenadeMessage, configGameplay.BroadcastDuration); + + var grenade = (TimedGrenadeProjectile)Pickup.Create(ItemType.GrenadeHE, x.Player.Position); + grenade?.Spawn(); + grenade?.FuseEnd(); + }, configChances.GrenadeChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.PocketDimensionMessage, configGameplay.BroadcastDuration); + + var newPos = Map.Rooms.First(roomIdentifier => roomIdentifier.Zone==FacilityZone.Other).Position; + + x.Player.Position = newPos + new UnityEngine.Vector3(0, configGameplay.TeleportHeightOffset, 0); + }, + configChances.PocketDimensionChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.AdvancedPositiveEffectMessage, configGameplay.BroadcastDuration); + ApplyRandomEffect(x.Player, configGameplay.Effects.AdvancedPositive); + }, configChances.AdvancedPositiveEffectChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.PositiveEffectMessage, configGameplay.BroadcastDuration); + ApplyRandomEffect(x.Player, configGameplay.Effects.Positive); + }, configChances.PositiveEffectChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.NegativeEffectMessage, configGameplay.BroadcastDuration); + ApplyRandomEffect(x.Player, configGameplay.Effects.Negative); + }, configChances.NegativeEffectChance) + .AddAction(x => + { + x.Player.SendBroadcast(configMessages.AdvancedNegativeEffectMessage, configGameplay.BroadcastDuration); + ApplyRandomEffect(x.Player, configGameplay.Effects.AdvancedNegative); + }, configChances.AdvancedNegativeEffectChance) + .AddAction(x => + { + var players = GetPlayers(); + + var randomPlayer = players[Random.Range(0,GetPlayers().Length)]; + + while(randomPlayer.RoleBase.Team is Team.Dead or Team.SCPs) randomPlayer = players[Random.Range(0,GetPlayers().Length)]; + + x.Player.SendBroadcast(configMessages.SwitchInventoryMessage, configGameplay.BroadcastDuration); + randomPlayer.SendBroadcast(configMessages.SwitchInventoryMessage, configGameplay.BroadcastDuration); + + var randomPlayerItems = new List(); + randomPlayer.Items.CopyTo(randomPlayerItems); + var items = x.Player.Items; + + randomPlayer.ClearInventory(); + foreach (var itemBase in items) + { + randomPlayer.AddItem(itemBase.Type); + } + + x.Player.ClearInventory(); + foreach (var randomPlayerItem in randomPlayerItems) + { + x.Player.AddItem(randomPlayerItem.Type); + } + }, configChances.SwitchInventoryChance); + + return; + + void ApplyRandomEffect(Player player, AdvancedEffectSettings settings) + { + var effectChosen = settings.Effects[Random.Range(0, settings.Effects.Length)]; + var effectSettings = settings.Settings[effectChosen]; + + player.ReferenceHub.playerEffectsController.ChangeState(effectChosen, effectSettings.Intensity, effectSettings.Duration, effectSettings.AddDuration); + } + + void SpawnItemAtPlayer(Player player, ItemType item) + { + var pickup = Pickup.Create(item, player.Position + new UnityEngine.Vector3(0,1,0)); + if (pickup == null) return; + + pickup.Spawn(); + } + + void SpawnRandomItemAtPlayer(Player player, ItemType[] items) + { + var itemIndex = Random.Range(0, items.Length); + SpawnItemAtPlayer(player, items[itemIndex]); + } + + Player[] GetPlayers() + { + return Player.Dictionary.Values.ToArray(); + } + } + + public void OnFlippedCoin(PlayerFlippedCoinEventArgs ev) + { + _executor.Execute(ev); + } + } +} \ No newline at end of file diff --git a/GamblingCoin/WeightedRandom.cs b/GamblingCoin/WeightedRandom.cs new file mode 100644 index 0000000..48c6e6f --- /dev/null +++ b/GamblingCoin/WeightedRandom.cs @@ -0,0 +1,82 @@ +namespace GamblingCoin; + +public class WeightedRandomExecutor +{ + private class WeightedAction + { + public Action Action { get; } + public double Weight { get; } + + public WeightedAction(Action action, double weight) + { + if (weight <= 0) + throw new ArgumentOutOfRangeException( + nameof(weight), + "Weight must be positive." + ); + + Action = action ?? throw new ArgumentNullException(nameof(action)); + Weight = weight; + } + } + + private readonly List _actions = new(); + private readonly Random _random = new(); + private double _totalWeight; + + /// + /// Adds a function to be potentially executed, along with its weight. + /// + /// The function to add. It must accept an argument of type TEvent. + /// The positive weight for this function. Higher weights mean higher probability. + public WeightedRandomExecutor AddAction(Action action, double weight) + { + var weightedAction = new WeightedAction(action, weight); + _actions.Add(weightedAction); + _totalWeight += weight; + + return this; + } + + /// + /// Executes one of the added functions randomly based on their weights. + /// The chosen function will be called with the provided 'ev' argument. + /// + /// The event argument to pass to the chosen function. + /// Thrown if no actions have been added. + public void Execute(TEvent ev) + { + if (_actions.Count == 0) + { + throw new InvalidOperationException( + "No actions have been added to execute." + ); + } + + if (_totalWeight <= 0) // Should not happen if AddAction validates weight > 0 + { + throw new InvalidOperationException( + "Total weight is zero or negative, cannot execute." + ); + } + + var randomNumber = _random.NextDouble() * _totalWeight; + double cumulativeWeight = 0; + + foreach (var weightedAction in _actions) + { + cumulativeWeight += weightedAction.Weight; + if (!(randomNumber < cumulativeWeight)) continue; + weightedAction.Action(ev); + return; // Exit after executing one action + } + + // Fallback in case of floating point inaccuracies, + // or if somehow randomNumber was exactly _totalWeight (NextDouble is < 1.0) + // This should ideally pick the last item if all weights were summed up. + if (_actions.Any()) + { + _actions.Last().Action(ev); + } + } +} \ No newline at end of file diff --git a/KeycardButModern/KeycardButModern.cs b/KeycardButModern/KeycardButModern.cs new file mode 100644 index 0000000..9e806b5 --- /dev/null +++ b/KeycardButModern/KeycardButModern.cs @@ -0,0 +1,66 @@ +using InventorySystem.Items.Keycards; +using LabApi.Events.Arguments.PlayerEvents; +using LabApi.Events.Handlers; +using LabApi.Features; +using LabApi.Features.Console; + +namespace KeycardButModern +{ + public class Plugin: LabApi.Loader.Features.Plugins.Plugin + { + public override string Name => "KeycardButModern"; + public override string Description => "Ever thought you wanted your keycard implanted in your body? No? Same."; + public override string Author => "Code002Lover"; + public override Version Version { get; } = new(1, 0, 0); + public override Version RequiredApiVersion { get; } = new (LabApiProperties.CompiledVersion); + + private void OnInteractingDoor(PlayerInteractingDoorEventArgs ev) + { + if (ev.CanOpen) + { + Logger.Debug("Door can be opened, no need for implant check"); + return; + } + + if (ev.Door.IsLocked) + { + Logger.Debug("Door has active locks"); + return; + } + + var permissions = ev.Door.Permissions; + + foreach (var playerItem in ev.Player.Items) + { + //is keycard? + if (playerItem.Type > ItemType.KeycardO5) continue; + if (playerItem.Base is not KeycardItem keycardItem) + { + continue; + } + + var keycardPermissions = keycardItem.GetPermissions(ev.Door.Base); + + Logger.Debug($"Item is a keycard: {keycardPermissions} vs {permissions} = {keycardPermissions & permissions}"); + + if ((keycardPermissions & permissions) != permissions) continue; + ev.Door.IsOpened = true; + Logger.Debug("Door can be opened"); + + return; + } + } + + public override void Enable() + { + Logger.Debug("starting..."); + PlayerEvents.InteractingDoor += OnInteractingDoor; + } + + public override void Disable() + { + PlayerEvents.InteractingDoor -= OnInteractingDoor; + Logger.Debug("unloading..."); + } + } +} \ No newline at end of file diff --git a/KeycardButModern/KeycardButModern.csproj b/KeycardButModern/KeycardButModern.csproj new file mode 100644 index 0000000..41fbf5a --- /dev/null +++ b/KeycardButModern/KeycardButModern.csproj @@ -0,0 +1,37 @@ + + + + net48 + enable + disable + 10 + + + + true + true + full + + + + true + false + none + + + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll + + + + + + + diff --git a/SecretPluginLaboratories.sln b/SecretPluginLaboratories.sln new file mode 100644 index 0000000..474eeed --- /dev/null +++ b/SecretPluginLaboratories.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GamblingCoin", "GamblingCoin\GamblingCoin.csproj", "{4EEC49AE-6037-4EC9-AC30-F38E7F394614}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeycardButModern", "KeycardButModern\KeycardButModern.csproj", "{41731D20-3035-457B-ACFF-C1710D179FA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisibleSpectators", "VisibleSpectators\VisibleSpectators.csproj", "{F320856D-6340-4DD5-89F7-EE44611DF8E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SCPList", "SCPList\SCPList.csproj", "{2EFB3C10-A917-4840-97CD-B36733D666DE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4EEC49AE-6037-4EC9-AC30-F38E7F394614}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EEC49AE-6037-4EC9-AC30-F38E7F394614}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EEC49AE-6037-4EC9-AC30-F38E7F394614}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EEC49AE-6037-4EC9-AC30-F38E7F394614}.Release|Any CPU.Build.0 = Release|Any CPU + {41731D20-3035-457B-ACFF-C1710D179FA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41731D20-3035-457B-ACFF-C1710D179FA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41731D20-3035-457B-ACFF-C1710D179FA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41731D20-3035-457B-ACFF-C1710D179FA5}.Release|Any CPU.Build.0 = Release|Any CPU + {F320856D-6340-4DD5-89F7-EE44611DF8E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F320856D-6340-4DD5-89F7-EE44611DF8E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F320856D-6340-4DD5-89F7-EE44611DF8E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F320856D-6340-4DD5-89F7-EE44611DF8E8}.Release|Any CPU.Build.0 = Release|Any CPU + {2EFB3C10-A917-4840-97CD-B36733D666DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2EFB3C10-A917-4840-97CD-B36733D666DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2EFB3C10-A917-4840-97CD-B36733D666DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2EFB3C10-A917-4840-97CD-B36733D666DE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/VisibleSpectators/VisibleSpectators.cs b/VisibleSpectators/VisibleSpectators.cs new file mode 100644 index 0000000..abe100c --- /dev/null +++ b/VisibleSpectators/VisibleSpectators.cs @@ -0,0 +1,115 @@ +using HintServiceMeow.Core.Enum; +using HintServiceMeow.Core.Models.Hints; +using HintServiceMeow.Core.Utilities; +using LabApi.Events.Arguments.PlayerEvents; +using LabApi.Events.Handlers; +using LabApi.Features; +using LabApi.Features.Console; +using LabApi.Features.Wrappers; +using LabApi.Loader.Features.Plugins; +using PlayerRoles; +using Timer = System.Timers.Timer; + +namespace VisibleSpectators +{ + public class Plugin : Plugin + { + public override string Name => "VisibleSpectators"; + public override string Author => "Code002Lover"; + public override Version Version { get; } = new(1, 0, 0); + public override string Description => "See your spectators"; + public override Version RequiredApiVersion { get; } = new (LabApiProperties.CompiledVersion); + + + private static Plugin _singleton; + private Timer _timer; + private readonly Dictionary _spectatorHints = new(); + + public override void Enable() + { + Logger.Debug("starting..."); + _singleton = this; + + PlayerEvents.ChangedSpectator += OnSpectate; + PlayerEvents.Joined += OnJoin; + + _timer = new Timer(1000); + _timer.Elapsed += (_, _) => UpdateSpectators(); + _timer.Start(); + } + + public override void Disable() + { + Logger.Debug("unloading..."); + + _timer.Stop(); + _timer.Dispose(); + _timer = null; + + PlayerEvents.Joined -= OnJoin; + PlayerEvents.ChangedSpectator -= OnSpectate; + + _singleton = null; + } + + private void UpdateSpectators() + { + foreach (var player in GetPlayers()) + { + UpdateSpectators(player); + } + } + + private void UpdateSpectators(Player player) + { + var spectators = string.Join("\n",player.CurrentSpectators.Select(x => x.DisplayName)); + if (player.Role == RoleTypeId.Spectator) + { + spectators = string.Join("\n",player.CurrentlySpectating?.CurrentSpectators.Select(x => x.DisplayName) ?? Array.Empty()); + } + + if (spectators.Length < 2) + { + spectators = Config!.NoSpectatorsMessage; + } + + + _spectatorHints[player].Text = $"{Config!.HeaderMessage}\n{spectators}\n{DateTime.UtcNow:HH:mm:ss}"; + + _spectatorHints[player].Hide = player.Role is RoleTypeId.Overwatch or RoleTypeId.Destroyed or RoleTypeId.None; + } + + private static Player[] GetPlayers() + { + return Player.Dictionary.Values.Where(x=>!x.IsHost).ToArray(); + } + + private static void OnSpectate(PlayerChangedSpectatorEventArgs ev) + { + _singleton.UpdateSpectators(ev.OldTarget); + _singleton.UpdateSpectators(ev.NewTarget); + } + + private void OnJoin(PlayerJoinedEventArgs ev) + { + var hint = new Hint + { + Text = $"{Config!.HeaderMessage}\n{Config!.NoSpectatorsMessage}", + Alignment = HintAlignment.Right, + YCoordinate = 100, + Hide = true + }; + + var playerDisplay = PlayerDisplay.Get(ev.Player); + playerDisplay.AddHint(hint); + + _spectatorHints[ev.Player] = hint; + } + } + + public class SpectatorConfig + { + public string HeaderMessage => "Spectators:"; + public string NoSpectatorsMessage => "No spectators"; + } +} \ No newline at end of file diff --git a/VisibleSpectators/VisibleSpectators.csproj b/VisibleSpectators/VisibleSpectators.csproj new file mode 100644 index 0000000..00a537d --- /dev/null +++ b/VisibleSpectators/VisibleSpectators.csproj @@ -0,0 +1,43 @@ + + + + net48 + enable + disable + 10 + + + + true + true + full + + + + true + false + none + + + + + ..\dependencies\0Harmony.dll + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll + + + ..\dependencies\HintServiceMeow-LabAPI.dll + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll + + + ..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll + + + + + + +