Compare commits

..

2 Commits

Author SHA1 Message Date
96596e9c08 refactor 2025-06-05 01:12:55 +02:00
17c654d889 add and fix a lot 2025-06-05 01:11:30 +02:00
40 changed files with 2118 additions and 1141 deletions

134
AfkSwap/AfkSwap.cs Normal file
View File

@ -0,0 +1,134 @@
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using MEC;
using PlayerRoles;
using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
using Random = UnityEngine.Random;
using Version = System.Version;
namespace AfkSwap;
public class AfkSwap : Plugin
{
private const float AfkTimeLimit = 60; // 1 minute in seconds
private readonly Dictionary<Player, DateTime> _afkPlayers = new();
private readonly object _lock = new();
private readonly Dictionary<Player, Vector3> _playerPositions = new();
private readonly Dictionary<Player, DateTime> _playerSpawnTimes = new();
public override string Name => "AfkSwap";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Swaps AFK players with spectators after one minute.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public override void Enable()
{
PlayerEvents.Spawned += OnPlayerSpawned;
Timing.RunCoroutine(CheckAfkPlayers());
}
public override void Disable()
{
PlayerEvents.Spawned -= OnPlayerSpawned;
lock (_lock)
{
_playerSpawnTimes.Clear();
_playerPositions.Clear();
_afkPlayers.Clear();
}
}
private void OnPlayerSpawned(PlayerSpawnedEventArgs ev)
{
var player = ev.Player;
Timing.CallDelayed(1, () =>
{
lock (_lock)
{
_playerSpawnTimes[player] = DateTime.Now;
_playerPositions[player] = player.Position;
_afkPlayers[player] = DateTime.Now;
}
Logger.Debug($"Player {player.DisplayName} spawned");
});
}
private IEnumerator<float> CheckAfkPlayers()
{
Logger.Debug("Starting Afk Checking");
while (true)
{
lock (_lock)
{
foreach (var playerTime in _playerSpawnTimes.ToList().Where(playerTime =>
(DateTime.Now - playerTime.Value).TotalSeconds >= AfkTimeLimit))
{
if (playerTime.Key.Role is RoleTypeId.Spectator or RoleTypeId.Destroyed or RoleTypeId.Overwatch
or RoleTypeId.Tutorial)
{
_playerSpawnTimes.Remove(playerTime.Key);
_playerPositions.Remove(playerTime.Key);
continue;
}
if (!_playerPositions[playerTime.Key].Equals(playerTime.Key.Position))
{
_playerSpawnTimes.Remove(playerTime.Key);
_playerPositions.Remove(playerTime.Key);
continue; // Player has moved, don't swap
}
_afkPlayers[playerTime.Key] = DateTime.Now;
SwapWithSpectator(playerTime.Key);
}
}
yield return Timing.WaitForSeconds(1);
}
// ReSharper disable once IteratorNeverReturns
}
private void SwapWithSpectator(Player afkPlayer)
{
var spectators = Player.ReadyList
.Where(p => p.Role == RoleTypeId.Spectator && (DateTime.Now - _afkPlayers[p]).TotalSeconds > 10).ToList();
if (!spectators.Any())
{
Logger.Warn("No spectators to swap to");
return;
}
var randomSpectator = spectators[Random.Range(0, spectators.Count)];
Logger.Debug($"Swapping {afkPlayer.DisplayName} with {randomSpectator.DisplayName}");
// Store the AFK player's position and role
var afkPosition = afkPlayer.Position;
var afkRole = afkPlayer.Role;
// Give the spectator the AFK player's role and position
randomSpectator.Role = afkRole;
randomSpectator.Position = afkPosition;
// Make the AFK player a spectator
afkPlayer.Role = RoleTypeId.Spectator;
// Remove the AFK player from tracking
_playerSpawnTimes.Remove(afkPlayer);
_playerPositions.Remove(afkPlayer);
_playerSpawnTimes[randomSpectator] = DateTime.Now;
_playerPositions[randomSpectator] = randomSpectator.Position;
// Broadcast the swap
afkPlayer.SendBroadcast($"You were swapped with {randomSpectator.DisplayName} due to inactivity.", 10);
randomSpectator.SendBroadcast($"You were swapped with {afkPlayer.DisplayName} due to them being AFK.", 5);
}
}

37
AfkSwap/AfkSwap.csproj Normal file
View File

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
</Project>

32
CandySetting/Class1.cs Normal file
View File

@ -0,0 +1,32 @@
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
namespace CandySetting;
public class CandySetting : Plugin
{
public override string Name => "CandySetting";
public override string Author => "HoherGeist, Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Edits # of Candy you can take";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public int MaxUses { get; set; } = 6;
public override void Enable()
{
PlayerEvents.InteractingScp330 += TakingCandy;
}
public override void Disable()
{
PlayerEvents.InteractingScp330 -= TakingCandy;
}
private void TakingCandy(PlayerInteractingScp330EventArgs ev)
{
ev.AllowPunishment = ev.Uses > MaxUses;
}
}

View File

@ -1,5 +1,4 @@
using CustomPlayerEffects;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
@ -7,8 +6,6 @@ using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using Mirror;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp3114;
using PlayerRoles.Ragdolls;
namespace CuffedFrenemies;
@ -33,10 +30,7 @@ public class CuffedFrenemies : Plugin
private static void OnCuff(PlayerCuffedEventArgs ev)
{
if (ev.Target.Team is Team.ClassD or Team.Scientists)
{
return;
}
if (ev.Target.Team is Team.ClassD or Team.Scientists) return;
if (ev.Target.Team == ev.Player.Team)
{
@ -44,10 +38,7 @@ public class CuffedFrenemies : Plugin
return;
}
if (ev.Target.Team is Team.SCPs or Team.Dead)
{
return;
}
if (ev.Target.Team is Team.SCPs or Team.Dead) return;
var newRole = ev.Target.Team == Team.ChaosInsurgency ? RoleTypeId.NtfPrivate : RoleTypeId.ChaosConscript;
Logger.Debug($"Setting role to {newRole}");
@ -62,12 +53,8 @@ public class CuffedFrenemies : Plugin
ev.Target.SetRole(newRole);
ev.Target.ClearItems();
foreach (var newItem in newItems)
{
ev.Target.Inventory.UserInventory.Items.Add(newItem.Serial,newItem.Base);
}
foreach (var newItem in newItems) ev.Target.Inventory.UserInventory.Items.Add(newItem.Serial, newItem.Base);
ev.Target.Position = newPos;
}
}

View File

@ -26,9 +26,6 @@
<Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>

View File

