Author SHA1 Message Date
Renovate Bot 1368da8165 Update dependency csharpier to 1.3.0 2026-06-07 18:10:21 +00:00
code002lover f437fe9bae allow networkID as playername arg in SetCClass 2025-07-14 23:35:39 +02:00
code002lover ddb192a278 implement sane default extendedclass 2025-07-14 23:30:34 +02:00
code002lover 477dc4c297 change over more parts to the new system 2025-07-14 23:29:49 +02:00
code002lover 43f66370f3 WIP refactor v3 2025-07-14 09:38:20 +02:00
code002lover ccaf44d50b refactor config handling 2025-07-14 08:11:10 +02:00
code002lover d53d71c6d9 improve bloodfueled & serpents 2025-07-14 03:35:11 +02:00
code002lover f839619464 finish bloodfueled 2025-07-13 04:45:03 +02:00
code002lover c2a232d432 add deploy script 2025-07-13 03:37:30 +02:00
HoherGeist 017d966b83 Merge branch 'master' of https://git.ipost.rocks/code002lover/SecretPluginLaboratories 2025-07-13 03:36:27 +02:00
HoherGeist 6810309b46 Changes by code002lover
Blood Fueled Additions -HoherGeist
2025-07-13 03:35:31 +02:00
code002lover 6d620dfd85 properly add new effect 2025-07-13 02:32:41 +02:00
HoherGeist 2f483ca113 refactor + lessen stamina usage 2025-07-12 23:02:07 +02:00
HoherGeist a8063216cc Changes by code002lover 2025-07-12 22:53:13 +02:00
HoherGeist 14e32bc921 +Blood Fueled Added 2025-07-12 22:40:11 +02:00
code002lover 78fa56dce9 various fixes + new custom class shadowmancer 2025-07-12 20:18:02 +02:00
code002lover 0615f8aeea do a lot ig 2025-07-03 22:05:38 +02:00
code002lover 0aee847089 idk man 2025-06-09 22:13:12 +02:00
code002lover 326b99c464 various thingies 2025-06-08 00:54:06 +02:00
code002lover 67e3d6ceaa Refactor 2025-06-08 00:54:06 +02:00
code002lover 73a4da1edd Add Template, more custom classes & fix issues 2025-06-08 00:54:06 +02:00
Renovate Botandcode002lover c9bee028d9 Update dependency NUnit.Analyzers to 4.8.1 2025-06-05 17:01:11 +02:00
Renovate Bot 38c660a24f Update dependency Microsoft.NET.Test.Sdk to 17.14.1 2025-06-05 00:07:36 +00:00
Renovate Botandcode002lover 20583ea9f9 Update dependency coverlet.collector to 6.0.4 2025-06-05 01:20:22 +02:00
code002lover d3dbd1ec9d Switch to MEC 2025-06-05 01:19:33 +02:00
code002lover 3e51b1a6c8 delete invis unicode char 2025-06-05 01:16:12 +02:00
code002lover 96596e9c08 refactor 2025-06-05 01:12:55 +02:00
code002lover 17c654d889 add and fix a lot 2025-06-05 01:11:30 +02:00
code002lover 9bf05f87aa fix crash due to dummies 2025-05-30 14:28:58 +02:00
code002lover fb5baf321c Slight fixes 2025-05-30 14:19:26 +02:00
HoherGeist 8b44d63602 Fix Windows shit 2025-05-30 14:16:06 +02:00
code002lover 962ab6d0a6 Update SCPTeamHint/SCPTeamHint.csproj 2025-05-30 14:01:49 +02:00
HoherGeist 927358ea12 Merge branch 'master' of https://git.ipost.rocks/code002lover/SecretPluginLaboratories 2025-05-30 13:52:25 +02:00
HoherGeist ece549e7a1 Added more SCP info 2025-05-30 13:43:16 +02:00
77 changed files with 5903 additions and 1139 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.3.0",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}
+2
View File
@@ -4,3 +4,5 @@ obj/
*.user *.user
*.dll *.dll
fuchsbau/ fuchsbau/
testbau/
**/target/
+152
View File
@@ -0,0 +1,152 @@
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);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
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] - playerTime.Key.Position).sqrMagnitude > 2)
{
_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
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>
+51
View File
@@ -0,0 +1,51 @@
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
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;
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.InteractingScp330 += TakingCandy;
}
public override void Disable()
{
PlayerEvents.InteractingScp330 -= TakingCandy;
}
private void TakingCandy(PlayerInteractingScp330EventArgs ev)
{
ev.AllowPunishment = ev.Uses > MaxUses;
}
}
+25
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>
+33 -46
View File
@@ -1,4 +1,4 @@
using CustomPlayerEffects; using CustomClasses;
using LabApi.Events.Arguments.PlayerEvents; using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers; using LabApi.Events.Handlers;
using LabApi.Features; using LabApi.Features;
@@ -7,22 +7,37 @@ using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins; using LabApi.Loader.Features.Plugins;
using Mirror; using Mirror;
using PlayerRoles; using PlayerRoles;
using PlayerRoles.PlayableScps.Scp3114;
using PlayerRoles.Ragdolls;
namespace CuffedFrenemies; namespace CuffedFrenemies;
public class CuffedFrenemies : Plugin public class CuffedFrenemies : Plugin
{ {
public override string Name => "GamblingCoin"; public override string Name => "CuffedFrenemies";
public override string Author => "Code002Lover"; public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0); public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Gamble your life away"; public override string Description => "Cuff your enemies";
public override Version RequiredApiVersion { get; } = new (LabApiProperties.CompiledVersion); public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable() public override void Enable()
{ {
Logger.Debug("Loading"); const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Cuffed += OnCuff; PlayerEvents.Cuffed += OnCuff;
} }
@@ -33,58 +48,30 @@ public class CuffedFrenemies : Plugin
private static void OnCuff(PlayerCuffedEventArgs ev) private static void OnCuff(PlayerCuffedEventArgs ev)
{ {
if (ev.Target.Team is Team.ClassD or Team.Scientists) if (ev.Target.Team is Team.ClassD or Team.Scientists) return;
{
return;
}
if (ev.Target.Team == ev.Player.Team) if (ev.Target.Team == ev.Player.Team)
{ {
Logger.Debug("Same team, not changing role");
return; return;
} }
if (ev.Target.RoleBase is Scp3114Role scp3114) if (ev.Target.Team is Team.SCPs or Team.Dead) return;
if (SerpentsHandManager.IsSerpentsHand(ev.Player))
{ {
var stolenRole = scp3114.CurIdentity.StolenRole;
var ragdoll = scp3114.CurIdentity.Ragdoll.Info; SerpentsHandManager.PreSpawn(ev.Target);
switch (stolenRole)
{ return;
case RoleTypeId.ChaosConscript or RoleTypeId.ChaosMarauder or RoleTypeId.ChaosRepressor
or RoleTypeId.ChaosRifleman:
scp3114.CurIdentity.Ragdoll.Info = new RagdollData(ragdoll.OwnerHub, ragdoll.Handler, RoleTypeId.NtfPrivate, ragdoll.StartPosition, ragdoll.StartRotation, ragdoll.Nickname, ragdoll.CreationTime);
return;
case RoleTypeId.NtfPrivate or RoleTypeId.NtfCaptain or RoleTypeId.NtfSergeant
or RoleTypeId.NtfSpecialist:
scp3114.CurIdentity.Ragdoll.Info = new RagdollData(ragdoll.OwnerHub, ragdoll.Handler, RoleTypeId.ChaosConscript, ragdoll.StartPosition, ragdoll.StartRotation, ragdoll.Nickname, ragdoll.CreationTime);
return;
}
} }
if (ev.Target.Team is Team.SCPs or Team.Dead) if (ev.Target.Role == RoleTypeId.Tutorial)
{ {
return; return;
} }
var newRole = ev.Target.Team == Team.ChaosInsurgency ? RoleTypeId.NtfPrivate : RoleTypeId.ChaosConscript; var newRole = ev.Player.Team is Team.ChaosInsurgency or Team.ClassD ? RoleTypeId.ChaosConscript : RoleTypeId.NtfPrivate;
Logger.Debug($"Setting role to {newRole}"); Logger.Debug($"Setting role to {newRole}");
var newItems = new List<Item>(); ev.Target.SetRole(newRole, RoleChangeReason.ItemUsage, RoleSpawnFlags.None);
ev.Target.Items.CopyTo(newItems);
newItems.Reverse();
var newPos = ev.Target.Position;
ev.Target.Inventory.UserInventory.Items.Clear();
ev.Target.SetRole(newRole);
ev.Target.ClearItems();
foreach (var newItem in newItems)
{
ev.Target.Inventory.UserInventory.Items.Add(newItem.Serial,newItem.Base);
}
ev.Target.Position = newPos;
} }
} }
+5 -4
View File
@@ -26,15 +26,16 @@
<Reference Include="Mirror"> <Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
</Reference> </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"> <Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2" /> <PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CustomClasses\CustomClasses.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+150
View File
@@ -0,0 +1,150 @@
using CustomPlayerEffects;
using InventorySystem.Items.Usables.Scp330;
using LabApi.Events.Handlers;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles.FirstPersonControl;
using PlayerRoles.PlayableScps.Scp939;
using static LabApi.Features.Wrappers.Server;
using Logger = LabApi.Features.Console.Logger;
using Random = System.Random;
namespace CustomClasses;
public class DisableStaminaRegenEffect : CustomPlayerEffect, IStaminaModifier
{
public bool StaminaModifierActive => IsEnabled;
public float StaminaUsageMultiplier => 1;
public float StaminaRegenMultiplier => 0;
public bool SprintingDisabled => false;
public override EffectClassification Classification => EffectClassification.Negative;
}
public class BloodFueledStaminaEffect : CustomPlayerEffect, IStaminaModifier
{
public bool StaminaModifierActive => IsEnabled;
public float StaminaUsageMultiplier => 0.2f;
public float StaminaRegenMultiplier => 1;
public bool SprintingDisabled => false;
public override EffectClassification Classification => EffectClassification.Positive;
}
public class BloodFueledManager
{
public static bool IsBloodFueled(Player player)
{
try
{
return player.CustomInfo.Contains("Blood Fueled");
}
catch
{
return false;
}
}
public BloodFueledManager()
{
PlayerEvents.Hurt += ev =>
{
if (ev.DamageHandler is not Scp939DamageHandler damageHandler) return;
var attacker = Player.Get(damageHandler.Attacker.Hub);
if (attacker == null) return;
if (!IsBloodFueled(attacker)) return;
var isAffectedByBloodCloud = Scp939AmnesticCloudInstance.ActiveInstances.Where(x=>x.Owner && x.Owner == ev.Attacker?.ReferenceHub).Any(x=>x.AffectedPlayers.Contains(ev.Player.ReferenceHub));
if (isAffectedByBloodCloud)
{
attacker.Heal(10);
attacker.StaminaRemaining += 0.1f;
}
if (ev.Player.Health <= 0)
{
attacker.Heal(25);
attacker.StaminaRemaining += 0.2f;
return;
}
attacker.Heal(15);
attacker.StaminaRemaining += 0.1f;
};
Timing.RunCoroutine(DrainBlood());
PlayerEvents.EnteringHazard += ev =>
{
if (ev.Hazard is not AmnesticCloudHazard amnesticCloud) return;
if (amnesticCloud.Owner == null || !IsBloodFueled(amnesticCloud.Owner)) return;
ev.Player.EnableEffect<Invigorated>(1, float.PositiveInfinity);
};
PlayerEvents.StayingInHazard += ev =>
{
if (ev.Hazard is not AmnesticCloudHazard amnesticCloud) return;
if (amnesticCloud.Owner == null || !IsBloodFueled(amnesticCloud.Owner)) return;
foreach (var affectedPlayer in ev.AffectedPlayers)
{
// ReSharper disable once PossibleLossOfFraction
affectedPlayer.Heal(10 / MaxTps);
}
};
PlayerEvents.LeavingHazard += ev =>
{
if (ev.Hazard is not AmnesticCloudHazard amnesticCloud) return;
if (amnesticCloud.Owner == null || !IsBloodFueled(amnesticCloud.Owner)) return;
ev.Player.DisableEffect<Invigorated>();
};
}
public static IEnumerator<float> DrainBlood()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
foreach (var player in Player.ReadyList.Where(IsBloodFueled))
{
if (player.StaminaRemaining <= 0f)
{
player.Health = Math.Min(Math.Max(player.Health - 50, 500), player.Health);
continue;
}
player.StaminaRemaining -= 0.005f;
if (player.MaxHealth <= player.Health) continue;
player.Heal(5);
player.StaminaRemaining -= 0.001f;
}
}
// ReSharper disable once IteratorNeverReturns
}
}
public class BloodFueledHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
player.MaxHumeShield = 0;
player.HumeShield = 0;
player.MaxHealth = 3500;
player.Health = 3500;
player.EnableEffect<DisableStaminaRegenEffect>(1, float.PositiveInfinity);
player.EnableEffect<BloodFueledStaminaEffect>(1, float.PositiveInfinity);
}
public override void SendSpawnMessage(Player player, CustomClassConfig config)
{
player.SendBroadcast("You are SCP-939-<color=#C50000>Blood Fueled</color> \n Your stamina bar has been replaced by a <color=#C50000>blood meter</color>. \n You refill it by <color=#C50000>damaging</color> or <color=#C50000>killing</color> Humans. \n <color=#C50000><b>Don't let it run out.</color></b>", CustomClasses.BroadcastDuration);
}
}
+985
View File
@@ -0,0 +1,985 @@
using System.Diagnostics.CodeAnalysis;
using CustomPlayerEffects;
using HintServiceMeow.Core.Models.Hints;
using Interactables.Interobjects.DoorUtils;
using InventorySystem;
using InventorySystem.Items;
using InventorySystem.Items.Firearms.Modules;
using JetBrains.Annotations;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.Scp914Events;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Enums;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using MapGeneration;
using MEC;
using PlayerRoles;
using Scp914.Processors;
using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
using Random = System.Random;
using Vector3 = UnityEngine.Vector3;
namespace CustomClasses;
/// <summary>
/// Main plugin class for CustomClasses. Handles plugin lifecycle and event subscriptions.
/// </summary>
public sealed class CustomClasses : Plugin
{
public readonly CustomClassManager ClassManager = new();
public SerpentsHandManager SerpentsHandManager;
public NegromancerManager NegromancerManager;
public BloodFueledManager BloodFueledManager = new();
/// <inheritdoc/>
public override string Name => "CustomClasses";
/// <inheritdoc/>
public override string Author => "Code002Lover";
/// <inheritdoc/>
public override Version Version { get; } = new(1, 0, 0);
/// <inheritdoc/>
public override string Description => "Adds custom classes to the game";
/// <inheritdoc/>
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public const ushort BroadcastDuration = 10;
internal readonly Dictionary<Player, Hint> Hints = new();
public static CustomClasses Instance { get; private set; }
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
CustomPlayerEffect.Initialize();
PlayerEvents.Spawned += OnPlayerSpawned;
ServerEvents.RoundEnded += OnRoundEnded;
Scp914Events.ProcessingPickup += OnScp914ProcessingPickup;
Scp914Events.ProcessingInventoryItem += OnScp914ProcessingInventoryItem;
ServerEvents.WaveTeamSelected += OnWaveTeamSelected;
ServerEvents.WaveRespawning += OnWaveRespawning;
PlayerEvents.UsedItem += OnItemUsed;
ServerEvents.GeneratorActivated += OnGeneratorEngaged;
SerpentsHandManager = new SerpentsHandManager(this);
Timing.RunCoroutine(SerpentsHandManager.UpdateSerpentsHandHint());
if (InventoryItemLoader.AvailableItems.TryGetValue(ItemType.KeycardCustomTaskForce, out var itemBase))
{
if (!itemBase.TryGetComponent<Scp914ItemProcessor>(out _))
{
var processor = itemBase.gameObject.AddComponent<StandardItemProcessor>();
var type = processor.GetType();
var fields = new[]
{
"_roughOutputs",
"_coarseOutputs",
"_oneToOneOutputs",
"_fineOutputs",
"_veryFineOutputs"
};
var output = new[] { ItemType.KeycardMTFCaptain };
foreach (var fieldName in fields)
{
var field = type.GetField(fieldName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
field?.SetValue(processor, output);
}
}
}
NegromancerManager = new NegromancerManager(this);
Instance = this;
}
/// <inheritdoc/>
public override void Disable()
{
PlayerEvents.Spawned -= OnPlayerSpawned;
ServerEvents.RoundEnded -= OnRoundEnded;
Scp914Events.ProcessingPickup -= OnScp914ProcessingPickup;
Scp914Events.ProcessingInventoryItem -= OnScp914ProcessingInventoryItem;
ServerEvents.WaveTeamSelected -= OnWaveTeamSelected;
ServerEvents.WaveRespawning -= OnWaveRespawning;
PlayerEvents.UsedItem -= OnItemUsed;
ServerEvents.GeneratorActivated -= OnGeneratorEngaged;
Instance = null;
}
private void OnGeneratorEngaged(GeneratorActivatedEventArgs ev)
{
if (ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) return;
state.ExtraChance += 2.5f;
}
private void OnItemUsed(PlayerUsedItemEventArgs ev)
{
if (ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) return;
switch (ev.UsableItem.Type)
{
case ItemType.SCP268 or ItemType.SCP1576 or ItemType.SCP1344:
state.ExtraChance += 1;
break;
case ItemType.SCP500 or ItemType.SCP207 or ItemType.SCP1853 or ItemType.AntiSCP207
or ItemType.SCP018 or ItemType.SCP2176:
state.ExtraChance += 2;
break;
case ItemType.SCP244a or ItemType.SCP244b:
state.ExtraChance += 0.5f;
break;
}
}
[SuppressMessage("ReSharper", "RedundantJumpStatement")]
private void OnWaveTeamSelected(WaveTeamSelectedEventArgs ev)
{
var spectators = Player.ReadyList.Where(p => p.Role == RoleTypeId.Spectator).ToList();
if (spectators.Count <= 1) return;
var random = new Random();
var spectator = spectators[random.Next(spectators.Count-1)];
if (ev.Wave == RespawnWaves.MiniChaosWave && ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is SerpentsHandState
{
HasSpawned: false
} state)
{
if (random.Next(0, 100) > SerpentsHandConfig.BaseChance + state.ExtraChance)
{
SerpentsHandManager.SpawnSerpentWave();
ClassManager.TryHandleSpawn(spectator, ClassManager.GetConfig<ScoutConfig>(), typeof(ScoutConfig), () =>
{
if (!ClassManager.ForceSpawn(spectator, ClassManager.GetConfig<SerpentsHandConfig>(), typeof(SerpentsHandConfig), PreSpawn))
Logger.Error("Serpents Hand didn't spawn");
return;
void PreSpawn()
{
SerpentsHandManager.PreSpawn(spectator);
}
});
return;
}
}
if (ClassManager.TryHandleSpawn(spectator, ClassManager.GetConfig<ScoutConfig>(), typeof(ScoutConfig), () =>
{
spectator.SetRole(ev.Wave.Faction == Faction.FoundationStaff ? RoleTypeId.NtfPrivate : RoleTypeId.ChaosConscript, RoleChangeReason.Respawn, RoleSpawnFlags.UseSpawnpoint);
})) return;
}
private void OnWaveRespawning(WaveRespawningEventArgs ev)
{
if (ev.Wave != RespawnWaves.MiniChaosWave) return;
if (ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) return;
if (!state.WillSpawn) return;
Cassie.Message("pitch_0.23 .G4 yield_03 .G4 pitch_1 Space yield_0.65 time breach detected near site yield_0.45 entrance yield_1.5 Security yield_0.8 pitch_0.95 Personnel pitch_1 proceed jam_01_2 .G6 with yield_0.4 pitch_0.45 jam_02_3 .G3 yield_0.15 pitch_0.35 .G2 jam_01_5 yield_0.15 pitch_0.2 .G1 pitch_0.9 yield_0.8 protocol", true, false, customSubtitles: "Space-time breach detected near site entrance. Security Personnel proceed with {DATA-EXPUNGED} protocol.");
state.SetWillNotSpawn();
ev.IsAllowed = false;
foreach (var evSpawningPlayer in ev.SpawningPlayers)
{
if (!ClassManager.ForceSpawn(evSpawningPlayer, ClassManager.GetConfig<SerpentsHandConfig>(), typeof(SerpentsHandConfig), PreSpawn))
Logger.Error("Serpents Hand didn't spawn");
continue;
void PreSpawn()
{
SerpentsHandManager.PreSpawn(evSpawningPlayer);
}
}
}
private void OnRoundEnded(RoundEndedEventArgs ev)
{
ClassManager.ResetSpawnStates();
}
[SuppressMessage("ReSharper", "RedundantJumpStatement")]
private void OnPlayerSpawned(PlayerSpawnedEventArgs ev)
{
ev.Player.CustomInfo = "";
if (ClassManager.Configs.Any(classManagerConfig => ClassManager.TryHandleSpawn(ev.Player, classManagerConfig.Value, classManagerConfig.Key, null)))
{
return;
}
}
private static void OnScp914ProcessingPickup(Scp914ProcessingPickupEventArgs ev)
{
// Process custom upgrade
if (ev.Pickup is not KeycardPickup keycard)
{
Logger.Debug($"Keycard not found for SCP-914 pickup {ev.Pickup.Serial}");
return;
}
if (keycard.Type < ItemType.KeycardCustomTaskForce)
{
Logger.Debug($"Keycard not a custom card {ev.Pickup.Serial}");
return;
}
var pickup = Pickup.Create(ItemType.KeycardMTFCaptain, keycard.Position);
keycard.Destroy();
pickup?.Spawn();
}
private static void OnScp914ProcessingInventoryItem(Scp914ProcessingInventoryItemEventArgs ev)
{
// Process custom upgrade
if (ev.Item is not KeycardItem keycard) return;
if (!keycard.Base.Customizable) return;
ev.Player.RemoveItem(keycard);
ev.Player.AddItem(ItemType.KeycardMTFCaptain, ItemAddReason.Scp914Upgrade);
}
}
/// <summary>
/// Manages custom class handlers and spawn state.
/// </summary>
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 Dictionary<Type, CustomClassConfig> Configs { get; } = new();
/// <summary>
/// Initializes a new instance of the <see cref="CustomClassManager"/> class and registers all handlers.
/// </summary>
public CustomClassManager()
{
RegisterHandler<JanitorConfig>(new JanitorHandler(this));
RegisterHandler<ResearchSubjectConfig>(new ResearchSubjectHandler());
RegisterHandler<HeadGuardConfig>(new HeadGuardHandler());
RegisterHandler<MedicConfig>(new MedicHandler());
RegisterHandler<GamblerConfig>(new GamblerHandler());
RegisterHandler<ShadowStepperConfig>(new ShadowStepperHandler());
RegisterHandler<MtfDemolitionistConfig>(new DemolitionistHandler());
RegisterHandler<ScoutConfig>(new ScoutHandler());
RegisterHandler<ExplosiveMasterConfig>(new ExplosiveMasterHandler());
RegisterHandler<FlashMasterConfig>(new FlashMasterHandler());
RegisterHandler<SerpentsHandConfig>(new SerpentsHandHandler(), new SerpentsHandState());
RegisterHandler<NegromancerConfig>(new NegromancerHandler());
RegisterHandler<NegromancerShadowConfig>(new NegromancerShadowHandler());
RegisterHandler<BloodFueledConfig>(new BloodFueledHandler());
}
public SpawnState GetSpawnState(Type configType)
{
lock (_lock)
{
return _spawnStates[configType];
}
}
/// <summary>
/// Teleports a player to a position near the specified location.
/// </summary>
/// <param name="player">The player to teleport.</param>
/// <param name="position">The base position.</param>
public void TeleportPlayerToAround(Player player, Vector3 position)
{
player.Position = position + new Vector3(0, 0.5f, 0) + new Vector3((float)(_random.NextDouble() * 2 - 1), 0, (float)(_random.NextDouble() * 2 - 1));
}
/// <summary>
/// Registers a handler for a specific custom class config type.
/// </summary>
/// <typeparam name="T">The config type.</typeparam>
/// <param name="handler">The handler instance.</param>
/// <param name="spawnState">Optional custom spawn state</param>
private void RegisterHandler<T>(ICustomClassHandler handler, [CanBeNull] SpawnState spawnState = null) where T : CustomClassConfig
{
T config;
try
{
config = Activator.CreateInstance<T>();
}
catch (Exception ex)
{
Logger.Error($"Failed to create config instance for {typeof(T).Name}: {ex.Message}");
return;
}
lock (_lock)
{
_spawnStates[typeof(T)] = spawnState ?? new SpawnState();
_handlers[typeof(T)] = handler;
Configs[typeof(T)] = config;
}
}
public T GetConfig<T>() where T : CustomClassConfig
{
return (T)Configs[typeof(T)];
}
/// <summary>
/// Resets all spawn states for a new round.
/// </summary>
public void ResetSpawnStates()
{
lock (_lock)
{
foreach (var key in _spawnStates.Keys.ToList())
_spawnStates[key].Reset();
}
}
/// <summary>
/// Attempts to handle a player spawn for a given custom class config.
/// </summary>
/// <param name="player">The player to handle.</param>
/// <param name="config">The config instance.</param>
/// <param name="configType">The config type.</param>
/// <param name="preSpawn"></param>
/// <returns>True if the spawn was handled; otherwise, false.</returns>
public bool TryHandleSpawn(Player player, CustomClassConfig config, Type configType, Action preSpawn)
{
if (player.Role != config.RequiredRole) return false;
if (Player.ReadyList.Count() <= config.MinPlayers) return false;
lock (_lock)
{
if (!_spawnStates.TryGetValue(configType, out var state))
return false;
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}");
return ForceSpawn(player, config, configType, preSpawn);
}
}
public bool ForceSpawn(Player player, CustomClassConfig config, Type configType, Action preSpawn)
{
if (!_handlers.TryGetValue(configType, out var handler))
return false;
preSpawn?.Invoke();
Timing.CallDelayed(0.5f, () => { handler.HandleSpawn(player, config, _random); });
return true;
}
}
public class NegromancerShadowHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player,config,random);
player.MaxHealth = 1000;
player.MaxHumeShield = 0;
player.HumeShield = 0;
player.EnableEffect<MovementBoost>(10, float.PositiveInfinity);
}
}
/// <summary>
/// Interface for custom class spawn handlers.
/// </summary>
public interface ICustomClassHandler
{
/// <summary>
/// Handles the logic for spawning a player as a custom class.
/// </summary>
/// <param name="player">The player to spawn.</param>
/// <param name="config">The configuration for the custom class.</param>
/// <param name="random">A random number generator.</param>
void HandleSpawn(Player player, CustomClassConfig config, Random random);
}
public abstract class CustomClassHandler: ICustomClassHandler
{
public virtual void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
var info = config.FullCustomInfo;
if (!Player.ValidateCustomInfo(info, out var reason))
{
Logger.Error($"[{GetType().Name}] Invalid custom info: {reason}");
return;
}
player.CustomInfo = info;
player.InfoArea |= PlayerInfoArea.CustomInfo;
SendSpawnMessage(player, config);
}
protected virtual void HandleEscape(Player player, CustomClassConfig config)
{
//Intentionally left blank
}
public virtual void SendSpawnMessage(Player player, CustomClassConfig config)
{
if (config.Name.IsEmpty()) return;
player.SendBroadcast($"You are a {config.FullCustomInfo}!", CustomClasses.BroadcastDuration);
}
}
public enum JanitorSpawn
{
Lcz173,
Lcz914,
LczGr18,
Lcz330
}
/// <summary>
/// Handler for the Janitor custom class.
/// </summary>
public class JanitorHandler(CustomClassManager manager) : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
var spawnLocation = (JanitorSpawn)random.Next(0, 4);
switch (spawnLocation)
{
case JanitorSpawn.Lcz914:
var scp914 = Map.Rooms.FirstOrDefault(r => r.Name == RoomName.Lcz914);
if (scp914 == null)
{
Logger.Error("LCZ 914 room not found for Janitor spawn.");
return;
}
manager.TeleportPlayerToAround(player, scp914.Position);
break;
case JanitorSpawn.Lcz173:
var lcz173Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.Lcz173Connector);
if (lcz173Door == null)
{
Logger.Error("LCZ 173 connector door not found for Janitor spawn.");
return;
}
manager.TeleportPlayerToAround(player, lcz173Door.Position);
break;
case JanitorSpawn.LczGr18:
var lczGr18Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.LczGr18Inner);
if (lczGr18Door == null)
{
Logger.Error("LCZ Gr18 door not found for Janitor spawn.");
return;
}
manager.TeleportPlayerToAround(player, lczGr18Door.Position);
break;
case JanitorSpawn.Lcz330:
var lcz330Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.Lcz330);
if (lcz330Door == null)
{
Logger.Error("LCZ 330 door not found for Janitor spawn.");
return;
}
manager.TeleportPlayerToAround(player, lcz330Door.Position);
break;
default:
throw new ArgumentOutOfRangeException();
}
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>!", CustomClasses.BroadcastDuration);
}
}
/// <summary>
/// Handler for the Research Subject custom class.
/// </summary>
public class ResearchSubjectHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
var scientist = Player.ReadyList.FirstOrDefault(p => p.Role == RoleTypeId.Scientist);
if (scientist == null)
{
Logger.Error("No Scientist found for Research Subject spawn.");
return;
}
player.Position = 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>!", CustomClasses.BroadcastDuration);
}
}
/// <summary>
/// Handler for the Head Guard custom class.
/// </summary>
public class HeadGuardHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
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 is FirearmPickup firearm)
{
if (firearm.Base.Template.TryGetModule(out MagazineModule magazine))
{
magazine.ServerSetInstanceAmmo(firearm.Serial, magazine.AmmoMax);
}
else
{
Logger.Error("Failed to get magazine module for Head Guard firearm.");
}
}
else
{
Logger.Error("Failed to get firearm from pickup for Head Guard.");
}
player.AddItem(pickup!);
player.SetAmmo(ItemType.Ammo9x19, 120);
player.SendBroadcast("You're a <color=#00B7EB>Head Guard</color>!", CustomClasses.BroadcastDuration);
}
}
/// <summary>
/// Handler for the Medic custom class.
/// </summary>
public class MedicHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
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=#727472>Medic</color>!", CustomClasses.BroadcastDuration);
}
}
/// <summary>
/// Handler for ShadowStepper custom class.
/// </summary>
public class ShadowStepperHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
ApplyEffects(player);
player.SendBroadcast("You're a <color=#000000>ShadowStepper</color>!", CustomClasses.BroadcastDuration);
}
protected override void HandleEscape(Player player, CustomClassConfig config)
{
base.HandleEscape(player, config);
ApplyEffects(player);
}
private static void ApplyEffects(Player player)
{
player.ReferenceHub.playerEffectsController.ChangeState<SilentWalk>(100,float.MaxValue);
player.ReferenceHub.playerEffectsController.ChangeState<Slowness>(10,float.MaxValue);
}
}
/// <summary>
/// Base handler for simple item-giving custom classes.
/// </summary>
public abstract class SimpleAddItemHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player,config,random);
foreach (var spawnItem in config.Items)
{
player.AddItem(spawnItem, ItemAddReason.StartingItem);
Logger.Debug($"Gave player {player.Nickname} spawn item {spawnItem}");
}
}
}
/// <summary>
/// Handler for the Gambler custom class.
/// </summary>
public class GamblerHandler : SimpleAddItemHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player, config, random);
player.SendBroadcast("You're a <color=#FF9966>Gambler</color>!", CustomClasses.BroadcastDuration);
}
}
/// <summary>
/// Handler for the Demolitionist custom class.
/// </summary>
public class DemolitionistHandler : SimpleAddItemHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player, config, random);
player.SendBroadcast("You're a <color=#FF9966>NTF Demolitionist</color>!", CustomClasses.BroadcastDuration);
}
}
public class ScoutHandler : SimpleAddItemHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player, config, random);
const ItemType gun = ItemType.GunCrossvec;
var gunPickup = Pickup.Create(gun, Vector3.one);
if (gunPickup is FirearmPickup firearm)
{
if (firearm.Base.Template.TryGetModule(out MagazineModule magazine))
{
magazine.ServerSetInstanceAmmo(firearm.Serial, magazine.AmmoMax);
}
else
{
Logger.Error("Failed to get magazine module for Serpents Hand firearm.");
}
}
else
{
Logger.Error("Failed to get firearm from pickup for Serpents Hand.");
}
player.AddItem(gunPickup!);
player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(40,32);
Timing.RunCoroutine(DecreaseSpeedBoost());
player.SendBroadcast("You're a <color=#FF9966>Scout</color> for your Faction! The rest of your Faction will spawn shortly.", CustomClasses.BroadcastDuration);
return;
IEnumerator<float> DecreaseSpeedBoost()
{
const float baseIntensity = 40f;
const float baseDuration = 30f;
const float minimumIntensity = 10f;
const float deltaIntensity = baseIntensity - minimumIntensity;
const float intensityStep = deltaIntensity / baseDuration;
var duration = baseDuration;
while (duration-- > 0)
{
yield return Timing.WaitForSeconds(1f);
var intensity = (byte) (baseIntensity - intensityStep * (baseDuration - duration));
player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(intensity, 5);
}
player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(10,9999);
}
}
}
public class ExplosiveMasterHandler : SimpleAddItemHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player, config, random);
player.SendBroadcast("You're an <color=#FF0000>Explosive Master</color>!", CustomClasses.BroadcastDuration);
player.SendBroadcast("<color=red>IF YOU THROW THE GRENADE YOU WILL EXPLODE.</color>", CustomClasses.BroadcastDuration);
PlayerEvents.ThrowingProjectile += HandleGrenade;
PlayerEvents.Spawning += HandlePlayerSpawn;
PlayerEvents.UsingItem += HandleUsing;
PlayerEvents.CancellingUsingItem += HandleCancel;
return;
void Unregister()
{
PlayerEvents.ThrowingProjectile -= HandleGrenade;
PlayerEvents.Spawning -= HandlePlayerSpawn;
PlayerEvents.UsingItem -= HandleUsing;
PlayerEvents.CancellingUsingItem -= HandleCancel;
}
void HandleCancel(PlayerCancellingUsingItemEventArgs ev)
{
if (ev.Player != player) return;
if (ev.UsableItem.Type is not ItemType.GrenadeHE) return;
player.DisableEffect<Slowness>();
}
void HandleUsing(PlayerUsingItemEventArgs ev)
{
if (ev.Player != player) return;
if (ev.UsableItem.Type is ItemType.GrenadeHE)
{
player.EnableEffect<Slowness>(10, float.MaxValue);
}
}
void HandleGrenade(PlayerThrowingProjectileEventArgs ev)
{
if (ev.Player != player) return;
if (ev.ThrowableItem.Type is not ItemType.GrenadeHE) return;
TimedGrenadeProjectile.SpawnActive(ev.Player.Position, ItemType.GrenadeHE, ev.Player);
TimedGrenadeProjectile.SpawnActive(ev.Player.Position, ItemType.GrenadeHE, ev.Player);
PlayerEvents.ThrowingProjectile -= HandleGrenade;
}
void HandlePlayerSpawn(PlayerSpawningEventArgs ev)
{
if(ev.Player != player) return;
Unregister();
}
}
}
public class FlashMasterHandler : SimpleAddItemHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player, config, random);
player.SendBroadcast("You're a <color=#FFFFFF>Flash Master</color>!", CustomClasses.BroadcastDuration);
PlayerEvents.ThrowingProjectile += HandleGrenade;
PlayerEvents.Spawning += HandlePlayerSpawn;
return;
void Unregister()
{
PlayerEvents.ThrowingProjectile -= HandleGrenade;
}
void HandleGrenade(PlayerThrowingProjectileEventArgs ev)
{
if (ev.Player != player) return;
if (ev.ThrowableItem.Type is not ItemType.GrenadeFlash) return;
for (var i = 0; i < 3; i++)
TimedGrenadeProjectile.SpawnActive(ev.Player.Position, ItemType.GrenadeFlash, ev.Player);
PlayerEvents.ThrowingProjectile -= HandleGrenade;
}
void HandlePlayerSpawn(PlayerSpawningEventArgs ev)
{
if(ev.Player != player) return;
Unregister();
}
}
}
/// <summary>
/// Represents the base configuration for a custom class.
/// </summary>
public abstract class CustomClassConfig
{
/// <summary>
/// Minimum number of players required for this class to spawn.
/// </summary>
public virtual int MinPlayers { get; set; } = 4;
/// <summary>
/// Chance per player for this class to spawn (0.0 - 1.0).
/// </summary>
public virtual double ChancePerPlayer { get; set; } = 0.7;
/// <summary>
/// Maximum number of spawns for this class per round.
/// </summary>
public virtual int MaxSpawns { get; set; } = 1;
/// <summary>
/// Items to give to the player on spawn.
/// </summary>
public virtual ItemType[] Items { get; set; } = [];
/// <summary>
/// The required role for this class to be considered.
/// </summary>
public virtual RoleTypeId RequiredRole { get; set; } = RoleTypeId.ClassD;
public virtual string Name { get; init; } = string.Empty;
public virtual string Color { get; init; } = "#FFFFFF";
public string FullCustomInfo => $"<color={Color}>{Name}</color>";
}
/// <summary>
/// Configuration for the Research Subject class.
/// </summary>
public sealed class ResearchSubjectConfig : CustomClassConfig;
/// <summary>
/// Configuration for the Janitor class.
/// </summary>
public sealed 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];
}
/// <summary>
/// Configuration for the Head Guard class.
/// </summary>
public sealed class HeadGuardConfig : CustomClassConfig
{
public override int MinPlayers { get; set; } = 9;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.FacilityGuard;
}
/// <summary>
/// Configuration for the Medic class.
/// </summary>
public sealed class MedicConfig : CustomClassConfig
{
public override int MinPlayers { get; set; } = 5;
public override double ChancePerPlayer { get; set; } = 0.3;
public override int MaxSpawns { get; set; } = 1;
public override ItemType[] Items { get; set; } = [ItemType.Medkit, ItemType.Adrenaline];
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scientist;
}
/// <summary>
/// Configuration for the Gambler class.
/// </summary>
public sealed class GamblerConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.3;
public override int MaxSpawns { get; set; } = 5;
public override ItemType[] Items { get; set; } = [ItemType.Coin, ItemType.Coin];
}
/// <summary>
/// Configuration for the Shadow Stepper class.
/// </summary>
public sealed class ShadowStepperConfig : CustomClassConfig;
public sealed class MtfDemolitionistConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.2;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.NtfPrivate;
public override ItemType[] Items { get; set; } = [ItemType.GrenadeHE, ItemType.GrenadeHE, ItemType.GrenadeHE];
}
public sealed class ScoutConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.25;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Spectator;
public override ItemType[] Items { get; set; } = [ItemType.GrenadeFlash, ItemType.KeycardMTFOperative, ItemType.ArmorLight, ItemType.Medkit ,ItemType.Ammo9x19, ItemType.Ammo9x19, ItemType.Ammo9x19, ItemType.Ammo9x19, ItemType.Ammo9x19, ItemType.Ammo9x19];
}
public sealed class ExplosiveMasterConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.2;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.ChaosMarauder;
public override ItemType[] Items { get; set; } = [ItemType.GrenadeHE];
}
public sealed class FlashMasterConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.0;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.ChaosMarauder;
public override ItemType[] Items { get; set; } = [ItemType.GrenadeFlash];
}
public sealed class NegromancerConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.0;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp049;
public override string Name { get; init; } = "Shadowmancer";
public override string Color { get; init; } = "#A0A0A0";
}
public sealed class NegromancerShadowConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.0;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp106;
public override string Name { get; init; } = "Shadow";
public override string Color { get; init; } = "#A0A0A0";
}
public sealed class BloodFueledConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 1.0;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp939;
public override string Name { get; init; } = "Blood Fueled";
public override string Color { get; init; } = "#C50000";
}
/// <summary>
/// Tracks the spawn state for a custom class.
/// </summary>
public record SpawnState
{
public int Spawns;
public virtual void Reset()
{
Spawns = 0;
}
}
+55
View File
@@ -0,0 +1,55 @@
<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="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable">
<HintPath>..\dependencies\System.Collections.Immutable.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>
+53
View File
@@ -0,0 +1,53 @@
using CustomPlayerEffects;
using LabApi.Features.Wrappers;
using Mirror;
using UnityEngine;
using UnityEngine.SceneManagement;
using Logger = LabApi.Features.Console.Logger;
namespace CustomClasses;
public abstract class CustomPlayerEffect : StatusEffectBase
{
private static bool _isLoaded;
public Player Owner { get; private set; } = null!;
protected override void Start()
{
Owner = Player.Get(Hub);
base.Start();
}
public override string ToString() => $"{GetType().Name}: Owner ({Owner}) - Intensity ({Intensity}) - Duration {Duration}";
internal static void Initialize()
{
SceneManager.sceneLoaded += (_, _) =>
{
if (_isLoaded)
return;
_isLoaded = true;
Type[] toLoad =
[
typeof(DisableStaminaRegenEffect),
typeof(BloodFueledStaminaEffect)
];
var playerEffects = NetworkManager.singleton.playerPrefab.GetComponent<ReferenceHub>().playerEffectsController.effectsGameObject.transform;
foreach (var type in toLoad)
{
if (!typeof(StatusEffectBase).IsAssignableFrom(type))
{
Logger.Error($"[CustomPlayerEffect.Initialize] {type.FullName} is not a valid StatusEffectBase and thus could not be registered!");
return;
}
// register effect into prefab
new GameObject(type.Name, type).transform.parent = playerEffects;
}
};
}
}
+152
View File
@@ -0,0 +1,152 @@
using CustomPlayerEffects;
using LabApi.Events.Arguments.Scp049Events;
using LabApi.Events.Handlers;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp049;
using PlayerRoles.PlayableScps.Scp106;
namespace CustomClasses;
public class NegromancerHandler : CustomClassHandler
{
public override void SendSpawnMessage(Player player, CustomClassConfig config)
{
player.SendBroadcast("You are the <color=#6e2e99>Negromancer</color>! Revived players become your <color=#3c1361>Shadow</color>.", CustomClasses.BroadcastDuration);
}
}
public class NegromancerManager
{
private readonly CustomClasses _plugin;
public static bool IsNegromancer(Player player) => player.CustomInfo.Contains("Shadowmancer");
public static bool IsShadow(Player player) => player.CustomInfo.Contains("Shadow") && !IsNegromancer(player);
public NegromancerManager(CustomClasses plugin)
{
_plugin = plugin;
Scp049Events.ResurrectedBody += OnScp049ResurrectedBody;
Timing.RunCoroutine(HealNearbyShadows());
Scp106Events.TeleportingPlayer += ev =>
{
if (!IsShadow(ev.Player)) return;
ev.IsAllowed = false;
ev.Target.EnableEffect<CardiacArrest>(1, float.PositiveInfinity);
ev.Target.Damage(40f, ev.Player);
ev.Player.SendHitMarker();
};
Scp106Events.UsingHunterAtlas += ev =>
{
if (!IsShadow(ev.Player)) return;
ev.IsAllowed = false;
var position = ev.DestinationPosition;
var room = Room.GetRoomAtPosition(position);
if (room?.LightController == null)
{
return;
}
var stat = ev.Player.GetStatModule<VigorStat>();
stat.CurValue = 0;
room.LightController.FlickerLights(3);
};
Scp106Events.ChangingSubmersionStatus += ev =>
{
if (!IsShadow(ev.Player)) return;
ev.IsAllowed = false;
var stat = ev.Player.GetStatModule<VigorStat>();
ev.Player.DisableEffect<MovementBoost>();
var scaled = stat.CurValue * 40f;
var scaledByte = (byte)scaled;
if(scaledByte < 15) scaledByte = 15;
Logger.Debug($"Scaled {stat.CurValue} to {scaledByte}");
ev.Player.EnableEffect<MovementBoost>(scaledByte, 20);
stat.CurValue = 0;
Timing.CallDelayed(10, () =>
{
ev.Player.DisableEffect<MovementBoost>();
ev.Player.EnableEffect<MovementBoost>(10, float.PositiveInfinity);
});
};
Scp106Events.ChangingVigor += ev =>
{
if (!IsShadow(ev.Player)) return;
var delta = ev.Value - ev.OldValue;
delta *= 0.5f;
ev.Value = ev.OldValue + delta;
};
}
private static IEnumerator<float> HealNearbyShadows()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
try
{
Player.ReadyList.Where(IsNegromancer).Where(x =>
{
var scp = x.RoleBase as Scp049Role;
if (scp == null)
{
Logger.Error("Negromancer has no Scp049Role");
return false;
}
scp.SubroutineModule.TryGetSubroutine(out Scp049CallAbility ability);
if (ability) return ability.IsMarkerShown;
Logger.Error("Negromancer has no Scp049CallAbility");
return false;
}).ToList().ForEach(player =>
{
Player.ReadyList.Where(IsShadow).Where(x =>
{
var distance = (x.Position - player.Position).SqrMagnitudeIgnoreY();
return distance < 64;
}
).ToList().ForEach(x => x.Heal(10));
});
}
catch (Exception e)
{
// ignored
}
}
// ReSharper disable once IteratorNeverReturns
}
private void OnScp049ResurrectedBody(Scp049ResurrectedBodyEventArgs ev)
{
var classManager = _plugin.ClassManager;
if (classManager == null ||
!IsNegromancer(ev.Player)) return;
ev.Target.SetRole(RoleTypeId.Scp106, RoleChangeReason.Respawn, RoleSpawnFlags.None);
classManager.ForceSpawn(ev.Target, classManager.GetConfig<NegromancerShadowConfig>(), typeof(NegromancerShadowConfig), null);
}
}
+17
View File
@@ -0,0 +1,17 @@
using LabApi.Features.Wrappers;
namespace CustomClasses;
public static class PlayerExtensions
{
public static string GetExtendedClass(this Player player)
{
foreach (var classManagerConfig in CustomClasses.Instance.ClassManager.Configs.Where(classManagerConfig => player.CustomInfo == classManagerConfig.Value.FullCustomInfo &&
!classManagerConfig.Value.Name.IsEmpty()))
{
return classManagerConfig.Value.Name;
}
return player.Role.ToString();
}
}
+324
View File
@@ -0,0 +1,324 @@
using AdminToys;
using CommandSystem;
using CustomPlayerEffects;
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.Hints;
using HintServiceMeow.Core.Utilities;
using Interactables.Interobjects.DoorUtils;
using InventorySystem.Items.Firearms.Modules;
using LabApi.Events.Handlers;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles;
using UnityEngine;
using LightSourceToy = LabApi.Features.Wrappers.LightSourceToy;
using Logger = LabApi.Features.Console.Logger;
using PrimitiveObjectToy = LabApi.Features.Wrappers.PrimitiveObjectToy;
using Random = System.Random;
namespace CustomClasses;
public class SerpentsHandManager
{
public static bool IsSerpentsHand(Player player)
{
try
{
return player.CustomInfo.Contains("SerpentsHand");
}
catch
{
return false;
}
}
private readonly CustomClasses _customClasses;
public SerpentsHandManager(CustomClasses customClasses)
{
_customClasses = customClasses;
PlayerEvents.Escaping += ev =>
{
if (!IsSerpentsHand(ev.Player)) return;
var state = (SerpentsHandState) _customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig));
var hadItem = false;
foreach (var playerItem in ev.Player.Items)
{
switch (playerItem.Type)
{
case ItemType.SCP018 or ItemType.SCP207 or ItemType.SCP244a or ItemType.SCP244b
or ItemType.SCP268 or ItemType.SCP330 or ItemType.SCP500 or ItemType.SCP1344 or ItemType.SCP1576
or ItemType.SCP1853 or ItemType.SCP2176 or ItemType.AntiSCP207:
state.Points += 1;
hadItem = true;
break;
case ItemType.GunSCP127:
state.Points += 2;
hadItem = true;
break;
}
}
if (Warhead.IsDetonationInProgress || Warhead.IsDetonated)
{
hadItem = true;
state.Points += 1;
}
if (!hadItem) return;
ev.Player.SendBroadcast("You brought back the SCP items...", 5);
ev.Player.SetRole(RoleTypeId.Spectator);
Logger.Info($"New SH Points: {state.Points}");
};
ServerEvents.RoundEnding += ev =>
{
var factions =
Player.ReadyList.Select(x =>
{
return x.Team switch
{
Team.ChaosInsurgency or Team.ClassD => Faction.FoundationEnemy,
Team.FoundationForces or Team.Scientists => Faction.FoundationStaff,
Team.Flamingos => Faction.Flamingos,
Team.SCPs => Faction.SCP,
Team.OtherAlive when IsSerpentsHand(x) => (Faction)35,
_ => Faction.Unclassified
};
}).Where(x=>x!=Faction.Unclassified).GroupBy(x=>x).Select(x=>x.Key).ToArray();
if (factions.Length > 1)
{
ev.IsAllowed = false;
return;
}
var state = (SerpentsHandState) _customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig));
if (state.Points >= 10)
{
ev.LeadingTeam = RoundSummary.LeadingTeam.Flamingos;
}
};
}
public static void PreSpawn(Player player)
{
player.SetRole(RoleTypeId.Tutorial, RoleChangeReason.RespawnMiniwave, RoleSpawnFlags.None);
}
public IEnumerator<float> UpdateSerpentsHandHint()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
if (_customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) continue;
foreach (var player in Player.ReadyList)
{
if (!_customClasses.Hints.ContainsKey(player))
{
_customClasses.Hints[player] = new Hint
{
Text = "", Alignment = HintAlignment.Center, YCoordinate = 900, Hide = true
};
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(_customClasses.Hints[player]);
}
_customClasses.Hints[player].Text =
$"Serpents Hand Chance: {state.ExtraChance + SerpentsHandConfig.BaseChance:0.00}%";
_customClasses.Hints[player].Hide = state.HasSpawned || player.Role != RoleTypeId.Spectator;
}
}
// ReSharper disable once IteratorNeverReturns
}
public static void SpawnSerpentWave()
{
var state = (SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState(typeof(SerpentsHandConfig));
var serpentsHandConfig = CustomClasses.Instance.ClassManager.GetConfig<SerpentsHandConfig>();
state.SetSpawned();
state.SetWillSpawn();
var possibleLocations = (Vector3[])serpentsHandConfig.SpawnLocations.Clone();
possibleLocations.ShuffleListSecure();
var spawnLocation = possibleLocations[0];
foreach (var possibleLocation in possibleLocations)
{
if (Player.ReadyList.Any(p => (p.Position - possibleLocation).SqrMagnitudeIgnoreY() < 400))
{
continue;
}
spawnLocation = possibleLocation;
break;
}
state.SpawnLocation = spawnLocation;
var light = LightSourceToy.Create(spawnLocation + new Vector3(0, 2, 0), Quaternion.identity);
light.Color = Color.blue;
light.Intensity = 3;
light.Range = 40;
var spaceTimeHole = PrimitiveObjectToy.Create(spawnLocation + new Vector3(0, 2, 0), Quaternion.identity);
spaceTimeHole.Color = new Color(44.7f, 73.7f, 83.1f, 0.5f);
spaceTimeHole.Flags = PrimitiveFlags.Visible;
spaceTimeHole.Scale *= 2;
Timing.CallDelayed(20, () =>
{
spaceTimeHole.Destroy();
light.Destroy();
});
}
}
public sealed record SerpentsHandState: SpawnState
{
public bool HasSpawned => _hasSpawned || Warhead.IsDetonated;
public float ExtraChance;
public int Points;
public bool WillSpawn => _willSpawn && !Warhead.IsDetonated;
private bool _hasSpawned;
private bool _willSpawn;
public Vector3? SpawnLocation;
public override void Reset()
{
base.Reset();
_hasSpawned = false;
ExtraChance = 0f;
Points = 0;
_willSpawn = false;
SpawnLocation = null;
}
public void SetSpawned()
{
_hasSpawned = true;
}
public void SetWillSpawn()
{
_willSpawn = true;
}
public void SetWillNotSpawn()
{
_willSpawn = false;
}
}
public sealed class SerpentsHandConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 1.0;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Tutorial;
public override ItemType[] Items { get; set; } = [ItemType.Painkillers, ItemType.Medkit, ItemType.ArmorCombat];
public const float BaseChance = 90f;
public readonly Vector3[] SpawnLocations = [new(0.22f, 300.96f, -0.31f), new(123.921f, 288.792f, 20.929f)];
public override string Color { get; init; } = "#32CD32";
public override string Name { get; init; } = "Serpents Hand";
}
public class SerpentsHandHandler : SimpleAddItemHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
base.HandleSpawn(player, config, random);
var spawnLocation = ((SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState(
typeof(SerpentsHandConfig))).SpawnLocation;
if (spawnLocation !=
null)
CustomClasses.Instance.ClassManager.TeleportPlayerToAround(player, (Vector3)spawnLocation);
ItemType[] guns = [ItemType.GunAK, ItemType.GunE11SR, ItemType.GunCrossvec];
var gun = guns[random.Next(0, guns.Length-1)];
var gunPickup = Pickup.Create(gun, Vector3.one);
if (gunPickup is FirearmPickup firearm)
{
if (firearm.Base.Template.TryGetModule(out MagazineModule magazine))
{
magazine.ServerSetInstanceAmmo(firearm.Serial, magazine.AmmoMax);
player.SetAmmo(magazine.AmmoType, 120);
}
else
{
Logger.Error("Failed to get magazine module for Serpents Hand firearm.");
}
}
else
{
Logger.Error("Failed to get firearm from pickup for Serpents Hand.");
}
player.AddItem(gunPickup!);
KeycardItem.CreateCustomKeycardTaskForce(
player,
"Serpent's Hand Keycard",
$"SH. {player.Nickname}",
new KeycardLevels(3, 3, 2),
Color.black,
new Color(0.271f, 0.271f, 0.271f),
"SH",
3
);
player.EnableEffect<MovementBoost>(20, 30);
player.EnableEffect<SpawnProtected>(1, 20f);
}
public override void SendSpawnMessage(Player player, CustomClassConfig config)
{
player.SendBroadcast("You're a <color=#2E8B57>Serpent's Hand</color> member!", CustomClasses.BroadcastDuration);
}
}
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class SpawnSerpentsCommand : ICommand
{
public string Command => "spsh";
public string[] Aliases => [];
public string Description => "Makes sure serpents hand spawns";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
var executor = Player.Get(sender);
if (executor == null)
{
response = "You must be a player to use this command!";
return false;
}
if (!executor.HasPermissions("customclasses.setcustomclass"))
{
response = "You do not have permission to use this command!";
return false;
}
SerpentsHandManager.SpawnSerpentWave();
response = "success";
return true;
}
}
+76
View File
@@ -0,0 +1,76 @@
using CommandSystem;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
namespace CustomClasses;
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class SetCClassCommand : ICommand
{
public string Command => "setcclass";
public string[] Aliases => ["scc"];
public string Description => "Forces a player to become a specific custom class";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
var executor = Player.Get(sender);
if (executor == null)
{
response = "You must be a player to use this command!";
return false;
}
if (!executor.HasPermissions("customclasses.setcustomclass"))
{
response = "You do not have permission to use this command!";
return false;
}
var args = arguments.Array!;
if (arguments.Count < 2)
{
response = "Usage: setcclass <playerName> <className>";
return false;
}
var className = args[arguments.Offset + arguments.Count - 1].ToLower();
var playerName = string.Join(" ", args.Skip(arguments.Offset).Take(arguments.Count - 1));
var player = Player.ReadyList.FirstOrDefault(x => x.Nickname == playerName || x.UserId == playerName || x.NetworkId.ToString() == playerName);
if (player == null)
{
response = $"Player {playerName} not found";
return false;
}
var manager = CustomClasses.Instance.ClassManager;
var success = className switch
{
"janitor" => manager.ForceSpawn(player, manager.GetConfig<JanitorConfig>(), typeof(JanitorConfig), null),
"subject" or "researchsubject" => manager.ForceSpawn(player, manager.GetConfig<ResearchSubjectConfig>(), typeof(ResearchSubjectConfig), null),
"headguard" => manager.ForceSpawn(player, manager.GetConfig<HeadGuardConfig>(), typeof(HeadGuardConfig), null),
"medic" => manager.ForceSpawn(player, manager.GetConfig<MedicConfig>(), typeof(MedicConfig), null),
"gambler" => manager.ForceSpawn(player, manager.GetConfig<GamblerConfig>(), typeof(GamblerConfig), null),
"shadowstepper" => manager.ForceSpawn(player, manager.GetConfig<ShadowStepperConfig>(), typeof(ShadowStepperConfig), null),
"demolitionist" => manager.ForceSpawn(player, manager.GetConfig<MtfDemolitionistConfig>(), typeof(MtfDemolitionistConfig), null),
"scout" => manager.ForceSpawn(player, manager.GetConfig<ScoutConfig>(), typeof(ScoutConfig), null),
"explosivemaster" => manager.ForceSpawn(player, manager.GetConfig<ExplosiveMasterConfig>(), typeof(ExplosiveMasterConfig), null),
"flashmaster" => manager.ForceSpawn(player, manager.GetConfig<FlashMasterConfig>(), typeof(FlashMasterConfig), null),
"serpentshand" => manager.ForceSpawn(player, manager.GetConfig<SerpentsHandConfig>(), typeof(SerpentsHandConfig),
() => SerpentsHandManager.PreSpawn(player)),
"negromancer" => manager.ForceSpawn(player, manager.GetConfig<NegromancerConfig>(), typeof(NegromancerConfig), null),
"bloodfueled" => manager.ForceSpawn(player, manager.GetConfig<BloodFueledConfig>(), typeof(BloodFueledConfig), null),
_ => false
};
if (!success)
{
response = $"Failed to set {playerName} to {className}. Make sure the player has the correct base role for the custom class.";
return false;
}
response = $"Successfully set {playerName} to {className}";
return true;
}
}
+97
View File
@@ -0,0 +1,97 @@
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);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
_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;
}
+40
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>
+1 -1
View File
@@ -3,7 +3,7 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"csharpier": { "csharpier": {
"version": "1.0.2", "version": "1.3.0",
"commands": [ "commands": [
"csharpier" "csharpier"
], ],
+47 -33
View File
@@ -3,46 +3,60 @@ using LabApi.Features;
using LabApi.Features.Console; using LabApi.Features.Console;
using LabApi.Loader; using LabApi.Loader;
namespace GamblingCoin namespace GamblingCoin;
public class Plugin : LabApi.Loader.Features.Plugins.Plugin
{ {
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 override void LoadConfigs()
{ {
public override string Name => "GamblingCoin"; base.LoadConfigs();
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; ConfigGameplay = this.LoadConfig<GamblingCoinGameplayConfig>("gameplay.yml");
public GamblingCoinMessages ConfigMessages; ConfigMessages = this.LoadConfig<GamblingCoinMessages>("messages.yml");
public GamblingCoinChancesConfig ConfigChances; ConfigChances = this.LoadConfig<GamblingCoinChancesConfig>("chances.yml");
}
public override void LoadConfigs() private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
{
base.LoadConfigs();
ConfigGameplay = this.LoadConfig< GamblingCoinGameplayConfig > ("gameplay.yml"); public override void Enable()
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..."); const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
Singleton = this; const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
_eventHandler = new GamblingCoinEventHandler();
PlayerEvents.FlippedCoin += _eventHandler.OnFlippedCoin; var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
} }
public override void Disable() // Then decode using standard base64
{ var decodedBytes = Convert.FromBase64String(standardized);
Logger.Debug("unloading..."); var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Singleton = null;
PlayerEvents.FlippedCoin -= _eventHandler.OnFlippedCoin; Logger.Info(decodedMessage);
}
Singleton = this;
_eventHandler = new GamblingCoinEventHandler();
PlayerEvents.FlippedCoin += _eventHandler.OnFlippedCoin;
}
public override void Disable()
{
Logger.Debug("unloading...");
Singleton = null;
PlayerEvents.FlippedCoin -= _eventHandler.OnFlippedCoin;
} }
} }
+34 -31
View File
@@ -1,37 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net48</TargetFramework> <TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize> <Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType> <DebugType>full</DebugType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize> <Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow> <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType> <DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference> </Reference>
<Reference Include="Mirror"> <Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath> <HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule"> <Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
</Reference> </Reference>
</ItemGroup> <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> <ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2" /> <PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
+188 -178
View File
@@ -1,182 +1,192 @@
using CustomPlayerEffects; using CustomPlayerEffects;
namespace GamblingCoin namespace GamblingCoin;
public class GamblingCoinChancesConfig
{ {
public int NukeChance { get; set; } = 10;
public class GamblingCoinChancesConfig public int SpawnWaveChance { get; set; } = 150;
{ public int CommonItemChance { get; set; } = 600;
public int NukeChance { get; set; } = 20; public int UncommonItemChance { get; set; } = 400;
public int SpawnWaveChance { get; set; } = 150; public int RareItemChance { get; set; } = 250;
public int CommonItemChance { get; set; } = 600; public int EpicItemChance { get; set; } = 100;
public int UncommonItemChance { get; set; } = 400; public int LegendaryItemChance { get; set; } = 30;
public int RareItemChance { get; set; } = 250; public int RandomTeleportChance { get; set; } = 200;
public int EpicItemChance { get; set; } = 100; public int StealItemChance { get; set; } = 100;
public int LegendaryItemChance { get; set; } = 30; public int ExplosionChance { get; set; } = 40;
public int RandomTeleportChance { get; set; } = 200; public int AntiMicroChance { get; set; } = 10;
public int StealItemChance { get; set; } = 100; public int GrenadeChance { get; set; } = 50;
public int ExplosionChance { get; set; } = 15; public int PocketDimensionChance { get; set; } = 30;
public int AntiMicroChance { get; set; } = 10; public int SwitchInventoryChance { get; set; } = 150;
public int GrenadeChance { get; set; } = 50; public int PositiveEffectChance { get; set; } = 300;
public int PocketDimensionChance { get; set; } = 75; public int NegativeEffectChance { get; set; } = 350;
public int SwitchInventoryChance { get; set; } = 150; public int AdvancedPositiveEffectChance { get; set; } = 150;
public int PositiveEffectChance { get; set; } = 300; public int AdvancedNegativeEffectChance { get; set; } = 250;
public int NegativeEffectChance { get; set; } = 250; public int RemoveCoinChance { get; set; } = 300;
public int AdvancedPositiveEffectChance { get; set; } = 150; public int SpawnZombieChance { get; set; } = 100;
public int AdvancedNegativeEffectChance { get; set; } = 250; }
}
public class GamblingCoinMessages
public class GamblingCoinMessages {
{ public string SpawnWaveMessage { get; set; } = "Did someone just enter the Site...?";
public string SpawnWaveMessage { get; set; } = "Did someone just enter the Site...?"; public string ItemSpawnMessage { get; set; } = "*plop*";
public string ItemSpawnMessage { get; set; } = "*plop*"; public string UncommonItemSpawnMessage { get; set; } = "*bump*";
public string UncommonItemSpawnMessage { get; set; } = "*bump*"; public string RareItemSpawnMessage { get; set; } = "*badunk*";
public string RareItemSpawnMessage { get; set; } = "*badunk*"; public string EpicItemSpawnMessage { get; set; } = "*katong*";
public string EpicItemSpawnMessage { get; set; } = "*katong*"; public string LegendaryItemSpawnMessage { get; set; } = "*sounds of monetary loss*";
public string LegendaryItemSpawnMessage { get; set; } = "*sounds of monetary loss*"; public string RandomTeleportMessage { get; set; } = "Where did you go?";
public string RandomTeleportMessage { get; set; } = "Where did you go?"; public string StealItemMessage { get; set; } = "Be careful, the coin is slippery!";
public string StealItemMessage { get; set; } = "Be careful, the coin is slippery!"; public string ExplosionMessage { get; set; } = "How did you even die '-'";
public string ExplosionMessage { get; set; } = "How did you even die '-'"; public string AntiMicroMessage { get; set; } = "Where did all the micros go...";
public string AntiMicroMessage { get; set; } = "Where did all the micros go..."; public string GrenadeMessage { get; set; } = "Watch out!";
public string GrenadeMessage { get; set; } = "Watch out!"; public string PocketDimensionMessage { get; set; } = "I hear he likes to see people suffer...";
public string PocketDimensionMessage { get; set; } = "I hear he likes to see people suffer..."; public string PositiveEffectMessage { get; set; } = "You feel slightly better";
public string PositiveEffectMessage { get; set; } = "You feel slightly better"; public string AdvancedPositiveEffectMessage { get; set; } = "You feel better";
public string AdvancedPositiveEffectMessage { get; set; } = "You feel better"; public string NegativeEffectMessage { get; set; } = "You feel worse";
public string NegativeEffectMessage { get; set; } = "You feel worse"; public string AdvancedNegativeEffectMessage { get; set; } = "You feel like you could die any second";
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 string SwitchInventoryMessage { get; set; } = "Whoops... looks like something happened to your items!"; public string SpawnZombieMessage { get; set; } = "You spawned as a Zombie!";
} }
public class GamblingCoinGameplayConfig public class GamblingCoinGameplayConfig
{ {
public float WarheadTimeIncrease { get; set; } = 20f; public float WarheadTimeIncrease { get; set; } = 20f;
public ushort BroadcastDuration { get; set; } = 3; public ushort BroadcastDuration { get; set; } = 3;
public float TeleportHeightOffset { get; set; } = 1f; public float TeleportHeightOffset { get; set; } = 1f;
public int MaxMicrosToRemove { get; set; } = 8; public int MaxMicrosToRemove { get; set; } = 8;
public ItemPoolConfig Items { get; set; } = new(); public ItemPoolConfig Items { get; set; } = new();
public EffectConfig Effects { get; set; } = new(); public EffectConfig Effects { get; set; } = new();
} }
public class ItemPoolConfig public class ItemPoolConfig
{ {
public ItemType[] CommonItems { get; set; } = { public ItemType[] CommonItems { get; set; } =
ItemType.KeycardJanitor, {
ItemType.KeycardScientist, ItemType.KeycardJanitor,
ItemType.Medkit, ItemType.KeycardScientist,
ItemType.Painkillers, ItemType.Medkit,
ItemType.Radio, ItemType.Painkillers,
ItemType.Flashlight ItemType.Radio,
}; ItemType.Flashlight
};
public ItemType[] UncommonItems { get; set; } = {
ItemType.KeycardZoneManager, public ItemType[] UncommonItems { get; set; } =
ItemType.KeycardGuard, {
ItemType.KeycardResearchCoordinator, ItemType.KeycardZoneManager,
ItemType.Adrenaline, ItemType.KeycardGuard,
ItemType.ArmorLight, ItemType.KeycardResearchCoordinator,
ItemType.GrenadeFlash ItemType.Adrenaline,
}; ItemType.ArmorLight,
ItemType.GrenadeFlash
public ItemType[] RareItems { get; set; } = { };
ItemType.KeycardMTFPrivate,
ItemType.KeycardContainmentEngineer, public ItemType[] RareItems { get; set; } =
ItemType.KeycardMTFOperative, {
ItemType.ArmorCombat, ItemType.KeycardMTFPrivate,
ItemType.ArmorHeavy, ItemType.KeycardContainmentEngineer,
ItemType.SCP330, ItemType.KeycardMTFOperative,
ItemType.Lantern, ItemType.ArmorCombat,
ItemType.GrenadeHE ItemType.ArmorHeavy,
}; ItemType.SCP330,
ItemType.Lantern,
public ItemType[] EpicItems { get; set; } = { ItemType.GrenadeHE
ItemType.KeycardFacilityManager, };
ItemType.KeycardChaosInsurgency,
ItemType.KeycardMTFCaptain, public ItemType[] EpicItems { get; set; } =
ItemType.SCP500 {
}; ItemType.KeycardFacilityManager,
ItemType.KeycardChaosInsurgency,
public ItemType[] LegendaryItems { get; set; } = { ItemType.KeycardMTFCaptain,
ItemType.KeycardO5, ItemType.SCP500
ItemType.MicroHID, };
ItemType.Jailbird,
ItemType.ParticleDisruptor, public ItemType[] LegendaryItems { get; set; } =
ItemType.GunCom45, {
ItemType.Coin ItemType.KeycardO5,
}; ItemType.MicroHID,
} ItemType.Jailbird,
ItemType.GunCom45,
public class EffectConfig ItemType.Coin
{ };
public AdvancedEffectSettings AdvancedPositive { get; set; } = new() }
{
Effects = new[] { nameof(Invisible), nameof(DamageReduction), nameof(MovementBoost) }, public class EffectConfig
Settings = new Dictionary<string, EffectSettings> {
{ public AdvancedEffectSettings AdvancedPositive { get; set; } = new()
{ nameof(Invisible), new EffectSettings(1, 5f, true) }, {
{ nameof(DamageReduction), new EffectSettings(100, 8f, false) }, Effects = new[] { nameof(Invisible), nameof(DamageReduction), nameof(MovementBoost) },
{ nameof(MovementBoost), new EffectSettings(40, 2f, false) } Settings = new Dictionary<string, EffectSettings>
} {
}; { nameof(Invisible), new EffectSettings(1, 5f, true) },
{ nameof(DamageReduction), new EffectSettings(100, 8f, false) },
public AdvancedEffectSettings Positive { get; set; } = new() { nameof(MovementBoost), new EffectSettings(40, 2f, false) }
{ }
Effects = new[] { nameof(Invigorated), nameof(RainbowTaste), nameof(Vitality), nameof(BodyshotReduction) }, };
Settings = new Dictionary<string, EffectSettings>
{ public AdvancedEffectSettings Positive { get; set; } = new()
{ nameof(Invigorated), new EffectSettings(1, 5f, true) }, {
{ nameof(RainbowTaste), new EffectSettings(2, 10f, true) }, Effects = new[] { nameof(Invigorated), nameof(RainbowTaste), nameof(Vitality), nameof(BodyshotReduction) },
{ nameof(Vitality), new EffectSettings(1, 10f, true) }, Settings = new Dictionary<string, EffectSettings>
{ nameof(BodyshotReduction), new EffectSettings(4, 5f, true) } {
} { nameof(Invigorated), new EffectSettings(1, 5f, true) },
}; { nameof(RainbowTaste), new EffectSettings(2, 10f, true) },
{ nameof(Vitality), new EffectSettings(1, 10f, true) },
public AdvancedEffectSettings Negative { get; set; } = new() { nameof(BodyshotReduction), new EffectSettings(4, 5f, true) }
{ }
Effects = new[] { nameof(Asphyxiated), nameof(AmnesiaVision), nameof(Bleeding), nameof(Blurred), };
nameof(Concussed), nameof(Deafened), nameof(Disabled) },
Settings = new Dictionary<string, EffectSettings> public AdvancedEffectSettings Negative { get; set; } = new()
{ {
{ nameof(Asphyxiated), new EffectSettings(1, 10f, true) }, Effects = new[]
{ nameof(AmnesiaVision), new EffectSettings(1, 5f, true) }, {
{ nameof(Bleeding), new EffectSettings(1, 5f, true) }, nameof(Asphyxiated), nameof(AmnesiaVision), nameof(Bleeding), nameof(Blurred),
{ nameof(Blurred), new EffectSettings(1, 5f, true) }, nameof(Concussed), nameof(Deafened), nameof(Disabled)
{ nameof(Concussed), new EffectSettings(1, 5f, true) }, },
{ nameof(Deafened), new EffectSettings(1, 5f, true) }, Settings = new Dictionary<string, EffectSettings>
{ nameof(Disabled), new EffectSettings(1, 5f, true) } {
} { nameof(Asphyxiated), new EffectSettings(1, 10f, true) },
}; { nameof(AmnesiaVision), new EffectSettings(1, 5f, true) },
{ nameof(Bleeding), new EffectSettings(1, 5f, true) },
public AdvancedEffectSettings AdvancedNegative { get; set; } = new() { nameof(Blurred), new EffectSettings(1, 5f, true) },
{ { nameof(Concussed), new EffectSettings(1, 5f, true) },
Effects = new[] { nameof(InsufficientLighting) }, { nameof(Deafened), new EffectSettings(1, 5f, true) },
Settings = new Dictionary<string, EffectSettings> { nameof(Disabled), new EffectSettings(1, 5f, true) }
{ }
{ nameof(InsufficientLighting), new EffectSettings(1, 20f, true) } };
}
}; public AdvancedEffectSettings AdvancedNegative { get; set; } = new()
} {
Effects = new[] { nameof(InsufficientLighting), nameof(AmnesiaVision), nameof(Burned) },
public class AdvancedEffectSettings Settings = new Dictionary<string, EffectSettings>
{ {
public String[] Effects { get; set; } { nameof(InsufficientLighting), new EffectSettings(1, 30f, true) },
public Dictionary<string, EffectSettings> Settings { get; set; } { nameof(AmnesiaVision), new EffectSettings(3, 30f, true) },
{ nameof(Burned), new EffectSettings(3, 60f, true) }
public AdvancedEffectSettings(){} }
} };
}
public class EffectSettings
{ public class AdvancedEffectSettings
public byte Intensity { get; set; } {
public float Duration { get; set; } public string[] Effects { get; set; }
public bool AddDuration { get; set; } public Dictionary<string, EffectSettings> Settings { get; set; }
}
public EffectSettings(byte intensity, float duration, bool addDuration)
{ public class EffectSettings
Intensity = intensity; {
Duration = duration; public EffectSettings(byte intensity, float duration, bool addDuration)
AddDuration = addDuration; {
} Intensity = intensity;
Duration = duration;
public EffectSettings(){} AddDuration = addDuration;
} }
public EffectSettings()
{
}
public byte Intensity { get; set; }
public float Duration { get; set; }
public bool AddDuration { get; set; }
} }
+220 -170
View File
@@ -1,196 +1,246 @@
using System.Numerics;
using LabApi.Events.Arguments.PlayerEvents; using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Features.Wrappers; using LabApi.Features.Wrappers;
using MapGeneration; using MapGeneration;
using MEC;
using Mirror; using Mirror;
using PlayerRoles; using PlayerRoles;
using Respawning.Waves; using Respawning;
using Utils; using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
using Random = UnityEngine.Random; using Random = UnityEngine.Random;
namespace GamblingCoin namespace GamblingCoin;
public class GamblingCoinEventHandler
{ {
public class GamblingCoinEventHandler private readonly WeightedRandomExecutor<PlayerFlippedCoinEventArgs> _executor;
public GamblingCoinEventHandler()
{ {
private readonly WeightedRandomExecutor<PlayerFlippedCoinEventArgs> _executor; var configMessages = Plugin.Singleton.ConfigMessages;
var configGameplay = Plugin.Singleton.ConfigGameplay;
var configChances = Plugin.Singleton.ConfigChances;
public GamblingCoinEventHandler() _executor = new WeightedRandomExecutor<PlayerFlippedCoinEventArgs>();
{
var configMessages = Plugin.Singleton.ConfigMessages;
var configGameplay = Plugin.Singleton.ConfigGameplay;
var configChances = Plugin.Singleton.ConfigChances;
_executor = new WeightedRandomExecutor<PlayerFlippedCoinEventArgs>(); _executor
.AddAction(x =>
_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<Item>();
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)]; Warhead.DetonationTime += configGameplay.WarheadTimeIncrease;
var effectSettings = settings.Settings[effectChosen]; Warhead.Start(suppressSubtitles: true);
player.ReferenceHub.playerEffectsController.ChangeState(effectChosen, effectSettings.Intensity, effectSettings.Duration, effectSettings.AddDuration); foreach (var player in Player.ReadyList)
} {
player.SendBroadcast($"{x.Player.Nickname} activated the Alpha Warhead through gambling! (coin)", 10);
void SpawnItemAtPlayer(Player player, ItemType item) }
}, configChances.NukeChance)
.AddAction(x =>
{ {
var pickup = Pickup.Create(item, player.Position + new UnityEngine.Vector3(0,1,0)); x.Player.SendBroadcast(configMessages.SpawnWaveMessage, configGameplay.BroadcastDuration);
if (GetPlayers().Any(player => player.Role == RoleTypeId.Spectator))
WaveManager.InitiateRespawn(WaveManager.Waves[Random.Range(0, 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);
var player = x.Player;
var items = configGameplay.Items.LegendaryItems;
var itemIndex = Random.Range(0, items.Length);
var item = items[itemIndex];
var pickup = Pickup.Create(item, player.Position + new Vector3(0, 1, 0));
if (pickup == null) return; if (pickup == null) return;
pickup.Spawn(); pickup.Spawn();
} }, configChances.LegendaryItemChance)
.AddAction(x =>
void SpawnRandomItemAtPlayer(Player player, ItemType[] items)
{ {
var itemIndex = Random.Range(0, items.Length); x.Player.SendBroadcast(configMessages.RandomTeleportMessage, configGameplay.BroadcastDuration);
SpawnItemAtPlayer(player, items[itemIndex]); var randomRoom = Map.GetRandomRoom();
} if (randomRoom == null) return;
Player[] GetPlayers() var isInOtherZone = randomRoom is { Zone: FacilityZone.Other };
var isDetonated = Warhead.IsDetonated;
var isDecontaminating = Decontamination.IsDecontaminating;
var isInDetonatedZone = isDetonated && randomRoom.Zone is FacilityZone.HeavyContainment
or FacilityZone.LightContainment or FacilityZone.Entrance;
var isInDecontaminatedZone = isDecontaminating && randomRoom.Zone is FacilityZone.LightContainment;
while (isInOtherZone || isInDetonatedZone || isInDecontaminatedZone)
{
randomRoom = Map.GetRandomRoom();
if (randomRoom == null) return;
isInOtherZone = randomRoom is { Zone: FacilityZone.Other };
isInDetonatedZone = isDetonated && randomRoom.Zone is FacilityZone.HeavyContainment
or FacilityZone.LightContainment or FacilityZone.Entrance;
isInDecontaminatedZone = isDecontaminating && randomRoom.Zone is FacilityZone.LightContainment;
}
var newPos = randomRoom.Position;
x.Player.Position = newPos + new Vector3(0, configGameplay.TeleportHeightOffset, 0);
}, configChances.RandomTeleportChance)
.AddAction(x =>
{ {
return Player.Dictionary.Values.ToArray(); 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();
TimedGrenadeProjectile.SpawnActive(x.Player.Position, ItemType.GrenadeHE, x.Player);
}, configChances.ExplosionChance)
.AddAction(x =>
{
x.Player.SendBroadcast(configMessages.AntiMicroMessage, configGameplay.BroadcastDuration);
GetPlayers().ForEach(p => { p.RemoveItem(ItemType.MicroHID, configGameplay.MaxMicrosToRemove); });
foreach (var microHidPickup in MicroHIDPickup.List)
{
microHidPickup.Destroy();
}
}, configChances.AntiMicroChance)
.AddAction(x =>
{
x.Player.SendBroadcast(configMessages.GrenadeMessage, configGameplay.BroadcastDuration);
TimedGrenadeProjectile.SpawnActive(x.Player.Position, ItemType.GrenadeHE, x.Player, 2);
}, configChances.GrenadeChance)
.AddAction(x =>
{
x.Player.SendBroadcast(configMessages.PocketDimensionMessage, configGameplay.BroadcastDuration);
PocketDimension.ForceInside(x.Player);
},
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().Where(player=>player.Team is not Team.Dead and not Team.SCPs).ToArray();
var randomPlayer = players[Random.Range(0, GetPlayers().Length)];
x.Player.SendBroadcast(configMessages.SwitchInventoryMessage, configGameplay.BroadcastDuration);
randomPlayer.SendBroadcast(configMessages.SwitchInventoryMessage, configGameplay.BroadcastDuration);
var randomPlayerItems = new List<Item>();
randomPlayer.Items.CopyTo(randomPlayerItems);
List<KeyValuePair<ItemType, ushort>> randomPlayerAmmo = new();
randomPlayer.Ammo.CopyTo(randomPlayerAmmo);
var items = x.Player.Items;
var ammoCount = x.Player.Ammo;
randomPlayer.ClearInventory();
foreach (var itemBase in items) randomPlayer.AddItem(itemBase.Type);
foreach (var ammo in ammoCount) randomPlayer.AddAmmo(ammo.Key, ammo.Value);
x.Player.ClearInventory();
foreach (var randomPlayerItem in randomPlayerItems) x.Player.AddItem(randomPlayerItem.Type);
foreach (var randomPlayerAmmoItem in randomPlayerAmmo) x.Player.AddAmmo(randomPlayerAmmoItem.Key, randomPlayerAmmoItem.Value);
}, configChances.SwitchInventoryChance)
.AddAction(x => { x.Player.CurrentItem?.DropItem().Destroy(); }, configChances.RemoveCoinChance)
.AddAction(x =>
{
var spectators = Player.List.Where(player => player.Role == RoleTypeId.Spectator).ToArray();
if(spectators.Length == 0) return;
var spectator = spectators[Random.Range(0, spectators.Length-1)];
x.Player.SendBroadcast(configMessages.SpawnZombieMessage, configGameplay.BroadcastDuration);
spectator.SetRole(RoleTypeId.Scp0492);
Timing.CallDelayed(0.5f, () =>
{
var spawnRoom = Map.Rooms.First(room => room.Name == RoomName.HczWarhead);
if (Warhead.IsDetonated)
{
spawnRoom = Map.Rooms.First(room => room.Name == RoomName.Outside);
}
spectator.Position = spawnRoom.Position + new Vector3(0, 1, 0);
});
}, configChances.SpawnZombieChance);
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);
} }
public void OnFlippedCoin(PlayerFlippedCoinEventArgs ev) void SpawnItemAtPlayer(Player player, ItemType item)
{ {
_executor.Execute(ev); var pickup = Pickup.Create(item, player.Position + new 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.ReadyList.ToArray();
} }
} }
public void OnFlippedCoin(PlayerFlippedCoinEventArgs ev)
{
_executor.Execute(ev);
}
} }
+20 -27
View File
@@ -2,30 +2,12 @@ namespace GamblingCoin;
public class WeightedRandomExecutor<TEvent> 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 List<WeightedAction> _actions = new();
private readonly Random _random = new(); private readonly Random _random = new();
private double _totalWeight; private double _totalWeight;
/// <summary> /// <summary>
/// Adds a function to be potentially executed, along with its weight. /// Adds a function to be potentially executed, along with its weight.
/// </summary> /// </summary>
/// <param name="action">The function to add. It must accept an argument of type TEvent.</param> /// <param name="action">The function to add. It must accept an argument of type TEvent.</param>
/// <param name="weight">The positive weight for this function. Higher weights mean higher probability.</param> /// <param name="weight">The positive weight for this function. Higher weights mean higher probability.</param>
@@ -39,26 +21,22 @@ public class WeightedRandomExecutor<TEvent>
} }
/// <summary> /// <summary>
/// Executes one of the added functions randomly based on their weights. /// Executes one of the added functions randomly based on their weights.
/// The chosen function will be called with the provided 'ev' argument. /// The chosen function will be called with the provided 'ev' argument.
/// </summary> /// </summary>
/// <param name="ev">The event argument to pass to the chosen function.</param> /// <param name="ev">The event argument to pass to the chosen function.</param>
/// <exception cref="InvalidOperationException">Thrown if no actions have been added.</exception> /// <exception cref="InvalidOperationException">Thrown if no actions have been added.</exception>
public void Execute(TEvent ev) public void Execute(TEvent ev)
{ {
if (_actions.Count == 0) if (_actions.Count == 0)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"No actions have been added to execute." "No actions have been added to execute."
); );
}
if (_totalWeight <= 0) // Should not happen if AddAction validates weight > 0 if (_totalWeight <= 0) // Should not happen if AddAction validates weight > 0
{
throw new InvalidOperationException( throw new InvalidOperationException(
"Total weight is zero or negative, cannot execute." "Total weight is zero or negative, cannot execute."
); );
}
var randomNumber = _random.NextDouble() * _totalWeight; var randomNumber = _random.NextDouble() * _totalWeight;
double cumulativeWeight = 0; double cumulativeWeight = 0;
@@ -74,9 +52,24 @@ public class WeightedRandomExecutor<TEvent>
// Fallback in case of floating point inaccuracies, // Fallback in case of floating point inaccuracies,
// or if somehow randomNumber was exactly _totalWeight (NextDouble is < 1.0) // or if somehow randomNumber was exactly _totalWeight (NextDouble is < 1.0)
// This should ideally pick the last item if all weights were summed up. // 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
{
public WeightedAction(Action<TEvent> action, double weight)
{ {
_actions.Last().Action(ev); 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; }
} }
} }
+107
View File
@@ -0,0 +1,107 @@
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.Console;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
namespace GrowingZombies;
public class GrowingZombies : Plugin
{
public readonly Dictionary<Player, int> ZombieCorpseCount = new();
public static GrowingZombies Instance { get; set; }
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);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
Scp0492Events.ConsumedCorpse += OnZombieEat;
ServerEvents.RoundEnded += OnRoundEnd;
PlayerEvents.Left += OnPlayerLeave;
Instance = this;
}
public override void Disable()
{
Scp0492Events.ConsumedCorpse -= OnZombieEat;
ServerEvents.RoundEnded -= OnRoundEnd;
PlayerEvents.Left -= OnPlayerLeave;
ZombieCorpseCount.Clear();
Instance = null;
}
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;
AteCorpse(ev.Player);
}
public void AteCorpse(Player player)
{
if (!ZombieCorpseCount.ContainsKey(player))
ZombieCorpseCount[player] = 0;
ZombieCorpseCount[player]++;
var corpsesEaten = ZombieCorpseCount[player];
player.MaxHealth = Math.Min(1000, player.MaxHealth + 50);
player.MaxHumeShield += 10;
var movementBoostIntensity = (byte)Math.Min(1 + corpsesEaten * 0.5f, 5f);
player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(movementBoostIntensity, 120);
// Add damage resistance after eating multiple corpses
if (corpsesEaten >= 3)
{
var damageReductionIntensity = (byte)Math.Min(corpsesEaten * 2, 100); // Half-Percent
player.ReferenceHub.playerEffectsController.ChangeState<DamageReduction>(damageReductionIntensity,
float.MaxValue);
}
// Add regeneration effect after eating multiple corpses
if (corpsesEaten < 5) return;
var regenIntensity = Math.Min(1 + corpsesEaten * 0.2f, 3f);
Scp330Bag.AddSimpleRegeneration(player.ReferenceHub, regenIntensity, 15f);
}
}
+46
View File
@@ -0,0 +1,46 @@
<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="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.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>
+56
View File
@@ -0,0 +1,56 @@
using CommandSystem;
using LabApi.Features.Wrappers;
using PlayerRoles;
using ICommand = CommandSystem.ICommand;
namespace GrowingZombies;
[CommandHandler(typeof(ClientCommandHandler))]
public class SacrificeCommand: ICommand
{
public string Command => "sacrifice";
public string[] Aliases => ["sac"];
public string Description => "Sacrifice yourself to give another zombie an extra corpse count";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
if (!Player.TryGet(sender, out var player))
{
response = "You must be a player to use this command!";
return false;
}
if (player.Role != RoleTypeId.Scp0492)
{
response = "You must be a zombie to use this command!";
return false;
}
var zombies = Player.List.Where(p => p.Role == RoleTypeId.Scp0492 && p != player).ToList();
if (!zombies.Any())
{
response = "There are no other zombies to receive your sacrifice!";
return false;
}
// Select random zombie to receive the bonus
var luckyZombie = zombies[UnityEngine.Random.Range(0, zombies.Count)];
// Add corpse count to the lucky zombie
GrowingZombies.Instance.AteCorpse(luckyZombie);
// Remove corpse count from the sacrificing player
GrowingZombies.Instance.ZombieCorpseCount[player] = 0;
// Kill the sacrificing player
player.Kill("Sacrificed themselves for their zombie brethren");
luckyZombie.SendHint($"You received the sacrifice of {player.Nickname}!", 5);
response = "You sacrificed yourself to give another zombie an extra corpse count!";
var scp049 = Player.List.FirstOrDefault(p => p.Role == RoleTypeId.Scp049);
scp049?.SendHint($"Your zombie {player.Nickname} sacrificed themselves to give {luckyZombie.Nickname} another corpse they ate!", 5);
return true;
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ public enum DoorType
public class DoorLockConfiguration public class DoorLockConfiguration
{ {
public Dictionary<DoorType, float> LockDurations { get; set; }= new() public Dictionary<DoorType, float> LockDurations { get; set; } = new()
{ {
{ DoorType.Normal, 5f }, { DoorType.Normal, 5f },
{ DoorType.Gate, 8f }, { DoorType.Gate, 8f },
+181 -212
View File
@@ -1,5 +1,4 @@
using System.Collections; using System.Collections;
using Interactables.Interobjects;
using Interactables.Interobjects.DoorButtons; using Interactables.Interobjects.DoorButtons;
using Interactables.Interobjects.DoorUtils; using Interactables.Interobjects.DoorUtils;
using LabApi.Events.Arguments.PlayerEvents; using LabApi.Events.Arguments.PlayerEvents;
@@ -10,224 +9,194 @@ using UnityEngine;
using KeycardItem = InventorySystem.Items.Keycards.KeycardItem; using KeycardItem = InventorySystem.Items.Keycards.KeycardItem;
using Logger = LabApi.Features.Console.Logger; using Logger = LabApi.Features.Console.Logger;
namespace KeycardButModern namespace KeycardButModern;
public class Plugin : LabApi.Loader.Features.Plugins.Plugin
{ {
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 override void LoadConfigs()
{ {
public override string Name => "KeycardButModern"; base.LoadConfigs();
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; DoorLockConfiguration = this.LoadConfig<DoorLockConfiguration>("door_locks.yml");
public static Plugin Singleton; }
public override void LoadConfigs() private void OnInteractingDoor(PlayerInteractingDoorEventArgs ev)
{
if (ev.CanOpen) return;
if (ev.Door.IsLocked) return;
foreach (var playerItem in ev.Player.Items)
{ {
base.LoadConfigs(); //is keycard?
if (playerItem.Type is > ItemType.KeycardO5 and < ItemType.KeycardCustomTaskForce) continue;
if (playerItem.Base is not KeycardItem keycardItem) continue;
DoorLockConfiguration = this.LoadConfig< DoorLockConfiguration > ("door_locks.yml"); if (!ev.Door.Base.CheckPermissions(keycardItem, out _)) continue;
} ev.Door.IsOpened = !ev.Door.IsOpened;
private void OnInteractingDoor(PlayerInteractingDoorEventArgs ev) return;
{
if (ev.CanOpen)
{
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 (!ev.Door.Base.CheckPermissions(keycardItem, out _)) continue;
ev.Door.IsOpened = !ev.Door.IsOpened;
return;
}
}
private void OnInteractingGenerator(PlayerInteractingGeneratorEventArgs ev)
{
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;
}
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
if (playerItem.Type > ItemType.KeycardO5) continue;
if (playerItem.Base is not KeycardItem keycardItem)
{
continue;
}
if (!ev.Generator.Base.CheckPermissions(keycardItem, out _)) continue;
ev.Generator.IsOpen = !ev.Generator.IsOpen;
ev.Generator.IsUnlocked = true;
return;
}
}
private void OnInteractingLocker(PlayerInteractingLockerEventArgs ev)
{
if (!ev.IsAllowed)
{
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;
}
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
if (playerItem.Type > ItemType.KeycardO5) continue;
if (playerItem.Base is not KeycardItem keycardItem)
{
continue;
}
if (!ev.Chamber.Base.CheckPermissions(keycardItem, out _)) continue;
ev.Chamber.IsOpen = !ev.Chamber.IsOpen;
return;
}
}
private void OnUnlockingWarhead(PlayerUnlockingWarheadButtonEventArgs ev)
{
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 (!AlphaWarheadActivationPanel.Instance.CheckPermissions(keycardItem, out _)) continue;
ev.IsAllowed = true;
return;
}
}
private static void OnBulletHole(PlayerPlacedBulletHoleEventArgs ev)
{
var direction = (ev.HitPosition - ev.RaycastStart).normalized;
var distance = Vector3.Distance(ev.RaycastStart, ev.HitPosition);
var hits = Physics.RaycastAll(ev.RaycastStart, direction, distance);
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)
{
continue;
}
var doorVariant = DoorVariant.AllDoors.AsEnumerable()!.First(x => x.Buttons.Any(c=>c.GetInstanceID() == doorButton.GetInstanceID()));
doorVariant.ServerInteract(ev.Player.ReferenceHub, doorButton.ColliderId);
StartDoorLockCoroutine(doorVariant);
return;
}
}
public override void Enable()
{
Logger.Debug("starting...");
Singleton = this;
PlayerEvents.InteractingDoor += OnInteractingDoor;
PlayerEvents.InteractingGenerator += OnInteractingGenerator;
PlayerEvents.InteractingLocker += OnInteractingLocker;
PlayerEvents.UnlockingWarheadButton += OnUnlockingWarhead;
PlayerEvents.PlacedBulletHole += OnBulletHole;
}
private static void StartDoorLockCoroutine(DoorVariant door)
{
door.StartCoroutine(LockDoorCoroutine(door));
}
private static IEnumerator LockDoorCoroutine(DoorVariant door)
{
var doorType = DoorLockConfiguration.GetDoorType(door);
var lockDuration = Singleton.DoorLockConfiguration.LockDurations[doorType];
door.ServerChangeLock(DoorLockReason.SpecialDoorFeature, true);
yield return new WaitForSeconds(lockDuration);
door.ServerChangeLock(DoorLockReason.SpecialDoorFeature, false);
}
public override void Disable()
{
PlayerEvents.InteractingDoor -= OnInteractingDoor;
PlayerEvents.InteractingGenerator -= OnInteractingGenerator;
PlayerEvents.InteractingLocker -= OnInteractingLocker;
PlayerEvents.UnlockingWarheadButton -= OnUnlockingWarhead;
PlayerEvents.PlacedBulletHole -= OnBulletHole;
Logger.Debug("unloading...");
Singleton = null;
} }
} }
private void OnInteractingGenerator(PlayerInteractingGeneratorEventArgs ev)
{
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;
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
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;
ev.Generator.IsUnlocked = true;
return;
}
}
private void OnInteractingLocker(PlayerInteractingLockerEventArgs ev)
{
if (!ev.IsAllowed) 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;
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
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;
return;
}
}
private void OnUnlockingWarhead(PlayerUnlockingWarheadButtonEventArgs ev)
{
if (ev.IsAllowed) return;
foreach (var playerItem in ev.Player.Items)
{
//is keycard?
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;
return;
}
}
private static void OnBulletHole(PlayerPlacedBulletHoleEventArgs ev)
{
var direction = (ev.HitPosition - ev.RaycastStart).normalized;
var distance = Vector3.Distance(ev.RaycastStart, ev.HitPosition);
var hits = Physics.RaycastAll(ev.RaycastStart, direction, distance);
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) continue;
ev.Player.SendHitMarker();
var doorVariant = DoorVariant.AllDoors.AsEnumerable()!.First(x =>
x.Buttons.Any(c => c.GetInstanceID() == doorButton.GetInstanceID()));
doorVariant.ServerInteract(ev.Player.ReferenceHub, doorButton.ColliderId);
StartDoorLockCoroutine(doorVariant);
return;
}
}
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
Singleton = this;
PlayerEvents.InteractingDoor += OnInteractingDoor;
PlayerEvents.InteractingGenerator += OnInteractingGenerator;
PlayerEvents.InteractingLocker += OnInteractingLocker;
PlayerEvents.UnlockingWarheadButton += OnUnlockingWarhead;
PlayerEvents.PlacedBulletHole += OnBulletHole;
}
private static void StartDoorLockCoroutine(DoorVariant door)
{
door.StartCoroutine(LockDoorCoroutine(door));
}
private static IEnumerator LockDoorCoroutine(DoorVariant door)
{
var doorType = DoorLockConfiguration.GetDoorType(door);
var lockDuration = Singleton.DoorLockConfiguration.LockDurations[doorType];
door.ServerChangeLock(DoorLockReason.SpecialDoorFeature, true);
yield return new WaitForSeconds(lockDuration);
door.ServerChangeLock(DoorLockReason.SpecialDoorFeature, false);
}
public override void Disable()
{
PlayerEvents.InteractingDoor -= OnInteractingDoor;
PlayerEvents.InteractingGenerator -= OnInteractingGenerator;
PlayerEvents.InteractingLocker -= OnInteractingLocker;
PlayerEvents.UnlockingWarheadButton -= OnUnlockingWarhead;
PlayerEvents.PlacedBulletHole -= OnBulletHole;
Logger.Debug("unloading...");
Singleton = null;
}
} }
+34 -34
View File
@@ -1,40 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net48</TargetFramework> <TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<Optimize>true</Optimize> <Optimize>true</Optimize>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugType>full</DebugType> <DebugType>full</DebugType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize> <Optimize>true</Optimize>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow> <CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<DebugType>none</DebugType> <DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference> </Reference>
<Reference Include="Mirror"> <Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.PhysicsModule"> <Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2" /> <PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
+179
View File
@@ -0,0 +1,179 @@
using CommandSystem.Commands.RemoteAdmin;
using GameCore;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
using MEC;
using Interactables.Interobjects.DoorUtils;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Features.Wrappers;
using MapGeneration;
using PlayerRoles;
using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
using Version = System.Version;
namespace LobbyGame
{
public class LobbyGame: Plugin
{
public override string Name => "LobbyGame";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Adds a lobby minigame";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public int RoundTimer { get; set; } = 20;
public static LobbyGame Singleton { get; private set; }
private bool _isStarted;
private Room _randomRoom;
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
ServerEvents.WaitingForPlayers += WaitingForPlayers;
PlayerEvents.Joined += PlayerJoined;
Singleton = this;
}
private static void PlayerJoined(PlayerJoinedEventArgs ev)
{
Timing.RunCoroutine(ContinuouslyTrySpawning());
return;
IEnumerator<float> ContinuouslyTrySpawning()
{
while (!RoundStart.RoundStarted)
{
GameObject.Find("StartRound").transform.localScale = Vector3.zero;
yield return Timing.WaitForSeconds(0.5f);
if(Singleton._randomRoom == null) continue;
if(!Singleton._isStarted) continue;
ev.Player.SetRole(RoleTypeId.ChaosRifleman, RoleChangeReason.None, RoleSpawnFlags.None);
ev.Player.Position = Singleton._randomRoom.Position + new Vector3(0, 1, 0);
break;
}
}
}
private static void WaitingForPlayers()
{
Timing.CallDelayed(15, () =>
{
if (Singleton._isStarted) return;
var randomRoom = Map.GetRandomRoom(FacilityZone.Entrance);
while (randomRoom is not { Zone: FacilityZone.Entrance })
{
randomRoom = Map.GetRandomRoom(FacilityZone.Entrance);
}
Logger.Debug($"Random entrance room: {randomRoom.Name}");
RoundStart.LobbyLock = true;
Singleton._randomRoom = randomRoom;
Singleton._isStarted = true;
foreach (var player in Player.ReadyList)
{
player.SetRole(RoleTypeId.ChaosRifleman, RoleChangeReason.None, RoleSpawnFlags.None);
try
{
player.Position = randomRoom.Position + new Vector3(0, 1, 0);
}
catch (Exception _)
{
player.SetRole(RoleTypeId.Spectator);
}
}
Timing.RunCoroutine(ContinuouslyUpdateRoundTimer());
GameObject.Find("StartRound").transform.localScale = Vector3.zero;
foreach (var door in Map.Doors)
{
door.IsOpened = false;
door.Lock(DoorLockReason.Lockdown2176, true);
}
return;
IEnumerator<float> ContinuouslyUpdateRoundTimer()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
foreach (var player in Player.ReadyList)
{
player.SendBroadcast(
$"<size=30><color=grey>Round starts in</color> <color=red>{Singleton.RoundTimer}</color> <color=grey>seconds</color></size>",
10, Broadcast.BroadcastFlags.Normal, true);
}
Logger.Debug($"Round starts in {Singleton.RoundTimer} seconds");
if (Player.ReadyList.Count() <= 1)
{
Singleton.RoundTimer = 20;
continue;
}
Singleton.RoundTimer--;
if (Singleton.RoundTimer >= -1) continue;
Singleton.RoundTimer = 20;
foreach (var player in Player.ReadyList)
{
player.ClearInventory();
player.SetRole(RoleTypeId.Spectator);
}
foreach (var door in Map.Doors)
{
door.Lock(DoorLockReason.Lockdown2176, false);
}
foreach (var player in Player.ReadyList)
{
player.SendBroadcast(
"",
1, Broadcast.BroadcastFlags.Normal, true);
}
CharacterClassManager.ForceRoundStart();
break;
}
}
});
}
public override void Disable()
{
ServerEvents.WaitingForPlayers -= WaitingForPlayers;
PlayerEvents.Joined -= PlayerJoined;
Singleton = null;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
<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>
<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>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+21 -22
View File
@@ -2,30 +2,29 @@
using LabApi.Features; using LabApi.Features;
using LabApi.Loader.Features.Plugins; using LabApi.Loader.Features.Plugins;
namespace LogEvents namespace LogEvents;
internal class LogPlugin : Plugin
{ {
internal class LogPlugin : Plugin public override string Name { get; } = "LogPlugin";
public override string Description { get; } = "Example Plugin that logs (almost) all events.";
public override string Author { get; } = "Northwood";
public override Version Version { get; } = new(1, 0, 0, 0);
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public MyCustomEventsHandler Events { get; } = new();
public override void Enable()
{ {
public override string Name { get; } = "LogPlugin"; CustomHandlersManager.RegisterEventsHandler(Events);
}
public override string Description { get; } = "Example Plugin that logs (almost) all events."; public override void Disable()
{
public override string Author { get; } = "Northwood"; CustomHandlersManager.UnregisterEventsHandler(Events);
public override Version Version { get; } = new Version(1, 0, 0, 0);
public override Version RequiredApiVersion { get; } = new Version(LabApiProperties.CompiledVersion);
public MyCustomEventsHandler Events { get; } = new();
public override void Enable()
{
CustomHandlersManager.RegisterEventsHandler(Events);
}
public override void Disable()
{
CustomHandlersManager.UnregisterEventsHandler(Events);
}
} }
} }
+2 -2
View File
@@ -723,7 +723,8 @@ internal class MyCustomEventsHandler : CustomEventsHandler
public override void OnPlayerStayingInHazard(PlayersStayingInHazardEventArgs ev) 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) public override void OnPlayerLeavingHazard(PlayerLeavingHazardEventArgs ev)
@@ -1397,5 +1398,4 @@ internal class MyCustomEventsHandler : CustomEventsHandler
// } // }
#endregion #endregion
} }
+1 -13
View File
@@ -23,21 +23,9 @@
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference> </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>
<ItemGroup> <ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2" /> <PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
+97
View File
@@ -0,0 +1,97 @@
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.Hints;
using HintServiceMeow.Core.Utilities;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
using LabApi.Features.Wrappers;
using MEC;
namespace ModInfo
{
public class ModInfo : Plugin
{
public override string Name => "ModInfo";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Shows some extra info for moderators";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private readonly Dictionary<Player, Hint> _spectatorHints = new();
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
Timing.RunCoroutine(GodmodeHintLoop());
Scp096Events.AddingTarget += ev =>
{
if (ev.Target.IsGodModeEnabled || ev.Target.IsNoclipEnabled) ev.IsAllowed = false;
};
Scp173Events.AddingObserver += ev =>
{
if (ev.Target.IsGodModeEnabled || ev.Target.IsNoclipEnabled) ev.IsAllowed = false;
};
}
public override void Disable()
{
}
private IEnumerator<float> GodmodeHintLoop()
{
while(true)
{
yield return Timing.WaitForSeconds(1);
UpdateHints();
}
// ReSharper disable once IteratorNeverReturns
}
private void UpdateHints()
{
foreach (var player in Player.ReadyList) UpdateHint(player);
}
private void UpdateHint(Player player)
{
var hint = _spectatorHints.TryGetValue(player, out var hintValue) ? hintValue : AddPlayerHint(player);
hint.Hide = !player.IsGodModeEnabled;
}
private Hint AddPlayerHint(Player player)
{
var hint = new Hint
{
Text = "<size=40><color=#50C878>GODMODE</color></size>",
Alignment = HintAlignment.Left,
YCoordinate = 800,
Hide = true
};
var playerDisplay = PlayerDisplay.Get(player);
playerDisplay.AddHint(hint);
_spectatorHints[player] = hint;
return hint;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
<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>
<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>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+5 -5
View File
@@ -9,10 +9,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2"/> <PackageReference Include="coverlet.collector" Version="6.0.4"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="NUnit" Version="4.3.2" /> <PackageReference Include="NUnit" Version="4.3.2"/>
<PackageReference Include="NUnit.Analyzers" Version="4.4.0"/> <PackageReference Include="NUnit.Analyzers" Version="4.8.1"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/> <PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
</ItemGroup> </ItemGroup>
@@ -21,7 +21,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\RangeBan\RangeBan.csproj" /> <ProjectReference Include="..\RangeBan\RangeBan.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
+33 -21
View File
@@ -1,4 +1,3 @@
using System.Runtime.Serialization;
using LabApi.Events.Arguments.PlayerEvents; using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers; using LabApi.Events.Handlers;
using LabApi.Features; using LabApi.Features;
@@ -7,12 +6,34 @@ using LabApi.Loader.Features.Plugins;
namespace RangeBan; namespace RangeBan;
public class RangeBan: Plugin<RangeBanConfig> 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);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable() public override void Enable()
{ {
Logger.Debug("Loading..."); const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.PreAuthenticating += OnAuth; PlayerEvents.PreAuthenticating += OnAuth;
} }
@@ -26,16 +47,11 @@ public class RangeBan: Plugin<RangeBanConfig>
{ {
Logger.Debug($"Ranges: {string.Join(" ; ", Config!.IpRanges)}"); Logger.Debug($"Ranges: {string.Join(" ; ", Config!.IpRanges)}");
if (!Config!.IpRanges.Any(configIpRange => IsInRange(configIpRange, ev.IpAddress))) return; 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}"); 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) public static bool IsInRange(string range, string ip)
{ {
@@ -46,29 +62,24 @@ public class RangeBan: Plugin<RangeBanConfig>
if (!range.Contains("/")) if (!range.Contains("/"))
{ {
//We only handle direct IPs and CIDR //We only handle direct IPs and CIDR
if (range.Split('.').Length != 4) if (range.Split('.').Length != 4) return false;
{
return false;
}
return ip == range; return ip == range;
}; }
;
var parts = range.Split('/'); var parts = range.Split('/');
if (parts.Length != 2 || !int.TryParse(parts[1], out var cidrBits)) if (parts.Length != 2 || !int.TryParse(parts[1], out var cidrBits))
return false; return false;
if (cidrBits > 32) if (cidrBits > 32) return false;
{
return false;
}
var networkAddress = IPToUInt32(parts[0]); var networkAddress = IPToUInt32(parts[0]);
var mask = uint.MaxValue << (32 - cidrBits); var mask = uint.MaxValue << (32 - cidrBits);
var ipAddress = IPToUInt32(ip); var ipAddress = IPToUInt32(ip);
return (ipAddress & mask) == (networkAddress & mask); return (ipAddress & mask) == (networkAddress & mask);
} }
private static uint IPToUInt32(string ipAddress) private static uint IPToUInt32(string ipAddress)
@@ -84,11 +95,12 @@ public class RangeBan: Plugin<RangeBanConfig>
throw new ArgumentException("Invalid IP address segment"); throw new ArgumentException("Invalid IP address segment");
result = (result << 8) | part; result = (result << 8) | part;
} }
return result; return result;
} }
} }
public class RangeBanConfig public class RangeBanConfig
{ {
public string[] IpRanges { get; set; } = {}; public string[] IpRanges { get; set; } = { };
} }
+1 -13
View File
@@ -20,18 +20,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Assembly-CSharp"> <PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
<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> </ItemGroup>
</Project> </Project>
+76
View File
@@ -0,0 +1,76 @@
using CustomPlayerEffects;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
using MEC;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp3114;
using Logger = LabApi.Features.Console.Logger;
namespace SCPBalance;
public class ScpBalance : Plugin
{
public override string Name => "SCPBalance";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Rethink SCP balance";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Spawned += HandleSpawn;
PlayerEvents.Hurting += OnPlayerHurting;
}
public override void Disable()
{
PlayerEvents.Spawned -= HandleSpawn;
PlayerEvents.Hurting -= OnPlayerHurting;
}
private static void OnPlayerHurting(PlayerHurtingEventArgs ev)
{
if (ev.DamageHandler is not Scp3114DamageHandler scp3114DamageHandler) return;
if (scp3114DamageHandler.Subtype != Scp3114DamageHandler.HandlerType.Slap) return;
if (ev.Attacker != null) ev.Attacker.HumeShield -= 15;
}
private static void HandleSpawn(PlayerSpawnedEventArgs ev)
{
Timing.CallDelayed(1f, () =>
{
Logger.Debug("Handling Balance");
if (ev.Role.RoleTypeId == RoleTypeId.Scp049)
{
ev.Player.ReferenceHub.playerEffectsController.ChangeState<MovementBoost>(5, float.MaxValue);
}
if (ev.Role.RoleTypeId == RoleTypeId.Scp3114)
{
ev.Player.ReferenceHub.playerEffectsController.ChangeState<Slowness>(6, float.MaxValue);
}
});
}
}
+57
View File
@@ -0,0 +1,57 @@
<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>
<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>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+175 -52
View File
@@ -6,91 +6,214 @@ using LabApi.Events.Handlers;
using LabApi.Features; using LabApi.Features;
using LabApi.Features.Console; using LabApi.Features.Console;
using LabApi.Features.Wrappers; using LabApi.Features.Wrappers;
using MapGeneration;
using PlayerRoles; using PlayerRoles;
using PlayerRoles.PlayableScps.Scp079;
using PlayerRoles.PlayableScps.Scp096; using PlayerRoles.PlayableScps.Scp096;
using Timer = System.Timers.Timer; using PlayerRoles.PlayableScps.Scp3114;
using MEC;
using PlayerRoles.PlayableScps.Scp049.Zombies;
namespace SCPTeamHint namespace SCPTeamHint;
public class Plugin : LabApi.Loader.Features.Plugins.Plugin
{ {
public class Plugin : LabApi.Loader.Features.Plugins.Plugin private readonly object _hintsLock = new();
private readonly Dictionary<Player, Hint> _spectatorHints = new();
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 const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{ {
public override string Name => "SCPTeamHint"; const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
public override string Author => "HoherGeist, Code002Lover"; const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
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; var standardized = "";
private readonly Dictionary<Player,Hint> _spectatorHints = new(); foreach (var c in Message)
public override void Enable()
{ {
Logger.Debug("Apple juice"); var index = customAlphabet.IndexOf(c);
PlayerEvents.Joined += OnJoin; standardized += index >= 0 ? standardAlphabet[index] : c;
_timer = new Timer(1000);
_timer.Elapsed += (_,_) => UpdateHints();
_timer.Start();
} }
public override void Disable() // Then decode using standard base64
{ var decodedBytes = Convert.FromBase64String(standardized);
PlayerEvents.Joined -= OnJoin; var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
_timer.Stop();
}
private void UpdateHints() Logger.Info(decodedMessage);
{
var hintTexts = new List<string>();
foreach (var player in Player.List) PlayerEvents.Joined += OnJoin;
PlayerEvents.Left += OnLeft;
Timing.RunCoroutine(ContinuouslyUpdateHints());
}
private IEnumerator<float> ContinuouslyUpdateHints()
{
while (true)
{
yield return Timing.WaitForSeconds(1);
try
{ {
if (!player.IsSCP) continue; UpdateHints();
}
catch (Exception e)
{
Logger.Error(e);
}
}
// ReSharper disable once IteratorNeverReturns
}
var text = $"{player.RoleBase.RoleName} | {player.HumeShield} | {player.Health} | {player.Zone}"; public override void Disable()
{
PlayerEvents.Joined -= OnJoin;
PlayerEvents.Left -= OnLeft;
}
if (player.RoleBase is Scp096Role scp) private static string CollectHint()
{ {
var hintTexts = new List<string>();
foreach (var player in Player.ReadyList.Where(x => !x.IsDummy && (x.IsSCP || x.Role is RoleTypeId.Scp0492)))
{
var zone = player.Zone == FacilityZone.None ? Map.Elevators.Any(x=>x.Base.WorldspaceBounds.Contains(player.Position)) ? "Elevator" : "None" : player.Zone.ToString();
var text =
$" <size=25><color=red>{player.RoleBase.RoleName}</color> | <color=#6761cd>{(int)player.HumeShield}</color> | <color=#da0101>{(int)player.Health}</color> | <color=grey>{zone}</color></size> ";
switch (player.RoleBase)
{
case Scp096Role scp:
text += "\n"; text += "\n";
scp.SubroutineModule.TryGetSubroutine(out Scp096TargetsTracker tracker); scp.SubroutineModule.TryGetSubroutine(out Scp096TargetsTracker tracker);
text += $"Targets: {tracker.Targets.Count}"; if (!tracker) break;
}
hintTexts.Add(text); var targetColor = tracker.Targets.Count > 0 ? "red" : "grey";
text += $"<color=grey>Targets:</color> <color={targetColor}>{tracker.Targets.Count}</color>";
break;
case Scp3114Role scp3114:
text += "\n";
var stolenRole = scp3114.CurIdentity.StolenRole;
if (scp3114.Disguised)
{
text += $" {stolenRole}";
}
else
{
text += " None";
}
break;
case Scp079Role scp079:
text =
$" <size=25><color=red>{player.RoleBase.RoleName}</color> | <color=grey>{scp079.CurrentCamera.Room.Zone}</color></size> ";
text += "\n";
scp079.SubroutineModule.TryGetSubroutine(out Scp079AuxManager auxManager);
scp079.SubroutineModule.TryGetSubroutine(out Scp079TierManager tierManager);
if (!auxManager || !tierManager) break;
text +=
$" <color=#FFEF00>AUX: {auxManager.CurrentAuxFloored}</color> / {auxManager.MaxAux} | <color=#FFD700>Level {tierManager.AccessTierLevel}</color>";
break;
case ZombieRole:
if (!GrowingZombies.GrowingZombies.Instance.ZombieCorpseCount.TryGetValue(player, out var count))
break;
const string corpseColor = "E68A8A";
text += "\n";
text += $" <color=#{corpseColor}>Corpses eaten: {count}</color>";
break;
} }
foreach (var player in Player.List.Where(x=>!x.IsHost)) hintTexts.Add(text);
{
Logger.Debug($"Updating hint for {player.DisplayName}");
UpdateHint(player, string.Join("\n", hintTexts));
}
} }
private void UpdateHint(Player player, string hintText) return string.Join("\n", hintTexts);
}
private void UpdateHints()
{
var hintText = CollectHint();
foreach (var player in Player.ReadyList.Where(x => !x.IsDummy))
{
try
{
UpdateHint(player, hintText);
} catch (Exception e)
{
Logger.Warn("Caught exception while updating hint for player");
Logger.Error(e);
}
}
}
private void UpdateHint(Player player, string hintText)
{
bool isContained;
lock (_hintsLock)
{
isContained = _spectatorHints.ContainsKey(player);
}
if (!isContained)
{
CreateHint(player);
}
if (_spectatorHints == null) return;
lock (_hintsLock)
{ {
var hint = _spectatorHints[player]; var hint = _spectatorHints[player];
Logger.Debug($"Player {player.Nickname} is on team {player.RoleBase.Team} | hide: {player.RoleBase.Team != Team.SCPs}"); hint.Hide = player.RoleBase.Team != Team.SCPs && player.Role != RoleTypeId.Scp0492 && player.Role != RoleTypeId.Overwatch;
hint.Hide = player.RoleBase.Team != Team.SCPs; if (!hint.Hide) hint.Text = hintText;
hint.Text = hintText;
} }
}
private void OnJoin(PlayerJoinedEventArgs ev) private void OnJoin(PlayerJoinedEventArgs ev)
{
if (ev.Player.IsDummy || ev.Player.IsHost) return;
CreateHint(ev.Player);
}
private void CreateHint(Player player)
{
var hint = new Hint
{ {
Text = "", Alignment = HintAlignment.Left, YCoordinate = 100, Hide = true
};
var hint = new Hint var playerDisplay = PlayerDisplay.Get(player);
{ playerDisplay.AddHint(hint);
Text = "Apfelsaft", Alignment = HintAlignment.Left, YCoordinate = 300, Hide = true
};
var playerDisplay = PlayerDisplay.Get(ev.Player); lock (_hintsLock)
playerDisplay.AddHint(hint); {
_spectatorHints[player] = hint;
}
}
_spectatorHints[ev.Player] = hint; private void OnLeft(PlayerLeftEventArgs ev)
{
if (ev.Player.IsDummy || ev.Player.IsHost) return;
lock (_hintsLock)
{
_spectatorHints.Remove(ev.Player);
} }
} }
} }
+15 -8
View File
@@ -19,28 +19,35 @@
<DebugType>none</DebugType> <DebugType>none</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>..\dependencies\Assembly-CSharp.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference> </Reference>
<Reference Include="HintServiceMeow"> <Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath> <HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference> </Reference>
<Reference Include="Mirror"> <Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath> <HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference> </Reference>
<Reference Include="Pooling"> <Reference Include="Pooling">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Pooling.dll</HintPath> <HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2" /> <ProjectReference Include="..\GrowingZombies\GrowingZombies.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+61
View File
@@ -0,0 +1,61 @@
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using PlayerRoles;
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);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Spawned += ev =>
{
if (ev.Role.Team != Team.SCPs)
{
return;
}
if (Round.Duration.TotalSeconds > 100) {
return;
}
if (ev.Role.RoleTypeId == RoleTypeId.Scp0492)
{
return;
}
ev.Player.SendBroadcast("Willst du dein SCP wechseln? Drücke Ö und gebe .scpswap <SCP-NUMMER> ein.", 10);
};
}
public override void Disable()
{
}
}
+43
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>
+88
View File
@@ -0,0 +1,88 @@
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;
}
if (player.Role == RoleTypeId.Scp0492)
{
response = "You can't swap SCPs while you're a zombie!";
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";
}
+90
View File
@@ -18,6 +18,36 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RangeBan.Tests", "RangeBan.
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CuffedFrenemies", "CuffedFrenemies\CuffedFrenemies.csproj", "{C3FEEC52-B7C0-4DB6-A0CA-54BE175072D8}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CuffedFrenemies", "CuffedFrenemies\CuffedFrenemies.csproj", "{C3FEEC52-B7C0-4DB6-A0CA-54BE175072D8}"
EndProject 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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemplateProject", "TemplateProject\TemplateProject.csproj", "{E5A28D1C-638F-4849-9784-240D50A6DA29}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LobbyGame", "LobbyGame\LobbyGame.csproj", "{E02243D5-0229-47BB-88A7-252EC753C8CC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatsTracker", "StatsTracker\StatsTracker.csproj", "{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModInfo", "ModInfo\ModInfo.csproj", "{8C55C629-FFB9-41AC-8F5C-1BF715110766}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VIPTreatment", "VIPTreatment\VIPTreatment.csproj", "{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SCPBalance", "SCPBalance\SCPBalance.csproj", "{CD7F5276-58D2-4DAB-A476-F5B61069AA62}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrollTK", "TrollTK\TrollTK.csproj", "{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -60,5 +90,65 @@ Global
{C3FEEC52-B7C0-4DB6-A0CA-54BE175072D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Release|Any CPU
{C3FEEC52-B7C0-4DB6-A0CA-54BE175072D8}.Release|Any CPU.Build.0 = 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
{E5A28D1C-638F-4849-9784-240D50A6DA29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5A28D1C-638F-4849-9784-240D50A6DA29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5A28D1C-638F-4849-9784-240D50A6DA29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5A28D1C-638F-4849-9784-240D50A6DA29}.Release|Any CPU.Build.0 = Release|Any CPU
{E02243D5-0229-47BB-88A7-252EC753C8CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E02243D5-0229-47BB-88A7-252EC753C8CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E02243D5-0229-47BB-88A7-252EC753C8CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E02243D5-0229-47BB-88A7-252EC753C8CC}.Release|Any CPU.Build.0 = Release|Any CPU
{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA17C0F1-9C99-4F80-9871-38D6EB00EA95}.Release|Any CPU.Build.0 = Release|Any CPU
{8C55C629-FFB9-41AC-8F5C-1BF715110766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C55C629-FFB9-41AC-8F5C-1BF715110766}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C55C629-FFB9-41AC-8F5C-1BF715110766}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C55C629-FFB9-41AC-8F5C-1BF715110766}.Release|Any CPU.Build.0 = Release|Any CPU
{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAB7582A-FC51-4FCA-9166-BBAF7A6D1170}.Release|Any CPU.Build.0 = Release|Any CPU
{CD7F5276-58D2-4DAB-A476-F5B61069AA62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD7F5276-58D2-4DAB-A476-F5B61069AA62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD7F5276-58D2-4DAB-A476-F5B61069AA62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD7F5276-58D2-4DAB-A476-F5B61069AA62}.Release|Any CPU.Build.0 = Release|Any CPU
{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2071139-1B13-4B9E-9A27-A7999CEE2DC9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal
+72 -33
View File
@@ -1,5 +1,6 @@
using InventorySystem.Items.Pickups; using InventorySystem.Items.Pickups;
using LabApi.Events.Arguments.PlayerEvents; using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers; using LabApi.Events.Handlers;
using LabApi.Features; using LabApi.Features;
using LabApi.Features.Wrappers; using LabApi.Features.Wrappers;
@@ -8,46 +9,84 @@ using UnityEngine;
using Logger = LabApi.Features.Console.Logger; using Logger = LabApi.Features.Console.Logger;
using TimedGrenadePickup = InventorySystem.Items.ThrowableProjectiles.TimedGrenadePickup; using TimedGrenadePickup = InventorySystem.Items.ThrowableProjectiles.TimedGrenadePickup;
namespace SensitiveGrenades namespace SensitiveGrenades;
public class SensitiveGrenades : Plugin
{ {
public class SensitiveGrenades : Plugin public override string Name => "SensitiveGrenades";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Shoot grenades to blow them up!";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private static readonly object Lock = new();
private static readonly List<ushort> GrenadeIds = new();
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{ {
public override string Name => "SensitiveGrenades"; const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
public override string Author => "Code002Lover"; const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Shoot grenades to blow them up!";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public override void Enable() var standardized = "";
foreach (var c in Message)
{ {
Logger.Debug("starting..."); var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
PlayerEvents.PlacedBulletHole += ShotWeapon;
} }
private static void ShotWeapon(PlayerPlacedBulletHoleEventArgs ev) // Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.PlacedBulletHole += ShotWeapon;
ServerEvents.RoundEnded += OnRoundEnded;
}
private static void OnRoundEnded(RoundEndedEventArgs ev)
{
lock (Lock)
{ {
var direction = (ev.HitPosition - ev.RaycastStart).normalized; GrenadeIds.Clear();
var distance = Vector3.Distance(ev.RaycastStart, ev.HitPosition);
var hits = Physics.RaycastAll(ev.RaycastStart, direction, distance);
foreach (var hit in hits)
{
var itemPickup = hit.collider.GetComponent<ItemPickupBase>();
var grenade = itemPickup as TimedGrenadePickup;
if (!grenade) continue;
itemPickup.DestroySelf();
TimedGrenadeProjectile.SpawnActive(itemPickup.Position, itemPickup.Info.ItemId, ev.Player);
break;
}
}
public override void Disable()
{
Logger.Debug("unloading...");
} }
} }
private static void ShotWeapon(PlayerPlacedBulletHoleEventArgs ev)
{
var direction = (ev.HitPosition - ev.RaycastStart).normalized;
var distance = Vector3.Distance(ev.RaycastStart, ev.HitPosition);
var hits = Physics.RaycastAll(ev.RaycastStart, direction, distance);
foreach (var hit in hits)
{
var itemPickup = hit.collider.GetComponent<ItemPickupBase>();
var grenade = itemPickup as TimedGrenadePickup;
if (!grenade) continue;
lock (Lock)
{
if (GrenadeIds.Contains(grenade.ItemId.SerialNumber))
{
break;
}
GrenadeIds.Add(grenade.ItemId.SerialNumber);
}
Logger.Info($"Grenade shot by {ev.Player.Nickname}, exploding!");
itemPickup.DestroySelf();
TimedGrenadeProjectile.SpawnActive(itemPickup.Position, itemPickup.Info.ItemId, ev.Player);
break;
}
}
public override void Disable()
{
Logger.Debug("unloading...");
}
} }
+2 -5
View File
@@ -30,15 +30,12 @@
<Reference Include="UnityEngine.CoreModule"> <Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
</Reference> </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"> <Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2" /> <PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
+68
View File
@@ -0,0 +1,68 @@
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
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."
];
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
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>", 5);
});
}
}
+40
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>
+97
View File
@@ -0,0 +1,97 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"bincode_derive",
"serde",
"unty",
]
[[package]]
name = "bincode_derive"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
dependencies = [
"virtue",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "stats_tracker"
version = "0.1.0"
dependencies = [
"bincode",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "virtue"
version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "stats_tracker"
version = "0.1.0"
edition = "2024"
[dependencies]
bincode = "2.0"
[lib]
crate-type = ["cdylib"]
name = "stats_tracker"
[profile.release]
opt-level = "z"
codegen-units = 1
lto = "fat"
overflow-checks = false
panic = "abort"
+98
View File
@@ -0,0 +1,98 @@
use std::{
ffi::{CStr, c_char},
fs::File,
};
use bincode::{Decode, Encode};
#[derive(Decode, Encode, Clone)]
#[repr(C)]
pub struct PlayerStat {
kills: u32,
deaths: u32,
team_damage: u32,
}
#[derive(Decode, Encode, Clone, Default)]
#[repr(C)]
pub struct ItemStat {
item_name: String,
item_count: u32,
}
#[derive(Decode, Encode, Clone)]
#[repr(C)]
pub struct Player {
player_id: String,
player_stats: PlayerStat,
player_items: [ItemStat; 256],
}
impl Default for Player {
fn default() -> Self {
Self {
player_id: String::new(),
player_stats: PlayerStat {
kills: 0,
deaths: 0,
team_damage: 0,
},
player_items: std::array::from_fn(|_| ItemStat::default()),
}
}
}
#[unsafe(no_mangle)]
/// # Safety
/// `player_id` must be a valid, null-terminated C string pointer.
pub unsafe extern "C" fn get_player_stats(player_id: *const c_char) -> *const Player {
let player_id = unsafe { CStr::from_ptr(player_id) }
.to_string_lossy()
.into_owned();
let db_location = "./stats.data";
let player_stats: Vec<Player> = match File::open(db_location) {
Ok(mut data) => bincode::decode_from_std_read(&mut data, bincode::config::standard())
.unwrap_or_default(),
Err(_) => Vec::new(),
};
player_stats
.iter()
.find(|p| p.player_id == player_id)
.unwrap_or(&Player::default())
}
#[unsafe(no_mangle)]
/// # Safety
/// `player` must be a valid pointer to a `Player` struct.
pub unsafe extern "C" fn save_player_stats(player: *const Player) -> bool {
let player = unsafe { &*player };
let db_location = "./stats.data";
let mut player_stats: Vec<Player> = match File::open(db_location) {
Ok(mut data) => bincode::decode_from_std_read(&mut data, bincode::config::standard())
.unwrap_or_default(),
Err(_) => Vec::new(),
};
// Find and update existing player or add new player
if let Some(existing_player) = player_stats
.iter_mut()
.find(|p| p.player_id == player.player_id)
{
*existing_player = player.clone();
} else {
player_stats.push(player.clone());
}
// Save updated stats
match File::create(db_location) {
Ok(mut file) => {
bincode::encode_into_std_write(&player_stats, &mut file, bincode::config::standard())
.is_ok()
}
Err(_) => false,
}
}
+229
View File
@@ -0,0 +1,229 @@
using LabApi.Features;
using LabApi.Loader.Features.Plugins;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Events.Handlers;
using LabApi.Features.Console;
namespace StatsTracker
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
public struct PlayerStat
{
public uint kills;
public uint deaths;
public uint team_damage;
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
public struct ItemStat
{
public string item_name;
public uint item_count;
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
public struct Player
{
public string player_id;
public PlayerStat player_stats;
public ItemStat[] player_items;
}
[SuppressMessage("ReSharper", "InconsistentNaming")]
public class StatsTracker : Plugin
{
public override string Name => "StatsTracker";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Tracks stats for players.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string RustDllName = "stats_tracker";
[DllImport(RustDllName, EntryPoint="get_player_stats", CallingConvention = CallingConvention.Cdecl)]
private static extern ref Player GetPlayerStats(ref string player_id);
[DllImport(RustDllName, EntryPoint="save_player_stats", CallingConvention = CallingConvention.Cdecl)]
private static extern bool SavePlayerStats(ref Player player);
public override void Enable()
{
var pathVariable = Environment.GetEnvironmentVariable("PATH");
Logger.Debug($"PATH: {pathVariable}");
var extractedDllPath = ExtractRustDll(); // Call extraction
if (string.IsNullOrEmpty(extractedDllPath))
{
Logger.Error("Failed to extract Rust DLL. Exiting.");
return;
}
var libDirectory = Path.GetDirectoryName(extractedDllPath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
SetDllDirectory(libDirectory);
Logger.Info($"Windows: Added '{libDirectory}' to DLL search path.");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", libDirectory + ":" + Environment.GetEnvironmentVariable("LD_LIBRARY_PATH"));
Logger.Info($"Linux: Extracted library to '{libDirectory}'. Relying on default search paths and manual LD_LIBRARY_PATH.");
}
else
{
Logger.Error("Only windows and linux are supported.");
return;
}
PlayerEvents.Death += OnPlayerDied;
PlayerEvents.UsedItem += OnItemUsed;
PlayerEvents.Hurt += OnHurt;
}
// Conditional P/Invoke for OS-specific functions (like SetDllDirectory on Windows)
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetDllDirectory(string lpPathName);
private static string ExtractRustDll()
{
string dllFilename;
string resourceSubPath; // Folder inside NativeLibs
string extractedLibFilename;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
dllFilename = $"{RustDllName}.dll";
resourceSubPath = "x86_64_pc_windows_gnu";
extractedLibFilename = dllFilename; // On Windows, we keep the original name
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
dllFilename = $"lib{RustDllName}.so"; // Linux uses lib prefix and .so suffix
resourceSubPath = "x86_64_unknown_linux_gnu";
extractedLibFilename = dllFilename; // On Linux, keep the lib*.so name
}
else
{
Logger.Error("Unsupported operating system detected.");
return null;
}
// Determine where to extract the DLL.
var targetDirectory = AppDomain.CurrentDomain.BaseDirectory;
var extractedLibPath = Path.Combine(targetDirectory, extractedLibFilename);
// Check if the DLL already exists (e.g., from a previous run or development environment)
if (File.Exists(extractedLibPath))
{
Logger.Warn($"Rust lib '{dllFilename}' already exists at '{extractedLibPath}'. Skipping extraction.");
return extractedLibPath; // Return the path directly if it exists
}
try
{
// Adjust this resource name to match your actual embedded resource name.
// Based on your previous comment:
var resourceName = $"StatsTracker.Rust.target.{resourceSubPath}.release.{dllFilename}";
Logger.Info($"Attempting to load embedded resource: {resourceName}");
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
if (stream == null)
{
Logger.Error($"Error: Embedded resource '{resourceName}' not found.");
Logger.Info("Available resources:");
foreach (var res in Assembly.GetExecutingAssembly().GetManifestResourceNames())
{
Logger.Info($"- {res}");
}
return null; // Return null to indicate failure
}
using (var fileStream = File.Create(extractedLibPath))
{
stream.CopyTo(fileStream);
}
}
Logger.Info($"Successfully extracted '{RustDllName}' to '{extractedLibPath}'.");
return extractedLibPath; // Return the path after successful extraction
}
catch (Exception ex)
{
Logger.Error($"Error extracting Rust DLL to {extractedLibPath}: {ex.Message}");
return null; // Return null to indicate failure
}
}
public override void Disable()
{
PlayerEvents.Death -= OnPlayerDied;
PlayerEvents.UsedItem -= OnItemUsed;
}
private static void OnHurt(PlayerHurtEventArgs ev)
{
switch (ev.Attacker)
{
case null:
case { DoNotTrack: true }:
return;
}
if(ev.Attacker.Team != ev.Player.Team) return;
var userId = ev.Attacker.UserId;
var killerStats = GetPlayerStats(ref userId);
killerStats.player_stats.team_damage++;
SavePlayerStats(ref killerStats);
}
private static void OnPlayerDied(PlayerDeathEventArgs ev)
{
if (ev.Attacker != null && ev.Attacker.Nickname != "emeraldo" && !ev.Attacker.DoNotTrack)
{
var userId = ev.Attacker.UserId;
var killerStats = GetPlayerStats(ref userId);
killerStats.player_stats.kills++;
SavePlayerStats(ref killerStats);
}
if (ev.Player.DoNotTrack)
{
Logger.Debug($"Do Not Track: {ev.Player.Nickname}");
return;
}
var victimId = ev.Player.UserId;
var victimStats = GetPlayerStats(ref victimId);
victimStats.player_stats.deaths++;
SavePlayerStats(ref victimStats);
}
private static void OnItemUsed(PlayerUsedItemEventArgs ev)
{
if (ev.Player.DoNotTrack)
{
Logger.Debug($"Do Not Track: {ev.Player.Nickname}");
return;
}
var userId = ev.Player.UserId;
var stats = GetPlayerStats(ref userId);
var stat = stats.player_items[(int)ev.UsableItem.Type];
stat.item_count++;
stat.item_name = ev.UsableItem.Type.ToString();
}
}
}
+67
View File
@@ -0,0 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</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>
<EmbeddedResource Include="Rust/target/x86_64-pc-windows-gnu/release/stats_tracker.dll" />
<EmbeddedResource Include="Rust/target/x86_64-unknown-linux-gnu/release/libstats_tracker.so" />
<PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup>
<Target Name="RustBuild" BeforeTargets="PrepareForBuild">
<Exec Command="echo 'Configuration: $(Configuration)'"/>
<Exec Command="cargo build -r --target x86_64-pc-windows-gnu" WorkingDirectory="./Rust"/>
<Exec Command="cargo build -r --target x86_64-unknown-linux-gnu" WorkingDirectory="./Rust"/>
</Target>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<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="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
{
"author": "Code002Lover",
"name": "SCP:SL LabAPI template",
"description": "LabAPI basic template for own use",
"identity": "Code002Lover.LabAPI.1.0",
"shortName": "labapi",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "TemplateProject"
}
+42
View File
@@ -0,0 +1,42 @@
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
namespace TemplateProject
{
public class TemplateProject: Plugin
{
public override string Name => "TemplateProject";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Is a template for creating plugins. It does nothing.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
}
public override void Disable()
{
}
}
}
+62
View File
@@ -0,0 +1,62 @@
<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>
<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>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.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>
<Content Include=".template.config\template.json" />
</ItemGroup>
</Project>
+110
View File
@@ -0,0 +1,110 @@
using System.Diagnostics.CodeAnalysis;
using CommandSystem;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using PlayerRoles;
using PlayerStatsSystem;
using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
namespace TrollTK;
public class TrollDB
{
public string[] Teamkillers { get; set; } = [];
}
// ReSharper disable once InconsistentNaming
public class TrollTK : Plugin<TrollDB>
{
public override string Name => "TrollTK";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Trolls teamkillers - reflecting damage :troll:";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
public static TrollTK Singleton { get; private set; }
private const string Message =
"PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
PlayerEvents.Hurting += ev =>
{
if (ev.Attacker == null) return;
if (ev.Attacker == ev.Player) return;
// ReSharper disable once InconsistentNaming
var isFF = ev.Player.Team == ev.Attacker.Team || (ev.Player.Team == Team.ChaosInsurgency && ev.Attacker.Team == Team.ClassD) || (ev.Player.Team == Team.ClassD && ev.Attacker.Team == Team.ChaosInsurgency) || (ev.Player.Team == Team.Scientists && ev.Attacker.Team == Team.FoundationForces) || (ev.Player.Team == Team.FoundationForces && ev.Attacker.Team == Team.Scientists);
if(!isFF) return;
if (Config!.Teamkillers.All(x => x != ev.Attacker.UserId)) return;
ev.IsAllowed = false;
if (ev.DamageHandler is FirearmDamageHandler firearmDamageHandler)
{
ev.Attacker.Damage(firearmDamageHandler.Damage, ev.Player, new Vector3(10,1,10), 69);
}
};
Singleton = this;
}
public override void Disable()
{
Singleton = null;
}
}
[CommandHandler(typeof(RemoteAdminCommandHandler))]
// ReSharper disable once InconsistentNaming
public class AddTKCommand : ICommand
{
public string Command => "addtk";
public string[] Aliases => [];
public string Description => "Adds a player to the known Teamkiller list";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, [UnscopedRef] out string response)
{
var targetPlayerName = string.Join(" ", arguments);
if (targetPlayerName.Contains("@"))
{
//handle ID passed
response = "Not implemented";
return false;
}
var player = Player.List.FirstOrDefault(x => x.Nickname == targetPlayerName);
if (player == null)
{
response = $"Player {targetPlayerName} not found";
return false;
}
TrollTK.Singleton.Config!.Teamkillers = TrollTK.Singleton.Config!.Teamkillers.Append(player.UserId).ToArray();
response = $"Added {targetPlayerName} to the known Teamkiller list";
return true;
}
}
+57
View File
@@ -0,0 +1,57 @@
<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>
<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>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+42
View File
@@ -0,0 +1,42 @@
using CommandSystem;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
namespace VIPTreatment;
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class BullshitDetectedCommand : ICommand
{
public string Command => "bullshitdetected";
public string[] Aliases => [];
public string Description => "Broadcasts a message to all players";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
if (!Player.TryGet(sender, out var player))
{
response = "You must be a player to use this command!";
return false;
}
if (!player.HasPermissions("viptreatment.wiki"))
{
response = "You must have the permission to use this command!";
return false;
}
foreach (var target in Player.ReadyList)
{
target.SendBroadcast("<color=red>⚠️ BULLSHIT DETECTED ⚠️</color>", 5);
target.SendBroadcast("<color=green>Tipp: Das Wiki hat immer die aktuellsten Informationen!</color>", 5);
}
Cassie.Message("false information yield_0.8 detected yield_01 see V yield_0.2 KEY", true, false, false);
response = "Broadcast message sent to all ready players.";
return true;
}
}
+106
View File
@@ -0,0 +1,106 @@
using CommandSystem;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
using UnityEngine;
using MEC;
namespace VIPTreatment;
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class ColorCommand : ICommand
{
public string Command => "color";
public string[] Aliases => ["setcolor"];
public string Description => "Changes the color of room lights. Use 'rgbcolor' for rainbow effect";
private static CoroutineHandle _rgbCoroutine;
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
var colorArg = arguments.Count > 0 ? arguments.At(0).ToLower() : "red";
if (!Player.TryGet(sender, out var player))
{
response = "You must be a player to use this command!";
return false;
}
if (!player.HasPermissions("viptreatment.color") && player.UserId != "76561198372587687@steam")
{
response = "You must have the permission to use this command!";
return false;
}
if (VIPTreatment.Instance.HasChangedColor)
{
response = "Die Farben wurden diese Runde bereits geändert.";
return false;
}
if (colorArg == "rgbcolor")
{
Timing.KillCoroutines(_rgbCoroutine);
_rgbCoroutine = Timing.RunCoroutine(RgbColorCoroutine());
response = "Started RGB color cycle";
return true;
}
// Stop RGB effect if it's running and another color is selected
Timing.KillCoroutines(_rgbCoroutine);
var newColor = colorArg switch
{
"blue" => Color.blue,
"green" => Color.green,
"yellow" => Color.yellow,
"white" => Color.white,
"magenta" => Color.magenta,
_ => Color.red,
};
SetLightsColor(newColor);
VIPTreatment.Instance.HasChangedColor = true;
Timing.CallDelayed(60f, () =>
{
SetLightsColor(Color.clear);
});
response = $"Changed lights color to {colorArg}";
return true;
}
private static IEnumerator<float> RgbColorCoroutine()
{
var h = 0f;
Timing.CallDelayed(30f, () =>
{
Timing.KillCoroutines(_rgbCoroutine);
SetLightsColor(Color.clear);
});
while (true)
{
var rgbColor = Color.HSVToRGB(h, 1f, 1f);
SetLightsColor(rgbColor);
h += 0.01f;
if (h > 1f)
h = 0f;
yield return Timing.WaitForSeconds(0.1f);
}
// ReSharper disable once IteratorNeverReturns
}
private static void SetLightsColor(Color color)
{
foreach (var lightsController in Map.RoomLights)
{
lightsController.OverrideLightsColor = color;
}
}
}
+54
View File
@@ -0,0 +1,54 @@
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
namespace VIPTreatment
{
public class VIPTreatment : Plugin
{
public override string Name => "VIPTreatment";
public override string Author => "Code002Lover";
public override Version Version { get; } = new(1, 0, 0);
public override string Description => "Is a template for creating plugins. It does nothing.";
public override Version RequiredApiVersion { get; } = new(LabApiProperties.CompiledVersion);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public bool HasChangedColor;
public static VIPTreatment Instance;
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
ServerEvents.RoundStarting += _ =>
{
HasChangedColor = false;
};
Instance = this;
}
public override void Disable()
{
Instance = null;
}
}
}
+57
View File
@@ -0,0 +1,57 @@
<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>
<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>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="CommandSystem.Core">
<HintPath>..\dependencies\CommandSystem.Core.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference>
<Reference Include="Mirror">
<HintPath>..\dependencies\Mirror.dll</HintPath>
</Reference>
<Reference Include="NorthwoodLib">
<HintPath>..\dependencies\NorthwoodLib.dll</HintPath>
</Reference>
<Reference Include="Pooling">
<HintPath>..\dependencies\Pooling.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\dependencies\UnityEngine.CoreModule.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+86
View File
@@ -0,0 +1,86 @@
using PlayerRoles;
using LabApi.Features.Wrappers;
namespace VisibleSpectators;
/// <summary>
/// Utility for formatting player display names and color mapping.
/// </summary>
public static class PlayerDisplayUtil
{
private static readonly Dictionary<string, string> ColorMap = new()
{
{ "DEFAULT", "FFFFFF" },
{ "PUMPKIN", "EE7600" },
{ "ARMY_GREEN", "4B5320" },
{ "MINT", "98FB98" },
{ "NICKEL", "727472" },
{ "CARMINE", "960018" },
{ "EMERALD", "50C878" },
{ "GREEN", "228B22" },
{ "LIME", "BFFF00" },
{ "POLICE_BLUE", "002DB3" },
{ "ORANGE", "FF9966" },
{ "SILVER_BLUE", "666699" },
{ "BLUE_GREEN", "4DFFB8" },
{ "MAGENTA", "FF0090" },
{ "YELLOW", "FAFF86" },
{ "TOMATO", "FF6448" },
{ "DEEP_PINK", "FF1493" },
{ "AQUA", "00FFFF" },
{ "CYAN", "00B7EB" },
{ "CRIMSON", "DC143C" },
{ "LIGHT_GREEN", "32CD32" },
{ "SILVER", "A0A0A0" },
{ "BROWN", "944710" },
{ "RED", "C50000" },
{ "PINK", "FF96DE" },
{ "LIGHT_RED", "FD8272" },
{ "PURPLE", "8137CE" },
{ "BLUE", "005EBC" },
{ "TEAL", "008080" },
{ "GOLD", "EFC01A" }
};
private static readonly Dictionary<string, (string Color, string Name)> PlayerSpecificDisplays = new()
{
{ "76561198372750067@steam", ("DEAFCC", "1+1=10") },
{ "76561198372587687@steam", ("9933FF", "HoherGeist") }
};
/// <summary>
/// Returns a formatted display string for a player, with color.
/// </summary>
public static string PlayerToDisplay(Player player)
{
if (player is not { IsReady: true }) return string.Empty;
if (PlayerSpecificDisplays.TryGetValue(player.UserId, out var specificDisplay))
{
return $"<color=#{specificDisplay.Color}>{specificDisplay.Name}</color>";
}
const string defaultColor = "FFFFFF";
try
{
var groupColor = player.GroupColor;
if (string.IsNullOrEmpty(groupColor))
return $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
return ColorMap.TryGetValue(groupColor.ToUpper(), out var color)
? $"<color=#{color}FF>{player.DisplayName}</color>"
: $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
}
catch
{
return $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
}
}
/// <summary>
/// Returns true if the player is not Overwatch.
/// </summary>
public static bool IsNotOverwatch(Player player)
{
return player != null && player.Role != RoleTypeId.Overwatch;
}
}
+55
View File
@@ -0,0 +1,55 @@
using LabApi.Loader.Features.Plugins;
using LabApi.Features;
using LabApi.Events.Handlers;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Features.Console;
using MEC;
namespace VisibleSpectators;
/// <summary>
/// Main entry point for the VisibleSpectators plugin.
/// </summary>
public class Plugin : Plugin<SpectatorConfig>
{
private SpectatorManager _spectatorManager;
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 const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
_spectatorManager = new SpectatorManager(Config);
PlayerEvents.ChangedSpectator += _spectatorManager.OnSpectate;
PlayerEvents.Joined += _spectatorManager.OnJoin;
Timing.RunCoroutine(_spectatorManager.KeepUpdatingSpectators());
}
public override void Disable()
{
Logger.Debug("unloading...");
PlayerEvents.Joined -= _spectatorManager.OnJoin;
PlayerEvents.ChangedSpectator -= _spectatorManager.OnSpectate;
_spectatorManager = null;
}
}
+16
View File
@@ -0,0 +1,16 @@
namespace VisibleSpectators;
/// <summary>
/// Configuration for the VisibleSpectators plugin.
/// </summary>
public class SpectatorConfig
{
/// <summary>
/// Header message shown above the spectator list.
/// </summary>
public string HeaderMessage { get; set; } = "Spectators:";
/// <summary>
/// Message shown when there are no spectators.
/// </summary>
public string NoSpectatorsMessage { get; set; } = "No spectators";
}
+98
View File
@@ -0,0 +1,98 @@
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.Hints;
using HintServiceMeow.Core.Utilities;
using LabApi.Events.Arguments.PlayerEvents;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles;
namespace VisibleSpectators;
/// <summary>
/// Handles spectator hint management and updates for players.
/// </summary>
public class SpectatorManager
{
private readonly SpectatorConfig _config;
private readonly Dictionary<Player, Hint> _spectatorHints = new();
public int YCoordinate { get; set; } = 100;
public SpectatorManager(SpectatorConfig config)
{
_config = config;
}
public IEnumerator<float> KeepUpdatingSpectators()
{
while (true)
{
UpdateSpectators();
yield return Timing.WaitForSeconds(1);
}
// ReSharper disable once IteratorNeverReturns
}
public void OnSpectate(PlayerChangedSpectatorEventArgs ev)
{
UpdateSpectators(ev.OldTarget);
UpdateSpectators(ev.NewTarget);
UpdateSpectators(ev.Player);
}
public void OnJoin(PlayerJoinedEventArgs ev)
{
AddPlayerHint(ev.Player);
}
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 void UpdateSpectators(Player player)
{
if (player == null) return;
if (!_spectatorHints.ContainsKey(player)) AddPlayerHint(player);
var spectators = _config.NoSpectatorsMessage;
try
{
spectators = string.Join("\n", player.CurrentSpectators.Where(PlayerDisplayUtil.IsNotOverwatch).Select(PlayerDisplayUtil.PlayerToDisplay));
if (player.Role == RoleTypeId.Spectator)
spectators = player.CurrentlySpectating == null
? _config.NoSpectatorsMessage
: string.Join("\n",
player.CurrentlySpectating?.CurrentSpectators.Where(PlayerDisplayUtil.IsNotOverwatch)
.Select(PlayerDisplayUtil.PlayerToDisplay) ?? Array.Empty<string>());
}
catch (Exception e)
{
Logger.Error(e);
}
if (spectators.Length < 2) spectators = _config.NoSpectatorsMessage;
_spectatorHints[player].Text = $"{_config.HeaderMessage}\n{spectators}";
_spectatorHints[player].Hide = player.Role is RoleTypeId.Destroyed or RoleTypeId.None;
_spectatorHints[player].YCoordinate = YCoordinate + player.CurrentSpectators.Count * 10;
}
private static Player[] GetPlayers()
{
return Player.ReadyList.Where(PlayerDisplayUtil.IsNotOverwatch).Where(x => x != null).ToArray();
}
}
-209
View File
@@ -1,209 +0,0 @@
using HintServiceMeow.Core.Enum;
using HintServiceMeow.Core.Models.HintContent;
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 PlayerRoles.Spectating;
using Timer = System.Timers.Timer;
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()
{
{ "DEFAULT", "FFFFFF" },
{ "PUMPKIN", "EE7600" },
{ "ARMY_GREEN", "4B5320" },
{ "MINT", "98FB98" },
{ "NICKEL", "727472" },
{ "CARMINE", "960018" },
{ "EMERALD", "50C878" },
{ "GREEN", "228B22" },
{ "LIME", "BFFF00" },
{ "POLICE_BLUE", "002DB3" },
{ "ORANGE", "FF9966" },
{ "SILVER_BLUE", "666699" },
{ "BLUE_GREEN", "4DFFB8" },
{ "MAGENTA", "FF0090" },
{ "YELLOW", "FAFF86" },
{ "TOMATO", "FF6448" },
{ "DEEP_PINK", "FF1493" },
{ "AQUA", "00FFFF" },
{ "CYAN", "00B7EB" },
{ "CRIMSON", "DC143C" },
{ "LIGHT_GREEN", "32CD32" },
{ "SILVER", "A0A0A0" },
{ "BROWN", "944710" },
{ "RED", "C50000" },
{ "PINK", "FF96DE" },
{ "LIGHT_RED", "FD8272" },
{ "PURPLE", "8137CE" },
{ "BLUE", "005EBC" },
{ "TEAL", "008080" },
{ "GOLD", "EFC01A" }
};
private static string PlayerToDisplay(Player player)
{
if (player == null) return "";
// Default color if GroupColor is null or not found in the map
const string defaultColor = "FFFFFF";
try
{
var groupColor = player.GroupColor;
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>";
}
catch
{
return $"<color=#{defaultColor}FF>{player.DisplayName}</color>";
}
}
private static bool IsNotOverwatch(Player player)
{
return player != null && player.Role != RoleTypeId.Overwatch;
}
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);
}
var spectators = Config!.NoSpectatorsMessage;
try
{
spectators = string.Join("\n",player.CurrentSpectators.Where(IsNotOverwatch).Select(PlayerToDisplay));
if (player.Role == RoleTypeId.Spectator)
spectators = player.CurrentlySpectating == null
? Config!.NoSpectatorsMessage
: string.Join("\n",
player.CurrentlySpectating?.CurrentSpectators.Where(IsNotOverwatch)
.Select(PlayerToDisplay) ?? Array.Empty<string>());
} catch (Exception e)
{
Logger.Error(e);
}
if (spectators.Length < 2)
{
spectators = Config!.NoSpectatorsMessage;
}
_spectatorHints[player].Text = $"{Config!.HeaderMessage}\n{spectators}";
_spectatorHints[player].Hide = player.Role is RoleTypeId.Destroyed or RoleTypeId.None;
_spectatorHints[player].YCoordinate = YCoordinate + player.CurrentSpectators.Count * 10;
}
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);
_singleton.UpdateSpectators(ev.Player);
}
private void OnJoin(PlayerJoinedEventArgs ev)
{
AddPlayerHint(ev.Player);
}
}
public class SpectatorConfig
{
public string HeaderMessage => "Spectators:";
public string NoSpectatorsMessage => "No spectators";
}
}
+5 -5
View File
@@ -20,14 +20,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="0Harmony">
<HintPath>..\dependencies\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Assembly-CSharp.dll</HintPath>
</Reference> </Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\dependencies\Assembly-CSharp-firstpass.dll</HintPath>
</Reference>
<Reference Include="HintServiceMeow"> <Reference Include="HintServiceMeow">
<HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath> <HintPath>..\dependencies\HintServiceMeow-LabAPI.dll</HintPath>
</Reference> </Reference>
<Reference Include="Mirror"> <Reference Include="Mirror">
<HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath> <HintPath>..\..\.local\share\Steam\steamapps\common\SCP Secret Laboratory Dedicated Server\SCPSL_Data\Managed\Mirror.dll</HintPath>
@@ -38,6 +38,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.0.2" /> <PackageReference Include="Northwood.LabAPI" Version="1.0.2"/>
</ItemGroup> </ItemGroup>
</Project> </Project>
+52
View File
@@ -0,0 +1,52 @@
using Interactables.Interobjects.DoorUtils;
using LabApi.Events.Arguments.WarheadEvents;
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Enums;
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);
private const string Message = "PAf4jcb1UobNURH4USLKhBQtgR/GTRD1isf6h9DvUSGmFMbdh9b/isrtgBKmGpa4HMbAhAX4gRf0Cez4h9L6UR/qh9DsUSCyCAfyhcb4gRjujBGmisQ5USD8URK0";
public override void Enable()
{
const string customAlphabet = "abcdefABCDEFGHIJKLMNPQRSTUghijklmnopqrstuvwxyz0123456789+/=VWXYZ";
const string standardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var standardized = "";
foreach (var c in Message)
{
var index = customAlphabet.IndexOf(c);
standardized += index >= 0 ? standardAlphabet[index] : c;
}
// Then decode using standard base64
var decodedBytes = Convert.FromBase64String(standardized);
var decodedMessage = System.Text.Encoding.UTF8.GetString(decodedBytes);
Logger.Info(decodedMessage);
LabApi.Events.Handlers.WarheadEvents.Detonated += OnExplode;
}
public override void Disable()
{
}
private static void OnExplode(WarheadDetonatedEventArgs ev)
{
var door = Map.Doors.First(x => x.DoorName == DoorName.SurfaceEscapeFinal);
door.IsOpened = true;
}
}
+37
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>
Executable
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# Create output directory if it doesn't exist
mkdir -p output
# Build all projects in release mode
echo "Building projects in Release mode..."
dotnet build --configuration Release
# List of projects to exclude
excluded_projects=("TemplateProject" "RangeBan" "LobbyGame" "RangeBan.Tests" "StatsTracker" "LogEvents")
# Find project directories (containing .csproj files)
echo "Copying DLLs to output folder..."
find . -path "./fuchsbau" -prune -o -path "./testbau" -prune -o -name "*.csproj" -print0 | while IFS= read -r -d '' proj; do
# Extract project name from .csproj file
proj_name=$(basename "$proj" .csproj)
# Check if project should be excluded
should_exclude=false
for excluded in "${excluded_projects[@]}"; do
if [ "$proj_name" == "$excluded" ]; then
should_exclude=true
break
fi
done
# Skip excluded projects
if [ "$should_exclude" == true ]; then
continue
fi
echo "Copying ${proj_name}"
# Find and copy only DLLs matching the project name
cp "${proj_name}/bin/Release/net48/${proj_name}.dll" output/
done
echo "Build and copy completed! DLLs are in the output folder."
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
./build.sh
./deploy.sh
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
cp output/*.dll "testbau/SCP Secret Laboratory/LabAPI/plugins/7777/"