9 changed files with 302 additions and 183 deletions
+102 -19
View File
@@ -1,5 +1,11 @@
using CustomPlayerEffects;
using InventorySystem.Items.Usables.Scp330;
using LabApi.Events.Handlers;
using LabApi.Features.Wrappers; using LabApi.Features.Wrappers;
using MEC;
using PlayerRoles.FirstPersonControl; using PlayerRoles.FirstPersonControl;
using PlayerRoles.PlayableScps.Scp939;
using static LabApi.Features.Wrappers.Server;
using Logger = LabApi.Features.Console.Logger; using Logger = LabApi.Features.Console.Logger;
using Random = System.Random; using Random = System.Random;
@@ -19,7 +25,7 @@ public class DisableStaminaRegenEffect : CustomPlayerEffect, IStaminaModifier
public class BloodFueledStaminaEffect : CustomPlayerEffect, IStaminaModifier public class BloodFueledStaminaEffect : CustomPlayerEffect, IStaminaModifier
{ {
public bool StaminaModifierActive => IsEnabled; public bool StaminaModifierActive => IsEnabled;
public float StaminaUsageMultiplier => 0.1f; public float StaminaUsageMultiplier => 0.2f;
public float StaminaRegenMultiplier => 1; public float StaminaRegenMultiplier => 1;
public bool SprintingDisabled => false; public bool SprintingDisabled => false;
@@ -29,14 +35,98 @@ public class BloodFueledStaminaEffect : CustomPlayerEffect, IStaminaModifier
public class BloodFueledManager public class BloodFueledManager
{ {
private readonly CustomClasses _plugin; public static bool IsBloodFueled(Player player)
public static bool IsBloodFueled(Player player) => player.CustomInfo.Contains("Blood Fueled");
public BloodFueledManager(CustomClasses plugin)
{ {
_plugin = plugin; 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
} }
} }
@@ -44,18 +134,6 @@ public class BloodFueledHandler : CustomClassHandler
{ {
public override void HandleSpawn(Player player, CustomClassConfig config, Random random) public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{ {
player.SendBroadcast("You are the <color=#6e2e99>Blood Fueled</color>!", CustomClasses.BroadcastDuration);
const string customInfo = "<color=#A0A0A0>Blood Fueled</color>";
if (!Player.ValidateCustomInfo(customInfo, out var reason))
{
Logger.Error($"Invalid custom info for Blood Fueled: {reason}");
}
else
{
player.CustomInfo = customInfo;
player.InfoArea |= PlayerInfoArea.CustomInfo;
}
player.MaxHumeShield = 0; player.MaxHumeShield = 0;
player.HumeShield = 0; player.HumeShield = 0;
player.MaxHealth = 3500; player.MaxHealth = 3500;
@@ -64,4 +142,9 @@ public class BloodFueledHandler : CustomClassHandler
player.EnableEffect<DisableStaminaRegenEffect>(1, float.PositiveInfinity); player.EnableEffect<DisableStaminaRegenEffect>(1, float.PositiveInfinity);
player.EnableEffect<BloodFueledStaminaEffect>(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);
}
} }
+65 -74
View File
@@ -17,7 +17,6 @@ using LabApi.Loader.Features.Plugins;
using MapGeneration; using MapGeneration;
using MEC; using MEC;
using PlayerRoles; using PlayerRoles;
using PlayerRoles.PlayableScps.Scp106;
using Scp914.Processors; using Scp914.Processors;
using UnityEngine; using UnityEngine;
using Logger = LabApi.Features.Console.Logger; using Logger = LabApi.Features.Console.Logger;
@@ -35,6 +34,7 @@ public sealed class CustomClasses : Plugin
public readonly CustomClassManager ClassManager = new(); public readonly CustomClassManager ClassManager = new();
public SerpentsHandManager SerpentsHandManager; public SerpentsHandManager SerpentsHandManager;
public NegromancerManager NegromancerManager; public NegromancerManager NegromancerManager;
public BloodFueledManager BloodFueledManager = new();
/// <inheritdoc/> /// <inheritdoc/>
public override string Name => "CustomClasses"; public override string Name => "CustomClasses";
@@ -53,45 +53,6 @@ public sealed class CustomClasses : Plugin
public const ushort BroadcastDuration = 10; public const ushort BroadcastDuration = 10;
/// <summary>
/// Configuration for the Janitor class.
/// </summary>
public JanitorConfig JanitorConfig { get; private set; } = new();
/// <summary>
/// Configuration for the Research Subject class.
/// </summary>
public ResearchSubjectConfig ResearchSubjectConfig { get; private set; } = new();
/// <summary>
/// Configuration for the Head Guard class.
/// </summary>
public HeadGuardConfig HeadGuardConfig { get; private set; } = new();
/// <summary>
/// Configuration for the Medic class.
/// </summary>
public MedicConfig MedicConfig { get; private set; } = new();
/// <summary>
/// Configuration for the Gambler class.
/// </summary>
public GamblerConfig GamblerConfig { get; private set; } = new();
/// <summary>
/// Configuration for the ShadowStepper class.
/// </summary>
public ShadowStepperConfig ShadowStepperConfig { get; private set; } = new();
public MtfDemolitionistConfig MtfDemolitionistConfig { get; private set; } = new();
public ScoutConfig ScoutConfig { get; private set; } = new();
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();
public BloodFueledConfig BloodFueledConfig { get; private set; } = new();
internal readonly Dictionary<Player, Hint> Hints = new(); internal readonly Dictionary<Player, Hint> Hints = new();
public static CustomClasses Instance { get; private set; } public static CustomClasses Instance { get; private set; }
@@ -221,12 +182,11 @@ public sealed class CustomClasses : Plugin
{ {
if (random.Next(0, 100) > SerpentsHandConfig.BaseChance + state.ExtraChance) if (random.Next(0, 100) > SerpentsHandConfig.BaseChance + state.ExtraChance)
{ {
state.SetSpawned(); SerpentsHandManager.SpawnSerpentWave();
state.SetWillSpawn();
ClassManager.TryHandleSpawn(spectator, ScoutConfig, typeof(ScoutConfig), () => ClassManager.TryHandleSpawn(spectator, ClassManager.GetConfig<ScoutConfig>(), typeof(ScoutConfig), () =>
{ {
if (!ClassManager.ForceSpawn(spectator, SerpentsHandConfig, typeof(SerpentsHandConfig), PreSpawn)) if (!ClassManager.ForceSpawn(spectator, ClassManager.GetConfig<SerpentsHandConfig>(), typeof(SerpentsHandConfig), PreSpawn))
Logger.Error("Serpents Hand didn't spawn"); Logger.Error("Serpents Hand didn't spawn");
return; return;
@@ -240,7 +200,7 @@ public sealed class CustomClasses : Plugin
} }
} }
if (ClassManager.TryHandleSpawn(spectator, ScoutConfig, typeof(ScoutConfig), () => if (ClassManager.TryHandleSpawn(spectator, ClassManager.GetConfig<ScoutConfig>(), typeof(ScoutConfig), () =>
{ {
spectator.SetRole(ev.Wave.Faction == Faction.FoundationStaff ? RoleTypeId.NtfPrivate : RoleTypeId.ChaosConscript, RoleChangeReason.Respawn, RoleSpawnFlags.UseSpawnpoint); spectator.SetRole(ev.Wave.Faction == Faction.FoundationStaff ? RoleTypeId.NtfPrivate : RoleTypeId.ChaosConscript, RoleChangeReason.Respawn, RoleSpawnFlags.UseSpawnpoint);
})) return; })) return;
@@ -260,7 +220,7 @@ public sealed class CustomClasses : Plugin
ev.IsAllowed = false; ev.IsAllowed = false;
foreach (var evSpawningPlayer in ev.SpawningPlayers) foreach (var evSpawningPlayer in ev.SpawningPlayers)
{ {
if (!ClassManager.ForceSpawn(evSpawningPlayer, SerpentsHandConfig, typeof(SerpentsHandConfig), PreSpawn)) if (!ClassManager.ForceSpawn(evSpawningPlayer, ClassManager.GetConfig<SerpentsHandConfig>(), typeof(SerpentsHandConfig), PreSpawn))
Logger.Error("Serpents Hand didn't spawn"); Logger.Error("Serpents Hand didn't spawn");
continue; continue;
@@ -281,16 +241,10 @@ public sealed class CustomClasses : Plugin
{ {
ev.Player.CustomInfo = ""; ev.Player.CustomInfo = "";
if (ClassManager.TryHandleSpawn(ev.Player, JanitorConfig, typeof(JanitorConfig), null)) return; if (ClassManager.Configs.Any(classManagerConfig => ClassManager.TryHandleSpawn(ev.Player, classManagerConfig.Value, classManagerConfig.Key, null)))
if (ClassManager.TryHandleSpawn(ev.Player, ResearchSubjectConfig, typeof(ResearchSubjectConfig), null)) return; {
if (ClassManager.TryHandleSpawn(ev.Player, HeadGuardConfig, typeof(HeadGuardConfig), null)) return;
if (ClassManager.TryHandleSpawn(ev.Player, MedicConfig, typeof(MedicConfig), null)) return;
if (ClassManager.TryHandleSpawn(ev.Player, GamblerConfig, typeof(GamblerConfig), null)) return;
if (ClassManager.TryHandleSpawn(ev.Player, ShadowStepperConfig, typeof(ShadowStepperConfig), null)) return;
if (ClassManager.TryHandleSpawn(ev.Player, MtfDemolitionistConfig, typeof(MtfDemolitionistConfig), null))
return; return;
if (ClassManager.TryHandleSpawn(ev.Player, ExplosiveMasterConfig, typeof(ExplosiveMasterConfig), null)) return; }
if (ClassManager.TryHandleSpawn(ev.Player, BloodFueledConfig, typeof(BloodFueledConfig), null)) return;
} }
private static void OnScp914ProcessingPickup(Scp914ProcessingPickupEventArgs ev) private static void OnScp914ProcessingPickup(Scp914ProcessingPickupEventArgs ev)
@@ -331,6 +285,7 @@ public class CustomClassManager
private readonly object _lock = new(); private readonly object _lock = new();
private readonly Random _random = new(); private readonly Random _random = new();
private readonly Dictionary<Type, SpawnState> _spawnStates = new(); private readonly Dictionary<Type, SpawnState> _spawnStates = new();
public Dictionary<Type, CustomClassConfig> Configs { get; } = new();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CustomClassManager"/> class and registers all handlers. /// Initializes a new instance of the <see cref="CustomClassManager"/> class and registers all handlers.
@@ -368,7 +323,7 @@ public class CustomClassManager
/// <param name="position">The base position.</param> /// <param name="position">The base position.</param>
public void TeleportPlayerToAround(Player player, Vector3 position) public void TeleportPlayerToAround(Player player, Vector3 position)
{ {
player.Position = position + new Vector3(0, 1, 0) + new Vector3((float)(_random.NextDouble() * 2 - 1), 0, (float)(_random.NextDouble() * 2 - 1)); player.Position = position + new Vector3(0, 0.5f, 0) + new Vector3((float)(_random.NextDouble() * 2 - 1), 0, (float)(_random.NextDouble() * 2 - 1));
} }
/// <summary> /// <summary>
@@ -379,13 +334,30 @@ public class CustomClassManager
/// <param name="spawnState">Optional custom spawn state</param> /// <param name="spawnState">Optional custom spawn state</param>
private void RegisterHandler<T>(ICustomClassHandler handler, [CanBeNull] SpawnState spawnState = null) where T : CustomClassConfig 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) lock (_lock)
{ {
_spawnStates[typeof(T)] = spawnState ?? new SpawnState(); _spawnStates[typeof(T)] = spawnState ?? new SpawnState();
_handlers[typeof(T)] = handler; _handlers[typeof(T)] = handler;
Configs[typeof(T)] = config;
} }
} }
public T GetConfig<T>() where T : CustomClassConfig
{
return (T)Configs[typeof(T)];
}
/// <summary> /// <summary>
/// Resets all spawn states for a new round. /// Resets all spawn states for a new round.
/// </summary> /// </summary>
@@ -445,22 +417,13 @@ public class NegromancerShadowHandler : CustomClassHandler
{ {
public override void HandleSpawn(Player player, CustomClassConfig config, Random random) public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{ {
base.HandleSpawn(player,config,random);
player.MaxHealth = 1000; player.MaxHealth = 1000;
player.MaxHumeShield = 0; player.MaxHumeShield = 0;
player.HumeShield = 0; player.HumeShield = 0;
player.EnableEffect<MovementBoost>(10, float.PositiveInfinity); 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;
}
} }
} }
@@ -480,12 +443,32 @@ public interface ICustomClassHandler
public abstract class CustomClassHandler: ICustomClassHandler public abstract class CustomClassHandler: ICustomClassHandler
{ {
public abstract void HandleSpawn(Player player, CustomClassConfig config, Random random); public virtual void HandleSpawn(Player player, CustomClassConfig config, Random random)
{
var info = config.FullCustomInfo;
public virtual void HandleEscape(Player player, CustomClassConfig config) 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 //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 public enum JanitorSpawn
@@ -644,7 +627,7 @@ public class ShadowStepperHandler : CustomClassHandler
player.SendBroadcast("You're a <color=#000000>ShadowStepper</color>!", CustomClasses.BroadcastDuration); player.SendBroadcast("You're a <color=#000000>ShadowStepper</color>!", CustomClasses.BroadcastDuration);
} }
public override void HandleEscape(Player player, CustomClassConfig config) protected override void HandleEscape(Player player, CustomClassConfig config)
{ {
base.HandleEscape(player, config); base.HandleEscape(player, config);
@@ -665,6 +648,7 @@ public abstract class SimpleAddItemHandler : CustomClassHandler
{ {
public override void HandleSpawn(Player player, CustomClassConfig config, Random random) public override void HandleSpawn(Player player, CustomClassConfig config, Random random)
{ {
base.HandleSpawn(player,config,random);
foreach (var spawnItem in config.Items) foreach (var spawnItem in config.Items)
{ {
player.AddItem(spawnItem, ItemAddReason.StartingItem); player.AddItem(spawnItem, ItemAddReason.StartingItem);
@@ -869,6 +853,11 @@ public abstract class CustomClassConfig
/// The required role for this class to be considered. /// The required role for this class to be considered.
/// </summary> /// </summary>
public virtual RoleTypeId RequiredRole { get; set; } = RoleTypeId.ClassD; 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> /// <summary>
@@ -959,9 +948,9 @@ public sealed class FlashMasterConfig : CustomClassConfig
public sealed class NegromancerConfig : CustomClassConfig public sealed class NegromancerConfig : CustomClassConfig
{ {
public override double ChancePerPlayer { get; set; } = 0.0; 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 RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp049;
public override ItemType[] Items { get; set; } = []; public override string Name { get; init; } = "Shadowmancer";
public override string Color { get; init; } = "#A0A0A0";
} }
public sealed class NegromancerShadowConfig : CustomClassConfig public sealed class NegromancerShadowConfig : CustomClassConfig
@@ -969,7 +958,8 @@ public sealed class NegromancerShadowConfig : CustomClassConfig
public override double ChancePerPlayer { get; set; } = 0.0; public override double ChancePerPlayer { get; set; } = 0.0;
public override int MaxSpawns { get; set; } = int.MaxValue; public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp106; public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp106;
public override ItemType[] Items { get; set; } = []; public override string Name { get; init; } = "Shadow";
public override string Color { get; init; } = "#A0A0A0";
} }
public sealed class BloodFueledConfig : CustomClassConfig public sealed class BloodFueledConfig : CustomClassConfig
@@ -977,7 +967,8 @@ public sealed class BloodFueledConfig : CustomClassConfig
public override double ChancePerPlayer { get; set; } = 1.0; public override double ChancePerPlayer { get; set; } = 1.0;
public override int MaxSpawns { get; set; } = int.MaxValue; public override int MaxSpawns { get; set; } = int.MaxValue;
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp939; public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Scp939;
public override ItemType[] Items { get; set; } = []; public override string Name { get; init; } = "Blood Fueled";
public override string Color { get; init; } = "#C50000";
} }
/// <summary> /// <summary>
+2 -14
View File
@@ -1,4 +1,3 @@
using System.Net;
using CustomPlayerEffects; using CustomPlayerEffects;
using LabApi.Events.Arguments.Scp049Events; using LabApi.Events.Arguments.Scp049Events;
using LabApi.Events.Handlers; using LabApi.Events.Handlers;
@@ -13,19 +12,9 @@ namespace CustomClasses;
public class NegromancerHandler : CustomClassHandler public class NegromancerHandler : CustomClassHandler
{ {
public override void HandleSpawn(Player player, CustomClassConfig config, Random random) 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); 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;
}
} }
} }
@@ -153,12 +142,11 @@ public class NegromancerManager
private void OnScp049ResurrectedBody(Scp049ResurrectedBodyEventArgs ev) private void OnScp049ResurrectedBody(Scp049ResurrectedBodyEventArgs ev)
{ {
var classManager = _plugin.ClassManager; var classManager = _plugin.ClassManager;
// Check if the reviver is a Negromancer
if (classManager == null || if (classManager == null ||
!IsNegromancer(ev.Player)) return; !IsNegromancer(ev.Player)) return;
ev.Target.SetRole(RoleTypeId.Scp106, RoleChangeReason.Respawn, RoleSpawnFlags.None); ev.Target.SetRole(RoleTypeId.Scp106, RoleChangeReason.Respawn, RoleSpawnFlags.None);
classManager.ForceSpawn(ev.Target, _plugin.NegromancerShadowConfig, typeof(NegromancerShadowConfig), null); 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();
}
}
+87 -47
View File
@@ -1,3 +1,4 @@
using AdminToys;
using CommandSystem; using CommandSystem;
using CustomPlayerEffects; using CustomPlayerEffects;
using HintServiceMeow.Core.Enum; using HintServiceMeow.Core.Enum;
@@ -11,14 +12,26 @@ using LabApi.Features.Wrappers;
using MEC; using MEC;
using PlayerRoles; using PlayerRoles;
using UnityEngine; using UnityEngine;
using LightSourceToy = LabApi.Features.Wrappers.LightSourceToy;
using Logger = LabApi.Features.Console.Logger; using Logger = LabApi.Features.Console.Logger;
using PrimitiveObjectToy = LabApi.Features.Wrappers.PrimitiveObjectToy;
using Random = System.Random; using Random = System.Random;
namespace CustomClasses; namespace CustomClasses;
public class SerpentsHandManager public class SerpentsHandManager
{ {
public static bool IsSerpentsHand(Player player) => player.CustomInfo.Contains("SerpentsHand"); public static bool IsSerpentsHand(Player player)
{
try
{
return player.CustomInfo.Contains("SerpentsHand");
}
catch
{
return false;
}
}
private readonly CustomClasses _customClasses; private readonly CustomClasses _customClasses;
public SerpentsHandManager(CustomClasses customClasses) public SerpentsHandManager(CustomClasses customClasses)
@@ -96,16 +109,6 @@ public class SerpentsHandManager
public static void PreSpawn(Player player) public static void PreSpawn(Player player)
{ {
player.SetRole(RoleTypeId.Tutorial, RoleChangeReason.RespawnMiniwave, RoleSpawnFlags.None); player.SetRole(RoleTypeId.Tutorial, RoleChangeReason.RespawnMiniwave, RoleSpawnFlags.None);
const string customInfo = "<color=#32CD32>SerpentsHand</color>";
if (!Player.ValidateCustomInfo(customInfo, out var reason))
{
Logger.Error($"Invalid custom info for Serpents Hand: {reason}");
}
else
{
player.CustomInfo = customInfo;
player.InfoArea |= PlayerInfoArea.CustomInfo;
}
} }
public IEnumerator<float> UpdateSerpentsHandHint() public IEnumerator<float> UpdateSerpentsHandHint()
@@ -114,8 +117,6 @@ public class SerpentsHandManager
{ {
yield return Timing.WaitForSeconds(1); yield return Timing.WaitForSeconds(1);
RoundSummary.singleton.ExtraTargets = Player.ReadyList.Count(IsSerpentsHand);
if (_customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) continue; if (_customClasses.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)) is not SerpentsHandState state) continue;
foreach (var player in Player.ReadyList) foreach (var player in Player.ReadyList)
@@ -138,19 +139,63 @@ public class SerpentsHandManager
} }
// ReSharper disable once IteratorNeverReturns // 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 sealed record SerpentsHandState: SpawnState
{ {
public bool HasSpawned => _hasSpawned || PanicDisable || Warhead.IsDetonated; public bool HasSpawned => _hasSpawned || Warhead.IsDetonated;
public float ExtraChance; public float ExtraChance;
public int Points; public int Points;
public bool WillSpawn => _willSpawn && !PanicDisable && !Warhead.IsDetonated; public bool WillSpawn => _willSpawn && !Warhead.IsDetonated;
private bool _hasSpawned; private bool _hasSpawned;
private bool _willSpawn; private bool _willSpawn;
public Vector3? SpawnLocation;
public bool PanicDisable;
public override void Reset() public override void Reset()
{ {
@@ -159,6 +204,7 @@ public sealed record SerpentsHandState: SpawnState
ExtraChance = 0f; ExtraChance = 0f;
Points = 0; Points = 0;
_willSpawn = false; _willSpawn = false;
SpawnLocation = null;
} }
public void SetSpawned() public void SetSpawned()
@@ -184,7 +230,11 @@ public sealed class SerpentsHandConfig : CustomClassConfig
public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Tutorial; public override RoleTypeId RequiredRole { get; set; } = RoleTypeId.Tutorial;
public override ItemType[] Items { get; set; } = [ItemType.Painkillers, ItemType.Medkit, ItemType.ArmorCombat]; public override ItemType[] Items { get; set; } = [ItemType.Painkillers, ItemType.Medkit, ItemType.ArmorCombat];
public const float BaseChance = 20f; 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 class SerpentsHandHandler : SimpleAddItemHandler
@@ -193,9 +243,11 @@ public class SerpentsHandHandler : SimpleAddItemHandler
{ {
base.HandleSpawn(player, config, random); base.HandleSpawn(player, config, random);
// player.Position = new Vector3(123.921f + (float)(random.NextDouble() * 2 - 1), 288.792f, 20.929f + (float)(random.NextDouble() * 2 - 1)); var spawnLocation = ((SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState(
typeof(SerpentsHandConfig))).SpawnLocation;
player.Position = new Vector3(0.22f + (float)(random.NextDouble() * 2 - 1), 300.96f, -0.31f + (float)(random.NextDouble() * 2 - 1)); if (spawnLocation !=
null)
CustomClasses.Instance.ClassManager.TeleportPlayerToAround(player, (Vector3)spawnLocation);
ItemType[] guns = [ItemType.GunAK, ItemType.GunE11SR, ItemType.GunCrossvec]; ItemType[] guns = [ItemType.GunAK, ItemType.GunE11SR, ItemType.GunCrossvec];
var gun = guns[random.Next(0, guns.Length-1)]; var gun = guns[random.Next(0, guns.Length-1)];
@@ -232,53 +284,41 @@ public class SerpentsHandHandler : SimpleAddItemHandler
player.EnableEffect<MovementBoost>(20, 30); 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); player.SendBroadcast("You're a <color=#2E8B57>Serpent's Hand</color> member!", CustomClasses.BroadcastDuration);
} }
} }
[CommandHandler(typeof(RemoteAdminCommandHandler))] [CommandHandler(typeof(RemoteAdminCommandHandler))]
public class PanicDisableRemoteAdminCommand : ICommand
{
public string Command => "panicdisableserpentshand";
public string[] Aliases => [];
public string Description => "Panic disable Serpents Hand.";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{
var state = (SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState(typeof(SerpentsHandConfig));
state.PanicDisable = true;
response = "Serpents Hand has been disabled.";
return true;
}
}
[CommandHandler(typeof(ClientCommandHandler))] [CommandHandler(typeof(ClientCommandHandler))]
public class PanicDisableCommand : ICommand public class SpawnSerpentsCommand : ICommand
{ {
public string Command => "panicdisableserpentshand"; public string Command => "spsh";
public string[] Aliases => []; public string[] Aliases => [];
public string Description => "Panic disable Serpents Hand."; public string Description => "Makes sure serpents hand spawns";
public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response) public bool Execute(ArraySegment<string> arguments, ICommandSender sender, out string response)
{ {
if (!Player.TryGet(sender, out var player)) var executor = Player.Get(sender);
if (executor == null)
{ {
response = "You must be a player to use this command!"; response = "You must be a player to use this command!";
return false; return false;
} }
if (!player.HasPermissions("panicdisable.serpentshand") && player.UserId != "76561198372587687@steam") if (!executor.HasPermissions("customclasses.setcustomclass"))
{ {
response = "You must have the permission to use this command!"; response = "You do not have permission to use this command!";
return false; return false;
} }
var state = (SerpentsHandState)CustomClasses.Instance.ClassManager.GetSpawnState(typeof(SerpentsHandConfig)); SerpentsHandManager.SpawnSerpentWave();
state.PanicDisable = true;
response = "Serpents Hand has been disabled."; response = "success";
return true; return true;
} }
} }
+16 -23
View File
@@ -33,41 +33,34 @@ public class SetCClassCommand : ICommand
return false; return false;
} }
// Find the last argument as the class name
var className = args[arguments.Offset + arguments.Count - 1].ToLower(); var className = args[arguments.Offset + arguments.Count - 1].ToLower();
// Join all arguments except the last one to get the full player name
var playerName = string.Join(" ", args.Skip(arguments.Offset).Take(arguments.Count - 1)); var playerName = string.Join(" ", args.Skip(arguments.Offset).Take(arguments.Count - 1));
var player = Player.ReadyList.FirstOrDefault(x => x.Nickname == playerName || x.UserId == playerName); var player = Player.ReadyList.FirstOrDefault(x => x.Nickname == playerName || x.UserId == playerName || x.NetworkId.ToString() == playerName);
if (player == null) if (player == null)
{ {
response = $"Player {playerName} not found"; response = $"Player {playerName} not found";
return false; return false;
} }
var customClasses = CustomClasses.Instance; var manager = CustomClasses.Instance.ClassManager;
var manager = new CustomClassManager();
var success = className switch var success = className switch
{ {
"janitor" => manager.ForceSpawn(player, customClasses.JanitorConfig, typeof(JanitorConfig), null), "janitor" => manager.ForceSpawn(player, manager.GetConfig<JanitorConfig>(), typeof(JanitorConfig), null),
"subject" or "researchsubject" => manager.ForceSpawn(player, customClasses.ResearchSubjectConfig, typeof(ResearchSubjectConfig), null), "subject" or "researchsubject" => manager.ForceSpawn(player, manager.GetConfig<ResearchSubjectConfig>(), typeof(ResearchSubjectConfig), null),
"headguard" => manager.ForceSpawn(player, customClasses.HeadGuardConfig, typeof(HeadGuardConfig), null), "headguard" => manager.ForceSpawn(player, manager.GetConfig<HeadGuardConfig>(), typeof(HeadGuardConfig), null),
"medic" => manager.ForceSpawn(player, customClasses.MedicConfig, typeof(MedicConfig), null), "medic" => manager.ForceSpawn(player, manager.GetConfig<MedicConfig>(), typeof(MedicConfig), null),
"gambler" => manager.ForceSpawn(player, customClasses.GamblerConfig, typeof(GamblerConfig), null), "gambler" => manager.ForceSpawn(player, manager.GetConfig<GamblerConfig>(), typeof(GamblerConfig), null),
"shadowstepper" => manager.ForceSpawn(player, customClasses.ShadowStepperConfig, typeof(ShadowStepperConfig), null), "shadowstepper" => manager.ForceSpawn(player, manager.GetConfig<ShadowStepperConfig>(), typeof(ShadowStepperConfig), null),
"demolitionist" => manager.ForceSpawn(player, customClasses.MtfDemolitionistConfig, typeof(MtfDemolitionistConfig), null), "demolitionist" => manager.ForceSpawn(player, manager.GetConfig<MtfDemolitionistConfig>(), typeof(MtfDemolitionistConfig), null),
"scout" => manager.ForceSpawn(player, customClasses.ScoutConfig, typeof(ScoutConfig), null), "scout" => manager.ForceSpawn(player, manager.GetConfig<ScoutConfig>(), typeof(ScoutConfig), null),
"explosivemaster" => manager.ForceSpawn(player, customClasses.ExplosiveMasterConfig, typeof(ExplosiveMasterConfig), null), "explosivemaster" => manager.ForceSpawn(player, manager.GetConfig<ExplosiveMasterConfig>(), typeof(ExplosiveMasterConfig), null),
"flashmaster" => manager.ForceSpawn(player, customClasses.FlashMasterConfig, typeof(FlashMasterConfig), null), "flashmaster" => manager.ForceSpawn(player, manager.GetConfig<FlashMasterConfig>(), typeof(FlashMasterConfig), null),
"serpentshand" => manager.ForceSpawn(player, customClasses.SerpentsHandConfig, typeof(SerpentsHandConfig), "serpentshand" => manager.ForceSpawn(player, manager.GetConfig<SerpentsHandConfig>(), typeof(SerpentsHandConfig),
() => () => SerpentsHandManager.PreSpawn(player)),
{ "negromancer" => manager.ForceSpawn(player, manager.GetConfig<NegromancerConfig>(), typeof(NegromancerConfig), null),
SerpentsHandManager.PreSpawn(player); "bloodfueled" => manager.ForceSpawn(player, manager.GetConfig<BloodFueledConfig>(), typeof(BloodFueledConfig), null),
}),
"negromancer" => manager.ForceSpawn(player, customClasses.NegromancerConfig, typeof(NegromancerConfig), null),
"bloodfueled" => manager.ForceSpawn(player, customClasses.BloodFueledConfig, typeof(BloodFueledConfig), null),
_ => false _ => false
}; };
+1 -1
View File
@@ -13,7 +13,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/> <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.8.1"/> <PackageReference Include="NUnit.Analyzers" Version="4.8.1"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/> <PackageReference Include="NUnit3TestAdapter" Version="5.2.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+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/"