@ -0,0 +1,235 @@
using Interactables.Interobjects.DoorUtils;
using InventorySystem.Items;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using MapGeneration;
using MEC;
using PlayerRoles;
using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
using Random = System.Random;
using Vector3 = UnityEngine.Vector3;
namespace CustomClasses;
public class CustomClasses : Plugin
{
private readonly CustomClassManager _classManager = new();
public override string Name => "CustomClasses";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Adds custom classes to the game";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public JanitorConfig JanitorConfig { get; set; } = new();
public ResearchSubjectConfig ResearchSubjectConfig { get; set; } = new();
public HeadGuardConfig HeadGuardConfig { get; set; } = new();
public override void Enable()
{
PlayerEvents.Spawned += OnPlayerSpawned;
ServerEvents.RoundEnded += OnRoundEnded;
}
public override void Disable()
{
PlayerEvents.Spawned -= OnPlayerSpawned;
ServerEvents.RoundEnded -= OnRoundEnded;
}
private void OnRoundEnded(RoundEndedEventArgs ev)
{
_classManager.ResetSpawnStates();
}
private void OnPlayerSpawned(PlayerSpawnedEventArgs ev)
{
if (_classManager.TryHandleSpawn(ev.Player, JanitorConfig, typeof(JanitorConfig))) return;
if (_classManager.TryHandleSpawn(ev.Player, ResearchSubjectConfig, typeof(ResearchSubjectConfig))) return;
if (_classManager.TryHandleSpawn(ev.Player, HeadGuardConfig, typeof(HeadGuardConfig))) return;
}
}
public class CustomClassManager
{
private readonly Dictionary<Type, ICustomClassHandler> _handlers = new();
private readonly object _lock = new();
private readonly Random _random = new();
private readonly Dictionary<Type, SpawnState> _spawnStates = new();
public CustomClassManager()
{
// Register handlers
RegisterHandler<JanitorConfig>(new JanitorHandler(this));
RegisterHandler<ResearchSubjectConfig>(new ResearchSubjectHandler(this));
RegisterHandler<HeadGuardConfig>(new HeadGuardHandler(this));
}
public void TeleportPlayerToAround(Player player, Vector3 position)
{
player.Position = position + new Vector3(0, 1, 0) + new Vector3((float)(_random.NextDouble() * 2), 0,
(float)(_random.NextDouble() * 2));
}
private void RegisterHandler<T>(ICustomClassHandler handler) where T : CustomClassConfig
{
lock (_lock)
{
_spawnStates[typeof(T)] = new SpawnState();
}
_handlers[typeof(T)] = handler;
}
public void ResetSpawnStates()
{
lock (_lock)
{
foreach (var key in _spawnStates.Keys.ToList()) _spawnStates[key] = new SpawnState();
}
}
public bool TryHandleSpawn(Player player, CustomClassConfig config, Type configType)
{
if (player.Role != config.RequiredRole) return false;
if (Player.ReadyList.Count() <= config.MinPlayers) return false;
lock (_lock)
{
var state = _spawnStates[configType];
if (state.Spawns >= config.MaxSpawns)
{
Logger.Debug($"Max spawns reached {configType} - {player.Nickname}");
return false;
}
if (_random.NextDouble() > config.ChancePerPlayer)
{
Logger.Debug($"Chance not met {configType} - {player.Nickname}");
return false;
}
state.Spawns++;
Logger.Debug($"Player spawning {configType} - {player.Nickname} - {state.Spawns} / {config.MaxSpawns}");
if (_handlers.TryGetValue(configType, out var handler)) return handler.HandleSpawn(player, config, _random);
}
return false;
}
}
public interface ICustomClassHandler
{
bool HandleSpawn(Player player, CustomClassConfig config, Random random);
}
public class JanitorHandler(CustomClassManager manager) : ICustomClassHandler
{
public bool HandleSpawn(Player player, CustomClassConfig config, Random random)
{
var scp914 = Map.Rooms.First(r => r.Name == RoomName.Lcz914);
Timing.CallDelayed(0.5f, () =>
{
manager.TeleportPlayerToAround(player, scp914.Position);
foreach (var spawnItem in config.Items)
{
player.AddItem(spawnItem, ItemAddReason.StartingItem);
Logger.Debug($"Gave player {player.Nickname} spawn item {spawnItem}");
}
player.SendBroadcast("You're a <color=#A0A0A0>Janitor</color>!", 3);
});
return true;
}
}
public class ResearchSubjectHandler(CustomClassManager manager) : ICustomClassHandler
{
public bool HandleSpawn(Player player, CustomClassConfig config, Random random)
{
var scientist = Player.ReadyList.First(p => p.Role == RoleTypeId.Scientist);
Timing.CallDelayed(0.5f, () =>
{
manager.TeleportPlayerToAround(player, scientist.Position);
foreach (var spawnItem in config.Items)
{
player.AddItem(spawnItem, ItemAddReason.StartingItem);
Logger.Debug($"Gave player {player.Nickname} spawn item {spawnItem}");
}
player.SendBroadcast("You're a <color=#944710>Research Subject</color>!", 3);
});
return true;
}
}
public class HeadGuardHandler(CustomClassManager manager) : ICustomClassHandler
{
public bool HandleSpawn(Player player, CustomClassConfig config, Random random)
{
Timing.CallDelayed(0.5f, () =>
{
player.RemoveItem(ItemType.KeycardGuard);
KeycardItem.CreateCustomKeycardTaskForce(player, "Head Guard Keycard", $"HG. {player.Nickname}",
new KeycardLevels(1, 1, 2), Color.blue, Color.cyan, "1", 0);
player.AddItem(ItemType.Adrenaline, ItemAddReason.StartingItem);
player.RemoveItem(ItemType.ArmorLight);
player.AddItem(ItemType.ArmorCombat, ItemAddReason.StartingItem);
player.RemoveItem(ItemType.GunFSP9);
var pickup = Pickup.Create(ItemType.GunCrossvec, Vector3.one);
if (pickup != null) player.AddItem(pickup);
player.SetAmmo(ItemType.Ammo9x19, 120);
player.SendBroadcast("You're a <color=#00B7EB>Head Guard</color>!", 3);
});
return true;
}
}
internal record SpawnState
{
public int Spawns;
}
public abstract class CustomClassConfig
{
public virtual int MinPlayers { get; set; } = 4;
public virtual double ChancePerPlayer { get; set; } = 0.7;
public virtual int MaxSpawns { get; set; } = 1;
public virtual ItemType[] Items { get; set; } = [];
public virtual RoleTypeId RequiredRole { get; set; } = RoleTypeId.ClassD;
}
public class ResearchSubjectConfig : CustomClassConfig;
public class JanitorConfig : CustomClassConfig
{
public override int MinPlayers { get; set; } = 5;
public override double ChancePerPlayer { get; set; } = 0.3;
public override int MaxSpawns { get; set; } = 2;
public override ItemType[] Items { get; set; } = [ItemType.KeycardJanitor];
}
public class HeadGuardConfig : CustomClassConfig
{
public override int MinPlayers { get; set; } = 9;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.FacilityGuard;
}

