various fixes + new custom class shadowmancer

This commit is contained in:
code002lover 2025-07-12 20:18:02 +02:00
parent 0615f8aeea
commit 78fa56dce9
10 changed files with 356 additions and 10 deletions

View File

@ -98,7 +98,7 @@ public class AfkSwap : Plugin
continue;
}
if (!_playerPositions[playerTime.Key].Equals(playerTime.Key.Position))
if ((_playerPositions[playerTime.Key] - playerTime.Key.Position).sqrMagnitude > 2)
{
_playerSpawnTimes.Remove(playerTime.Key);
_playerPositions.Remove(playerTime.Key);

View File

@ -11,11 +11,13 @@ using LabApi.Events.Arguments.Scp914Events;
using LabApi.Events.Arguments.ServerEvents;
using LabApi.Events.Handlers;
using LabApi.Features;
using LabApi.Features.Enums;
using LabApi.Features.Wrappers;
using LabApi.Loader.Features.Plugins;
using MapGeneration;
using MEC;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp106;
using Scp914.Processors;
using UnityEngine;
using Logger = LabApi.Features.Console.Logger;
@ -32,6 +34,7 @@ public sealed class CustomClasses : Plugin
{
public readonly CustomClassManager ClassManager = new();
public SerpentsHandManager SerpentsHandManager;
public NegromancerManager NegromancerManager;
/// <inheritdoc/>
public override string Name => "CustomClasses";
@ -77,6 +80,8 @@ public sealed class CustomClasses : Plugin
public ExplosiveMasterConfig ExplosiveMasterConfig { get; private set; } = new();
public FlashMasterConfig FlashMasterConfig { get; private set; } = new();
public SerpentsHandConfig SerpentsHandConfig { get; private set; } = new();
public NegromancerConfig NegromancerConfig { get; private set; } = new();
public NegromancerShadowConfig NegromancerShadowConfig { get; private set; } = new();
internal readonly Dictionary<Player, Hint> Hints = new();
@ -142,6 +147,7 @@ public sealed class CustomClasses : Plugin
}
}
NegromancerManager = new NegromancerManager(this);
Instance = this;
}
@ -330,6 +336,8 @@ public class CustomClassManager
RegisterHandler<ExplosiveMasterConfig>(new ExplosiveMasterHandler());
RegisterHandler<FlashMasterConfig>(new FlashMasterHandler());
RegisterHandler<SerpentsHandConfig>(new SerpentsHandHandler(), new SerpentsHandState());
RegisterHandler<NegromancerConfig>(new NegromancerHandler());
RegisterHandler<NegromancerShadowConfig>(new NegromancerShadowHandler());
}
public SpawnState GetSpawnState(Type configType)
@ -420,6 +428,29 @@ public class CustomClassManager
}
}
public class NegromancerShadowHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
player.MaxHealth = 1000;
player.MaxHumeShield = 0;
player.HumeShield = 0;
player.EnableEffect<MovementBoost>(10, float.PositiveInfinity);
const string customInfo = "<color=#A0A0A0>Shadow</color>";
if (!Player.ValidateCustomInfo(customInfo, out var reason))
{
Logger.Error($"Invalid custom info for Shadow: {reason}");
}
else
{
player.CustomInfo = customInfo;
player.InfoArea |= PlayerInfoArea.CustomInfo;
}
}
}
/// <summary>
/// Interface for custom class spawn handlers.
/// </summary>
@ -444,6 +475,15 @@ public abstract class CustomClassHandler: ICustomClassHandler
}
}
public enum JanitorSpawn
{
Lcz173,
Lcz914,
LczGr18,
Lcz330
}
/// <summary>
/// Handler for the Janitor custom class.
/// </summary>
@ -451,13 +491,51 @@ public class JanitorHandler(CustomClassManager manager) : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
var scp914 = Map.Rooms.FirstOrDefault(r => r.Name == RoomName.Lcz914);
if (scp914 == null)
var spawnLocation = (JanitorSpawn)random.Next(0, 4);
switch (spawnLocation)
{
Logger.Error("LCZ 914 room not found for Janitor spawn.");
return;
case JanitorSpawn.Lcz914:
var scp914 = Map.Rooms.FirstOrDefault(r => r.Name == RoomName.Lcz914);
if (scp914 == null)
{
Logger.Error("LCZ 914 room not found for Janitor spawn.");
return;
}
manager.TeleportPlayerToAround(player, scp914.Position);
break;
case JanitorSpawn.Lcz173:
var lcz173Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.Lcz173Connector);
if (lcz173Door == null)
{
Logger.Error("LCZ 173 connector door not found for Janitor spawn.");
return;
}
manager.TeleportPlayerToAround(player, lcz173Door.Position);
break;
case JanitorSpawn.LczGr18:
var lczGr18Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.LczGr18Inner);
if (lczGr18Door == null)
{
Logger.Error("LCZ Gr18 door not found for Janitor spawn.");
return;
}
manager.TeleportPlayerToAround(player, lczGr18Door.Position);
break;
case JanitorSpawn.Lcz330:
var lcz330Door = Map.Doors.FirstOrDefault(x => x.DoorName == DoorName.Lcz330);
if (lcz330Door == null)
{
Logger.Error("LCZ 330 door not found for Janitor spawn.");
return;
}
manager.TeleportPlayerToAround(player, lcz330Door.Position);
break;
default:
throw new ArgumentOutOfRangeException();
}
manager.TeleportPlayerToAround(player, scp914.Position);
foreach (var spawnItem in config.Items)
{
player.AddItem(spawnItem, ItemAddReason.StartingItem);
@ -865,6 +943,22 @@ public sealed class FlashMasterConfig : CustomClassConfig
public override ItemType[] Items { get; set; } = [ItemType.GrenadeFlash];
}
public sealed class NegromancerConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.0;
public override int MaxSpawns { get; set; } = 1;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp049;
public override ItemType[] Items { get; set; } = [];
}
public sealed class NegromancerShadowConfig : CustomClassConfig
{
public override double ChancePerPlayer { get; set; } = 0.0;
public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp106;
public override ItemType[] Items { get; set; } = [];
}
/// <summary>
/// Tracks the spawn state for a custom class.
/// </summary>

