306 lines
11 KiB
C#

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