View File

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,79 @@
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using MEC;
using Logger = LabApi.Features.Console.Logger;
using Random = System.Random;
using Vector3 = UnityEngine.Vector3;
namespace CustomItemSpawn;
public class CustomItemSpawn : Plugin<ItemConfig>
{
private static CustomItemSpawn _singleton;
public override string Name => "CustomItemSpawn";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Spawns items in a custom location.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public override void Enable()
{
_singleton = this;
ServerEvents.RoundStarted += OnRoundStart;
}
public override void Disable()
{
ServerEvents.RoundStarted -= OnRoundStart;
_singleton = null;
}
private static void OnRoundStart()
{
Timing.CallDelayed(10, SpawnItems);
}
private static void SpawnItems()
{
Random rng = new();
foreach (var pickup in from configPair in _singleton.Config!.Items
let itemType = configPair.Key
let config = configPair.Value
where rng.NextDouble() * 100f <= config.Chance
select Pickup.Create(itemType, config.Position + new Vector3(0, 1, 0)))
{
if (pickup == null)
{
Logger.Error("Could not create pickup.");
break;
}
pickup.Spawn();
Logger.Debug($"Spawned Pickup: {pickup.Base} @ {pickup.Position}");
}
}
}
public class ItemConfig
{
public Dictionary<ItemType, SpecificConfig> Items { get; set; } = new()
{
{
ItemType.GunAK,
new SpecificConfig
{
Position = new Vector3(0, 0, 0),
Chance = 100f
}
}
};
}
public class SpecificConfig
{
public Vector3 Position { get; set; }
public float Chance { get; set; } = 100f;
}

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
</Project>

View File

@ -3,20 +3,22 @@ using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Loader;
namespace GamblingCoin
{
namespace GamblingCoin;
public class Plugin : LabApi.Loader.Features.Plugins.Plugin
{
public static Plugin Singleton;
private GamblingCoinEventHandler _eventHandler;
public GamblingCoinChancesConfig ConfigChances;
public GamblingCoinGameplayConfig ConfigGameplay;
public GamblingCoinMessages ConfigMessages;
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();
@ -26,10 +28,6 @@ namespace GamblingCoin
ConfigChances = this.LoadConfig<GamblingCoinChancesConfig>("chances.yml");
}
public static Plugin Singleton;
private GamblingCoinEventHandler _eventHandler;
public override void Enable()
{
Logger.Debug("starting...");
@ -45,4 +43,3 @@ namespace GamblingCoin
PlayerEvents.FlippedCoin -= _eventHandler.OnFlippedCoin;
}
}
}

View File

@ -1,7 +1,6 @@
using CustomPlayerEffects;
namespace GamblingCoin
{
namespace GamblingCoin;
public class GamblingCoinChancesConfig
{
@ -60,7 +59,8 @@ namespace GamblingCoin
public class ItemPoolConfig
{
public ItemType[] CommonItems { get; set; } = {
public ItemType[] CommonItems { get; set; } =
{
ItemType.KeycardJanitor,
ItemType.KeycardScientist,
ItemType.Medkit,
@ -69,7 +69,8 @@ namespace GamblingCoin
ItemType.Flashlight
};
public ItemType[] UncommonItems { get; set; } = {
public ItemType[] UncommonItems { get; set; } =
{
ItemType.KeycardZoneManager,
ItemType.KeycardGuard,
ItemType.KeycardResearchCoordinator,
@ -78,7 +79,8 @@ namespace GamblingCoin
ItemType.GrenadeFlash
};
public ItemType[] RareItems { get; set; } = {
public ItemType[] RareItems { get; set; } =
{
ItemType.KeycardMTFPrivate,
ItemType.KeycardContainmentEngineer,
ItemType.KeycardMTFOperative,
@ -89,14 +91,16 @@ namespace GamblingCoin
ItemType.GrenadeHE
};
public ItemType[] EpicItems { get; set; } = {
public ItemType[] EpicItems { get; set; } =
{
ItemType.KeycardFacilityManager,
ItemType.KeycardChaosInsurgency,
ItemType.KeycardMTFCaptain,
ItemType.SCP500
};
public ItemType[] LegendaryItems { get; set; } = {
public ItemType[] LegendaryItems { get; set; } =
{
ItemType.KeycardO5,
ItemType.MicroHID,
ItemType.Jailbird,
@ -132,8 +136,11 @@ namespace GamblingCoin
public AdvancedEffectSettings Negative { get; set; } = new()
{
Effects = new[] { nameof(Asphyxiated), nameof(AmnesiaVision), nameof(Bleeding), nameof(Blurred),
nameof(Concussed), nameof(Deafened), nameof(Disabled) },
Effects = new[]
{
nameof(Asphyxiated), nameof(AmnesiaVision), nameof(Bleeding), nameof(Blurred),
nameof(Concussed), nameof(Deafened), nameof(Disabled)
},
Settings = new Dictionary<string, EffectSettings>
{
{ nameof(Asphyxiated), new EffectSettings(1, 10f, true) },
@ -148,12 +155,12 @@ namespace GamblingCoin
public AdvancedEffectSettings AdvancedNegative { get; set; } = new()
{
Effects = new[] { nameof(InsufficientLighting), nameof(AmnesiaVision) },
Effects = new[] { nameof(InsufficientLighting), nameof(AmnesiaVision), nameof(Burned) },
Settings = new Dictionary<string, EffectSettings>
{
{ nameof(InsufficientLighting), new EffectSettings(1, 20f, true) },
{ nameof(InsufficientLighting), new EffectSettings(1, 30f, true) },
{ nameof(AmnesiaVision), new EffectSettings(3, 30f, true) },
{ nameof(Bleeding), new EffectSettings(3, 40f, true) },
{ nameof(Burned), new EffectSettings(3, 60f, true) }
}
};
}
@ -162,16 +169,10 @@ namespace GamblingCoin
{
public string[] Effects { get; set; }
public Dictionary<string, EffectSettings> 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;
@ -179,6 +180,11 @@ namespace GamblingCoin
AddDuration = addDuration;
}
public EffectSettings(){}
public EffectSettings()
{
}
public byte Intensity { get; set; }
public float Duration { get; set; }
public bool AddDuration { get; set; }
}

View File

@ -1,20 +1,15 @@
using System.Diagnostics;
using System.Numerics;
using CustomPlayerEffects;
using InventorySystem.Items;
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 Respawning;
using UnityEngine;
using MicroHIDItem = InventorySystem.Items.MicroHID.MicroHIDItem;
using Random = UnityEngine.Random;
namespace GamblingCoin
{
namespace GamblingCoin;
public class GamblingCoinEventHandler
{
private readonly WeightedRandomExecutor<PlayerFlippedCoinEventArgs> _executor;
@ -37,9 +32,8 @@ namespace GamblingCoin
{
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)]);
}
if (GetPlayers().Any(player => player.Role == RoleTypeId.Spectator))
WaveManager.InitiateRespawn(WaveManager.Waves[Random.Range(0, WaveManager.Waves.Count)]);
}, configChances.SpawnWaveChance)
.AddAction(x =>
{
@ -71,7 +65,7 @@ namespace GamblingCoin
var itemIndex = Random.Range(0, items.Length);
var item = items[itemIndex];
var pickup = Pickup.Create(item, player.Position + new UnityEngine.Vector3(0,1,0));
var pickup = Pickup.Create(item, player.Position + new Vector3(0, 1, 0));
if (pickup == null) return;
if (item is ItemType.MicroHID)
@ -83,7 +77,7 @@ namespace GamblingCoin
microItem.EnergyManager.ServerSetEnergy(microItem.ItemId.SerialNumber, 100);
var newPickup = host.DropItem(micro);
newPickup.Position = player.Position + new UnityEngine.Vector3(0, 1, 0);
newPickup.Position = player.Position + new Vector3(0, 1, 0);
return;
}
@ -115,7 +109,8 @@ namespace GamblingCoin
var newPos = randomRoom.Position;
x.Player.Position = newPos + new UnityEngine.Vector3(0, configGameplay.TeleportHeightOffset, 0);;
x.Player.Position = newPos + new Vector3(0, configGameplay.TeleportHeightOffset, 0);
;
}, configChances.RandomTeleportChance)
.AddAction(x =>
{
@ -147,11 +142,7 @@ namespace GamblingCoin
{
x.Player.SendBroadcast(configMessages.PocketDimensionMessage, configGameplay.BroadcastDuration);
var newPos = Map.Rooms.First(roomIdentifier => roomIdentifier.Zone==FacilityZone.Other).Position;
x.Player.ReferenceHub.playerEffectsController.ChangeState(nameof(Corroding),1, 999, true);
x.Player.Position = newPos + new UnityEngine.Vector3(0, configGameplay.TeleportHeightOffset, 0);
PocketDimension.ForceInside(x.Player);
},
configChances.PocketDimensionChance)
.AddAction(x =>
@ -180,7 +171,8 @@ namespace GamblingCoin
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)];
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);
@ -190,21 +182,12 @@ namespace GamblingCoin
var items = x.Player.Items;
randomPlayer.ClearInventory();
foreach (var itemBase in items)
{
randomPlayer.AddItem(itemBase.Type);
}
foreach (var itemBase in items) randomPlayer.AddItem(itemBase.Type);
x.Player.ClearInventory();
foreach (var randomPlayerItem in randomPlayerItems)
{
x.Player.AddItem(randomPlayerItem.Type);
}
foreach (var randomPlayerItem in randomPlayerItems) x.Player.AddItem(randomPlayerItem.Type);
}, configChances.SwitchInventoryChance)
.AddAction(x =>
{
x.Player.CurrentItem?.DropItem().Destroy();
}, configChances.RemoveCoinChance);
.AddAction(x => { x.Player.CurrentItem?.DropItem().Destroy(); }, configChances.RemoveCoinChance);
return;
@ -213,12 +196,13 @@ namespace GamblingCoin
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);
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));
var pickup = Pickup.Create(item, player.Position + new Vector3(0, 1, 0));
if (pickup == null) return;
pickup.Spawn();
@ -232,7 +216,7 @@ namespace GamblingCoin
Player[] GetPlayers()
{
return Player.Dictionary.Values.ToArray();
return Player.ReadyList.ToArray();
}
}
@ -241,4 +225,3 @@ namespace GamblingCoin
_executor.Execute(ev);
}
}
}