View File

@ -0,0 +1,164 @@
using System.Net;
using CustomPlayerEffects;
using LabApi.Events.Arguments.Scp049Events;
using LabApi.Events.Handlers;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles;
using PlayerRoles.PlayableScps.Scp049;
using PlayerRoles.PlayableScps.Scp106;
namespace CustomClasses;
public class NegromancerHandler : CustomClassHandler
{
public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
player.SendBroadcast("You are the <color=#6e2e99>Negromancer</color>! Revived players become your <color=#3c1361>Shadow</color>.", CustomClasses.BroadcastDuration);
const string customInfo = "<color=#A0A0A0>Shadowmancer</color>";
if (!Player.ValidateCustomInfo(customInfo, out var reason))
{
Logger.Error($"Invalid custom info for Negromancer: {reason}");
}
else
{
player.CustomInfo = customInfo;
player.InfoArea |= PlayerInfoArea.CustomInfo;
}
}
}
public class NegromancerManager
{
private readonly CustomClasses _plugin;
public static bool IsNegromancer(Player player) => player.CustomInfo.Contains("Shadowmancer");
public static bool IsShadow(Player player) => player.CustomInfo.Contains("Shadow") && !IsNegromancer(player);
public NegromancerManager(CustomClasses plugin)
{
_plugin = plugin;
Scp049Events.ResurrectedBody += OnScp049ResurrectedBody;
Timing.RunCoroutine(HealNearbyShadows());
Scp106Events.TeleportingPlayer += ev =>
{
if (!IsShadow(ev.Player)) return;
ev.IsAllowed = false;
ev.Target.EnableEffect<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;
// Check if the reviver is a Negromancer
if (classManager == null ||
!IsNegromancer(ev.Player)) return;
ev.Target.SetRole(RoleTypeId.Scp106, RoleChangeReason.Respawn, RoleSpawnFlags.None);
classManager.ForceSpawn(ev.Target, _plugin.NegromancerShadowConfig, typeof(NegromancerShadowConfig), null);
}
}

View File

@ -47,6 +47,12 @@ public class SerpentsHandManager
}
}
if (Warhead.IsDetonationInProgress || Warhead.IsDetonated)
{
hadItem = true;
state.Points += 1;
}
if (!hadItem) return;
ev.Player.SendBroadcast("You brought back the SCP items...", 5);
@ -108,6 +114,8 @@ public class SerpentsHandManager
{
yield return Timing.WaitForSeconds(1);
RoundSummary.singleton.ExtraTargets = Player.ReadyList.Count(IsSerpentsHand);
if (_customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) continue;
foreach (var player in Player.ReadyList)
@ -134,10 +142,10 @@ public class SerpentsHandManager
public sealed record SerpentsHandState: SpawnState
{
public bool HasSpawned => _hasSpawned || PanicDisable;
public bool HasSpawned => _hasSpawned || PanicDisable || Warhead.IsDetonated;
public float ExtraChance;
public int Points;
public bool WillSpawn => _willSpawn && !PanicDisable;
public bool WillSpawn => _willSpawn && !PanicDisable && !Warhead.IsDetonated;
private bool _hasSpawned;
private bool _willSpawn;

View File

@ -1,9 +1,11 @@
using CommandSystem;
using LabApi.Features.Permissions;
using LabApi.Features.Wrappers;
namespace CustomClasses;
[CommandHandler(typeof(RemoteAdminCommandHandler))]
[CommandHandler(typeof(ClientCommandHandler))]
public class SetCClassCommand : ICommand
{
public string Command => "setcclass";
@ -11,6 +13,19 @@ public class SetCClassCommand : ICommand
public string Description => "Forces a player to become a specific custom class";
public bool Execute(ArraySegment<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)
{
@ -51,6 +66,7 @@ public class SetCClassCommand : ICommand
{
SerpentsHandManager.PreSpawn(player);
}),
"negromancer" => manager.ForceSpawn(player, customClasses.NegromancerConfig, typeof(NegromancerConfig), null),
_ => false
};

View File

@ -46,6 +46,11 @@ public class ScpSwap : Plugin
return;
}
if (ev.Role.RoleTypeId == RoleTypeId.Scp0492)
{
return;
}
ev.Player.SendBroadcast("Willst du dein SCP wechseln? Drücke Ö und gebe .scpswap <SCP-NUMMER> ein.", 10);
};
}

View File

@ -33,6 +33,12 @@ public class SwapCommand : ICommand
return false;
}
if (player.Role == RoleTypeId.Scp0492)
{
response = "You can't swap SCPs while you're a zombie!";
return false;
}
List<string> validScp =
[
"049",

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;
}
}

View File

@ -1,5 +1,4 @@
using PlayerRoles;
using LabApi.Features;
using LabApi.Features.Wrappers;
namespace VisibleSpectators;
@ -43,12 +42,24 @@ public static class PlayerDisplayUtil
{ "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
{

View File

@ -12,7 +12,7 @@ excluded_projects=("TemplateProject" "RangeBan" "LobbyGame" "RangeBan.Tests" "St
# Find project directories (containing .csproj files)
echo "Copying DLLs to output folder..."
for proj in $(find . -name "*.csproj"); do
find . -path "./fuchsbau" -prune -o -path "./testbau" -prune -o -name "*.csproj" -print0 | while IFS= read -r -d '' proj; do
# Extract project name from .csproj file
proj_name=$(basename "$proj" .csproj)