View File

@ -2,24 +2,6 @@ namespace GamblingCoin;
public class WeightedRandomExecutor<TEvent>
{
private class WeightedAction
{
public Action<TEvent> Action { get; }
public double Weight { get; }
public WeightedAction(Action<TEvent> 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<WeightedAction> _actions = new();
private readonly Random _random = new();
private double _totalWeight;
@ -47,18 +29,14 @@ public class WeightedRandomExecutor<TEvent>
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;
@ -74,9 +52,24 @@ public class WeightedRandomExecutor<TEvent>
// 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())
if (_actions.Any()) _actions.Last().Action(ev);
}
private class WeightedAction
{
_actions.Last().Action(ev);
}
public WeightedAction(Action<TEvent> action, double weight)
{
if (weight <= 0)
throw new ArgumentOutOfRangeException(
nameof(weight),
"Weight must be positive."
);
Action = action ?? throw new ArgumentNullException(nameof(action));
Weight = weight;
}
public Action<TEvent> Action { get; }
public double Weight { get; }
}
}

View File

@ -0,0 +1,75 @@
using CustomPlayerEffects;
using InventorySystem.Items.Usables.Scp330;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.Scp0492Events;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
namespace GrowingZombies;
public class GrowingZombies : Plugin
{
private readonly Dictionary<Player, int> _zombieCorpseCount = new();
public override string Name => "GrowingZombies";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Makes zombies grow stronger as they eat more";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public override void Enable()
{
Scp0492Events.ConsumedCorpse += OnZombieEat;
ServerEvents.RoundEnded += OnRoundEnd;
PlayerEvents.Left += OnPlayerLeave;
}
public override void Disable()
{
Scp0492Events.ConsumedCorpse -= OnZombieEat;
ServerEvents.RoundEnded -= OnRoundEnd;
PlayerEvents.Left -= OnPlayerLeave;
_zombieCorpseCount.Clear();
}
private void OnRoundEnd(RoundEndedEventArgs ev)
{
_zombieCorpseCount.Clear();
}
private void OnPlayerLeave(PlayerLeftEventArgs ev)
{
_zombieCorpseCount.Remove(ev.Player);
}
private void OnZombieEat(Scp0492ConsumedCorpseEventArgs ev)
{
if (!ev?.Player.ReferenceHub.playerEffectsController)
return;
// Increment corpse count for this zombie
if (!_zombieCorpseCount.ContainsKey(ev.Player))
_zombieCorpseCount[ev.Player] = 0;
_zombieCorpseCount[ev.Player]++;
var corpsesEaten = _zombieCorpseCount[ev.Player];
ev.Player.MaxHealth += 50;
var movementBoostIntensity = (byte)Math.Min(1 + corpsesEaten * 0.1f, 3f);
ev.Player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(movementBoostIntensity, 30);
// Add damage resistance after eating multiple corpses
var damageResistance = (byte)Math.Min(0.5 - corpsesEaten * 0.5f, 2f);
if (corpsesEaten >= 3)
ev.Player.ReferenceHub.playerEffectsController.ChangeState<DamageReduction>(damageResistance, 20);
// Add regeneration effect after eating multiple corpses
if (corpsesEaten < 5) return;
var regenIntensity = Math.Min(1 + corpsesEaten * 0.2f, 3f);
Scp330Bag.AddSimpleRegeneration(ev.Player.ReferenceHub, regenIntensity, 15f);
}
}

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
</Project>

View File

@ -1,5 +1,4 @@
using System.Collections;
using Interactables.Interobjects;
using Interactables.Interobjects.DoorButtons;
using Interactables.Interobjects.DoorUtils;
using LabApi.Events.Arguments.PlayerEvents;
@ -10,19 +9,19 @@ using UnityEngine;
using KeycardItem = InventorySystem.Items.Keycards.KeycardItem;
using Logger = LabApi.Features.Console.Logger;
namespace KeycardButModern
{
namespace KeycardButModern;
public class Plugin : LabApi.Loader.Features.Plugins.Plugin
{
public static Plugin Singleton;
public DoorLockConfiguration DoorLockConfiguration;
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);
public DoorLockConfiguration DoorLockConfiguration;
public static Plugin Singleton;
public override void LoadConfigs()
{
base.LoadConfigs();
@ -32,24 +31,15 @@ namespace KeycardButModern
private void OnInteractingDoor(PlayerInteractingDoorEventArgs ev)
{
if (ev.CanOpen)
{
return;
}
if (ev.CanOpen) return;
if (ev.Door.IsLocked)
{
return;
}
if (ev.Door.IsLocked) return;
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
if (playerItem.Type > ItemType.KeycardO5) continue;
if (playerItem.Base is not KeycardItem keycardItem)
{
continue;
}
if (playerItem.Type is > ItemType.KeycardO5 and < ItemType.KeycardCustomTaskForce) continue;
if (playerItem.Base is not KeycardItem keycardItem) continue;
if (!ev.Door.Base.CheckPermissions(keycardItem, out _)) continue;
ev.Door.IsOpened = !ev.Door.IsOpened;
@ -60,34 +50,23 @@ namespace KeycardButModern
private void OnInteractingGenerator(PlayerInteractingGeneratorEventArgs ev)
{
if (!ev.IsAllowed)
{
return;
}
if (!ev.IsAllowed) return;
if (ev.Player.CurrentItem?.Base is KeycardItem keycard)
{
if (ev.Generator.Base.CheckPermissions(keycard, out _))
{
ev.Generator.IsUnlocked = true;
return;
}
}
if (ev.Generator.IsUnlocked)
{
return;
}
if (ev.Generator.IsUnlocked) return;
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
if (playerItem.Type > ItemType.KeycardO5) continue;
if (playerItem.Base is not KeycardItem keycardItem)
{
continue;
}
if (playerItem.Type is > ItemType.KeycardO5 and < ItemType.KeycardCustomTaskForce) continue;
if (playerItem.Base is not KeycardItem keycardItem) continue;
if (!ev.Generator.Base.CheckPermissions(keycardItem, out _)) continue;
ev.Generator.IsOpen = !ev.Generator.IsOpen;
@ -99,29 +78,19 @@ namespace KeycardButModern
private void OnInteractingLocker(PlayerInteractingLockerEventArgs ev)
{
if (!ev.IsAllowed)
{
return;
}
if (!ev.IsAllowed) return;
if (ev.Chamber.Base.RequiredPermissions == DoorPermissionFlags.None)
{
return;
}
if (ev.Chamber.Base.RequiredPermissions == DoorPermissionFlags.None) return;
if (ev.Player.CurrentItem?.Base is KeycardItem keycard)
{
if (ev.Chamber.Base.CheckPermissions(keycard, out _)) return;
}
if (ev.Chamber.Base.CheckPermissions(keycard, out _))
return;
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
if (playerItem.Type > ItemType.KeycardO5) continue;
if (playerItem.Base is not KeycardItem keycardItem)
{
continue;
}
if (playerItem.Type is > ItemType.KeycardO5 and < ItemType.KeycardCustomTaskForce) continue;
if (playerItem.Base is not KeycardItem keycardItem) continue;
if (!ev.Chamber.Base.CheckPermissions(keycardItem, out _)) continue;
ev.Chamber.IsOpen = !ev.Chamber.IsOpen;
@ -132,19 +101,13 @@ namespace KeycardButModern
private void OnUnlockingWarhead(PlayerUnlockingWarheadButtonEventArgs ev)
{
if (ev.IsAllowed)
{
return;
}
if (ev.IsAllowed) return;
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
if (playerItem.Type > ItemType.KeycardO5) continue;
if (playerItem.Base is not KeycardItem keycardItem)
{
continue;
}
if (playerItem.Type is > ItemType.KeycardO5 and < ItemType.KeycardCustomTaskForce) continue;
if (playerItem.Base is not KeycardItem keycardItem) continue;
if (!AlphaWarheadActivationPanel.Instance.CheckPermissions(keycardItem, out _)) continue;
ev.IsAllowed = true;
@ -161,25 +124,14 @@ namespace KeycardButModern
foreach (var hit in hits)
{
ButtonVariant doorButton = hit.collider.GetComponent<LcdButton>();
if (!doorButton)
{
doorButton = hit.collider.GetComponent<CheckpointKeycardButton>();
}
if (!doorButton)
{
doorButton = hit.collider.GetComponent<KeycardButton>();
}
if (!doorButton)
{
doorButton = hit.collider.GetComponent<SimpleButton>();
}
if (!doorButton) doorButton = hit.collider.GetComponent<CheckpointKeycardButton>();
if (!doorButton) doorButton = hit.collider.GetComponent<KeycardButton>();
if (!doorButton) doorButton = hit.collider.GetComponent<SimpleButton>();
if (!doorButton)
{
continue;
}
if (!doorButton) continue;
var doorVariant = DoorVariant.AllDoors.AsEnumerable()!.First(x => x.Buttons.Any(c=>c.GetInstanceID() == doorButton.GetInstanceID()));
var doorVariant = DoorVariant.AllDoors.AsEnumerable()!.First(x =>
x.Buttons.Any(c => c.GetInstanceID() == doorButton.GetInstanceID()));
doorVariant.ServerInteract(ev.Player.ReferenceHub, doorButton.ColliderId);
StartDoorLockCoroutine(doorVariant);
@ -199,7 +151,6 @@ namespace KeycardButModern
PlayerEvents.UnlockingWarheadButton += OnUnlockingWarhead;
PlayerEvents.PlacedBulletHole += OnBulletHole;
}
private static void StartDoorLockCoroutine(DoorVariant door)
@ -230,4 +181,3 @@ namespace KeycardButModern
Singleton = null;
}
}
}

View File

@ -2,8 +2,8 @@
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
namespace LogEvents
{
namespace LogEvents;
internal class LogPlugin : Plugin
{
public override string Name { get; } = "LogPlugin";
@ -12,9 +12,9 @@ namespace LogEvents
public override string Author { get; } = "Northwood";
public override Version Version { get; } = new Version(1, 0, 0, 0);
public override Version Version { get; } = new(1, 0, 0, 0);
public override Version RequiredApiVersion { get; } = new Version(LabApiProperties.CompiledVersion);
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public MyCustomEventsHandler Events { get; } = new();
@ -28,4 +28,3 @@ namespace LogEvents
CustomHandlersManager.UnregisterEventsHandler(Events);
}
}
}

View File

@ -723,7 +723,8 @@ internal class MyCustomEventsHandler : CustomEventsHandler
public override void OnPlayerStayingInHazard(PlayersStayingInHazardEventArgs ev)
{
Logger.Info($"{nameof(OnPlayerStayingInHazard)} triggered by {string.Join(", ", ev.AffectedPlayers.Select(x => x.UserId))}");
Logger.Info(
$"{nameof(OnPlayerStayingInHazard)} triggered by {string.Join(", ", ev.AffectedPlayers.Select(x => x.UserId))}");
}
public override void OnPlayerLeavingHazard(PlayerLeavingHazardEventArgs ev)
@ -1397,5 +1398,4 @@ internal class MyCustomEventsHandler : CustomEventsHandler
// }
#endregion
}

View File

@ -23,18 +23,6 @@
<Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.Physics2DModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.Physics2DModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>

View File

@ -1,5 +1,4 @@
using System.Runtime.Serialization;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
@ -9,6 +8,11 @@ namespace RangeBan;
public class RangeBan : Plugin<RangeBanConfig>
{
public override string Name => "RangeBan";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Ban IP Ranges with ease";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public override void Enable()
{
@ -26,16 +30,11 @@ public class RangeBan: Plugin<RangeBanConfig>
{
Logger.Debug($"Ranges: {string.Join(" ; ", Config!.IpRanges)}");
if (!Config!.IpRanges.Any(configIpRange => IsInRange(configIpRange, ev.IpAddress))) return;
ev.RejectCustom("Your IP belongs to a banned player, please contact the server administrator for more information.");
ev.RejectCustom(
"Your IP belongs to a banned player, please contact the server administrator for more information.");
Logger.Warn($"Player with IP {ev.IpAddress} got kicked. UserId: {ev.UserId}");
}
public override string Name => "RangeBan";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Ban IP Ranges with ease";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public static bool IsInRange(string range, string ip)
{
@ -46,29 +45,24 @@ public class RangeBan: Plugin<RangeBanConfig>
if (!range.Contains("/"))
{
//We only handle direct IPs and CIDR
if (range.Split('.').Length != 4)
{
return false;
}
if (range.Split('.').Length != 4) return false;
return ip == range;
};
}
;
var parts = range.Split('/');
if (parts.Length != 2 || !int.TryParse(parts[1], out var cidrBits))
return false;
if (cidrBits > 32)
{
return false;
}
if (cidrBits > 32) return false;
var networkAddress = IPToUInt32(parts[0]);
var mask = uint.MaxValue << (32 - cidrBits);
var ipAddress = IPToUInt32(ip);
return (ipAddress & mask) == (networkAddress & mask);
}
private static uint IPToUInt32(string ipAddress)
@ -84,6 +78,7 @@ public class RangeBan: Plugin<RangeBanConfig>
throw new ArgumentException("Invalid IP address segment");
result = (result << 8) | part;
}
return result;
}
}

View File

@ -19,18 +19,6 @@
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>

View File

@ -12,23 +12,25 @@ using PlayerRoles.PlayableScps.Scp096;
using PlayerRoles.PlayableScps.Scp3114;
using Timer = System.Timers.Timer;
namespace SCPTeamHint
{
namespace SCPTeamHint;
public class Plugin : LabApi.Loader.Features.Plugins.Plugin
{
private readonly object _hintsLock = new();
private readonly Dictionary<Player, Hint> _spectatorHints = new();
private Timer _timer;
public override string Name => "SCPTeamHint";
public override string Author => "HoherGeist, Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Displays information about your SCP Teammates";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private Timer _timer;
private readonly Dictionary<Player,Hint> _spectatorHints = new();
public override void Enable()
{
Logger.Debug("Apple juice");
PlayerEvents.Joined += OnJoin;
PlayerEvents.Left += OnLeft;
_timer = new Timer(1000);
_timer.Elapsed += (_, _) => UpdateHints();
@ -38,19 +40,22 @@ namespace SCPTeamHint
public override void Disable()
{
PlayerEvents.Joined -= OnJoin;
_timer.Stop();
PlayerEvents.Left -= OnLeft;
_timer?.Stop();
_timer?.Dispose();
_timer = null;
}
private void UpdateHints()
{
var hintTexts = new List<string>();
foreach (var player in Player.List)
lock (_hintsLock)
{
if (player.IsDummy || player.IsHost) continue;
if (!player.IsSCP) continue;
var text = $" <size=25><color=red>{player.RoleBase.RoleName}</color> | <color=#6761cd>{player.HumeShield}</color> | <color=#da0101>{player.Health}</color> | <color=grey>{player.Zone}</color></size> ";
foreach (var player in Player.ReadyList.Where(x => !x.IsHost && !x.IsDummy && x.IsSCP))
{
var text =
$" <size=25><color=red>{player.RoleBase.RoleName}</color> | <color=#6761cd>{player.HumeShield}</color> | <color=#da0101>{player.Health}</color> | <color=grey>{player.Zone}</color></size> ";
switch (player.RoleBase)
{
@ -59,6 +64,8 @@ namespace SCPTeamHint
scp.SubroutineModule.TryGetSubroutine(out Scp096TargetsTracker tracker);
if (!tracker) break;
text += $"Targets: {tracker.Targets.Count}";
break;
case Scp3114Role scp3114:
@ -78,43 +85,65 @@ namespace SCPTeamHint
scp079.SubroutineModule.TryGetSubroutine(out Scp079AuxManager auxManager);
scp079.SubroutineModule.TryGetSubroutine(out Scp079TierManager tierManager);
text += $" <color=grey>AUX: {auxManager.CurrentAuxFloored} / {auxManager.MaxAux} Level: {tierManager.AccessTierLevel}</color>";
if (!auxManager || !tierManager) break;
text +=
$" <color=grey>AUX: {auxManager.CurrentAuxFloored} / {auxManager.MaxAux} | Level {tierManager.AccessTierLevel}</color>";
break;
}
hintTexts.Add(text);
}
foreach (var player in Player.List.Where(x=>!x.IsHost))
var hintText = string.Join("\n", hintTexts);
foreach (var player in Player.ReadyList.Where(x => !x.IsHost && !x.IsDummy))
{
Logger.Debug($"Updating hint for {player.DisplayName}");
UpdateHint(player, string.Join("\n", hintTexts));
UpdateHint(player, hintText);
}
}
}
private void UpdateHint(Player player, string hintText)
{
var hint = _spectatorHints[player];
if (!_spectatorHints.TryGetValue(player, out var hint))
{
Logger.Debug($"No hint found for player {player.DisplayName}");
return;
}
Logger.Debug($"Player {player.Nickname} is on team {player.RoleBase.Team} | hide: {player.RoleBase.Team != Team.SCPs}");
Logger.Debug(
$"Player {player.Nickname} is on team {player.RoleBase.Team} | hide: {player.RoleBase.Team != Team.SCPs}");
hint.Hide = player.RoleBase.Team != Team.SCPs;
hint.Text = hintText;
if (!hint.Hide) hint.Text = hintText;
}
private void OnJoin(PlayerJoinedEventArgs ev)
{
if (ev.Player.IsDummy || ev.Player.IsHost) return;
var hint = new Hint
{
Text = "Apfelsaft", Alignment = HintAlignment.Left, YCoordinate = 100, Hide = true
Text = "", Alignment = HintAlignment.Left, YCoordinate = 100, Hide = true
};
var playerDisplay = PlayerDisplay.Get(ev.Player);
playerDisplay.AddHint(hint);
lock (_hintsLock)
{
_spectatorHints[ev.Player] = hint;
}
}
private void OnLeft(PlayerLeftEventArgs ev)
{
if (ev.Player.IsDummy || ev.Player.IsHost) return;
lock (_hintsLock)
{
_spectatorHints.Remove(ev.Player);
}
}
}

View File

@ -20,17 +20,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>

21
ScpSwap/ScpSwap.cs Normal file
View File

@ -0,0 +1,21 @@
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
namespace ScpSwap;
public class ScpSwap : Plugin
{
public override string Name => "ScpSwap";
public override string Author => "HoherGeist, Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Swap SCPs.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public override void Enable()
{
}
public override void Disable()
{
}
}

43
ScpSwap/ScpSwap.csproj Normal file
View File

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
</Project>

82
ScpSwap/SwapCommand.cs Normal file
View File

@ -0,0 +1,82 @@
using CommandSystem;
using LabApi.Features.Wrappers;
using PlayerRoles;
namespace ScpSwap;
[CommandHandler(typeof(ClientCommandHandler))]
public class SwapCommand : ICommand
{
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
if (arguments.Count != 1)
{
response = "Usage: .scpswap <SCP_NUMBER>";
return false;
}
if (!Player.TryGet(sender, out var player))
{
response = "You must be a player to use this command!";
return false;
}
if (Round.Duration.TotalSeconds > 120)
{
response = "You can't swap SCPs during a round!";
return false;
}
if (!player.IsSCP)
{
response = "You must be an SCP to use this command!";
return false;
}
List<string> validScp =
[
"049",
"079",
"096",
"106",
"173",
"939"
];
var arg = arguments.First();
if (!validScp.Contains(arg))
{
response = "Invalid SCP number.";
return false;
}
if (Player.List.Where(x => x.IsSCP).Select(x => x.RoleBase)
.Any(playerRole => playerRole.RoleName == "SCP-" + arg))
{
response = "Already exists";
return false;
}
var role = arg switch
{
"049" => RoleTypeId.Scp049,
"079" => RoleTypeId.Scp079,
"096" => RoleTypeId.Scp096,
"106" => RoleTypeId.Scp106,
"173" => RoleTypeId.Scp173,
"939" => RoleTypeId.Scp939,
_ => throw new ArgumentOutOfRangeException()
};
player.SetRole(role);
response = "Swapping...";
return true;
}
public string Command { get; } = "scpswap";
public string[] Aliases { get; } = ["ss"];
public string Description { get; } = "Swaps SCPs";
}

View File

@ -18,6 +18,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RangeBan.Tests", "RangeBan.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CuffedFrenemies", "CuffedFrenemies\CuffedFrenemies.csproj", "{C3FEEC52-B7C0-4DB6-A0CA-54BE175072D8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CandySetting", "CandySetting\CandySetting.csproj", "{DF3E3243-BD16-4484-BA01-020170FFC871}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScpSwap", "ScpSwap\ScpSwap.csproj", "{B56CA1D5-0927-4542-B967-5E7F5B092E50}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomItemSpawn", "CustomItemSpawn\CustomItemSpawn.csproj", "{887DC217-999F-400B-8918-6737B7694BEE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AfkSwap", "AfkSwap\AfkSwap.csproj", "{A21DBED5-2B6C-4298-B2CC-DE8F4BAC9766}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WarheadEvents", "WarheadEvents\WarheadEvents.csproj", "{83EB26E9-C1EB-43C9-B010-BBE0F7F05EE9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomClasses", "CustomClasses\CustomClasses.csproj", "{234E0C4B-5CD2-4FEB-8222-950959E6C082}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerHints", "ServerHints\ServerHints.csproj", "{146AC6C6-AFE6-4EEA-B2F4-6403AD7189D9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrowingZombies", "GrowingZombies\GrowingZombies.csproj", "{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -60,5 +76,37 @@ Global
{C3FEEC52-B7C0-4DB6-A0CA-54BE175072D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3FEEC52-B7C0-4DB6-A0CA-54BE175072D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3FEEC52-B7C0-4DB6-A0CA-54BE175072D8}.Release|Any CPU.Build.0 = Release|Any CPU
{DF3E3243-BD16-4484-BA01-020170FFC871}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF3E3243-BD16-4484-BA01-020170FFC871}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF3E3243-BD16-4484-BA01-020170FFC871}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF3E3243-BD16-4484-BA01-020170FFC871}.Release|Any CPU.Build.0 = Release|Any CPU
{B56CA1D5-0927-4542-B967-5E7F5B092E50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B56CA1D5-0927-4542-B967-5E7F5B092E50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B56CA1D5-0927-4542-B967-5E7F5B092E50}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B56CA1D5-0927-4542-B967-5E7F5B092E50}.Release|Any CPU.Build.0 = Release|Any CPU
{887DC217-999F-400B-8918-6737B7694BEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{887DC217-999F-400B-8918-6737B7694BEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{887DC217-999F-400B-8918-6737B7694BEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{887DC217-999F-400B-8918-6737B7694BEE}.Release|Any CPU.Build.0 = Release|Any CPU
{A21DBED5-2B6C-4298-B2CC-DE8F4BAC9766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A21DBED5-2B6C-4298-B2CC-DE8F4BAC9766}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A21DBED5-2B6C-4298-B2CC-DE8F4BAC9766}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A21DBED5-2B6C-4298-B2CC-DE8F4BAC9766}.Release|Any CPU.Build.0 = Release|Any CPU
{83EB26E9-C1EB-43C9-B010-BBE0F7F05EE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{83EB26E9-C1EB-43C9-B010-BBE0F7F05EE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{83EB26E9-C1EB-43C9-B010-BBE0F7F05EE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{83EB26E9-C1EB-43C9-B010-BBE0F7F05EE9}.Release|Any CPU.Build.0 = Release|Any CPU
{234E0C4B-5CD2-4FEB-8222-950959E6C082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{234E0C4B-5CD2-4FEB-8222-950959E6C082}.Debug|Any CPU.Build.0 = Debug|Any CPU
{234E0C4B-5CD2-4FEB-8222-950959E6C082}.Release|Any CPU.ActiveCfg = Release|Any CPU
{234E0C4B-5CD2-4FEB-8222-950959E6C082}.Release|Any CPU.Build.0 = Release|Any CPU
{146AC6C6-AFE6-4EEA-B2F4-6403AD7189D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{146AC6C6-AFE6-4EEA-B2F4-6403AD7189D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{146AC6C6-AFE6-4EEA-B2F4-6403AD7189D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{146AC6C6-AFE6-4EEA-B2F4-6403AD7189D9}.Release|Any CPU.Build.0 = Release|Any CPU
{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5751F8D6-7A8D-4C2C-B7E9-A8A3DB324329}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -8,8 +8,8 @@ using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
using TimedGrenadePickup = InventorySystem.Items.ThrowableProjectiles.TimedGrenadePickup;
namespace SensitiveGrenades
{
namespace SensitiveGrenades;
public class SensitiveGrenades : Plugin
{
public override string Name => "SensitiveGrenades";
@ -50,4 +50,3 @@ namespace SensitiveGrenades
Logger.Debug("unloading...");
}
}
}

View File

@ -30,9 +30,6 @@
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.Physics2DModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.Physics2DModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference>

View File

@ -0,0 +1,49 @@
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using MEC;
namespace ServerHints;
public class ServerHints : Plugin
{
public override string Name => "ServerHints";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Adds hints for custom features.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public string[] Hints { get; set; } =
[
"Man kann gegnerische Einheiten festnehmen, um sie zu seiner Seite zu bringen.",
"Als Hausmeister beginnst du in der Nähe von SCP-914.",
"Du kannst als SCP mit .scpswap <SCP nummer> deine Rolle tauschen. (Ö)",
"Es gibt auf der Surface versteckte Items.",
"Man kann mehr als 2 Candies nehmen.",
"Man braucht seine Karte nicht in der Hand zu halten.",
"Man kann Türen aufschießen",
"Wenn man Granaten anschießt, explodieren sie sofort."
];
public override void Enable()
{
ServerEvents.RoundStarted += OnRoundStarted;
}
public override void Disable()
{
ServerEvents.RoundStarted -= OnRoundStarted;
}
private void OnRoundStarted()
{
var random = new Random();
var hint = Hints[random.Next(Hints.Length)];
Timing.CallDelayed(1, () =>
{
foreach (var player in Player.ReadyList) player.SendBroadcast($"<color=grey>{hint}</color>", 3);
});
}
}

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
</Project>

View File

@ -1,5 +1,4 @@
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.HintContent;
using HintServiceMeow.Core.Models.Hints;
using HintServiceMeow.Core.Utilities;
using LabApi.Events.Arguments.PlayerEvents;
@ -9,77 +8,13 @@ using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using PlayerRoles;
using PlayerRoles.Spectating;
using Timer = System.Timers.Timer;
namespace VisibleSpectators
{
namespace VisibleSpectators;
public class Plugin : Plugin<SpectatorConfig>
{
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);
public int YCoordinate { get; set; } = 100;
private static Plugin _singleton;
private Timer _timer;
private readonly Dictionary<Player,Hint> _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 AddPlayerHint(Player player)
{
if (player == null) return;
var hint = new Hint
{
Text = $"{Config!.HeaderMessage}\n{Config!.NoSpectatorsMessage}",
Alignment = HintAlignment.Right,
YCoordinate = YCoordinate,
Hide = true
};
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(hint);
_spectatorHints[player] = hint;
}
private static readonly Dictionary<string, string> GetColorMap = new()
{
@ -115,9 +50,68 @@ namespace VisibleSpectators
{ "GOLD", "EFC01A" }
};
private readonly Dictionary<Player, Hint> _spectatorHints = new();
private Timer _timer;
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);
public int YCoordinate { get; set; } = 100;
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 AddPlayerHint(Player player)
{
var hint = new Hint
{
Text = $"{Config!.HeaderMessage}\n{Config!.NoSpectatorsMessage}",
Alignment = HintAlignment.Right,
YCoordinate = YCoordinate,
Hide = true
};
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(hint);
_spectatorHints[player] = hint;
}
private static string PlayerToDisplay(Player player)
{
if (player == null) return "";
if (!player.IsReady) return "";
// Default color if GroupColor is null or not found in the map
const string defaultColor = "FFFFFF";
@ -128,7 +122,9 @@ namespace VisibleSpectators
if (string.IsNullOrEmpty(groupColor))
return $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
return GetColorMap.TryGetValue(groupColor.ToUpper(), out var color) ? $"<color=#{color}FF>{player.DisplayName}</color>" : $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
return GetColorMap.TryGetValue(groupColor.ToUpper(), out var color)
? $"<color=#{color}FF>{player.DisplayName}</color>"
: $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
}
catch
{
@ -143,16 +139,8 @@ namespace VisibleSpectators
private void UpdateSpectators(Player player)
{
if (player == null)
{
return;
}
// Safety check - if player doesn't have a hint, create one
if (!_spectatorHints.ContainsKey(player))
{
AddPlayerHint(player);
}
if (!_spectatorHints.ContainsKey(player)) AddPlayerHint(player);
var spectators = Config!.NoSpectatorsMessage;
@ -165,15 +153,13 @@ namespace VisibleSpectators
: string.Join("\n",
player.CurrentlySpectating?.CurrentSpectators.Where(IsNotOverwatch)
.Select(PlayerToDisplay) ?? Array.Empty<string>());
} catch (Exception e)
}
catch (Exception e)
{
Logger.Error(e);
}
if (spectators.Length < 2)
{
spectators = Config!.NoSpectatorsMessage;
}
if (spectators.Length < 2) spectators = Config!.NoSpectatorsMessage;
_spectatorHints[player].Text = $"{Config!.HeaderMessage}\n{spectators}";
@ -185,7 +171,7 @@ namespace VisibleSpectators
private static Player[] GetPlayers()
{
return Player.Dictionary.Values.Where(x=>!x.IsHost).ToArray();
return Player.ReadyList.Where(IsNotOverwatch).ToArray();
}
private static void OnSpectate(PlayerChangedSpectatorEventArgs ev)
@ -203,7 +189,6 @@ namespace VisibleSpectators
public class SpectatorConfig
{
public string HeaderMessage => "Spectators:";
public string NoSpectatorsMessage => "No spectators";
}
public string HeaderMessage { get; set; } = "Spectators:";
public string NoSpectatorsMessage { get; set; } = "No spectators";
}

View File

@ -20,9 +20,6 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference>

View File

@ -0,0 +1,32 @@
using Interactables.Interobjects.DoorUtils;
using LabApi.Events.Arguments.WarheadEvents;
using LabApi.Features;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
namespace WarheadEvents;
public class WarheadEvents : Plugin
{
public override string Name => "WarheadEvents";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Misc. stuff for after the Warhead explosion.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public override void Enable()
{
LabApi.Events.Handlers.WarheadEvents.Detonated += OnExplode;
}
public override void Disable()
{
}
private static void OnExplode(WarheadDetonatedEventArgs ev)
{
var door = Door.Get(DoorVariant.AllDoors.First(x => x.DoorName.ToUpper() == "ESCAPE_FINAL"));
door.IsOpened = true;
}
}

View File

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
</Project>