diff --git a/README.md b/README.md index 0e8fbe7..7b3b9b2 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,17 @@ ### Notice 07/13/2025 -I decided to discontinue this project due to serveral reason. 1. CounterStrikeSharp's capability lacking a feature that I want to expand the feature. 2. Performanace issues, I take a measure after testing with 64 players comparing with CS2Fixes zombie reborn, ZombieSharp is worse. 3. In real life stuff, I'm hunting for a job right now lol. 4. Incoming of ModSharp (even it could take to 2035). +## Notes: + +**The plugin forces a player to become a zombie by calling InfectClient() which drops all weapons except knife, switches them to Terrorist team, and applies zombie class attributes (health, speed calculated as data.Speed / 250f, knockback, etc.) through ClassesApplyToPlayer() with a verification system that retries multiple times to ensure successful application.** + +**This is a temporary fix to restore functionality after CS2 updates. While stable for gameplay, some features may require further refinement as the CS2 API evolves. Please report any issues and contribute improvements to help maintain this essential zombie mod for the CS2 community.** + ### I TESTED RUNNING THIS PLUGIN WITH CS2FIXES. AS LONG AS YOU DON'T ENABLE ZOMBIE:REBORN, YOU CAN USE CS2FIXES ALONG WITH ZOMBIESHARP PLUGIN. Zombie-Sharp is a Zombie Mode plugin for CS2 referencing the features and functions from the previous SourcePawn Zombie:Reloaded plugin. You can say this is the Zombie:Reloaded remake but in C#. Here is the list of features. diff --git a/ZombieSharp/Api/Api.cs b/ZombieSharp/Api/Api.cs index c54f792..10ba51b 100644 --- a/ZombieSharp/Api/Api.cs +++ b/ZombieSharp/Api/Api.cs @@ -1,36 +1,44 @@ -using CounterStrikeSharp.API.Core; -using ZombieSharp.Plugin; -using ZombieSharpAPI; - -namespace ZombieSharp.Api; - -public class ZombieSharpInterface : IZombieSharpAPI -{ - public event Func? OnClientInfect; - public event Func? OnClientHumanize; - - public HookResult? ZS_OnClientInfect(CCSPlayerController client, CCSPlayerController? attacker, bool motherzombie, bool force) - { - return OnClientInfect?.Invoke(client, attacker, motherzombie, force); - } - - public HookResult? ZS_OnClientHumanize(CCSPlayerController client, bool force) - { - return OnClientHumanize?.Invoke(client, force); - } - - public bool ZS_IsClientHuman(CCSPlayerController client) - { - return Infect.IsClientHuman(client); - } - - public bool ZS_IsClientInfect(CCSPlayerController client) - { - return Infect.IsClientInfect(client); - } - - public void ZS_RespawnClient(CCSPlayerController client) - { - Respawn.RespawnClient(client); - } +using CounterStrikeSharp.API.Core; +using ZombieSharp.Plugin; +using ZombieSharpAPI; + +namespace ZombieSharp.Api; + +public class ZombieSharpInterface : IZombieSharpAPI +{ + public event Func? OnClientInfect; + public event Func? OnClientHumanize; + + public HookResult? ZS_OnClientInfect(CCSPlayerController client, CCSPlayerController? attacker, bool motherzombie, bool force) + { + return OnClientInfect?.Invoke(client, attacker, motherzombie, force); + } + + public void Hook_OnInfectClient(Func handler) + { + OnClientInfect += (client, attacker, motherzombie, force) => + { + return handler(client, attacker, motherzombie, force, false); + }; + } + + public HookResult? ZS_OnClientHumanize(CCSPlayerController client, bool force) + { + return OnClientHumanize?.Invoke(client, force); + } + + public bool ZS_IsClientHuman(CCSPlayerController client) + { + return Infect.IsClientHuman(client); + } + + public bool ZS_IsClientZombie(CCSPlayerController controller) + { + return Infect.IsClientZombie(controller); + } + + public void ZS_RespawnClient(CCSPlayerController client) + { + Respawn.RespawnClient(client); + } } \ No newline at end of file diff --git a/ZombieSharp/Models/CAttackInfo.cs b/ZombieSharp/Models/CAttackInfo.cs index 74ba613..f9fe2ee 100644 --- a/ZombieSharp/Models/CAttackInfo.cs +++ b/ZombieSharp/Models/CAttackInfo.cs @@ -1,36 +1,36 @@ -using System.Runtime.InteropServices; -using CounterStrikeSharp.API.Core; - -namespace ZombieSharp.Models; - -[StructLayout(LayoutKind.Explicit)] -public struct CAttackerInfo -{ - public CAttackerInfo(CEntityInstance attacker) - { - NeedInit = false; - IsWorld = true; - Attacker = attacker.EntityHandle.Raw; - if (attacker.DesignerName != "cs_player_controller") return; - - var controller = attacker.As(); - IsWorld = false; - IsPawn = true; - AttackerUserId = (ushort)(controller.UserId ?? 0xFFFF); - TeamNum = controller.TeamNum; - TeamChecked = controller.TeamNum; - } - - [FieldOffset(0x0)] public bool NeedInit = true; - [FieldOffset(0x1)] public bool IsPawn = false; - [FieldOffset(0x2)] public bool IsWorld = false; - - [FieldOffset(0x4)] - public uint Attacker; - - [FieldOffset(0x8)] - public ushort AttackerUserId; - - [FieldOffset(0x0C)] public int TeamChecked = -1; - [FieldOffset(0x10)] public int TeamNum = -1; +using System.Runtime.InteropServices; +using CounterStrikeSharp.API.Core; + +namespace ZombieSharp.Models; + +[StructLayout(LayoutKind.Explicit)] +public struct CAttackerInfo +{ + public CAttackerInfo(CEntityInstance attacker) + { + NeedInit = false; + IsWorld = true; + Attacker = attacker.EntityHandle.Raw; + if (attacker.DesignerName != "cs_player_controller") return; + + var controller = attacker.As(); + IsWorld = false; + IsPawn = true; + AttackerUserId = (ushort)(controller.UserId ?? 0xFFFF); + TeamNum = controller.TeamNum; + TeamChecked = controller.TeamNum; + } + + [FieldOffset(0x0)] public bool NeedInit = true; + [FieldOffset(0x1)] public bool IsPawn = false; + [FieldOffset(0x2)] public bool IsWorld = false; + + [FieldOffset(0x4)] + public uint Attacker; + + [FieldOffset(0x8)] + public ushort AttackerUserId; + + [FieldOffset(0x0C)] public int TeamChecked = -1; + [FieldOffset(0x10)] public int TeamNum = -1; } \ No newline at end of file diff --git a/ZombieSharp/Models/ClassAttribute.cs b/ZombieSharp/Models/ClassAttribute.cs index 0bc7c5d..e297e4a 100644 --- a/ZombieSharp/Models/ClassAttribute.cs +++ b/ZombieSharp/Models/ClassAttribute.cs @@ -1,23 +1,23 @@ -namespace ZombieSharp.Models; - -public class ClassAttribute -{ - public string? Name { get; set; } - public bool Enable { get; set; } = false; - public int Team { get; set; } - public string? Model { get; set; } - public bool MotherZombie { get; set; } = false; - public float NapalmTime { get; set; } = 0f; - public int Health { get; set; } = 100; - public float Regen_Interval { get; set; } = 0f; - public int Regen_Amount { get; set; } = 0; - public float Knockback { get; set; } - public float Speed { get; set; } = 250f; -} - -public class PlayerClasses -{ - public ClassAttribute? HumanClass { get; set; } = null; - public ClassAttribute? ZombieClass { get; set; } = null; - public ClassAttribute? ActiveClass { get; set; } = null; +namespace ZombieSharp.Models; + +public class ClassAttribute +{ + public string? Name { get; set; } + public bool Enable { get; set; } = false; + public int Team { get; set; } + public string? Model { get; set; } + public bool MotherZombie { get; set; } = false; + public float NapalmTime { get; set; } = 0f; + public int Health { get; set; } = 100; + public float Regen_Interval { get; set; } = 0f; + public int Regen_Amount { get; set; } = 0; + public float Knockback { get; set; } + public float Speed { get; set; } = 250f; +} + +public class PlayerClasses +{ + public ClassAttribute? HumanClass { get; set; } = null; + public ClassAttribute? ZombieClass { get; set; } = null; + public ClassAttribute? ActiveClass { get; set; } = null; } \ No newline at end of file diff --git a/ZombieSharp/Models/ConVarList.cs b/ZombieSharp/Models/ConVarList.cs index 0a5e9a9..b173b8b 100644 --- a/ZombieSharp/Models/ConVarList.cs +++ b/ZombieSharp/Models/ConVarList.cs @@ -1,37 +1,37 @@ -using CounterStrikeSharp.API.Modules.Cvars; -using CounterStrikeSharp.API.Modules.Cvars.Validators; - -namespace ZombieSharp; - -public partial class ZombieSharp -{ - public FakeConVar CVAR_FirstInfectionTimer = new("zs_infect_timer", "First Infection Countdown", 15f, default, new RangeValidator(5.0f, 60.0f)); - public FakeConVar CVAR_MotherZombieRatio = new("zs_infect_motherzombie_ratio", "Mother zombie ratio", 7f, default, new RangeValidator(1.0f, 64.0f)); - public FakeConVar CVAR_MotherZombieTeleport = new("zs_infect_motherzombie_spawn", "Teleport Motherzombie back to spawn", false, default, new RangeValidator(false, true)); - public FakeConVar CVAR_CashOnDamage = new("zs_infect_cash_damage_enable", "Enable earning cash from damaging player", false, default, new RangeValidator(false, true)); - public FakeConVar CVAR_TimeoutWinner = new("zs_infect_timeout_winner", "Winner team when timeout (0 = Zombie | 1 = Human)", 1, default, new RangeValidator(0, 1)); - - public FakeConVar CVAR_DefaultHuman = new("zs_classes_default_human", "Default classes for human", "human_default"); - public FakeConVar CVAR_DefaultZombie = new("zs_classes_default_zombie", "Default classes for human", "zombie_default"); - public FakeConVar CVAR_MotherZombie = new("zs_classes_motherzombie", "Default classes for motherzombie", "motherzombie"); - public FakeConVar CVAR_RandomClassesOnConnect = new("zs_classes_random_connect", "Assign Random Classes on player connect", true, default, new RangeValidator(false, true)); - public FakeConVar CVAR_RandomClassesOnSpawn = new("zs_classes_random_spawn", "Assign Random Classes on player spawn", true, default, new RangeValidator(false, true)); - public FakeConVar CVAR_AllowSavingClass = new("zs_classes_allow_save", "Allowing player to save their classes", true, default, new RangeValidator(false, true)); - public FakeConVar CVAR_AllowChangeClass = new("zs_classes_allow_change", "Allowing player to change their classes", true, default, new RangeValidator(false, true)); - - public FakeConVar CVAR_WeaponPurchaseEnable = new("zs_weapon_purchase_enable", "Enable Weapon purchase via command", true, default, new RangeValidator(false, true)); - public FakeConVar CVAR_WeaponRestrictEnable = new("zs_weapon_restrict_enable", "Enable Weapon restriction", true, default, new RangeValidator(false, true)); - public FakeConVar CVAR_WeaponBuyZoneOnly = new("zs_weapon_purchase_buyzone", "Only allowing weapon purchase in buyzone only", false, default, new RangeValidator(false, true)); - - public FakeConVar CVAR_TeleportAllow = new("zs_ztele_enable", "Allowing player to use !ztele for teleport", true, default, new RangeValidator(false, true)); - - public FakeConVar CVAR_RespawnEnable = new("zs_respawn_enable", "Allowing respawn after die", false, default, new RangeValidator(false, true)); - public FakeConVar CVAR_RespawnDelay = new("zs_respawn_delay", "Respawn Delaying", 5f, default, new RangeValidator(0.1f, 60.0f)); - public FakeConVar CVAR_AllowRespawnJoinLate = new("zs_respawn_allow_join_late", "Allowing player who join game late to spawn during the round", false, default, new RangeValidator(false, true)); - public FakeConVar CVAR_RespawnTeam = new("zs_respawn_team", "Specify team to respawn with after death (0 = Zombie | 1 = Human | 2 = Player Team before death)", 0, default, new RangeValidator(0, 2)); - - public FakeConVar CVAR_HumanWinOverlayParticle = new("zs_human_overlay_particle", "Human win overlay particle path", string.Empty); - public FakeConVar CVAR_HumanWinOverlayMaterial = new("zs_human_overlay_material", "Human win overlay material path", string.Empty); - public FakeConVar CVAR_ZombieWinOverlayParticle = new("zs_zombie_overlay_particle", "Zombie win overlay particle path", string.Empty); - public FakeConVar CVAR_ZombieWinOverlayMaterial = new("zs_zombie_overlay_material", "Zombie win overlay material path", string.Empty); +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Cvars.Validators; + +namespace ZombieSharp; + +public partial class ZombieSharp +{ + public FakeConVar CVAR_FirstInfectionTimer = new("zs_infect_timer", "First Infection Countdown", 15f, default, new RangeValidator(5.0f, 60.0f)); + public FakeConVar CVAR_MotherZombieRatio = new("zs_infect_motherzombie_ratio", "Mother zombie ratio", 7f, default, new RangeValidator(1.0f, 64.0f)); + public FakeConVar CVAR_MotherZombieTeleport = new("zs_infect_motherzombie_spawn", "Teleport Motherzombie back to spawn", false, default, new RangeValidator(false, true)); + public FakeConVar CVAR_CashOnDamage = new("zs_infect_cash_damage_enable", "Enable earning cash from damaging player", false, default, new RangeValidator(false, true)); + public FakeConVar CVAR_TimeoutWinner = new("zs_infect_timeout_winner", "Winner team when timeout (0 = Zombie | 1 = Human)", 1, default, new RangeValidator(0, 1)); + + public FakeConVar CVAR_DefaultHuman = new("zs_classes_default_human", "Default classes for human", "human_default"); + public FakeConVar CVAR_DefaultZombie = new("zs_classes_default_zombie", "Default classes for human", "zombie_default"); + public FakeConVar CVAR_MotherZombie = new("zs_classes_motherzombie", "Default classes for motherzombie", "motherzombie"); + public FakeConVar CVAR_RandomClassesOnConnect = new("zs_classes_random_connect", "Assign Random Classes on player connect", true, default, new RangeValidator(false, true)); + public FakeConVar CVAR_RandomClassesOnSpawn = new("zs_classes_random_spawn", "Assign Random Classes on player spawn", true, default, new RangeValidator(false, true)); + public FakeConVar CVAR_AllowSavingClass = new("zs_classes_allow_save", "Allowing player to save their classes", true, default, new RangeValidator(false, true)); + public FakeConVar CVAR_AllowChangeClass = new("zs_classes_allow_change", "Allowing player to change their classes", true, default, new RangeValidator(false, true)); + + public FakeConVar CVAR_WeaponPurchaseEnable = new("zs_weapon_purchase_enable", "Enable Weapon purchase via command", true, default, new RangeValidator(false, true)); + public FakeConVar CVAR_WeaponRestrictEnable = new("zs_weapon_restrict_enable", "Enable Weapon restriction", true, default, new RangeValidator(false, true)); + public FakeConVar CVAR_WeaponBuyZoneOnly = new("zs_weapon_purchase_buyzone", "Only allowing weapon purchase in buyzone only", false, default, new RangeValidator(false, true)); + + public FakeConVar CVAR_TeleportAllow = new("zs_ztele_enable", "Allowing player to use !ztele for teleport", true, default, new RangeValidator(false, true)); + + public FakeConVar CVAR_RespawnEnable = new("zs_respawn_enable", "Allowing respawn after die", false, default, new RangeValidator(false, true)); + public FakeConVar CVAR_RespawnDelay = new("zs_respawn_delay", "Respawn Delaying", 5f, default, new RangeValidator(0.1f, 60.0f)); + public FakeConVar CVAR_AllowRespawnJoinLate = new("zs_respawn_allow_join_late", "Allowing player who join game late to spawn during the round", false, default, new RangeValidator(false, true)); + public FakeConVar CVAR_RespawnTeam = new("zs_respawn_team", "Specify team to respawn with after death (0 = Zombie | 1 = Human | 2 = Player Team before death)", 0, default, new RangeValidator(0, 2)); + + public FakeConVar CVAR_HumanWinOverlayParticle = new("zs_human_overlay_particle", "Human win overlay particle path", string.Empty); + public FakeConVar CVAR_HumanWinOverlayMaterial = new("zs_human_overlay_material", "Human win overlay material path", string.Empty); + public FakeConVar CVAR_ZombieWinOverlayParticle = new("zs_zombie_overlay_particle", "Zombie win overlay particle path", string.Empty); + public FakeConVar CVAR_ZombieWinOverlayMaterial = new("zs_zombie_overlay_material", "Zombie win overlay material path", string.Empty); } \ No newline at end of file diff --git a/ZombieSharp/Models/GameConfigs.cs b/ZombieSharp/Models/GameConfigs.cs index 18f5ebc..15adec4 100644 --- a/ZombieSharp/Models/GameConfigs.cs +++ b/ZombieSharp/Models/GameConfigs.cs @@ -1,40 +1,43 @@ -namespace ZombieSharp.Models; - -public class GameConfigs -{ - // Infection stuff. - public float FirstInfectionTimer { get; set; } = 15f; - public float MotherZombieRatio { get; set; } = 7f; - public bool MotherZombieTeleport { get; set; } = false; - public bool CashOnDamage { get; set; } = false; - public int TimeoutWinner { get; set; } = 1; - - // human default class stuff. - public string? DefaultHumanBuffer { get; set; } - public string? DefaultZombieBuffer { get; set; } - public string? MotherZombieBuffer { get; set; } - public bool RandomClassesOnConnect { get; set; } = true; - public bool RandomClassesOnSpawn { get; set; } = false; - public bool AllowSavingClass { get; set; } = true; - public bool AllowChangeClass { get; set; } = true; - - // weapons section - public bool WeaponPurchaseEnable { get; set; } = false; - public bool WeaponRestrictEnable { get; set; } = true; - public bool WeaponBuyZoneOnly { get; set; } = false; - - // teleport section - public bool TeleportAllow { get; set; } = true; - - // respawn section - public bool RespawnEnable { get; set; } = false; - public float RespawnDelay { get; set; } = 5.0f; - public bool AllowRespawnJoinLate { get; set; } = false; - public int RespawTeam { get; set; } = 0; - - // win overlay stuff - public string HumanWinOverlayParticle { get; set; } = string.Empty; - public string HumanWinOverlayMaterial { get; set; } = string.Empty; - public string ZombieWinOverlayParticle { get; set; } = string.Empty; - public string ZombieWinOverlayMaterial { get; set; } = string.Empty; +namespace ZombieSharp.Models; + +public class GameConfigs +{ + // Infection stuff. + public float FirstInfectionTimer { get; set; } = 15f; + public float MotherZombieRatio { get; set; } = 7f; + public bool MotherZombieTeleport { get; set; } = false; + public bool CashOnDamage { get; set; } = false; + public int TimeoutWinner { get; set; } = 1; + + // human default class stuff. + public string? DefaultHumanBuffer { get; set; } + public string? DefaultZombieBuffer { get; set; } + public string? MotherZombieBuffer { get; set; } + public bool RandomClassesOnConnect { get; set; } = true; + public bool RandomClassesOnSpawn { get; set; } = false; + public bool AllowSavingClass { get; set; } = true; + public bool AllowChangeClass { get; set; } = true; + + // weapons section + public bool WeaponPurchaseEnable { get; set; } = false; + public bool WeaponRestrictEnable { get; set; } = true; + public bool WeaponBuyZoneOnly { get; set; } = false; + + // teleport section + public bool TeleportAllow { get; set; } = true; + + // respawn section + public bool RespawnEnable { get; set; } = false; + public float RespawnDelay { get; set; } = 5.0f; + public bool AllowRespawnJoinLate { get; set; } = false; + public int RespawTeam { get; set; } = 0; + + // infection settings + public float MaxKnifeRange { get; set; } = 100.0f; + + // win overlay stuff + public string HumanWinOverlayParticle { get; set; } = string.Empty; + public string HumanWinOverlayMaterial { get; set; } = string.Empty; + public string ZombieWinOverlayParticle { get; set; } = string.Empty; + public string ZombieWinOverlayMaterial { get; set; } = string.Empty; } \ No newline at end of file diff --git a/ZombieSharp/Models/HitGroupData.cs b/ZombieSharp/Models/HitGroupData.cs index 8fb5625..c7e1e23 100644 --- a/ZombieSharp/Models/HitGroupData.cs +++ b/ZombieSharp/Models/HitGroupData.cs @@ -1,7 +1,7 @@ -namespace ZombieSharp.Models; - -public class HitGroupData -{ - public int Index { get; set; } - public float Knockback { get ; set; } = 0f; +namespace ZombieSharp.Models; + +public class HitGroupData +{ + public int Index { get; set; } + public float Knockback { get ; set; } = 0f; } \ No newline at end of file diff --git a/ZombieSharp/Models/PlayerData.cs b/ZombieSharp/Models/PlayerData.cs index bb8438f..0791535 100644 --- a/ZombieSharp/Models/PlayerData.cs +++ b/ZombieSharp/Models/PlayerData.cs @@ -1,14 +1,14 @@ -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Timers; - -namespace ZombieSharp.Models; - -public class PlayerData -{ - public static Dictionary? ZombiePlayerData { get; set; } = []; - public static Dictionary? PlayerClassesData { get; set; } = []; - public static Dictionary? PlayerPurchaseCount { get; set; } = []; - public static Dictionary? PlayerSpawnData { get; set; } = []; - public static Dictionary? PlayerBurnData { get; set; } = []; - public static Dictionary? PlayerRegenData { get; set; } = []; -} +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Timers; + +namespace ZombieSharp.Models; + +public class PlayerData +{ + public static Dictionary? ZombiePlayerData { get; set; } = []; + public static Dictionary? PlayerClassesData { get; set; } = []; + public static Dictionary? PlayerPurchaseCount { get; set; } = []; + public static Dictionary? PlayerSpawnData { get; set; } = []; + public static Dictionary? PlayerBurnData { get; set; } = []; + public static Dictionary? PlayerRegenData { get; set; } = []; +} diff --git a/ZombieSharp/Models/SpawnData.cs b/ZombieSharp/Models/SpawnData.cs index 334a80a..372d226 100644 --- a/ZombieSharp/Models/SpawnData.cs +++ b/ZombieSharp/Models/SpawnData.cs @@ -1,9 +1,9 @@ -using CounterStrikeSharp.API.Modules.Utils; - -namespace ZombieSharp.Models; - -public class SpawnData -{ - public Vector PlayerPosition { get; set; } = new(0, 0, 0); - public QAngle PlayerAngle { get; set; } = new(0, 0, 0); +using CounterStrikeSharp.API.Modules.Utils; + +namespace ZombieSharp.Models; + +public class SpawnData +{ + public Vector PlayerPosition { get; set; } = new(0, 0, 0); + public QAngle PlayerAngle { get; set; } = new(0, 0, 0); } \ No newline at end of file diff --git a/ZombieSharp/Models/WeaponAttribute.cs b/ZombieSharp/Models/WeaponAttribute.cs index 80efaa2..f3cc364 100644 --- a/ZombieSharp/Models/WeaponAttribute.cs +++ b/ZombieSharp/Models/WeaponAttribute.cs @@ -1,23 +1,23 @@ -namespace ZombieSharp.Models; - -public class WeaponAttribute -{ - public string? WeaponName { get; set; } - public string? WeaponEntity { get; set; } - public float Knockback { get; set; } = 1f; - public int WeaponSlot { get; set; } - public int Price { get; set; } - public int MaxPurchase { get; set; } = 0; - public bool Restrict { get; set; } = false; - public List? PurchaseCommand { get; set; } = []; -} - -public class PurchaseCount -{ - public PurchaseCount() - { - WeaponCount = new Dictionary(); - } - - public Dictionary? WeaponCount { get; set; } = []; +namespace ZombieSharp.Models; + +public class WeaponAttribute +{ + public string? WeaponName { get; set; } + public string? WeaponEntity { get; set; } + public float Knockback { get; set; } = 1f; + public int WeaponSlot { get; set; } + public int Price { get; set; } + public int MaxPurchase { get; set; } = 0; + public bool Restrict { get; set; } = false; + public List? PurchaseCommand { get; set; } = []; +} + +public class PurchaseCount +{ + public PurchaseCount() + { + WeaponCount = new Dictionary(); + } + + public Dictionary? WeaponCount { get; set; } = []; } \ No newline at end of file diff --git a/ZombieSharp/Models/ZombiePlayer.cs b/ZombieSharp/Models/ZombiePlayer.cs index 3d421ad..6647be3 100644 --- a/ZombieSharp/Models/ZombiePlayer.cs +++ b/ZombieSharp/Models/ZombiePlayer.cs @@ -1,20 +1,20 @@ -namespace ZombieSharp.Models; - -public class ZombiePlayer -{ - public enum MotherZombieStatus - { - NONE = 0, - CHOSEN = 1, - LAST = 2 - } - - public ZombiePlayer(MotherZombieStatus status = MotherZombieStatus.NONE, bool zombie = false) - { - MotherZombie = status; - Zombie = zombie; - } - - public MotherZombieStatus MotherZombie { get; set; } = MotherZombieStatus.NONE; - public bool Zombie { get; set; } = false; -} +namespace ZombieSharp.Models; + +public class ZombiePlayer +{ + public enum MotherZombieStatus + { + NONE = 0, + CHOSEN = 1, + LAST = 2 + } + + public ZombiePlayer(MotherZombieStatus status = MotherZombieStatus.NONE, bool zombie = false) + { + MotherZombie = status; + Zombie = zombie; + } + + public MotherZombieStatus MotherZombie { get; set; } = MotherZombieStatus.NONE; + public bool Zombie { get; set; } = false; +} diff --git a/ZombieSharp/Plugin/Classes.cs b/ZombieSharp/Plugin/Classes.cs index e6477a0..fae4124 100644 --- a/ZombieSharp/Plugin/Classes.cs +++ b/ZombieSharp/Plugin/Classes.cs @@ -1,390 +1,471 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Menu; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using ZombieSharp.Database; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class Classes(ZombieSharp core, DatabaseMain database, ILogger logger) -{ - private readonly ZombieSharp _core = core; - private readonly ILogger _logger = logger; - private readonly DatabaseMain _database = database; - public static Dictionary? ClassesConfig = []; - - public static ClassAttribute? DefaultHuman = null; - public static ClassAttribute? DefaultZombie = null; - public static ClassAttribute? MotherZombie = null; - - public void ClassesOnLoad() - { - _core.AddCommand("css_zclass", "Select Class Menu Command", ClassesMainMenuCommand); - } - - public void ClassesOnMapStart() - { - // make sure this one is null. - ClassesConfig = null; - - // initial class config data. - ClassesConfig = new Dictionary(); - - var configPath = Path.Combine(ZombieSharp.ConfigPath, "playerclasses.jsonc"); - - if(!File.Exists(configPath)) - { - _logger.LogCritical("[ClassesOnMapStart] Couldn't find a playerclasses.jsonc file!"); - return; - } - - _logger.LogInformation("[ClassesOnMapStart] Load Player Classes file."); - - // we get data from jsonc file. - ClassesConfig = JsonConvert.DeserializeObject>(File.ReadAllText(configPath)); - - // settings is loaded before classes so we can get default human and zombie. - if(GameSettings.Settings != null) - { - if(!string.IsNullOrEmpty(GameSettings.Settings.DefaultHumanBuffer)) - { - var uniqueName = GameSettings.Settings.DefaultHumanBuffer; - - if(!ClassesConfig!.ContainsKey(uniqueName)) - { - _logger.LogCritical("[ClassesOnMapStart] Couldn't get classes \"{0}\" from playerclasses.jsonc", uniqueName); - } - - DefaultHuman = ClassesConfig[uniqueName]; - - _logger.LogInformation("[ClassesOnMapStart] Classes {0} is default class for human", DefaultHuman.Name); - } - - if(!string.IsNullOrEmpty(GameSettings.Settings.DefaultZombieBuffer)) - { - var uniqueName = GameSettings.Settings.DefaultZombieBuffer; - - if(!ClassesConfig!.ContainsKey(uniqueName)) - { - _logger.LogCritical("[ClassesOnMapStart] Couldn't get classes \"{0}\" from playerclasses.jsonc", uniqueName); - } - - DefaultZombie = ClassesConfig[uniqueName]; - - _logger.LogInformation("[ClassesOnMapStart] Classes {0} is default class for zombie", DefaultZombie.Name); - } - - if(!string.IsNullOrEmpty(GameSettings.Settings.MotherZombieBuffer)) - { - var uniqueName = GameSettings.Settings.MotherZombieBuffer; - - if(!ClassesConfig!.ContainsKey(uniqueName)) - { - _logger.LogCritical("[ClassesOnMapStart] Couldn't get classes \"{0}\" from playerclasses.jsonc", uniqueName); - } - - MotherZombie = ClassesConfig[uniqueName]; - - _logger.LogInformation("[ClassesOnMapStart] Classes {0} is mother zombie classes.", MotherZombie.Name); - } - - if(DefaultHuman == null) - { - _logger.LogCritical("[ClassesOnMapStart] Human class from GameSettings is empty or null!"); - } - - if(DefaultZombie == null) - { - _logger.LogCritical("[ClassesOnMapStart] Zombie class from GameSettings is empty or null!"); - } - - if(MotherZombie == null) - { - _logger.LogInformation("[ClassesOnMapStart] Mother Zombie class from GameSettings is empty or null, when player infect will use their selected zombie class instead."); - } - } - } - - public void ClassesOnClientPutInServer(CCSPlayerController client) - { - if(DefaultHuman == null || DefaultZombie == null) - { - _logger.LogError("[ClassesOnClientPutInServer] Default class is null!"); - } - - if(PlayerData.PlayerClassesData == null) - { - _logger.LogError("[ClassesOnClientPutInServer] PlayerClassesData is null!"); - return; - } - - if(GameSettings.Settings?.RandomClassesOnConnect ?? false) - { - PlayerData.PlayerClassesData[client].HumanClass = Utils.GetRandomPlayerClasses(1); - PlayerData.PlayerClassesData[client].ZombieClass = Utils.GetRandomPlayerClasses(0); - } - - else - { - PlayerData.PlayerClassesData[client].HumanClass = DefaultHuman; - PlayerData.PlayerClassesData[client].ZombieClass = DefaultZombie; - - if(!client.IsBot) - { - var steamid = client.AuthorizedSteamID?.SteamId64; - //_logger.LogInformation("[ClassesOnClientPutInServer] client {0} join with steamid: {1}", client.PlayerName, steamid); - - if(steamid == null || !steamid.HasValue) - { - _logger.LogError("[ClassesOnClientPutInServer] client {0} steam id is null!", client.PlayerName); - return; - } - - Task.Run(async () => - { - _logger.LogInformation("[ClassesOnClientPutInServer] Getting data of {0}", steamid.Value); - var data = await _database.GetPlayerClassData(steamid.Value); - - if(data == null) - { - await _database.InsertPlayerClassData(steamid.Value, PlayerData.PlayerClassesData[client]); - _logger.LogInformation("[ClassesOnClientPutInServer] Client {0} data is null, initialize a new one.", steamid.Value); - } - - else - { - if(data.HumanClass?.Enable ?? false) - PlayerData.PlayerClassesData[client].HumanClass = data.HumanClass; - - if(data.ZombieClass?.Enable ?? false) - PlayerData.PlayerClassesData[client].ZombieClass = data.ZombieClass; - } - }); - } - } - - if(PlayerData.PlayerClassesData?[client].HumanClass == null) - _logger.LogInformation("[ClassesOnClientPutInServer] {0} is not null, but client got null anyway.", DefaultHuman?.Name); - - if(PlayerData.PlayerClassesData?[client].ZombieClass == null) - _logger.LogInformation("[ClassesOnClientPutInServer] {0} is not null, but client got null anyway.", DefaultZombie?.Name); - } - - public void ClassesApplyToPlayer(CCSPlayerController client, ClassAttribute? data) - { - if(client == null) - { - _logger.LogError("[ClassesApplyToPlayer] Player is null!"); - return; - } - - if(data == null) - { - _logger.LogError("[ClassesApplyToPlayer] Class data is null!"); - return; - } - - var playerPawn = client.PlayerPawn.Value; - - if(playerPawn == null) - { - _logger.LogError("[ClassesApplyToPlayer] Player Pawn is null!"); - return; - } - - Server.NextWorldUpdate(() => - { - // if the model is not empty string and model path is not same as current model we have. - if(!string.IsNullOrEmpty(data.Model) && playerPawn.IsValid) - { - // set it. - if(data.Model != "default") - playerPawn.SetModel(data.Model); - - // change to cs2 default model blyat - else - { - if(data.Team == 0) - playerPawn.SetModel("characters/models/tm_phoenix/tm_phoenix.vmdl"); - - else - playerPawn.SetModel("characters/models/ctm_sas/ctm_sas.vmdl"); - } - } - - if(data.Team == 0) - { - playerPawn.ArmorValue = 0; - client.PawnHasHelmet = false; - } - }); - - _core.AddTimer(0.3f, () => - { - if(!Utils.IsPlayerAlive(client)) - return; - - playerPawn.Health = data.Health; - Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_iHealth"); - if(data.Team == 0) - { - playerPawn.ArmorValue = 0; - client.PawnHasHelmet = false; - } - - HealthRegen.RegenOnApplyClass(client, data); - }); - - // set speed - playerPawn.VelocityModifier = data.Speed / 250f; - - // set active player data. - PlayerData.PlayerClassesData![client].ActiveClass = data; - } - - public void ClassesOnPlayerSpawn(CCSPlayerController? client) - { - if(client == null) - return; - - if(PlayerData.PlayerClassesData == null) - { - return; - } - - if(!PlayerData.PlayerClassesData!.ContainsKey(client)) - { - return; - } - - if(GameSettings.Settings?.RandomClassesOnSpawn ?? false) - { - PlayerData.PlayerClassesData[client].HumanClass = Utils.GetRandomPlayerClasses(1); - PlayerData.PlayerClassesData[client].ZombieClass = Utils.GetRandomPlayerClasses(0); - } - } - - public void ClassesOnPlayerHurt(CCSPlayerController? client) - { - _core?.AddTimer(0.5f, () => - { - // need to be alive - if(client == null || client.Handle == IntPtr.Zero) - return; - - if(!Utils.IsPlayerAlive(client)) - return; - - // prevent error obviously. - if(!PlayerData.PlayerClassesData!.ContainsKey(client)) - { - return; - } - - if(PlayerData.PlayerClassesData?[client].ActiveClass == null) - return; - - // if speed is equal to 250 then we don't need this. - if(PlayerData.PlayerClassesData?[client].ActiveClass?.Speed == 250f) - return; - - // set speed back to velomodify - client.PlayerPawn.Value!.VelocityModifier = PlayerData.PlayerClassesData![client].ActiveClass!.Speed / 250f; - }); - } - - // CLASSES MENU - [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] - public void ClassesMainMenuCommand(CCSPlayerController? client, CommandInfo? info) - { - if(client == null) - return; - - if(!GameSettings.Settings?.AllowChangeClass ?? true) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.FeatureDisbled"]}"); - return; - } - - if(!PlayerData.PlayerClassesData?.ContainsKey(client) ?? false) - { - _logger.LogError("[ClassesMenuCommand] {0} is not in PlayerClassesData!", client.PlayerName); - return; - } - - var menu = new ChatMenu($" {_core.Localizer["Prefix"]} {_core.Localizer["Classes.MainMenu"]}"); - menu.AddMenuOption(_core.Localizer["Classes.MainMenu.Zombie"], (client, option) => ClassesSelectMenu(client, 0)); - menu.AddMenuOption(_core.Localizer["Classes.MainMenu.Human"], (client, option) => ClassesSelectMenu(client, 1)); - menu.ExitButton = true; - MenuManager.OpenChatMenu(client, menu); - } - - public void ClassesSelectMenu(CCSPlayerController client, int team) - { - if(ClassesConfig == null) - { - _logger.LogError("[ClassesSelectMenu] ClassesConfig is null!"); - return; - } - - string title; - - if (team == 0) - title = $" {_core.Localizer["Prefix"]} {_core.Localizer["Classes.Select.Zombie"]}"; - - else - title = $" {_core.Localizer["Prefix"]} {_core.Localizer["Classes.Select.Human"]}"; - - var selectmenu = new ChatMenu(title); - var menuhandle = (CCSPlayerController client, ChatMenuOption option) => - { - if (option.Text == "Back") - { - ClassesMainMenuCommand(client, null); - return; - } - - if (team == 0) - PlayerData.PlayerClassesData![client].ZombieClass = ClassesConfig.FirstOrDefault(x => x.Value.Name == option.Text).Value; - - else - PlayerData.PlayerClassesData![client].HumanClass = ClassesConfig.FirstOrDefault(x => x.Value.Name == option.Text).Value; - - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Classes.SelectSuccess"]}"); - MenuManager.CloseActiveMenu(client); - - // update their player class into database - if(GameSettings.Settings?.AllowSavingClass ?? true) - { - var steamid = client.AuthorizedSteamID?.SteamId64; - - if(steamid == null || !steamid.HasValue) - { - _logger.LogError("[ClassesMenuCommand] SteamID of {0} is null!", client.PlayerName); - return; - } - - Task.Run(async () => await _database.InsertPlayerClassData(steamid.Value, PlayerData.PlayerClassesData![client])); - } - }; - - foreach (var playerclass in ClassesConfig) - { - if (playerclass.Value.Team == team) - { - bool alreadyselected = playerclass.Value == PlayerData.PlayerClassesData?[client].HumanClass || playerclass.Value == PlayerData.PlayerClassesData?[client].ZombieClass; - bool motherzombie = playerclass.Value.MotherZombie; - bool disable = !playerclass.Value.Enable; - - selectmenu.AddMenuOption(playerclass.Value.Name!, menuhandle, alreadyselected || motherzombie || disable); - } - } - - selectmenu.AddMenuOption("Back", menuhandle); - selectmenu.ExitButton = true; - MenuManager.OpenChatMenu(client, selectmenu); - } +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Menu; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using ZombieSharp.Database; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class Classes(ZombieSharp core, DatabaseMain database, ILogger logger) +{ + private readonly ZombieSharp _core = core; + private readonly ILogger _logger = logger; + private readonly DatabaseMain _database = database; + public static Dictionary? ClassesConfig = []; + + public static ClassAttribute? DefaultHuman = null; + public static ClassAttribute? DefaultZombie = null; + public static ClassAttribute? MotherZombie = null; + + public void ClassesOnLoad() + { + _core.AddCommand("css_zclass", "Select Class Menu Command", ClassesMainMenuCommand); + } + + public void ClassesOnMapStart() + { + // make sure this one is null. + ClassesConfig = null; + + // initial class config data. + ClassesConfig = new Dictionary(); + + var configPath = Path.Combine(ZombieSharp.ConfigPath, "playerclasses.jsonc"); + + if(!File.Exists(configPath)) + { + _logger.LogCritical("[ClassesOnMapStart] Couldn't find a playerclasses.jsonc file!"); + return; + } + + _logger.LogInformation("[ClassesOnMapStart] Loading Player Classes file."); + + // we get data from jsonc file. + ClassesConfig = JsonConvert.DeserializeObject>(File.ReadAllText(configPath)); + + // settings is loaded before classes so we can get default human and zombie. + if(GameSettings.Settings != null) + { + if(!string.IsNullOrEmpty(GameSettings.Settings.DefaultHumanBuffer)) + { + var uniqueName = GameSettings.Settings.DefaultHumanBuffer; + + if(!ClassesConfig!.ContainsKey(uniqueName)) + { + _logger.LogCritical("[ClassesOnMapStart] Couldn't get classes \"{0}\" from playerclasses.jsonc", uniqueName); + } + + DefaultHuman = ClassesConfig[uniqueName]; + + _logger.LogInformation("[ClassesOnMapStart] Default human class: {0}", DefaultHuman.Name); + } + + if(!string.IsNullOrEmpty(GameSettings.Settings.DefaultZombieBuffer)) + { + var uniqueName = GameSettings.Settings.DefaultZombieBuffer; + + if(!ClassesConfig!.ContainsKey(uniqueName)) + { + _logger.LogCritical("[ClassesOnMapStart] Couldn't get classes \"{0}\" from playerclasses.jsonc", uniqueName); + } + + DefaultZombie = ClassesConfig[uniqueName]; + + _logger.LogInformation("[ClassesOnMapStart] Default zombie class: {0}", DefaultZombie.Name); + } + + if(!string.IsNullOrEmpty(GameSettings.Settings.MotherZombieBuffer)) + { + var uniqueName = GameSettings.Settings.MotherZombieBuffer; + + if(!ClassesConfig!.ContainsKey(uniqueName)) + { + _logger.LogCritical("[ClassesOnMapStart] Couldn't get classes \"{0}\" from playerclasses.jsonc", uniqueName); + } + + MotherZombie = ClassesConfig[uniqueName]; + + _logger.LogInformation("[ClassesOnMapStart] Mother zombie class: {0}", MotherZombie.Name); + } + + if(DefaultHuman == null) + { + _logger.LogCritical("[ClassesOnMapStart] Human class from GameSettings is empty or null!"); + } + + if(DefaultZombie == null) + { + _logger.LogCritical("[ClassesOnMapStart] Zombie class from GameSettings is empty or null!"); + } + + if(MotherZombie == null) + { + _logger.LogInformation("[ClassesOnMapStart] Mother Zombie class not configured, will use selected zombie class for mother zombies."); + } + } + } + + public void ClassesOnClientPutInServer(CCSPlayerController client) + { + if(DefaultHuman == null || DefaultZombie == null) + { + _logger.LogError("[ClassesOnClientPutInServer] Default class is null!"); + } + + if(PlayerData.PlayerClassesData == null) + { + _logger.LogError("[ClassesOnClientPutInServer] PlayerClassesData is null!"); + return; + } + + if(GameSettings.Settings?.RandomClassesOnConnect ?? false) + { + PlayerData.PlayerClassesData[client].HumanClass = Utils.GetRandomPlayerClasses(1); + PlayerData.PlayerClassesData[client].ZombieClass = Utils.GetRandomPlayerClasses(0); + } + + else + { + PlayerData.PlayerClassesData[client].HumanClass = DefaultHuman; + PlayerData.PlayerClassesData[client].ZombieClass = DefaultZombie; + + if(!client.IsBot) + { + var steamid = client.AuthorizedSteamID?.SteamId64; + //_logger.LogInformation("[ClassesOnClientPutInServer] client {0} join with steamid: {1}", client.PlayerName, steamid); + + if(steamid == null || !steamid.HasValue) + { + _logger.LogError("[ClassesOnClientPutInServer] client {0} steam id is null!", client.PlayerName); + return; + } + + Task.Run(async () => + { + _logger.LogInformation("[ClassesOnClientPutInServer] Loading player data for SteamID {0}", steamid.Value); + var data = await _database.GetPlayerClassData(steamid.Value); + + if(data == null) + { + await _database.InsertPlayerClassData(steamid.Value, PlayerData.PlayerClassesData[client]); + _logger.LogInformation("[ClassesOnClientPutInServer] Creating new data entry for SteamID {0}", steamid.Value); + } + + else + { + if(data.HumanClass?.Enable ?? false) + PlayerData.PlayerClassesData[client].HumanClass = data.HumanClass; + + if(data.ZombieClass?.Enable ?? false) + PlayerData.PlayerClassesData[client].ZombieClass = data.ZombieClass; + } + }); + } + } + + if(PlayerData.PlayerClassesData?[client].HumanClass == null) + _logger.LogWarning("[ClassesOnClientPutInServer] Human class is null for {0}", client.PlayerName); + + if(PlayerData.PlayerClassesData?[client].ZombieClass == null) + _logger.LogWarning("[ClassesOnClientPutInServer] Zombie class is null for {0}", client.PlayerName); + } + + public void ClassesApplyToPlayer(CCSPlayerController client, ClassAttribute? data) + { + if(client == null) + { + _logger.LogError("[ClassesApplyToPlayer] Player is null!"); + return; + } + + if(data == null) + { + _logger.LogError("[ClassesApplyToPlayer] Class data is null!"); + return; + } + + if(!Utils.IsPlayerAlive(client)) + { + _logger.LogError("[ClassesApplyToPlayer] Player {name} is not alive!", client.PlayerName); + return; + } + + var playerPawn = client.PlayerPawn.Value; + + if(playerPawn == null || !playerPawn.IsValid) + { + _logger.LogError("[ClassesApplyToPlayer] Player Pawn is null or invalid!"); + return; + } + + // Apply model changes immediately + Server.NextWorldUpdate(() => + { + if(!Utils.IsPlayerAlive(client) || playerPawn == null || !playerPawn.IsValid) + return; + + // Apply model if specified + if(!string.IsNullOrEmpty(data.Model)) + { + try + { + if(data.Model != "default") + { + playerPawn.SetModel(data.Model); + _logger.LogDebug("[ClassesApplyToPlayer] Applied model {model} to {name}", data.Model, client.PlayerName); + } + else + { + // Use default CS2 models + if(data.Team == 0) // Zombie team + playerPawn.SetModel("characters/models/tm_phoenix/tm_phoenix.vmdl"); + else // Human team + playerPawn.SetModel("characters/models/ctm_sas/ctm_sas.vmdl"); + } + } + catch(Exception ex) + { + _logger.LogError("[ClassesApplyToPlayer] Failed to set model for {name}: {error}", client.PlayerName, ex.Message); + } + } + + // Remove armor for zombies immediately + if(data.Team == 0) + { + playerPawn.ArmorValue = 0; + client.PawnHasHelmet = false; + Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_ArmorValue"); + } + }); + + // Apply health, armor, and regeneration with a delay to ensure proper application + _core.AddTimer(0.1f, () => + { + if(!Utils.IsPlayerAlive(client) || playerPawn == null || !playerPawn.IsValid) + return; + + try + { + // Set health + playerPawn.Health = data.Health; + Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_iHealth"); + + // Double-check armor removal for zombies + if(data.Team == 0) + { + playerPawn.ArmorValue = 0; + client.PawnHasHelmet = false; + Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_ArmorValue"); + } + + // Apply regeneration settings + HealthRegen.RegenOnApplyClass(client, data); + + _logger.LogDebug("[ClassesApplyToPlayer] Applied health ({health}) to {name}", data.Health, client.PlayerName); + } + catch(Exception ex) + { + _logger.LogError("[ClassesApplyToPlayer] Failed to apply health/armor to {name}: {error}", client.PlayerName, ex.Message); + } + }); + + // Apply speed with multiple attempts to ensure it sticks + var speedApplyAttempts = 0; + var maxSpeedAttempts = 3; + + Action applySpeed = null!; + applySpeed = () => + { + if(!Utils.IsPlayerAlive(client) || playerPawn == null || !playerPawn.IsValid) + return; + + try + { + var targetSpeed = data.Speed / 250f; + playerPawn.VelocityModifier = targetSpeed; + Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_flVelocityModifier"); + + _logger.LogDebug("[ClassesApplyToPlayer] Applied speed ({speed}) to {name} (attempt {attempt})", + data.Speed, client.PlayerName, speedApplyAttempts + 1); + + // Verify speed was applied correctly and retry if needed + _core.AddTimer(0.1f, () => + { + if(Utils.IsPlayerAlive(client) && playerPawn != null && playerPawn.IsValid) + { + var currentSpeed = playerPawn.VelocityModifier; + if(Math.Abs(currentSpeed - targetSpeed) > 0.01f && speedApplyAttempts < maxSpeedAttempts - 1) + { + speedApplyAttempts++; + _logger.LogWarning("[ClassesApplyToPlayer] Speed application retry for {name} - expected: {expected}, actual: {actual}", + client.PlayerName, targetSpeed, currentSpeed); + applySpeed(); + } + } + }); + } + catch(Exception ex) + { + _logger.LogError("[ClassesApplyToPlayer] Failed to apply speed to {name}: {error}", client.PlayerName, ex.Message); + } + }; + + // Initial speed application + _core.AddTimer(0.05f, applySpeed); + + // Set active player data + if(PlayerData.PlayerClassesData != null && PlayerData.PlayerClassesData.ContainsKey(client)) + { + PlayerData.PlayerClassesData[client].ActiveClass = data; + } + + _logger.LogInformation("[ClassesApplyToPlayer] Successfully applied class {className} to {playerName}", + data.Name ?? "Unknown", client.PlayerName); + } + + public void ClassesOnPlayerSpawn(CCSPlayerController? client) + { + if(client == null) + return; + + if(PlayerData.PlayerClassesData == null) + { + return; + } + + if(!PlayerData.PlayerClassesData!.ContainsKey(client)) + { + return; + } + + if(GameSettings.Settings?.RandomClassesOnSpawn ?? false) + { + PlayerData.PlayerClassesData[client].HumanClass = Utils.GetRandomPlayerClasses(1); + PlayerData.PlayerClassesData[client].ZombieClass = Utils.GetRandomPlayerClasses(0); + } + } + + public void ClassesOnPlayerHurt(CCSPlayerController? client) + { + _core?.AddTimer(0.5f, () => + { + // need to be alive + if(client == null || client.Handle == IntPtr.Zero) + return; + + if(!Utils.IsPlayerAlive(client)) + return; + + // prevent error obviously. + if(!PlayerData.PlayerClassesData!.ContainsKey(client)) + { + return; + } + + if(PlayerData.PlayerClassesData?[client].ActiveClass == null) + return; + + // if speed is equal to 250 then we don't need this. + if(PlayerData.PlayerClassesData?[client].ActiveClass?.Speed == 250f) + return; + + // set speed back to velomodify + client.PlayerPawn.Value!.VelocityModifier = PlayerData.PlayerClassesData![client].ActiveClass!.Speed / 250f; + }); + } + + // CLASSES MENU + [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] + public void ClassesMainMenuCommand(CCSPlayerController? client, CommandInfo? info) + { + if(client == null) + return; + + if(!GameSettings.Settings?.AllowChangeClass ?? true) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.FeatureDisbled"]}"); + return; + } + + if(!PlayerData.PlayerClassesData?.ContainsKey(client) ?? false) + { + _logger.LogError("[ClassesMenuCommand] {0} is not in PlayerClassesData!", client.PlayerName); + return; + } + + var menu = new ChatMenu($" {_core.Localizer["Prefix"]} {_core.Localizer["Classes.MainMenu"]}"); + menu.AddMenuOption(_core.Localizer["Classes.MainMenu.Zombie"], (client, option) => ClassesSelectMenu(client, 0)); + menu.AddMenuOption(_core.Localizer["Classes.MainMenu.Human"], (client, option) => ClassesSelectMenu(client, 1)); + menu.ExitButton = true; + MenuManager.OpenChatMenu(client, menu); + } + + public void ClassesSelectMenu(CCSPlayerController client, int team) + { + if(ClassesConfig == null) + { + _logger.LogError("[ClassesSelectMenu] ClassesConfig is null!"); + return; + } + + string title; + + if (team == 0) + title = $" {_core.Localizer["Prefix"]} {_core.Localizer["Classes.Select.Zombie"]}"; + + else + title = $" {_core.Localizer["Prefix"]} {_core.Localizer["Classes.Select.Human"]}"; + + var selectmenu = new ChatMenu(title); + var menuhandle = (CCSPlayerController client, ChatMenuOption option) => + { + if (option.Text == "Back") + { + ClassesMainMenuCommand(client, null); + return; + } + + if (team == 0) + PlayerData.PlayerClassesData![client].ZombieClass = ClassesConfig.FirstOrDefault(x => x.Value.Name == option.Text).Value; + + else + PlayerData.PlayerClassesData![client].HumanClass = ClassesConfig.FirstOrDefault(x => x.Value.Name == option.Text).Value; + + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Classes.SelectSuccess"]}"); + MenuManager.CloseActiveMenu(client); + + // update their player class into database + if(GameSettings.Settings?.AllowSavingClass ?? true) + { + var steamid = client.AuthorizedSteamID?.SteamId64; + + if(steamid == null || !steamid.HasValue) + { + _logger.LogError("[ClassesMenuCommand] SteamID of {0} is null!", client.PlayerName); + return; + } + + Task.Run(async () => await _database.InsertPlayerClassData(steamid.Value, PlayerData.PlayerClassesData![client])); + } + }; + + foreach (var playerclass in ClassesConfig) + { + if (playerclass.Value.Team == team) + { + bool alreadyselected = playerclass.Value == PlayerData.PlayerClassesData?[client].HumanClass || playerclass.Value == PlayerData.PlayerClassesData?[client].ZombieClass; + bool motherzombie = playerclass.Value.MotherZombie; + bool disable = !playerclass.Value.Enable; + + selectmenu.AddMenuOption(playerclass.Value.Name!, menuhandle, alreadyselected || motherzombie || disable); + } + } + + selectmenu.AddMenuOption("Back", menuhandle); + selectmenu.ExitButton = true; + MenuManager.OpenChatMenu(client, selectmenu); + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/ConVars.cs b/ZombieSharp/Plugin/ConVars.cs index 4eed43f..e4cbe58 100644 --- a/ZombieSharp/Plugin/ConVars.cs +++ b/ZombieSharp/Plugin/ConVars.cs @@ -1,261 +1,261 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Modules.Cvars; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class ConVars(ZombieSharp core, Weapons weapons, ILogger logger) -{ - private ZombieSharp _core = core; - private readonly Weapons _weapon = weapons; - private readonly ILogger _logger = logger; - - public void ConVarOnLoad() - { - if(GameSettings.Settings == null) - { - _logger.LogError("[ConVarOnLoad] Game Settings is null! ConVar will not proceed any longer!"); - return; - } - - // we hook convar changed and apply it to our GameSettings. - _core.CVAR_FirstInfectionTimer.ValueChanged += (sender, value) => { - GameSettings.Settings.FirstInfectionTimer = value; - }; - - _core.CVAR_MotherZombieRatio.ValueChanged += (sender, value) => { - GameSettings.Settings.MotherZombieRatio = value; - }; - - _core.CVAR_MotherZombieTeleport.ValueChanged += (sender, value) => { - GameSettings.Settings.MotherZombieTeleport = value; - }; - - _core.CVAR_CashOnDamage.ValueChanged += (sender, value) => { - GameSettings.Settings.CashOnDamage = value; - }; - - _core.CVAR_TimeoutWinner.ValueChanged += (sender, value) => { - GameSettings.Settings.TimeoutWinner = value; - }; - - // class section - _core.CVAR_DefaultHuman.ValueChanged += (sender, value) => { - GameSettings.Settings.DefaultHumanBuffer = value; - - if(!Classes.ClassesConfig!.ContainsKey(value)) - { - _logger.LogCritical("[ConVarOnLoad] Couldn't get classes \"{0}\" from playerclasses.jsonc", value); - return; - } - - Classes.DefaultHuman = Classes.ClassesConfig[value]; - }; - - _core.CVAR_DefaultZombie.ValueChanged += (sender, value) => { - GameSettings.Settings.DefaultZombieBuffer = value; - - if(!Classes.ClassesConfig!.ContainsKey(value)) - { - _logger.LogCritical("[ConVarOnLoad] Couldn't get classes \"{0}\" from playerclasses.jsonc", value); - return; - } - - Classes.DefaultZombie = Classes.ClassesConfig[value]; - }; - - _core.CVAR_MotherZombie.ValueChanged += (sender, value) => { - GameSettings.Settings.MotherZombieBuffer = value; - - if(!Classes.ClassesConfig!.ContainsKey(value)) - { - _logger.LogCritical("[ConVarOnLoad] Couldn't get classes \"{0}\" from playerclasses.jsonc", value); - return; - } - - Classes.MotherZombie = Classes.ClassesConfig[value]; - }; - - _core.CVAR_RandomClassesOnConnect.ValueChanged += (sender, value) => { - GameSettings.Settings.RandomClassesOnConnect = value; - }; - - _core.CVAR_RandomClassesOnSpawn.ValueChanged += (sender, value) => { - GameSettings.Settings.RandomClassesOnSpawn = value; - }; - - _core.CVAR_AllowSavingClass.ValueChanged += (sender, value) => { - GameSettings.Settings.AllowSavingClass = value; - }; - - _core.CVAR_AllowChangeClass.ValueChanged += (sender, value) => { - GameSettings.Settings.AllowChangeClass = value; - }; - - // weapon section - _core.CVAR_WeaponPurchaseEnable.ValueChanged += (sender, value) => { - GameSettings.Settings.WeaponPurchaseEnable = value; - _logger.LogInformation("[ConVarChanged] zs_weapon_purchase_enable changed to {0}", value); - _weapon.IntialWeaponPurchaseCommand(); - }; - - _core.CVAR_WeaponRestrictEnable.ValueChanged += (sender, value) => { - GameSettings.Settings.WeaponRestrictEnable = value; - }; - - _core.CVAR_WeaponBuyZoneOnly.ValueChanged += (sender, value) => { - GameSettings.Settings.WeaponBuyZoneOnly = value; - }; - - // Teleport - _core.CVAR_TeleportAllow.ValueChanged += (sender, value) => { - GameSettings.Settings.TeleportAllow = value; - }; - - // respawn - _core.CVAR_RespawnEnable.ValueChanged += (sender, value) => { - GameSettings.Settings.RespawnEnable = value; - - _logger.LogInformation("[ConVarChanged] zs_respawn_enable changed to {0}", value); - Server.PrintToChatAll($" {_core.Localizer["Prefix"]} zs_respawn_enable changed to {value}"); - - _core.AddTimer(1.0f, () => { - foreach(var player in Utilities.GetPlayers()) - { - if(player == null) - continue; - - if(Utils.IsPlayerAlive(player)) - continue; - - if(player.Team == CsTeam.None || player.Team == CsTeam.Spectator) - continue; - - Respawn.RespawnClient(player); - } - }); - - }; - _core.CVAR_RespawnDelay.ValueChanged += (sender, value) => { - GameSettings.Settings.RespawnDelay = value; - }; - _core.CVAR_AllowRespawnJoinLate.ValueChanged += (sender, value) => { - GameSettings.Settings.AllowRespawnJoinLate = value; - }; - _core.CVAR_RespawnTeam.ValueChanged += (sender, value) => { - GameSettings.Settings.RespawTeam = value; - }; - - // overlay stuff - _core.CVAR_HumanWinOverlayParticle.ValueChanged += (sender, value) => { - GameSettings.Settings.HumanWinOverlayParticle = value; - }; - _core.CVAR_HumanWinOverlayMaterial.ValueChanged += (sender, value) => { - GameSettings.Settings.HumanWinOverlayMaterial = value; - }; - _core.CVAR_ZombieWinOverlayParticle.ValueChanged += (sender, value) => { - GameSettings.Settings.ZombieWinOverlayParticle = value; - }; - _core.CVAR_ZombieWinOverlayMaterial.ValueChanged += (sender, value) => { - GameSettings.Settings.ZombieWinOverlayMaterial = value; - }; - - // create convar first. - _core.RegisterFakeConVars(typeof(ConVar)); - } - - public void ConVarExecuteOnMapStart(string mapname) - { - CreateExecuteFile(); - - Server.ExecuteCommand("exec zombiesharp/zombiesharp.cfg"); - - var configFolder = Path.Combine(Server.GameDirectory, "csgo/cfg/zombiesharp/"); - var mapConfig = Path.Combine(configFolder, mapname + ".cfg"); - - if (File.Exists(mapConfig)) - { - _logger.LogInformation("[ConVarOnMapStart] Found Map cfg file loading {0}", mapConfig); - Server.ExecuteCommand($"exec zombiesharp/{mapname}.cfg"); - } - } - - public void CreateExecuteFile() - { - var configFolder = Path.Combine(Server.GameDirectory, "csgo/cfg/zombiesharp/"); - - if (!Directory.Exists(configFolder)) - { - _logger.LogInformation("[CreateExecuteFile] Couldn't find directory, so proceed creating {0}", configFolder); - Directory.CreateDirectory(configFolder); - } - - var configPath = Path.Combine(configFolder, "zombiesharp.cfg"); - - if (File.Exists(configPath)) - return; - - - _logger.LogInformation("[CreateExecuteFile] Creating {0}", configPath); - - var configFile = File.CreateText(configPath); - - configFile.WriteLine($"// This file is generated by ZombieSharp.dll at {DateTime.Today}"); - configFile.WriteLine(); - - // hard code but try creating the list of > is not possible with me, so I leave that method to my ruskie friend. - CreateConVarLine(configFile, _core.CVAR_FirstInfectionTimer); - CreateConVarLine(configFile, _core.CVAR_MotherZombieRatio); - CreateConVarLine(configFile, _core.CVAR_MotherZombieTeleport); - CreateConVarLine(configFile, _core.CVAR_CashOnDamage); - CreateConVarLine(configFile, _core.CVAR_TimeoutWinner); - - CreateConVarLine(configFile, _core.CVAR_DefaultHuman); - CreateConVarLine(configFile, _core.CVAR_DefaultZombie); - CreateConVarLine(configFile, _core.CVAR_MotherZombie); - CreateConVarLine(configFile, _core.CVAR_RandomClassesOnConnect); - CreateConVarLine(configFile, _core.CVAR_RandomClassesOnSpawn); - CreateConVarLine(configFile, _core.CVAR_AllowSavingClass); - CreateConVarLine(configFile, _core.CVAR_AllowChangeClass); - - CreateConVarLine(configFile, _core.CVAR_WeaponPurchaseEnable); - CreateConVarLine(configFile, _core.CVAR_WeaponRestrictEnable); - CreateConVarLine(configFile, _core.CVAR_WeaponBuyZoneOnly); - - CreateConVarLine(configFile, _core.CVAR_TeleportAllow); - - CreateConVarLine(configFile, _core.CVAR_RespawnEnable); - CreateConVarLine(configFile, _core.CVAR_RespawnDelay); - CreateConVarLine(configFile, _core.CVAR_AllowRespawnJoinLate); - CreateConVarLine(configFile, _core.CVAR_RespawnTeam); - - CreateConVarLine(configFile, _core.CVAR_HumanWinOverlayParticle); - CreateConVarLine(configFile, _core.CVAR_HumanWinOverlayMaterial); - CreateConVarLine(configFile, _core.CVAR_ZombieWinOverlayParticle); - CreateConVarLine(configFile, _core.CVAR_ZombieWinOverlayMaterial); - - configFile.Close(); - } - - public void CreateConVarLine(StreamWriter configFile, FakeConVar fakeConVar) where T : IComparable - { - if(configFile == null) - { - _logger.LogCritical("[CreateConVarLine] Config File is null"); - return; - } - - var command = fakeConVar.Name; - var value = fakeConVar.Value; - var description = fakeConVar.Description; - - configFile.WriteLine($"// {description}"); - configFile.WriteLine($"// -"); - configFile.WriteLine($"// Default: {value}"); - configFile.WriteLine($"{command} {value}"); - // empty file. - configFile.WriteLine(); - } +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class ConVars(ZombieSharp core, Weapons weapons, ILogger logger) +{ + private ZombieSharp _core = core; + private readonly Weapons _weapon = weapons; + private readonly ILogger _logger = logger; + + public void ConVarOnLoad() + { + if(GameSettings.Settings == null) + { + _logger.LogError("[ConVarOnLoad] Game Settings is null! ConVar will not proceed any longer!"); + return; + } + + // we hook convar changed and apply it to our GameSettings. + _core.CVAR_FirstInfectionTimer.ValueChanged += (sender, value) => { + GameSettings.Settings.FirstInfectionTimer = value; + }; + + _core.CVAR_MotherZombieRatio.ValueChanged += (sender, value) => { + GameSettings.Settings.MotherZombieRatio = value; + }; + + _core.CVAR_MotherZombieTeleport.ValueChanged += (sender, value) => { + GameSettings.Settings.MotherZombieTeleport = value; + }; + + _core.CVAR_CashOnDamage.ValueChanged += (sender, value) => { + GameSettings.Settings.CashOnDamage = value; + }; + + _core.CVAR_TimeoutWinner.ValueChanged += (sender, value) => { + GameSettings.Settings.TimeoutWinner = value; + }; + + // class section + _core.CVAR_DefaultHuman.ValueChanged += (sender, value) => { + GameSettings.Settings.DefaultHumanBuffer = value; + + if(!Classes.ClassesConfig!.ContainsKey(value)) + { + _logger.LogCritical("[ConVarOnLoad] Couldn't get classes \"{0}\" from playerclasses.jsonc", value); + return; + } + + Classes.DefaultHuman = Classes.ClassesConfig[value]; + }; + + _core.CVAR_DefaultZombie.ValueChanged += (sender, value) => { + GameSettings.Settings.DefaultZombieBuffer = value; + + if(!Classes.ClassesConfig!.ContainsKey(value)) + { + _logger.LogCritical("[ConVarOnLoad] Couldn't get classes \"{0}\" from playerclasses.jsonc", value); + return; + } + + Classes.DefaultZombie = Classes.ClassesConfig[value]; + }; + + _core.CVAR_MotherZombie.ValueChanged += (sender, value) => { + GameSettings.Settings.MotherZombieBuffer = value; + + if(!Classes.ClassesConfig!.ContainsKey(value)) + { + _logger.LogCritical("[ConVarOnLoad] Couldn't get classes \"{0}\" from playerclasses.jsonc", value); + return; + } + + Classes.MotherZombie = Classes.ClassesConfig[value]; + }; + + _core.CVAR_RandomClassesOnConnect.ValueChanged += (sender, value) => { + GameSettings.Settings.RandomClassesOnConnect = value; + }; + + _core.CVAR_RandomClassesOnSpawn.ValueChanged += (sender, value) => { + GameSettings.Settings.RandomClassesOnSpawn = value; + }; + + _core.CVAR_AllowSavingClass.ValueChanged += (sender, value) => { + GameSettings.Settings.AllowSavingClass = value; + }; + + _core.CVAR_AllowChangeClass.ValueChanged += (sender, value) => { + GameSettings.Settings.AllowChangeClass = value; + }; + + // weapon section + _core.CVAR_WeaponPurchaseEnable.ValueChanged += (sender, value) => { + GameSettings.Settings.WeaponPurchaseEnable = value; + _logger.LogInformation("[ConVarChanged] zs_weapon_purchase_enable changed to {0}", value); + _weapon.IntialWeaponPurchaseCommand(); + }; + + _core.CVAR_WeaponRestrictEnable.ValueChanged += (sender, value) => { + GameSettings.Settings.WeaponRestrictEnable = value; + }; + + _core.CVAR_WeaponBuyZoneOnly.ValueChanged += (sender, value) => { + GameSettings.Settings.WeaponBuyZoneOnly = value; + }; + + // Teleport + _core.CVAR_TeleportAllow.ValueChanged += (sender, value) => { + GameSettings.Settings.TeleportAllow = value; + }; + + // respawn + _core.CVAR_RespawnEnable.ValueChanged += (sender, value) => { + GameSettings.Settings.RespawnEnable = value; + + _logger.LogInformation("[ConVarChanged] zs_respawn_enable changed to {0}", value); + Server.PrintToChatAll($" {_core.Localizer["Prefix"]} zs_respawn_enable changed to {value}"); + + _core.AddTimer(1.0f, () => { + foreach(var player in Utilities.GetPlayers()) + { + if(player == null) + continue; + + if(Utils.IsPlayerAlive(player)) + continue; + + if(player.Team == CsTeam.None || player.Team == CsTeam.Spectator) + continue; + + Respawn.RespawnClient(player); + } + }); + + }; + _core.CVAR_RespawnDelay.ValueChanged += (sender, value) => { + GameSettings.Settings.RespawnDelay = value; + }; + _core.CVAR_AllowRespawnJoinLate.ValueChanged += (sender, value) => { + GameSettings.Settings.AllowRespawnJoinLate = value; + }; + _core.CVAR_RespawnTeam.ValueChanged += (sender, value) => { + GameSettings.Settings.RespawTeam = value; + }; + + // overlay stuff + _core.CVAR_HumanWinOverlayParticle.ValueChanged += (sender, value) => { + GameSettings.Settings.HumanWinOverlayParticle = value; + }; + _core.CVAR_HumanWinOverlayMaterial.ValueChanged += (sender, value) => { + GameSettings.Settings.HumanWinOverlayMaterial = value; + }; + _core.CVAR_ZombieWinOverlayParticle.ValueChanged += (sender, value) => { + GameSettings.Settings.ZombieWinOverlayParticle = value; + }; + _core.CVAR_ZombieWinOverlayMaterial.ValueChanged += (sender, value) => { + GameSettings.Settings.ZombieWinOverlayMaterial = value; + }; + + // create convar first. + _core.RegisterFakeConVars(typeof(ConVar)); + } + + public void ConVarExecuteOnMapStart(string mapname) + { + CreateExecuteFile(); + + Server.ExecuteCommand("exec zombiesharp/zombiesharp.cfg"); + + var configFolder = Path.Combine(Server.GameDirectory, "csgo/cfg/zombiesharp/"); + var mapConfig = Path.Combine(configFolder, mapname + ".cfg"); + + if (File.Exists(mapConfig)) + { + _logger.LogInformation("[ConVarOnMapStart] Found Map cfg file loading {0}", mapConfig); + Server.ExecuteCommand($"exec zombiesharp/{mapname}.cfg"); + } + } + + public void CreateExecuteFile() + { + var configFolder = Path.Combine(Server.GameDirectory, "csgo/cfg/zombiesharp/"); + + if (!Directory.Exists(configFolder)) + { + _logger.LogInformation("[CreateExecuteFile] Couldn't find directory, so proceed creating {0}", configFolder); + Directory.CreateDirectory(configFolder); + } + + var configPath = Path.Combine(configFolder, "zombiesharp.cfg"); + + if (File.Exists(configPath)) + return; + + + _logger.LogInformation("[CreateExecuteFile] Creating {0}", configPath); + + var configFile = File.CreateText(configPath); + + configFile.WriteLine($"// This file is generated by ZombieSharp.dll at {DateTime.Today}"); + configFile.WriteLine(); + + // hard code but try creating the list of > is not possible with me, so I leave that method to my ruskie friend. + CreateConVarLine(configFile, _core.CVAR_FirstInfectionTimer); + CreateConVarLine(configFile, _core.CVAR_MotherZombieRatio); + CreateConVarLine(configFile, _core.CVAR_MotherZombieTeleport); + CreateConVarLine(configFile, _core.CVAR_CashOnDamage); + CreateConVarLine(configFile, _core.CVAR_TimeoutWinner); + + CreateConVarLine(configFile, _core.CVAR_DefaultHuman); + CreateConVarLine(configFile, _core.CVAR_DefaultZombie); + CreateConVarLine(configFile, _core.CVAR_MotherZombie); + CreateConVarLine(configFile, _core.CVAR_RandomClassesOnConnect); + CreateConVarLine(configFile, _core.CVAR_RandomClassesOnSpawn); + CreateConVarLine(configFile, _core.CVAR_AllowSavingClass); + CreateConVarLine(configFile, _core.CVAR_AllowChangeClass); + + CreateConVarLine(configFile, _core.CVAR_WeaponPurchaseEnable); + CreateConVarLine(configFile, _core.CVAR_WeaponRestrictEnable); + CreateConVarLine(configFile, _core.CVAR_WeaponBuyZoneOnly); + + CreateConVarLine(configFile, _core.CVAR_TeleportAllow); + + CreateConVarLine(configFile, _core.CVAR_RespawnEnable); + CreateConVarLine(configFile, _core.CVAR_RespawnDelay); + CreateConVarLine(configFile, _core.CVAR_AllowRespawnJoinLate); + CreateConVarLine(configFile, _core.CVAR_RespawnTeam); + + CreateConVarLine(configFile, _core.CVAR_HumanWinOverlayParticle); + CreateConVarLine(configFile, _core.CVAR_HumanWinOverlayMaterial); + CreateConVarLine(configFile, _core.CVAR_ZombieWinOverlayParticle); + CreateConVarLine(configFile, _core.CVAR_ZombieWinOverlayMaterial); + + configFile.Close(); + } + + public void CreateConVarLine(StreamWriter configFile, FakeConVar fakeConVar) where T : IComparable + { + if(configFile == null) + { + _logger.LogCritical("[CreateConVarLine] Config File is null"); + return; + } + + var command = fakeConVar.Name; + var value = fakeConVar.Value; + var description = fakeConVar.Description; + + configFile.WriteLine($"// {description}"); + configFile.WriteLine($"// -"); + configFile.WriteLine($"// Default: {value}"); + configFile.WriteLine($"{command} {value}"); + // empty file. + configFile.WriteLine(); + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/Events.cs b/ZombieSharp/Plugin/Events.cs index fb37db1..6acddcc 100644 --- a/ZombieSharp/Plugin/Events.cs +++ b/ZombieSharp/Plugin/Events.cs @@ -1,289 +1,311 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; -using ZombieSharp.Models; -using static CounterStrikeSharp.API.Core.Listeners; - -namespace ZombieSharp.Plugin; - -public class Events(ZombieSharp core, Infect infect, GameSettings settings, Classes classes, Weapons weapons, Teleport teleport, Respawn respawn, Napalm napalm, ConVars convar, HitGroup hitgroup, ILogger logger) -{ - private readonly ZombieSharp _core = core; - private readonly Infect _infect = infect; - private readonly Classes _classes = classes; - private readonly GameSettings _settings = settings; - private readonly Weapons _weapons = weapons; - private readonly ILogger _logger = logger; - private readonly Teleport _teleport = teleport; - private readonly Napalm _napalm = napalm; - private readonly Respawn _respawn = respawn; - private readonly ConVars _convar = convar; - private readonly HitGroup _hitgroup = hitgroup; - - public void EventOnLoad() - { - _core.RegisterListener(OnClientPutInServer); - _core.RegisterListener(OnClientDisconnect); - _core.RegisterListener(OnMapStart); - _core.RegisterListener(OnPrecahceResources); - - _core.RegisterEventHandler(OnPlayerHurt); - _core.RegisterEventHandler(OnPlayerSpawn); - _core.RegisterEventHandler(OnRoundFreezeEnd); - _core.RegisterEventHandler(OnRoundStart); - _core.RegisterEventHandler(OnPreRoundStart); - _core.RegisterEventHandler(OnPlayerDeath); - _core.RegisterEventHandler(OnWarmupEnd); - _core.RegisterEventHandler(OnRoundEnd); - _core.RegisterEventHandler(OnPlayerTeam); - _core.RegisterEventHandler(OnPreRestart); - } - - public void EventOnUnload() - { - _core.RemoveListener(OnClientPutInServer); - _core.RemoveListener(OnClientDisconnect); - _core.RemoveListener(OnMapStart); - _core.RemoveListener(OnPrecahceResources); - - _core.DeregisterEventHandler(OnPlayerHurt); - _core.DeregisterEventHandler(OnPlayerSpawn); - _core.DeregisterEventHandler(OnRoundFreezeEnd); - _core.DeregisterEventHandler(OnRoundStart); - _core.DeregisterEventHandler(OnPreRoundStart); - _core.DeregisterEventHandler(OnPlayerDeath); - _core.DeregisterEventHandler(OnWarmupEnd); - _core.DeregisterEventHandler(OnRoundEnd); - _core.DeregisterEventHandler(OnPlayerTeam); - } - - public void OnClientPutInServer(int playerslot) - { - var client = Utilities.GetPlayerFromSlot(playerslot); - - if(client == null) - return; - - PlayerData.ZombiePlayerData?.Add(client, new()); - PlayerData.PlayerClassesData?.Add(client, new()); - PlayerData.PlayerPurchaseCount?.Add(client, new()); - PlayerData.PlayerSpawnData?.Add(client, new()); - PlayerData.PlayerBurnData?.Add(client, null); - PlayerData.PlayerRegenData?.Add(client, null); - - _classes?.ClassesOnClientPutInServer(client); - } - - public void OnClientDisconnect(int playerslot) - { - var client = Utilities.GetPlayerFromSlot(playerslot); - - if(client == null) - return; - - PlayerData.ZombiePlayerData?.Remove(client); - PlayerData.PlayerClassesData?.Remove(client); - PlayerData.PlayerPurchaseCount?.Remove(client); - PlayerData.PlayerSpawnData?.Remove(client); - PlayerData.PlayerBurnData?.Remove(client); - HealthRegen.RegenOnClientDisconnect(client); - } - - // if reload a plugin this part won't be executed until you change map; - public void OnMapStart(string mapname) - { - _settings.GameSettingsOnMapStart(); - _weapons.WeaponsOnMapStart(); - _classes.ClassesOnMapStart(); - _hitgroup.HitGroupOnMapStart(); - _convar.ConVarOnLoad(); - _convar.ConVarExecuteOnMapStart(mapname); - - Server.ExecuteCommand("sv_predictable_damage_tag_ticks 0"); - Server.ExecuteCommand("mp_ignore_round_win_conditions 1"); - Server.ExecuteCommand("mp_give_player_c4 0"); - } - - public void OnPrecahceResources(ResourceManifest manifest) - { - if(Classes.ClassesConfig == null) - { - _logger.LogCritical("[OnPrecahceResources] The player classes config is null or not loaded yet!"); - return; - } - - foreach(var classes in Classes.ClassesConfig.Values) - { - if(!string.IsNullOrEmpty(classes.Model)) - { - if(classes.Model != "default") - manifest.AddResource(classes.Model!); - } - } - - manifest.AddResource("particles\\oylsister\\env_fire_large.vpcf"); - manifest.AddResource("soundevents\\soundevents_zsharp.vsndevts"); - } - - public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info) - { - var client = @event.Userid; - var attacker = @event.Attacker; - var weapon = @event.Weapon; - var dmgHealth = @event.DmgHealth; - var hitgroups = @event.Hitgroup; - - _infect.InfectOnPlayerHurt(client, attacker); - Knockback.KnockbackClient(client, attacker, weapon, dmgHealth, hitgroups); - Utils.UpdatedPlayerCash(attacker, dmgHealth); - _napalm.NapalmOnHurt(client, attacker, weapon, dmgHealth); - _classes.ClassesOnPlayerHurt(client); - - return HookResult.Continue; - } - - public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info) - { - // check the player count if there is any team that all dead. - if(Infect.InfectHasStarted()) - RoundEnd.CheckGameStatus(); - - // play sound for zombie when killed by human. - var client = @event.Userid; - - if(client == null) - return HookResult.Continue; - - if(Infect.IsClientInfect(client)) - Utils.EmitSound(client, "zr.amb.zombie_die"); - - _respawn.RespawnOnPlayerDeath(client); - HealthRegen.RegenOnPlayerDeath(client); - - return HookResult.Continue; - } - - public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) - { - var client = @event.Userid; - - if(client == null) - return HookResult.Continue; - - // when player join server this automatically trigger so we have to prevent this so they can switch team later. - if(client.Team == CsTeam.None || client.Team == CsTeam.Spectator) - return HookResult.Continue; - - _classes.ClassesOnPlayerSpawn(client); - - if(Infect.InfectHasStarted()) - { - var team = GameSettings.Settings?.RespawTeam ?? 0; - - if(team == 0) - _infect.InfectClient(client); - - else if(team == 1) - _infect.HumanizeClient(client); - - // get the player team - else - { - // not zombie - if(!PlayerData.ZombiePlayerData?[client].Zombie ?? false) - _infect.HumanizeClient(client); - - // human - else - _infect.InfectClient(client); - } - } - - else - _infect.HumanizeClient(client); - - // refresh purchase count here. - Utils.RefreshPurchaseCount(client); - _core.AddTimer(0.2f, () => _teleport.TeleportOnPlayerSpawn(client)); - - return HookResult.Continue; - } - - public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info) - { - var client = @event.Userid; - var team = @event.Team; - var isBot = @event.Isbot; - - if(isBot) - return HookResult.Continue; - - //Server.PrintToChatAll($"{client?.PlayerName} join team {team}."); - - if(!GameSettings.Settings?.AllowRespawnJoinLate ?? false) - return HookResult.Continue; - - if(team > 1) - { - _core.AddTimer(1.0f, () => { - if(client == null) - { - //Server.PrintToChatAll("Client is fucking null!"); - return; - } - - Respawn.RespawnClient(client); - }); - } - - return HookResult.Continue; - } - - public HookResult OnPreRoundStart(EventCsPreRestart @event, GameEventInfo info) - { - _infect.InfectOnPreRoundStart(); - return HookResult.Continue; - } - - public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) - { - _infect.InfectKillInfectionTimer(); - Utils.RemoveRoundObjective(); - RoundEnd.RoundEndOnRoundStart(); - Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.GameInfo"]}"); - return HookResult.Continue; - } - - public HookResult OnWarmupEnd(EventWarmupEnd @event, GameEventInfo info) - { - Infect.InfectStarted = false; - _infect.InfectKillInfectionTimer(); - return HookResult.Continue; - } - - public HookResult OnRoundFreezeEnd(EventRoundFreezeEnd @event, GameEventInfo info) - { - _infect.InfectOnRoundFreezeEnd(); - RoundEnd.RoundEndOnRoundFreezeEnd(); - return HookResult.Continue; - } - - public HookResult OnRoundEnd(EventRoundEnd @event, GameEventInfo info) - { - Infect.InfectStarted = false; - _infect.InfectKillInfectionTimer(); - _infect.InfectOnRoundEnd(); - RoundEnd.RoundEndOnRoundEnd(); - return HookResult.Continue; - } - - public HookResult OnPreRestart(EventCsPreRestart @event, GameEventInfo info) - { - Infect.InfectStarted = false; - _infect.InfectOnPreRoundStart(false); - _infect.InfectKillInfectionTimer(); - RoundEnd.RoundEndOnRoundEnd(); - return HookResult.Continue; - } +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; +using ZombieSharp.Models; +using static CounterStrikeSharp.API.Core.Listeners; + +namespace ZombieSharp.Plugin; + +public class Events(ZombieSharp core, Infect infect, GameSettings settings, Classes classes, Weapons weapons, Teleport teleport, Respawn respawn, Napalm napalm, ConVars convar, HitGroup hitgroup, ILogger logger) +{ + private readonly ZombieSharp _core = core; + private readonly Infect _infect = infect; + private readonly Classes _classes = classes; + private readonly GameSettings _settings = settings; + private readonly Weapons _weapons = weapons; + private readonly ILogger _logger = logger; + private readonly Teleport _teleport = teleport; + private readonly Napalm _napalm = napalm; + private readonly Respawn _respawn = respawn; + private readonly ConVars _convar = convar; + private readonly HitGroup _hitgroup = hitgroup; + + public void EventOnLoad() + { + _core.RegisterListener(OnClientPutInServer); + _core.RegisterListener(OnClientDisconnect); + _core.RegisterListener(OnMapStart); + _core.RegisterListener(OnPrecahceResources); + + _core.RegisterEventHandler(OnPlayerHurt); + _core.RegisterEventHandler(OnPlayerSpawn); + _core.RegisterEventHandler(OnRoundFreezeEnd); + _core.RegisterEventHandler(OnRoundStart); + _core.RegisterEventHandler(OnPreRoundStart); + _core.RegisterEventHandler(OnPlayerDeath); + _core.RegisterEventHandler(OnWarmupEnd); + _core.RegisterEventHandler(OnRoundEnd); + _core.RegisterEventHandler(OnPlayerTeam); + _core.RegisterEventHandler(OnPreRestart); + } + + public void EventOnUnload() + { + _core.RemoveListener(OnClientPutInServer); + _core.RemoveListener(OnClientDisconnect); + _core.RemoveListener(OnMapStart); + _core.RemoveListener(OnPrecahceResources); + + _core.DeregisterEventHandler(OnPlayerHurt); + _core.DeregisterEventHandler(OnPlayerSpawn); + _core.DeregisterEventHandler(OnRoundFreezeEnd); + _core.DeregisterEventHandler(OnRoundStart); + _core.DeregisterEventHandler(OnPreRoundStart); + _core.DeregisterEventHandler(OnPlayerDeath); + _core.DeregisterEventHandler(OnWarmupEnd); + _core.DeregisterEventHandler(OnRoundEnd); + _core.DeregisterEventHandler(OnPlayerTeam); + } + + public void OnClientPutInServer(int playerslot) + { + var client = Utilities.GetPlayerFromSlot(playerslot); + + if(client == null) + return; + + PlayerData.ZombiePlayerData?.Add(client, new()); + PlayerData.PlayerClassesData?.Add(client, new()); + PlayerData.PlayerPurchaseCount?.Add(client, new()); + PlayerData.PlayerSpawnData?.Add(client, new()); + PlayerData.PlayerBurnData?.Add(client, null); + PlayerData.PlayerRegenData?.Add(client, null); + + _classes?.ClassesOnClientPutInServer(client); + } + + public void OnClientDisconnect(int playerslot) + { + var client = Utilities.GetPlayerFromSlot(playerslot); + + if(client == null) + return; + + PlayerData.ZombiePlayerData?.Remove(client); + PlayerData.PlayerClassesData?.Remove(client); + PlayerData.PlayerPurchaseCount?.Remove(client); + PlayerData.PlayerSpawnData?.Remove(client); + PlayerData.PlayerBurnData?.Remove(client); + HealthRegen.RegenOnClientDisconnect(client); + + _infect?.CleanupPlayerData(client); + } + + // if reload a plugin this part won't be executed until you change map; + public void OnMapStart(string mapname) + { + _settings.GameSettingsOnMapStart(); + _weapons.WeaponsOnMapStart(); + _classes.ClassesOnMapStart(); + _hitgroup.HitGroupOnMapStart(); + _convar.ConVarOnLoad(); + _convar.ConVarExecuteOnMapStart(mapname); + + Server.ExecuteCommand("sv_predictable_damage_tag_ticks 0"); + Server.ExecuteCommand("mp_ignore_round_win_conditions 1"); + Server.ExecuteCommand("mp_give_player_c4 0"); + Server.ExecuteCommand("mp_autoteambalance 0"); + Server.ExecuteCommand("mp_limitteams 0"); + Server.ExecuteCommand("mp_teammates_are_enemies 0"); + } + + public void OnPrecahceResources(ResourceManifest manifest) + { + if(Classes.ClassesConfig == null) + { + _logger.LogCritical("[OnPrecahceResources] The player classes config is null or not loaded yet!"); + return; + } + + foreach(var classes in Classes.ClassesConfig.Values) + { + if(!string.IsNullOrEmpty(classes.Model)) + { + if(classes.Model != "default") + manifest.AddResource(classes.Model!); + } + } + + manifest.AddResource("particles\\oylsister\\env_fire_large.vpcf"); + manifest.AddResource("soundevents\\soundevents_zsharp.vsndevts"); + } + + public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info) + { + var client = @event.Userid; + var attacker = @event.Attacker; + var weapon = @event.Weapon; + var dmgHealth = @event.DmgHealth; + var hitgroups = @event.Hitgroup; + + _infect.InfectOnPlayerHurt(client, attacker); + Knockback.KnockbackClient(client, attacker, weapon, dmgHealth, hitgroups); + Utils.UpdatedPlayerCash(attacker, dmgHealth); + _napalm.NapalmOnHurt(client, attacker, weapon, dmgHealth); + _classes.ClassesOnPlayerHurt(client); + + return HookResult.Continue; + } + + public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info) + { + // check the player count if there is any team that all dead. + if(Infect.InfectHasStarted()) + RoundEnd.CheckGameStatus(); + + // play sound for zombie when killed by human. + var client = @event.Userid; + + if(client == null) + return HookResult.Continue; + + if(Infect.IsClientZombie(client)) + Utils.EmitSound(client, "zr.amb.zombie_die"); + + _respawn.RespawnOnPlayerDeath(client); + HealthRegen.RegenOnPlayerDeath(client); + + return HookResult.Continue; + } + + public HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info) + { + var client = @event.Userid; + + if (client == null) + return HookResult.Continue; + + if (client.Team == CsTeam.None || client.Team == CsTeam.Spectator) + return HookResult.Continue; + + _classes.ClassesOnPlayerSpawn(client); + + if(Infect.IsTestMode) + { + _infect.HumanizeClient(client); + return HookResult.Continue; + } + + // Apply appropriate class based on infection status + if(Infect.InfectHasStarted()) + { + var team = GameSettings.Settings?.RespawTeam ?? 0; + + if(team == 0) // Force zombie + { + _infect.InfectClient(client); + } + else if(team == 1) // Force human + { + _infect.HumanizeClient(client); + } + else // Keep previous team (2) + { + // Check if player was zombie before + if(PlayerData.ZombiePlayerData?[client].Zombie ?? false) + { + _infect.InfectClient(client); + } + else + { + _infect.HumanizeClient(client); + } + } + } + else + { + // Before infection starts, everyone is human + _infect.HumanizeClient(client); + + // Apply human class attributes immediately + var humanClass = PlayerData.PlayerClassesData?[client].HumanClass; + if(humanClass != null) + { + _core.AddTimer(0.2f, () => _classes.ClassesApplyToPlayer(client, humanClass)); + } + } + + Utils.RefreshPurchaseCount(client); + _core.AddTimer(0.2f, () => _teleport.TeleportOnPlayerSpawn(client)); + + return HookResult.Continue; + } + + public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info) + { + var client = @event.Userid; + var team = @event.Team; + var isBot = @event.Isbot; + + if(isBot) + return HookResult.Continue; + + //Server.PrintToChatAll($"{client?.PlayerName} join team {team}."); + + if(!GameSettings.Settings?.AllowRespawnJoinLate ?? false) + return HookResult.Continue; + + if(team > 1) + { + _core.AddTimer(1.0f, () => { + if(client == null) + { + //Server.PrintToChatAll("Client is fucking null!"); + return; + } + + Respawn.RespawnClient(client); + }); + } + + return HookResult.Continue; + } + + public HookResult OnPreRoundStart(EventCsPreRestart @event, GameEventInfo info) + { + _infect.InfectOnPreRoundStart(); + return HookResult.Continue; + } + + public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) + { + _infect.InfectKillInfectionTimer(); + Utils.RemoveRoundObjective(); + RoundEnd.RoundEndOnRoundStart(); + Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.GameInfo"]}"); + return HookResult.Continue; + } + + public HookResult OnWarmupEnd(EventWarmupEnd @event, GameEventInfo info) + { + Infect.InfectStarted = false; + _infect.InfectKillInfectionTimer(); + return HookResult.Continue; + } + + public HookResult OnRoundFreezeEnd(EventRoundFreezeEnd @event, GameEventInfo info) + { + _infect.InfectOnRoundFreezeEnd(); + RoundEnd.RoundEndOnRoundFreezeEnd(); + return HookResult.Continue; + } + + public HookResult OnRoundEnd(EventRoundEnd @event, GameEventInfo info) + { + Infect.InfectStarted = false; + _infect.InfectKillInfectionTimer(); + _infect.InfectOnRoundEnd(); + RoundEnd.RoundEndOnRoundEnd(); + return HookResult.Continue; + } + + public HookResult OnPreRestart(EventCsPreRestart @event, GameEventInfo info) + { + Infect.InfectStarted = false; + _infect.InfectOnPreRoundStart(false); + _infect.InfectKillInfectionTimer(); + RoundEnd.RoundEndOnRoundEnd(); + return HookResult.Continue; + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/GameSettings.cs b/ZombieSharp/Plugin/GameSettings.cs index b7448ed..a46402c 100644 --- a/ZombieSharp/Plugin/GameSettings.cs +++ b/ZombieSharp/Plugin/GameSettings.cs @@ -1,30 +1,30 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class GameSettings(ILogger logger) -{ - private readonly ILogger _logger = logger; - public static GameConfigs? Settings = null; - - public void GameSettingsOnMapStart() - { - Settings = null; - - // initial class config data. - Settings = new(); - - var configPath = Path.Combine(ZombieSharp.ConfigPath, "gamesettings.jsonc"); - - if(!File.Exists(configPath)) - { - _logger.LogCritical("[GameSettingsOnMapStart] Couldn't find a gamesettings.jsonc file!"); - return; - } - - _logger.LogInformation("[GameSettingsOnMapStart] Load Game settings file."); - Settings = JsonConvert.DeserializeObject(File.ReadAllText(configPath)); - } +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class GameSettings(ILogger logger) +{ + private readonly ILogger _logger = logger; + public static GameConfigs? Settings = null; + + public void GameSettingsOnMapStart() + { + Settings = null; + + // initial class config data. + Settings = new(); + + var configPath = Path.Combine(ZombieSharp.ConfigPath, "gamesettings.jsonc"); + + if(!File.Exists(configPath)) + { + _logger.LogCritical("[GameSettingsOnMapStart] Couldn't find a gamesettings.jsonc file!"); + return; + } + + _logger.LogInformation("[GameSettingsOnMapStart] Loading Game settings file."); + Settings = JsonConvert.DeserializeObject(File.ReadAllText(configPath)); + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/HealthRegen.cs b/ZombieSharp/Plugin/HealthRegen.cs index fa4e545..8ec70cd 100644 --- a/ZombieSharp/Plugin/HealthRegen.cs +++ b/ZombieSharp/Plugin/HealthRegen.cs @@ -1,151 +1,151 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Timers; -using Microsoft.Extensions.Logging; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class HealthRegen -{ - private static ZombieSharp? _core; - private static ILogger? _logger; - - public HealthRegen(ZombieSharp core, ILogger logger) - { - _core = core; - _logger = logger; - } - - public static void RegenOnClientDisconnect(CCSPlayerController client) - { - // if player data is null - if (PlayerData.PlayerRegenData == null) - { - _logger?.LogError("[RegenOnClientDisconnect] PlayerRegenData is null"); - return; - } - - // search for the player in the dictionary - if (!PlayerData.PlayerRegenData.ContainsKey(client)) - { - _logger?.LogError("[RegenOnClientDisconnect] Player not found in PlayerRegenData"); - return; - } - - // we stop the timer first. - RegenKillTimer(client); - - // remove it. - PlayerData.PlayerRegenData.Remove(client); - } - - public static void RegenOnPlayerDeath(CCSPlayerController client) - { - // we stop the timer first. - RegenKillTimer(client); - } - - public static void RegenOnApplyClass(CCSPlayerController client, ClassAttribute classData) - { - // we stop the timer first. - RegenKillTimer(client); - - // we start the timer. - if(classData == null) - { - _logger?.LogError("[RegenOnPlayerSpawn] ClassAttribute is null"); - return; - } - - if(classData.Regen_Interval <= 0 || classData.Regen_Amount <= 0) - { - return; - } - - // if player data is null - if (PlayerData.PlayerRegenData == null) - { - _logger?.LogError("[RegenKillTimer] PlayerRegenData is null"); - return; - } - - // search for the player in the dictionary - if (!PlayerData.PlayerRegenData.ContainsKey(client)) - { - _logger?.LogInformation("[RegenKillTimer] Player {name} not found in PlayerRegenData, but add a new one anyway.", client.PlayerName); - PlayerData.PlayerRegenData.Add(client, null); - } - - PlayerData.PlayerRegenData[client] = _core?.AddTimer(classData.Regen_Interval, () => - { - if(client == null) - { - _logger?.LogError("[RegenOnPlayerSpawn] CCSPlayerController is null"); - return; - } - - if(!Utils.IsPlayerAlive(client)) - { - RegenKillTimer(client); - return; - } - - var playerPawn = client.PlayerPawn.Value; - - if(playerPawn == null) - { - _logger?.LogError("[RegenOnPlayerSpawn] PlayerPawn is null"); - RegenKillTimer(client); - return; - } - - // if client health plus with regen amount is greater than class health then we set the health to class health. - if(playerPawn.Health + classData.Regen_Amount >= classData.Health) - { - Server.NextWorldUpdate(() => { - playerPawn.Health = classData.Health; - Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_iHealth"); - }); - return; - } - - if(playerPawn.Health > classData.Health) - { - // just return if the health is greater than class health. - return; - } - - // else we add the regen amount to the health. - Server.NextWorldUpdate(() => { - playerPawn.Health += classData.Regen_Amount; - Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_iHealth"); - }); - - }, TimerFlags.REPEAT|TimerFlags.STOP_ON_MAPCHANGE); - } - - public static void RegenKillTimer(CCSPlayerController client) - { - // if player data is null - if (PlayerData.PlayerRegenData == null) - { - _logger?.LogError("[RegenKillTimer] PlayerRegenData is null"); - return; - } - - // search for the player in the dictionary - if (!PlayerData.PlayerRegenData.ContainsKey(client)) - { - _logger?.LogError("[RegenKillTimer] Player not found in PlayerRegenData"); - return; - } - - // we stop the timer first. - if(PlayerData.PlayerRegenData[client] != null) - { - PlayerData.PlayerRegenData[client]?.Kill(); - PlayerData.PlayerRegenData[client] = null; - } - } +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Timers; +using Microsoft.Extensions.Logging; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class HealthRegen +{ + private static ZombieSharp? _core; + private static ILogger? _logger; + + public HealthRegen(ZombieSharp core, ILogger logger) + { + _core = core; + _logger = logger; + } + + public static void RegenOnClientDisconnect(CCSPlayerController client) + { + // if player data is null + if (PlayerData.PlayerRegenData == null) + { + _logger?.LogError("[RegenOnClientDisconnect] PlayerRegenData is null"); + return; + } + + // search for the player in the dictionary + if (!PlayerData.PlayerRegenData.ContainsKey(client)) + { + _logger?.LogError("[RegenOnClientDisconnect] Player not found in PlayerRegenData"); + return; + } + + // we stop the timer first. + RegenKillTimer(client); + + // remove it. + PlayerData.PlayerRegenData.Remove(client); + } + + public static void RegenOnPlayerDeath(CCSPlayerController client) + { + // we stop the timer first. + RegenKillTimer(client); + } + + public static void RegenOnApplyClass(CCSPlayerController client, ClassAttribute classData) + { + // we stop the timer first. + RegenKillTimer(client); + + // we start the timer. + if(classData == null) + { + _logger?.LogError("[RegenOnPlayerSpawn] ClassAttribute is null"); + return; + } + + if(classData.Regen_Interval <= 0 || classData.Regen_Amount <= 0) + { + return; + } + + // if player data is null + if (PlayerData.PlayerRegenData == null) + { + _logger?.LogError("[RegenKillTimer] PlayerRegenData is null"); + return; + } + + // search for the player in the dictionary + if (!PlayerData.PlayerRegenData.ContainsKey(client)) + { + _logger?.LogWarning("[RegenKillTimer] Player {name} not found in PlayerRegenData, creating new entry", client.PlayerName); + PlayerData.PlayerRegenData.Add(client, null); + } + + PlayerData.PlayerRegenData[client] = _core?.AddTimer(classData.Regen_Interval, () => + { + if(client == null) + { + _logger?.LogError("[RegenOnPlayerSpawn] CCSPlayerController is null"); + return; + } + + if(!Utils.IsPlayerAlive(client)) + { + RegenKillTimer(client); + return; + } + + var playerPawn = client.PlayerPawn.Value; + + if(playerPawn == null) + { + _logger?.LogError("[RegenOnPlayerSpawn] PlayerPawn is null"); + RegenKillTimer(client); + return; + } + + // if client health plus with regen amount is greater than class health then we set the health to class health. + if(playerPawn.Health + classData.Regen_Amount >= classData.Health) + { + Server.NextWorldUpdate(() => { + playerPawn.Health = classData.Health; + Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_iHealth"); + }); + return; + } + + if(playerPawn.Health > classData.Health) + { + // just return if the health is greater than class health. + return; + } + + // else we add the regen amount to the health. + Server.NextWorldUpdate(() => { + playerPawn.Health += classData.Regen_Amount; + Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_iHealth"); + }); + + }, TimerFlags.REPEAT|TimerFlags.STOP_ON_MAPCHANGE); + } + + public static void RegenKillTimer(CCSPlayerController client) + { + // if player data is null + if (PlayerData.PlayerRegenData == null) + { + _logger?.LogError("[RegenKillTimer] PlayerRegenData is null"); + return; + } + + // search for the player in the dictionary + if (!PlayerData.PlayerRegenData.ContainsKey(client)) + { + _logger?.LogError("[RegenKillTimer] Player not found in PlayerRegenData"); + return; + } + + // we stop the timer first. + if(PlayerData.PlayerRegenData[client] != null) + { + PlayerData.PlayerRegenData[client]?.Kill(); + PlayerData.PlayerRegenData[client] = null; + } + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/HitGroup.cs b/ZombieSharp/Plugin/HitGroup.cs index d4138a0..5b648e7 100644 --- a/ZombieSharp/Plugin/HitGroup.cs +++ b/ZombieSharp/Plugin/HitGroup.cs @@ -1,54 +1,54 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class HitGroup(ILogger logger) -{ - private readonly ILogger _logger = logger; - public static Dictionary? HitGroupConfigs = []; - - public void HitGroupOnMapStart() - { - // make sure this one is null. - HitGroupConfigs = null; - - // initial class config data. - HitGroupConfigs = new Dictionary(); - - var configPath = Path.Combine(ZombieSharp.ConfigPath, "hitgroups.jsonc"); - - if(!File.Exists(configPath)) - { - _logger.LogCritical("[HitGroupOnMapStart] Couldn't find a hitgroups.jsonc file!"); - return; - } - - _logger.LogInformation("[HitGroupOnMapStart] Load Player Classes file."); - - // we get data from jsonc file. - HitGroupConfigs = JsonConvert.DeserializeObject>(File.ReadAllText(configPath)); - } - - public static float GetHitGroupKnockback(int hitgroups) - { - if(HitGroupConfigs == null) - return 1.0f; - - var index = GetHitGroupDataByIndex(hitgroups); - - if(index == null) - return 1.0f; - - return index.Knockback; - } - - public static HitGroupData? GetHitGroupDataByIndex(int hitgroups) - { - if(HitGroupConfigs == null) - return null; - - return HitGroupConfigs.Where(p => p.Value.Index == hitgroups).FirstOrDefault().Value; - } +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class HitGroup(ILogger logger) +{ + private readonly ILogger _logger = logger; + public static Dictionary? HitGroupConfigs = []; + + public void HitGroupOnMapStart() + { + // make sure this one is null. + HitGroupConfigs = null; + + // initial class config data. + HitGroupConfigs = new Dictionary(); + + var configPath = Path.Combine(ZombieSharp.ConfigPath, "hitgroups.jsonc"); + + if(!File.Exists(configPath)) + { + _logger.LogCritical("[HitGroupOnMapStart] Couldn't find a hitgroups.jsonc file!"); + return; + } + + _logger.LogInformation("[HitGroupOnMapStart] Loading Player Classes file."); + + // we get data from jsonc file. + HitGroupConfigs = JsonConvert.DeserializeObject>(File.ReadAllText(configPath)); + } + + public static float GetHitGroupKnockback(int hitgroups) + { + if(HitGroupConfigs == null) + return 1.0f; + + var index = GetHitGroupDataByIndex(hitgroups); + + if(index == null) + return 1.0f; + + return index.Knockback; + } + + public static HitGroupData? GetHitGroupDataByIndex(int hitgroups) + { + if(HitGroupConfigs == null) + return null; + + return HitGroupConfigs.Where(p => p.Value.Index == hitgroups).FirstOrDefault().Value; + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/Hook.cs b/ZombieSharp/Plugin/Hook.cs index c8ff013..9ebb1a5 100644 --- a/ZombieSharp/Plugin/Hook.cs +++ b/ZombieSharp/Plugin/Hook.cs @@ -1,181 +1,215 @@ -using System.Diagnostics; -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Memory; -using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; - -namespace ZombieSharp.Plugin; - -public class Hook(ZombieSharp core, Weapons weapons, Respawn respawn, ILogger logger) -{ - private readonly ZombieSharp _core = core; - private readonly Weapons _weapons = weapons; - private readonly Respawn _respawn = respawn; - private readonly ILogger _logger = logger; - - public void HookOnLoad() - { - VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Hook(OnCanAcquire, HookMode.Pre); - VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre); - - _core.AddCommandListener("jointeam", OnClientJoinTeam, HookMode.Pre); - } - - public void HookOnUnload() - { - VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnCanAcquire, HookMode.Pre); - VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre); - - _core.RemoveCommandListener("jointeam", OnClientJoinTeam, HookMode.Pre); - } - - public HookResult OnCanAcquire(DynamicHook hook) - { - var itemService = hook.GetParam(0); - var weapon = VirtualFunctions.GetCSWeaponDataFromKey(-1, hook.GetParam(1).ItemDefinitionIndex.ToString()); - var method = hook.GetParam(2); - var client = itemService.Pawn.Value.Controller.Value?.As(); - - if(client == null) - return HookResult.Continue; - - // if client is infect and weapon is not a knife. - if(Infect.IsClientInfect(client) && !weapon.Name.Contains("knife")) - { - hook.SetReturn(AcquireResult.NotAllowedByProhibition); - return HookResult.Handled; - } - - var restirctEnable = GameSettings.Settings?.WeaponRestrictEnable ?? false; - var purchaseEnable = GameSettings.Settings?.WeaponPurchaseEnable ?? false; - - // weapon restrict section. - if(restirctEnable) - { - // if player buy from menu tell them they can't. - if(Weapons.IsRestricted(weapon.Name)) - { - var attribute = Weapons.GetWeaponAttributeByEntityName(weapon.Name); - - if(method == AcquireMethod.Buy) - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.IsRestricted", attribute?.WeaponName!]}"); - - hook.SetReturn(AcquireResult.NotAllowedByProhibition); - return HookResult.Handled; - } - - else - { - if(method == AcquireMethod.Buy && purchaseEnable) - { - var attribute = Weapons.GetWeaponAttributeByEntityName(weapon.Name); - - if(attribute != null) - { - _weapons.PurchaseWeapon(client, attribute); - hook.SetReturn(AcquireResult.NotAllowedByProhibition); - return HookResult.Handled; - } - } - } - } - - else - { - if(method == AcquireMethod.Buy && purchaseEnable) - { - var attribute = Weapons.GetWeaponAttributeByEntityName(weapon.Name); - - if(attribute != null) - { - _weapons.PurchaseWeapon(client, attribute); - hook.SetReturn(AcquireResult.NotAllowedByProhibition); - return HookResult.Handled; - } - } - } - - return HookResult.Continue; - } - - public HookResult OnTakeDamage(DynamicHook hook) - { - var victim = hook.GetParam(0); - var info = hook.GetParam(1); - - var client = Utils.GetCCSPlayerController(victim); - var attacker = Utils.GetCCSPlayerController(info.Attacker.Value); - - if(client == null || attacker == null) - return HookResult.Continue; - - if(info.Inflictor.Value?.DesignerName == "inferno") - { - // prevent self damage from molotov. - var inferno = new CInferno(info.Inflictor.Value.Handle); - if(client == inferno.OwnerEntity.Value) - return HookResult.Handled; - - // if human step on it then we just stop here. - if(Infect.IsClientHuman(client)) - return HookResult.Handled; - - // if zombie step on it then we make them walking slow. - else if(Infect.IsClientInfect(client)) - Utils.SetStamina(client, 40.0f); - } - - if(info.Inflictor.Value?.DesignerName == "hegrenade") - { - Knockback.KnockbackClientExplosion(client, info.Inflictor.Value, info.Damage); - } - - // prevent death from backstabing. - if(Infect.IsClientInfect(attacker) && Infect.IsClientHuman(client)) - info.Damage = 1; - - return HookResult.Continue; - } - - public HookResult OnClientJoinTeam(CCSPlayerController? client, CommandInfo info) - { - // check for client null again. - if(client == null) - return HookResult.Continue; - - //Server.PrintToChatAll($"{client.PlayerName} is doing {info.GetArg(0)} {info.GetArg(1)}"); - - var team = (CsTeam)int.Parse(info.GetArg(1)); - - // for spectator case we allow this - if(team == CsTeam.Spectator || team == CsTeam.None) - { - if(Utils.IsPlayerAlive(client)) - client.CommitSuicide(false, true); - - client.SwitchTeam(CsTeam.Spectator); - } - - else - { - if((GameSettings.Settings?.RespawnEnable ?? true) && (GameSettings.Settings?.AllowRespawnJoinLate ?? true)) - { - if(team == client.Team) - { - client.PrintToChat("You're choosing the same team!"); - return HookResult.Continue; - } - - if(Utils.IsPlayerAlive(client)) - client.CommitSuicide(false, true); - - client.SwitchTeam(team); - } - } - - return HookResult.Continue; - } +using System.Diagnostics; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; +using static CounterStrikeSharp.API.Core.Listeners; + +namespace ZombieSharp.Plugin; + +public class Hook(ZombieSharp core, Weapons weapons, Respawn respawn, ILogger logger) +{ + private readonly ZombieSharp _core = core; + private readonly Weapons _weapons = weapons; + private readonly Respawn _respawn = respawn; + private readonly ILogger _logger = logger; + + public void HookOnLoad() + { + VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Hook(OnCanAcquire, HookMode.Pre); + VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre); + + _core.AddCommandListener("jointeam", OnClientJoinTeam, HookMode.Pre); + } + + public void HookOnUnload() + { + VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnCanAcquire, HookMode.Pre); + VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre); + + _core.RemoveCommandListener("jointeam", OnClientJoinTeam, HookMode.Pre); + } + + public HookResult OnCanAcquire(DynamicHook hook) + { + var itemService = hook.GetParam(0); + var weapon = VirtualFunctions.GetCSWeaponDataFromKey(-1, hook.GetParam(1).ItemDefinitionIndex.ToString()); + var method = hook.GetParam(2); + var client = itemService.Pawn.Value.Controller.Value?.As(); + + if(client == null) + return HookResult.Continue; + + // if client is infect and weapon is not a knife. + if(Infect.IsClientZombie(client) && !weapon.Name.Contains("knife")) + { + hook.SetReturn(AcquireResult.NotAllowedByProhibition); + return HookResult.Handled; + } + + var restirctEnable = GameSettings.Settings?.WeaponRestrictEnable ?? false; + var purchaseEnable = GameSettings.Settings?.WeaponPurchaseEnable ?? false; + + // weapon restrict section. + if(restirctEnable) + { + // if player buy from menu tell them they can't. + if(Weapons.IsRestricted(weapon.Name)) + { + var attribute = Weapons.GetWeaponAttributeByEntityName(weapon.Name); + + if(method == AcquireMethod.Buy) + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.IsRestricted", attribute?.WeaponName!]}"); + + hook.SetReturn(AcquireResult.NotAllowedByProhibition); + return HookResult.Handled; + } + + else + { + if(method == AcquireMethod.Buy && purchaseEnable) + { + var attribute = Weapons.GetWeaponAttributeByEntityName(weapon.Name); + + if(attribute != null) + { + _weapons.PurchaseWeapon(client, attribute); + hook.SetReturn(AcquireResult.NotAllowedByProhibition); + return HookResult.Handled; + } + } + } + } + + else + { + if(method == AcquireMethod.Buy && purchaseEnable) + { + var attribute = Weapons.GetWeaponAttributeByEntityName(weapon.Name); + + if(attribute != null) + { + _weapons.PurchaseWeapon(client, attribute); + hook.SetReturn(AcquireResult.NotAllowedByProhibition); + return HookResult.Handled; + } + } + } + + return HookResult.Continue; + } + + public HookResult OnTakeDamage(DynamicHook hook) + { + var victim = hook.GetParam(0); + var info = hook.GetParam(1); + + var client = Utils.GetCCSPlayerController(victim); + var attacker = Utils.GetCCSPlayerController(info.Attacker.Value); + + if(client == null || attacker == null) + return HookResult.Continue; + + if(info.Inflictor.Value?.DesignerName == "inferno") + { + // prevent self damage from molotov. + var inferno = new CInferno(info.Inflictor.Value.Handle); + if(client == inferno.OwnerEntity.Value) + return HookResult.Handled; + + // if human step on it then we just stop here. + if(Infect.IsClientHuman(client)) + return HookResult.Handled; + + // if zombie step on it then we make them walking slow. + else if(Infect.IsClientZombie(client)) + Utils.SetStamina(client, 40.0f); + } + + if(info.Inflictor.Value?.DesignerName == "hegrenade") + { + Knockback.KnockbackClientExplosion(client, info.Inflictor.Value, info.Damage); + } + + // prevent death from backstabing. + if (Infect.IsClientZombie(attacker) && Infect.IsClientHuman(client)) + { + if (Infect.IsTestMode) + { + return HookResult.Handled; + } + var distance = Utils.GetPlayerDistance(client, attacker); + + // if distance is not null but player is too far for knife range, then we blocked it. + if (distance != null && distance.Value > GameSettings.Settings?.MaxKnifeRange) + { + return HookResult.Handled; + } + info.Damage = 1; + } + + return HookResult.Continue; + } + + public HookResult OnClientJoinTeam(CCSPlayerController? client, CommandInfo info) + { + // check for client null again. + if(client == null) + return HookResult.Continue; + + var team = (CsTeam)int.Parse(info.GetArg(1)); + + // for spectator case we allow this + if(team == CsTeam.Spectator || team == CsTeam.None) + { + + if(Utils.IsPlayerAlive(client)) + client.CommitSuicide(false, true); + + client.SwitchTeam(CsTeam.Spectator); + return HookResult.Handled; + } + else + { + // If infection has started, prevent manual team switching + if(Infect.InfectHasStarted()) + { + _logger.LogWarning("[OnClientJoinTeam] {0} attempting team switch during infection - enforcing zombie status", client.PlayerName); + + // Force the team assignment based on zombie status + _core.AddTimer(0.05f, () => { + if(client == null || !client.IsValid) + return; + + bool isZombie = Infect.IsClientZombie(client); + CsTeam targetTeam = isZombie ? CsTeam.Terrorist : CsTeam.CounterTerrorist; + + if(client.Team != targetTeam) + client.SwitchTeam(targetTeam); + }); + return HookResult.Handled; + } + + + + if((GameSettings.Settings?.RespawnEnable ?? true) && (GameSettings.Settings?.AllowRespawnJoinLate ?? true)) + { + if(team == client.Team) + { + //client.PrintToChat("You're choosing the same team!"); + return HookResult.Continue; + } + + if(Utils.IsPlayerAlive(client)) + client.CommitSuicide(false, true); + + client.SwitchTeam(team); + } + } + + return HookResult.Continue; + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/Infect.cs b/ZombieSharp/Plugin/Infect.cs index 284ecea..6474eeb 100644 --- a/ZombieSharp/Plugin/Infect.cs +++ b/ZombieSharp/Plugin/Infect.cs @@ -1,483 +1,615 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Admin; -using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Timers; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; -using ZombieSharp.Api; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class Infect(ZombieSharp core, ILogger logger, Classes classes, ZombieSharpInterface api) -{ - private ZombieSharp _core = core; - private readonly ILogger _logger = logger; - private Classes? _classes = classes; - private readonly ZombieSharpInterface _api = api; - public static bool InfectStarted = false; - private CounterStrikeSharp.API.Modules.Timers.Timer? _firstInfection = null; - private CounterStrikeSharp.API.Modules.Timers.Timer? _infectCountTimer = null; - private int _infectCountNumber = 0; - - public void InfectOnLoad() - { - _core.AddCommand("zs_infect", "Infection Command", InfectClientCommand); - _core.AddCommand("zs_human", "Humanize Command", HumanizeClientCommand); - } - - [CommandHelper(1, "zs_infect ")] - [RequiresPermissions("@css/slay")] - public void InfectClientCommand(CCSPlayerController? client, CommandInfo info) - { - var target = info.GetArgTargetResult(1); - - if(target == null || target.Count() <= 0) - { - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.TargetInvalid"]}"); - return; - } - - var mother = false; - - if(!InfectHasStarted()) - mother = true; - - foreach(var player in target) - { - if(player == null) continue; - if(!Utils.IsPlayerAlive(player)) - { - if(target.Count() < 2) - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeAlive"]}"); - - continue; - } - - if(IsClientInfect(player)) - { - if(target.Count() < 2) - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.AlreadyZombie"]}"); - - continue; - } - - InfectClient(player, null, mother, true); - } - - if(target.Count() > 1) - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.SuccessAll"]}"); - - else if(target.Count() < 2 && target.FirstOrDefault() != null) - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.Success", target.FirstOrDefault()!.PlayerName]}"); - } - - [CommandHelper(1, "zs_human ")] - [RequiresPermissions("@css/slay")] - public void HumanizeClientCommand(CCSPlayerController? client, CommandInfo info) - { - var target = info.GetArgTargetResult(1); - - if(target == null || target.Count() <= 0) - { - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.TargetInvalid"]}"); - return; - } - - foreach(var player in target) - { - if(player == null) continue; - if(!Utils.IsPlayerAlive(player)) - { - if(target.Count() < 2) - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeAlive"]}"); - - continue; - } - - if(IsClientHuman(player)) - { - if(target.Count() < 2) - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Human.AlreadyHuman"]}"); - - continue; - } - - HumanizeClient(player, true); - } - - if(target.Count() > 1) - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Human.SuccessAll"]}"); - - else if(target.Count() < 2 && target.FirstOrDefault() != null) - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Human.Success", target.FirstOrDefault()!.PlayerName]}"); - } - - public void InfectOnRoundFreezeEnd() - { - // kill timer just in case. - InfectKillInfectionTimer(); - - if(GameSettings.Settings == null) - { - _logger.LogCritical("[InfectOnRoundFreezeEnd] Game Settings is null!"); - _infectCountNumber = 15; - } - - else - _infectCountNumber = (int)GameSettings.Settings.FirstInfectionTimer; - - _firstInfection = _core.AddTimer(_infectCountNumber + 1, InfectMotherZombie, TimerFlags.STOP_ON_MAPCHANGE); - - _infectCountTimer = _core.AddTimer(1f, () => - { - if(_infectCountNumber < 0) - { - InfectKillInfectionTimer(); - return; - } - - Utils.PrintToCenterAll($" {_core.Localizer["Infect.Countdown", _infectCountNumber]}"); - _infectCountNumber--; - - }, TimerFlags.REPEAT|TimerFlags.STOP_ON_MAPCHANGE); - } - - public void InfectKillInfectionTimer() - { - if(_firstInfection != null) - { - _firstInfection.Kill(); - _firstInfection = null; - } - - if(_infectCountTimer != null) - { - _infectCountTimer.Kill(); - _infectCountTimer = null; - } - } - - public void InfectMotherZombie() - { - // if infection already started then stop it. - if(InfectHasStarted()) - return; - - // we get how much zombie we need. - var currentPlayer = Utilities.GetPlayers(); - var ratio = 7f; - - if(GameSettings.Settings == null) - { - _logger.LogCritical("[InfectMotherZombie] Game Settings is null!"); - ratio = 7f; - } - - else - ratio = GameSettings.Settings.MotherZombieRatio; - - var requireZombie = (int)Math.Ceiling(currentPlayer.Count / ratio); - - // get the list of candidate - List candidate = new(); - - foreach(var player in currentPlayer) - { - // null or not alive player. - if(player == null || !player.PawnIsAlive) - continue; - - // not in the data list. - if(!PlayerData.ZombiePlayerData!.ContainsKey(player)) - continue; - - if(PlayerData.ZombiePlayerData[player].MotherZombie == ZombiePlayer.MotherZombieStatus.NONE) - candidate.Add(player); - } - - // if candidate is not enough - if(candidate.Count < requireZombie) - { - // tell player that mother zombie cycle has been reset - Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.MotherZombieReset"]}"); - - foreach(var player in currentPlayer) - { - // null or not alive player. - if(player == null || !player.PawnIsAlive) - continue; - - // not in the data list. - if(!PlayerData.ZombiePlayerData!.ContainsKey(player)) - continue; - - if(PlayerData.ZombiePlayerData[player].MotherZombie == ZombiePlayer.MotherZombieStatus.LAST) - { - // add to candidate. - candidate.Add(player); - - // Mother zombie status to none. - PlayerData.ZombiePlayerData[player].MotherZombie = ZombiePlayer.MotherZombieStatus.NONE; - } - } - } - - // we will infect motherzombie here. - // we have to loop all candidate in order to ensure we get all required zombie as we want. - // variable check total zombie made. - var infected = 0; - - foreach(var player in candidate) - { - // we stop here if all zombie is made. - if(infected >= requireZombie) - return; - - // check every time. - if(player == null || !player.IsValid || !player.PawnIsAlive) - continue; - - // infect them with motherzombie true - InfectClient(player, null, true); - - // motherzombie made + - infected++; - } - } - - public void InfectOnPlayerHurt(CCSPlayerController? client, CCSPlayerController? attacker) - { - if(client == null || attacker == null) - return; - - if(IsClientHuman(client) && IsClientInfect(attacker)) - InfectClient(client, attacker); - } - - public void InfectOnPreRoundStart(bool switchTeam = true) - { - if(PlayerData.ZombiePlayerData == null) - { - _logger.LogCritical("[InfectOnPreRoundStart] ZombiePlayers is invalid!"); - return; - } - - foreach (var player in Utilities.GetPlayers()) - { - if(player == null || !player.IsValid) - { - _logger.LogError("[InfectOnPreRoundStart] Player key is not invalid!"); - continue; - } - - // if player is not in the dictionary then skip it. - if(!PlayerData.ZombiePlayerData.ContainsKey(player)) - { - _logger.LogError("[InfectOnPreRoundStart] Player {name} is not in ZombiePlayersData!", player.PlayerName); - continue; - } - - // set to false. - PlayerData.ZombiePlayerData[player].Zombie = false; - - // switch their team to CT. and make sure they are not spectator or else they will get spawned with another. - if(player.Team != CsTeam.Spectator && player.Team != CsTeam.None && switchTeam) - player.SwitchTeam(CsTeam.CounterTerrorist); - } - } - - // we need to set mother zombie to last if they are chosen. so when mother zombie candidate run out we bring them to motherzombie cycle again. - public void InfectOnRoundEnd() - { - if(PlayerData.ZombiePlayerData == null) - { - _logger.LogCritical("[InfectOnRoundEnd] ZombiePlayers data is null!"); - return; - } - - foreach(var player in Utilities.GetPlayers()) - { - if(player == null || !player.IsValid) - { - _logger.LogError("[InfectOnRoundEnd] Player is not invalid!"); - continue; - } - - // check if player is in dictionary and motherzombie is chosen then we have to set them to last. - if(!PlayerData.ZombiePlayerData.ContainsKey(player)) - { - _logger.LogError("[InfectOnRoundEnd] Player {name} is not in ZombiePlayersData!", player.PlayerName); - continue; - } - - // set mother zombie status to Last. - if(PlayerData.ZombiePlayerData[player].MotherZombie == ZombiePlayer.MotherZombieStatus.CHOSEN) - PlayerData.ZombiePlayerData[player].MotherZombie = ZombiePlayer.MotherZombieStatus.LAST; - } - } - - public void InfectClient(CCSPlayerController client, CCSPlayerController? attacker = null, bool motherzombie = false, bool force = false) - { - if(client == null) - { - _logger.LogError("[InfectClient] Client is null!"); - return; - } - - var result = _api.ZS_OnClientInfect(client, attacker, motherzombie, force); - - if(result == HookResult.Handled || result == HookResult.Stop) - { - _logger.LogInformation("[InfectClient] {name} Infection has been stopped by API", client.PlayerName); - return; - } - - // if infect is not started yet then tell them yeah. - if(!InfectHasStarted()) - { - InfectKillInfectionTimer(); - InfectStarted = true; - } - - if(PlayerData.ZombiePlayerData == null) - { - _logger.LogCritical("[InfectClient] ZombiePlayers data is null!"); - return; - } - - if(!PlayerData.ZombiePlayerData.ContainsKey(client)) - { - _logger.LogCritical("[InfectClient] client is not in ZombiePlayersData!"); - return; - } - - // set player zombie to true. - PlayerData.ZombiePlayerData[client].Zombie = true; - - // we get player class for applying attribute here. - var applyClass = PlayerData.PlayerClassesData?[client].ZombieClass; - - // set motherzombie status to chosen - if(motherzombie) - { - PlayerData.ZombiePlayerData[client].MotherZombie = ZombiePlayer.MotherZombieStatus.CHOSEN; - - // if mother zombie class is specificed then change it, or else we just use player setting class from above. - if(Classes.MotherZombie != null) - applyClass = Classes.MotherZombie; - - // if teleport zombie back to spawn is enabled then we teleport them back to spawn. - if(GameSettings.Settings?.MotherZombieTeleport ?? false) - { - Server.NextWorldUpdate(() => - { - var pos = PlayerData.PlayerSpawnData?[client].PlayerPosition; - var angle = PlayerData.PlayerSpawnData?[client].PlayerAngle; - - if(pos == null || angle == null) - { - _logger.LogError("[InfectClient] Position of {name} is null!", client.PlayerName); - return; - } - - Teleport.TeleportClientToSpawn(client, pos, angle); - }); - } - } - // switch team to terrorist - client.SwitchTeam(CsTeam.Terrorist); - - // remove all player weapon - Utils.DropAllWeapon(client); - - //scream sound. - Utils.EmitSound(client, "zr.amb.scream"); - - // apply class attribute. - _classes?.ClassesApplyToPlayer(client, applyClass); - - // create fake killfeed - if(attacker != null) - { - // fire event. - EventPlayerDeath @event = new EventPlayerDeath(false); - - @event.Userid = client; - @event.Attacker = attacker; - @event.Weapon = "knife"; - @event.FireEvent(false); - - // update player score - attacker.ActionTrackingServices!.MatchStats.Kills += 1; - client.ActionTrackingServices!.MatchStats.Deaths += 1; - } - - // tell them you have been infect - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.BecomeZombie"]}"); - } - - public void HumanizeClient(CCSPlayerController client, bool force = false) - { - if(client == null) - { - _logger.LogError("[HumanizeClient] Client is null!"); - return; - } - - var result = _api.ZS_OnClientHumanize(client, force); - - if(result == HookResult.Handled || result == HookResult.Stop) - { - _logger.LogInformation("[HumanizeClient] {name} Humanize has been stopped by API", client.PlayerName); - return; - } - - if(PlayerData.ZombiePlayerData == null) - { - _logger.LogCritical("[HumanizeClient] ZombiePlayers data is null!"); - return; - } - - if(!PlayerData.ZombiePlayerData.ContainsKey(client)) - { - _logger.LogCritical("[HumanizeClient] client is not in ZombiePlayersData!"); - return; - } - - // set player zombie to false - PlayerData.ZombiePlayerData[client].Zombie = false; - - // switch team to terrorist - client.SwitchTeam(CsTeam.CounterTerrorist); - - // if force then tell them that you have been force - if(force) - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.BecomeHuman"]}"); - - // apply class attribute. - _classes?.ClassesApplyToPlayer(client, PlayerData.PlayerClassesData?[client].HumanClass!); - } - - public static bool InfectHasStarted() - { - return InfectStarted; - } - - public static bool IsClientInfect(CCSPlayerController client) - { - if(!PlayerData.ZombiePlayerData!.ContainsKey(client)) - return false; - - return PlayerData.ZombiePlayerData[client].Zombie; - } - - public static bool IsClientHuman(CCSPlayerController client) - { - if(!PlayerData.ZombiePlayerData!.ContainsKey(client)) - return false; - - return !PlayerData.ZombiePlayerData[client].Zombie; - } -} +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Timers; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; +using ZombieSharp.Api; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class Infect(ZombieSharp core, ILogger logger, Classes classes, ZombieSharpInterface api) +{ + private ZombieSharp _core = core; + private readonly ILogger _logger = logger; + private Classes? _classes = classes; + private readonly ZombieSharpInterface _api = api; + public static bool InfectStarted = false; + public static bool IsTestMode = false; + private CounterStrikeSharp.API.Modules.Timers.Timer? _firstInfection = null; + private CounterStrikeSharp.API.Modules.Timers.Timer? _infectCountTimer = null; + private int _infectCountNumber = 0; + + public void InfectOnLoad() + { + _core.AddCommand("zs_infect", "Infection Command", InfectClientCommand); + _core.AddCommand("zs_human", "Humanize Command", HumanizeClientCommand); + _core.AddCommand("zs_testmode", "TestMode Command", TestModeCommand); + } + + [CommandHelper(1, "zs_infect ")] + [RequiresPermissions("@css/slay")] + public void InfectClientCommand(CCSPlayerController? client, CommandInfo info) + { + var target = info.GetArgTargetResult(1); + + if(target == null || target.Count() <= 0) + { + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.TargetInvalid"]}"); + return; + } + + var mother = false; + + if(!InfectHasStarted()) + mother = true; + + foreach(var player in target) + { + if(player == null) continue; + if(!Utils.IsPlayerAlive(player)) + { + if(target.Count() < 2) + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeAlive"]}"); + + continue; + } + + if(IsClientZombie(player)) + { + if(target.Count() < 2) + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.AlreadyZombie"]}"); + + continue; + } + + InfectClient(player, null, mother, true); + } + + if(target.Count() > 1) + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.SuccessAll"]}"); + + else if(target.Count() < 2 && target.FirstOrDefault() != null) + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.Success", target.FirstOrDefault()!.PlayerName]}"); + } + + [CommandHelper(1, "zs_human ")] + [RequiresPermissions("@css/slay")] + public void HumanizeClientCommand(CCSPlayerController? client, CommandInfo info) + { + var target = info.GetArgTargetResult(1); + + if(target == null || target.Count() <= 0) + { + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.TargetInvalid"]}"); + return; + } + + foreach(var player in target) + { + if(player == null) continue; + if(!Utils.IsPlayerAlive(player)) + { + if(target.Count() < 2) + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeAlive"]}"); + + continue; + } + + if(IsClientHuman(player)) + { + if(target.Count() < 2) + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Human.AlreadyHuman"]}"); + + continue; + } + + HumanizeClient(player, true); + } + + if(target.Count() > 1) + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Human.SuccessAll"]}"); + + else if(target.Count() < 2 && target.FirstOrDefault() != null) + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Human.Success", target.FirstOrDefault()!.PlayerName]}"); + } + + [CommandHelper(1, "zs_testmode <0-1>")] + [RequiresPermissions("@css/slay")] + public void TestModeCommand(CCSPlayerController? client, CommandInfo info) + { + var mode = int.TryParse(info.ArgByIndex(1), out var number); + + if (!mode) + { + info.ReplyToCommand($" {_core.Localizer["Prefix"]} invalid value."); + return; + } + + var result = Convert.ToBoolean(number); + + if (result) + { + IsTestMode = true; + info.ReplyToCommand($" {_core.Localizer["Prefix"]} Test mode has been activated."); + return; + } + else + { + IsTestMode = false; + info.ReplyToCommand($" {_core.Localizer["Prefix"]} Test mode has been deactivated."); + return; + } + } + + public void InfectOnRoundFreezeEnd() + { + // kill timer just in case. + InfectKillInfectionTimer(); + + if(GameSettings.Settings == null) + { + _logger.LogCritical("[InfectOnRoundFreezeEnd] Game Settings is null!"); + _infectCountNumber = 15; + } + + else + _infectCountNumber = (int)GameSettings.Settings.FirstInfectionTimer; + + _firstInfection = _core.AddTimer(_infectCountNumber + 1, InfectMotherZombie, TimerFlags.STOP_ON_MAPCHANGE); + + _infectCountTimer = _core.AddTimer(1f, () => + { + if(_infectCountNumber < 0) + { + InfectKillInfectionTimer(); + return; + } + + Utils.PrintToCenterAll($" {_core.Localizer["Infect.Countdown", _infectCountNumber]}"); + _infectCountNumber--; + + }, TimerFlags.REPEAT|TimerFlags.STOP_ON_MAPCHANGE); + } + + public void InfectKillInfectionTimer() + { + if(_firstInfection != null) + { + _firstInfection.Kill(); + _firstInfection = null; + } + + if(_infectCountTimer != null) + { + _infectCountTimer.Kill(); + _infectCountTimer = null; + } + } + + public void InfectMotherZombie() + { + if (InfectHasStarted()) + { + _logger.LogWarning("[InfectMotherZombie] Infection already started, aborting mother zombie selection"); + return; + } + + var currentPlayer = Utilities.GetPlayers(); + + // if player is alone then let them play alone as human. + if (currentPlayer.Where(p => p.TeamNum > 1).Count() <= 1) + { + // set infect to true so when player die they restart the round. + InfectStarted = true; + return; + } + var ratio = GameSettings.Settings?.MotherZombieRatio ?? 7f; + + if (GameSettings.Settings == null) + { + _logger.LogCritical("[InfectMotherZombie] Game Settings is null!"); + } + + var requireZombie = (int)Math.Ceiling(currentPlayer.Count / ratio); + + List candidate = []; + + foreach (var player in currentPlayer) + { + if (player == null || !player.PawnIsAlive || !PlayerData.ZombiePlayerData!.ContainsKey(player)) + { + continue; + } + + if (PlayerData.ZombiePlayerData[player].MotherZombie == ZombiePlayer.MotherZombieStatus.NONE) + { + candidate.Add(player); + } + } + + + + if (candidate.Count < requireZombie) + { + Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.MotherZombieReset"]}"); + + foreach (var player in currentPlayer) + { + if (player == null || !player.PawnIsAlive || !PlayerData.ZombiePlayerData!.ContainsKey(player)) + continue; + + if (PlayerData.ZombiePlayerData[player].MotherZombie == ZombiePlayer.MotherZombieStatus.LAST) + { + candidate.Add(player); + PlayerData.ZombiePlayerData[player].MotherZombie = ZombiePlayer.MotherZombieStatus.NONE; + } + } + + + } + + var n = candidate.Count; + Random rng = new(); + + while (n > 1) + { + n--; + int k = rng.Next(n + 1); + var temp = candidate[k]; + candidate[k] = candidate[n]; + candidate[n] = temp; + } + + + + var infected = 0; + foreach (var player in candidate) + { + if (infected >= requireZombie) + { + break; + } + + if (player == null || !player.IsValid || !player.PawnIsAlive) + { + continue; + } + + + + InfectClient(player, null, true); + infected++; + } + + + } + + public void InfectOnPlayerHurt(CCSPlayerController? client, CCSPlayerController? attacker) + { + if (client == null || attacker == null) + return; + + bool clientIsHuman = IsClientHuman(client); + bool attackerIsZombie = IsClientZombie(attacker); + + if (!clientIsHuman || !attackerIsZombie) + return; + + InfectClient(client, attacker); + } + + public void InfectOnPreRoundStart(bool switchTeam = true) + { + if (PlayerData.ZombiePlayerData == null) + { + _logger.LogCritical("[InfectOnPreRoundStart] ZombiePlayers data is null!"); + return; + } + + foreach (var player in Utilities.GetPlayers()) + { + if (player == null || !player.IsValid || !PlayerData.ZombiePlayerData.ContainsKey(player)) + { + if (player != null) + _logger.LogError("[InfectOnPreRoundStart] Player {name} is not in ZombiePlayersData!", player.PlayerName); + continue; + } + + PlayerData.ZombiePlayerData[player].Zombie = false; + if (player.Team != CsTeam.Spectator && player.Team != CsTeam.None && switchTeam) + { + player.SwitchTeam(CsTeam.CounterTerrorist); + + // Force team switch with delay to ensure it takes effect + _core.AddTimer(0.1f, () => { + if(player == null || !player.IsValid) + return; + + if(player.Team != CsTeam.CounterTerrorist && player.Team != CsTeam.Spectator && player.Team != CsTeam.None) + { + player.SwitchTeam(CsTeam.CounterTerrorist); + _logger.LogInformation("[InfectOnPreRoundStart] Forced team switch for {0} to CounterTerrorist", player.PlayerName); + } + }); + } + } + } + + // we need to set mother zombie to last if they are chosen. so when mother zombie candidate run out we bring them to motherzombie cycle again. + public void InfectOnRoundEnd() + { + if(PlayerData.ZombiePlayerData == null) + { + _logger.LogCritical("[InfectOnRoundEnd] ZombiePlayers data is null!"); + return; + } + + foreach(var player in Utilities.GetPlayers()) + { + if(player == null || !player.IsValid) + { + _logger.LogError("[InfectOnRoundEnd] Player is not invalid!"); + continue; + } + + // check if player is in dictionary and motherzombie is chosen then we have to set them to last. + if(!PlayerData.ZombiePlayerData.ContainsKey(player)) + { + _logger.LogError("[InfectOnRoundEnd] Player {name} is not in ZombiePlayersData!", player.PlayerName); + continue; + } + + // set mother zombie status to Last. + if(PlayerData.ZombiePlayerData[player].MotherZombie == ZombiePlayer.MotherZombieStatus.CHOSEN) + PlayerData.ZombiePlayerData[player].MotherZombie = ZombiePlayer.MotherZombieStatus.LAST; + } + } + + public void InfectClient(CCSPlayerController client, CCSPlayerController? attacker = null, bool motherzombie = false, bool force = false) + { + if (IsTestMode) + { + return; + } + + if (client == null) + { + _logger.LogError("[InfectClient] Client is null!"); + return; + } + + var result = _api.ZS_OnClientInfect(client, attacker, motherzombie, force); + if (result == HookResult.Handled || result == HookResult.Stop) + { + _logger.LogInformation("[InfectClient] {name} Infection has been stopped by API", client.PlayerName); + return; + } + + if (!InfectHasStarted()) + { + InfectKillInfectionTimer(); + InfectStarted = true; + } + + if (PlayerData.ZombiePlayerData == null || !PlayerData.ZombiePlayerData.ContainsKey(client)) + { + _logger.LogCritical("[InfectClient] ZombiePlayers data is null or client is not in ZombiePlayersData!"); + return; + } + + PlayerData.ZombiePlayerData[client].Zombie = true; + var applyClass = PlayerData.PlayerClassesData?[client].ZombieClass; + + if (motherzombie) + { + PlayerData.ZombiePlayerData[client].MotherZombie = ZombiePlayer.MotherZombieStatus.CHOSEN; + if (Classes.MotherZombie != null) + applyClass = Classes.MotherZombie; + + if (GameSettings.Settings?.MotherZombieTeleport ?? false) + { + Server.NextWorldUpdate(() => + { + var pos = PlayerData.PlayerSpawnData?[client].PlayerPosition; + var angle = PlayerData.PlayerSpawnData?[client].PlayerAngle; + + if (pos == null || angle == null) + { + _logger.LogError("[InfectClient] Position of {name} is null!", client.PlayerName); + return; + } + + Teleport.TeleportClientToSpawn(client, pos, angle); + }); + } + } + + // Step 1: Drop all weapons safely and give knife + Utils.DropAllWeaponsExceptKnife(client); + + // Step 2: Switch team + client.SwitchTeam(CsTeam.Terrorist); + + // Force team switch with delay to ensure it takes effect with more aggressive retries + _core.AddTimer(0.05f, () => { + if(client == null || !client.IsValid) + { + _logger.LogError("[InfectClient] Client became invalid during team switch delay"); + return; + } + + if(client.Team != CsTeam.Terrorist) + { + client.SwitchTeam(CsTeam.Terrorist); + + // Additional retry if first attempt fails + _core.AddTimer(0.1f, () => { + if(client == null || !client.IsValid) + { + _logger.LogError("[InfectClient] Client became invalid during second team switch delay"); + return; + } + + if(client.Team != CsTeam.Terrorist) + { + client.SwitchTeam(CsTeam.Terrorist); + _logger.LogError("[InfectClient] Team switch failed after 2 attempts for {0} - Current team: {1}", client.PlayerName, client.Team); + } + }); + } + + // Step 3: Apply zombie class after successful team switch with additional delay + _core.AddTimer(0.05f, () => { + if(client == null || !client.IsValid) + { + _logger.LogError("[InfectClient] Client became invalid during class application delay"); + return; + } + + if (applyClass != null) + { + _classes?.ClassesApplyToPlayer(client, applyClass); + } + else + { + _logger.LogError("[InfectClient] No zombie class to apply for {0}!", client.PlayerName); + } + + // Step 4: Play infection sound + Utils.EmitSound(client, "zr.amb.scream"); + }); + }); + + if (attacker != null) + { + EventPlayerDeath @event = new(false) + { + Userid = client, + Attacker = attacker, + Weapon = "knife" + }; + @event.Assister = null; + @event.FireEvent(false); + + attacker.ActionTrackingServices!.MatchStats.Kills += 1; + client.ActionTrackingServices!.MatchStats.Deaths += 1; + } + + if (force) + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.BecomeZombie"]}"); + } + + public void HumanizeClient(CCSPlayerController client, bool force = false) + { + if(client == null) + { + _logger.LogError("[HumanizeClient] Client is null!"); + return; + } + + var result = _api.ZS_OnClientHumanize(client, force); + + if(result == HookResult.Handled || result == HookResult.Stop) + { + _logger.LogInformation("[HumanizeClient] {name} Humanize has been stopped by API", client.PlayerName); + return; + } + + if(PlayerData.ZombiePlayerData == null) + { + _logger.LogCritical("[HumanizeClient] ZombiePlayers data is null!"); + return; + } + + if(!PlayerData.ZombiePlayerData.ContainsKey(client)) + { + _logger.LogCritical("[HumanizeClient] client is not in ZombiePlayersData!"); + return; + } + + // set player zombie to false + PlayerData.ZombiePlayerData[client].Zombie = false; + + // switch team to Counter-Terrorist + client.SwitchTeam(CsTeam.CounterTerrorist); + + // if force then tell them that you have been force + if(force) + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Infect.BecomeHuman"]}"); + + // Apply human class attributes with delay to ensure proper application + var humanClass = PlayerData.PlayerClassesData?[client].HumanClass; + if(humanClass != null) + { + _core.AddTimer(0.1f, () => + { + if(!Utils.IsPlayerAlive(client)) + return; + + _logger.LogInformation("[HumanizeClient] Applying human class {className} to {playerName}", + humanClass.Name ?? "Unknown", client.PlayerName); + + _classes?.ClassesApplyToPlayer(client, humanClass); + + // Ensure human gets proper attributes + var playerPawn = client.PlayerPawn.Value; + if(playerPawn != null && playerPawn.IsValid) + { + _core.AddTimer(0.1f, () => + { + if(Utils.IsPlayerAlive(client) && playerPawn.IsValid) + { + // Set human health + playerPawn.Health = humanClass.Health; + Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_iHealth"); + + // Set human speed + var targetSpeed = humanClass.Speed / 250f; + playerPawn.VelocityModifier = targetSpeed; + Utilities.SetStateChanged(playerPawn, "CBaseEntity", "m_flVelocityModifier"); + + _logger.LogInformation("[HumanizeClient] Applied human attributes - Health: {health}, Speed: {speed}", + humanClass.Health, humanClass.Speed); + } + }); + } + }); + } + else + { + _logger.LogError("[HumanizeClient] Human class is null for {playerName}!", client.PlayerName); + } + } + + public static bool InfectHasStarted() + { + return InfectStarted; + } + + public static bool IsClientZombie(CCSPlayerController client) + { + if(!PlayerData.ZombiePlayerData!.ContainsKey(client)) + return false; + + return PlayerData.ZombiePlayerData[client].Zombie; + } + + public static bool IsClientHuman(CCSPlayerController client) + { + if(!PlayerData.ZombiePlayerData!.ContainsKey(client)) + return false; + + return !PlayerData.ZombiePlayerData[client].Zombie; + } + + public void CleanupPlayerData(CCSPlayerController client) + { + if (client == null) + return; + + _logger.LogInformation("[CleanupPlayerData] Cleaned up infection data for {0}", client.PlayerName); + } +} diff --git a/ZombieSharp/Plugin/Knockback.cs b/ZombieSharp/Plugin/Knockback.cs index 8995076..cc4918b 100644 --- a/ZombieSharp/Plugin/Knockback.cs +++ b/ZombieSharp/Plugin/Knockback.cs @@ -1,165 +1,165 @@ -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Entities; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class Knockback -{ - private static ILogger? _logger; - - public Knockback(ILogger logger) - { - _logger = logger; - } - - public static void KnockbackClient(CCSPlayerController? client, CCSPlayerController? attacker, string weaponname, float dmgHealth, int hitgroups) - { - if(weaponname.Contains("hegrenade")) - return; - - if(client == null || attacker == null) - return; - - // ent world also trigger hurt event - if(attacker.DesignerName != "cs_player_controller") - return; - - // knockback is for zombie only. - if (!Infect.IsClientHuman(attacker) || !Infect.IsClientInfect(client)) - return; - - var clientPos = client.PlayerPawn.Value?.AbsOrigin; - var attackerPos = attacker.PlayerPawn.Value?.AbsOrigin; - - if(clientPos == null || attackerPos == null) - return; - - var direction = clientPos - attackerPos; - var normalizedDir = NormalizeVector(direction); - - // Class Data section. - if(PlayerData.PlayerClassesData == null) - { - _logger?.LogError("[KnockbackClient] PlayerClassesData is null!"); - return; - } - - if(!PlayerData.PlayerClassesData.ContainsKey(client)) - { - _logger?.LogError("[KnockbackClient] client {0} is not in playerdata!", client.PlayerName); - return; - } - - if(PlayerData.PlayerClassesData[client].ActiveClass == null) - { - _logger?.LogError("[KnockbackClient] client {0} active class is null!", client.PlayerName); - return; - } - - float weaponknockback = 1.0f; - - // weapon knockback section - if(Weapons.WeaponsConfig == null) - _logger?.LogError("[KnockbackClient] Weapon Data is null!"); - - if(weaponname.Contains("knife")) - { - var weapon = Weapons.GetWeaponAttributeByEntityName(weaponname); - - if(weapon != null) - weaponknockback = weapon.Knockback; - } - - else - { - var activeWeapon = attacker.PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value; - var weaponString = Utils.FindWeaponItemDefinition(activeWeapon); - - WeaponAttribute? weapon = null; - - if(weaponString != null) - weapon = Weapons.GetWeaponAttributeByEntityName(weaponString!); - - if(weapon != null) - weaponknockback = weapon.Knockback; - } - - var hitgroupsKnockback = HitGroup.GetHitGroupKnockback(hitgroups); - - var pushVelocity = normalizedDir * dmgHealth * PlayerData.PlayerClassesData[client].ActiveClass!.Knockback * weaponknockback * hitgroupsKnockback; - client.PlayerPawn.Value?.AbsVelocity.Add(pushVelocity); - } - - public static void KnockbackClientExplosion(CCSPlayerController? client, CBaseEntity? grenade, float dmgHealth) - { - if(client == null || grenade == null) - return; - - // knockback is for zombie only. - if (!Infect.IsClientInfect(client)) - return; - - var clientPos = client.PlayerPawn.Value?.AbsOrigin; - var grenadePos = grenade.AbsOrigin; - - if(clientPos == null || grenadePos == null) - return; - - var direction = clientPos - grenadePos; - var normalizedDir = NormalizeVector(direction); - - // Class Data section. - if(PlayerData.PlayerClassesData == null) - { - _logger?.LogError("[KnockbackClientExplosion] PlayerClassesData is null!"); - return; - } - - if(!PlayerData.PlayerClassesData.ContainsKey(client)) - { - _logger?.LogError("[KnockbackClientExplosion] client {name} is not in playerdata!", client.PlayerName); - return; - } - - if(PlayerData.PlayerClassesData[client].ActiveClass == null) - { - _logger?.LogError("[KnockbackClientExplosion] client {name} active class is null!", client.PlayerName); - return; - } - - float weaponknockback = 1.0f; - - // weapon knockback section - if(Weapons.WeaponsConfig == null) - _logger?.LogError("[KnockbackClientExplosion] Weapon Data is null!"); - - var weapon = Weapons.GetWeaponAttributeByEntityName("weapon_hegrenade"); - - if(weapon != null) - weaponknockback = weapon.Knockback; - - var pushVelocity = normalizedDir * dmgHealth * PlayerData.PlayerClassesData[client].ActiveClass!.Knockback * weaponknockback; - client.PlayerPawn.Value?.AbsVelocity.Add(pushVelocity); - } - - private static Vector NormalizeVector(Vector vector) - { - var x = vector.X; - var y = vector.Y; - var z = vector.Z; - - var magnitude = MathF.Sqrt(x * x + y * y + z * z); - - if (magnitude != 0.0) - { - x /= magnitude; - y /= magnitude; - z /= magnitude; - } - - return new Vector(x, y, z); - } +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Entities; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class Knockback +{ + private static ILogger? _logger; + + public Knockback(ILogger logger) + { + _logger = logger; + } + + public static void KnockbackClient(CCSPlayerController? client, CCSPlayerController? attacker, string weaponname, float dmgHealth, int hitgroups) + { + if(weaponname.Contains("hegrenade")) + return; + + if(client == null || attacker == null) + return; + + // ent world also trigger hurt event + if(attacker.DesignerName != "cs_player_controller") + return; + + // knockback is for zombie only. + if (!Infect.IsClientHuman(attacker) || !Infect.IsClientZombie(client)) + return; + + var clientPos = client.PlayerPawn.Value?.AbsOrigin; + var attackerPos = attacker.PlayerPawn.Value?.AbsOrigin; + + if(clientPos == null || attackerPos == null) + return; + + var direction = clientPos - attackerPos; + var normalizedDir = NormalizeVector(direction); + + // Class Data section. + if(PlayerData.PlayerClassesData == null) + { + _logger?.LogError("[KnockbackClient] PlayerClassesData is null!"); + return; + } + + if(!PlayerData.PlayerClassesData.ContainsKey(client)) + { + _logger?.LogError("[KnockbackClient] client {0} is not in playerdata!", client.PlayerName); + return; + } + + if(PlayerData.PlayerClassesData[client].ActiveClass == null) + { + _logger?.LogError("[KnockbackClient] client {0} active class is null!", client.PlayerName); + return; + } + + float weaponknockback = 1.0f; + + // weapon knockback section + if(Weapons.WeaponsConfig == null) + _logger?.LogError("[KnockbackClient] Weapon Data is null!"); + + if(weaponname.Contains("knife")) + { + var weapon = Weapons.GetWeaponAttributeByEntityName(weaponname); + + if(weapon != null) + weaponknockback = weapon.Knockback; + } + + else + { + var activeWeapon = attacker.PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value; + var weaponString = Utils.FindWeaponItemDefinition(activeWeapon); + + WeaponAttribute? weapon = null; + + if(weaponString != null) + weapon = Weapons.GetWeaponAttributeByEntityName(weaponString!); + + if(weapon != null) + weaponknockback = weapon.Knockback; + } + + var hitgroupsKnockback = HitGroup.GetHitGroupKnockback(hitgroups); + + var pushVelocity = normalizedDir * dmgHealth * PlayerData.PlayerClassesData[client].ActiveClass!.Knockback * weaponknockback * hitgroupsKnockback; + client.PlayerPawn.Value?.AbsVelocity.Add(pushVelocity); + } + + public static void KnockbackClientExplosion(CCSPlayerController? client, CBaseEntity? grenade, float dmgHealth) + { + if(client == null || grenade == null) + return; + + // knockback is for zombie only. + if (!Infect.IsClientZombie(client)) + return; + + var clientPos = client.PlayerPawn.Value?.AbsOrigin; + var grenadePos = grenade.AbsOrigin; + + if(clientPos == null || grenadePos == null) + return; + + var direction = clientPos - grenadePos; + var normalizedDir = NormalizeVector(direction); + + // Class Data section. + if(PlayerData.PlayerClassesData == null) + { + _logger?.LogError("[KnockbackClientExplosion] PlayerClassesData is null!"); + return; + } + + if(!PlayerData.PlayerClassesData.ContainsKey(client)) + { + _logger?.LogError("[KnockbackClientExplosion] client {name} is not in playerdata!", client.PlayerName); + return; + } + + if(PlayerData.PlayerClassesData[client].ActiveClass == null) + { + _logger?.LogError("[KnockbackClientExplosion] client {name} active class is null!", client.PlayerName); + return; + } + + float weaponknockback = 1.0f; + + // weapon knockback section + if(Weapons.WeaponsConfig == null) + _logger?.LogError("[KnockbackClientExplosion] Weapon Data is null!"); + + var weapon = Weapons.GetWeaponAttributeByEntityName("weapon_hegrenade"); + + if(weapon != null) + weaponknockback = weapon.Knockback; + + var pushVelocity = normalizedDir * dmgHealth * PlayerData.PlayerClassesData[client].ActiveClass!.Knockback * weaponknockback; + client.PlayerPawn.Value?.AbsVelocity.Add(pushVelocity); + } + + private static Vector NormalizeVector(Vector vector) + { + var x = vector.X; + var y = vector.Y; + var z = vector.Z; + + var magnitude = MathF.Sqrt(x * x + y * y + z * z); + + if (magnitude != 0.0) + { + x /= magnitude; + y /= magnitude; + z /= magnitude; + } + + return new Vector(x, y, z); + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/Napalm.cs b/ZombieSharp/Plugin/Napalm.cs index 3322182..7d53ed9 100644 --- a/ZombieSharp/Plugin/Napalm.cs +++ b/ZombieSharp/Plugin/Napalm.cs @@ -1,152 +1,152 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Admin; -using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Timers; -using Microsoft.Extensions.Logging; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class Napalm(ZombieSharp core, ILogger logger) -{ - private readonly ZombieSharp _core = core; - private readonly ILogger _logger = logger; - - public void NapalmOnLoad() - { - _core.AddCommand("css_burn", "Burn Test Command", Command_Burn); - } - - [RequiresPermissions("@css/slay")] - [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] - public void Command_Burn(CCSPlayerController? client, CommandInfo info) - { - info.ReplyToCommand("Start Burn"); - IgnitePawn(client, null, 1, 5f); - } - - public void NapalmOnHurt(CCSPlayerController? client, CCSPlayerController? attacker, string weapon, int damage) - { - if(!weapon.Contains("hegrenade")) - return; - - if(client == null) - return; - - var time = PlayerData.PlayerClassesData?[client].ActiveClass?.NapalmTime; - - if(time <= 0) - return; - - IgnitePawn(client, attacker, damage, time ?? 1); - } - - public bool IgnitePawn(CCSPlayerController? client, CCSPlayerController? attacker = null, int damage = 1, float duration = 1) - { - // if player is null why bother to do it. - if(client == null || client.PlayerPawn.Value == null) - return false; - - if(PlayerData.PlayerBurnData == null) - { - _logger.LogError("[IgnitePawn] PlayerBurnData is null!"); - return false; - } - - var playerPawn = client.PlayerPawn.Value; - - if(!PlayerData.PlayerBurnData.ContainsKey(client)) - { - PlayerData.PlayerBurnData.Add(client, null); - } - - var particle = PlayerData.PlayerBurnData[client]; - - if(particle != null) - { - particle.DissolveStartTime = Server.CurrentTime + duration; - return true; - } - - var position = playerPawn.AbsOrigin; - - position!.Z += 15; - - particle = Utilities.CreateEntityByName("info_particle_system"); - - particle!.StartActive = true; - particle.EffectName = "particles\\oylsister\\env_fire_large.vpcf"; - particle.DataCP = 1; - particle.DataCPValue.X = position.X; - particle.DataCPValue.Y = position.Y; - particle.DataCPValue.Z = position.Z; - - particle.DissolveStartTime = Server.CurrentTime + duration; - - particle.DispatchSpawn(); - - particle.Teleport(position, null, null); - particle.AcceptInput("SetParent", playerPawn, null, "!activator"); - - PlayerData.PlayerBurnData[client] = particle; - - CounterStrikeSharp.API.Modules.Timers.Timer? timer = null; - - timer = _core.AddTimer(0.3f, () => - { - //Server.PrintToChatAll($"Dissolve = {PlayerData.PlayerBurnData[client].DissolveStartTime} | Current = {Server.CurrentTime}"); - - if(client == null) - { - timer?.Kill(); - return; - } - - if(PlayerData.PlayerBurnData == null) - { - _logger.LogError("[IgnitePawn] PlayerBurnData is null!"); - timer?.Kill(); - return; - } - - if(!PlayerData.PlayerBurnData.ContainsKey(client)) - { - _logger.LogError("[IgnitePawn] PlayerBurnData doesn't has this client"); - timer?.Kill(); - return; - } - - if(PlayerData.PlayerBurnData[client] == null) - { - timer?.Kill(); - return; - } - - if(PlayerData.PlayerBurnData[client]?.DesignerName != "info_particle_system") - { - _logger.LogError("[IgnitePawn] Found unexpected entity type: {0}", particle.DesignerName); - timer?.Kill(); - PlayerData.PlayerBurnData[client] = null; - return; - } - - if(PlayerData.PlayerBurnData[client]?.DissolveStartTime < Server.CurrentTime || !Utils.IsPlayerAlive(client)) - { - PlayerData.PlayerBurnData[client]?.AcceptInput("Stop"); - PlayerData.PlayerBurnData[client]?.AddEntityIOEvent("Kill", PlayerData.PlayerBurnData[client], null, "", 0.1f); - timer?.Kill(); - PlayerData.PlayerBurnData[client] = null; - return; - } - - if(damage > 0) - Utils.TakeDamage(client, attacker, damage); - - Utils.SetStamina(client, 40.0f); - - }, TimerFlags.REPEAT|TimerFlags.STOP_ON_MAPCHANGE); - - return true; - } +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Timers; +using Microsoft.Extensions.Logging; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class Napalm(ZombieSharp core, ILogger logger) +{ + private readonly ZombieSharp _core = core; + private readonly ILogger _logger = logger; + + public void NapalmOnLoad() + { + _core.AddCommand("css_burn", "Burn Test Command", Command_Burn); + } + + [RequiresPermissions("@css/slay")] + [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] + public void Command_Burn(CCSPlayerController? client, CommandInfo info) + { + info.ReplyToCommand("Start Burn"); + IgnitePawn(client, null, 1, 5f); + } + + public void NapalmOnHurt(CCSPlayerController? client, CCSPlayerController? attacker, string weapon, int damage) + { + if(!weapon.Contains("hegrenade")) + return; + + if(client == null) + return; + + var time = PlayerData.PlayerClassesData?[client].ActiveClass?.NapalmTime; + + if(time <= 0) + return; + + IgnitePawn(client, attacker, damage, time ?? 1); + } + + public bool IgnitePawn(CCSPlayerController? client, CCSPlayerController? attacker = null, int damage = 1, float duration = 1) + { + // if player is null why bother to do it. + if(client == null || client.PlayerPawn.Value == null) + return false; + + if(PlayerData.PlayerBurnData == null) + { + _logger.LogError("[IgnitePawn] PlayerBurnData is null!"); + return false; + } + + var playerPawn = client.PlayerPawn.Value; + + if(!PlayerData.PlayerBurnData.ContainsKey(client)) + { + PlayerData.PlayerBurnData.Add(client, null); + } + + var particle = PlayerData.PlayerBurnData[client]; + + if(particle != null) + { + particle.DissolveStartTime = Server.CurrentTime + duration; + return true; + } + + var position = playerPawn.AbsOrigin; + + position!.Z += 15; + + particle = Utilities.CreateEntityByName("info_particle_system"); + + particle!.StartActive = true; + particle.EffectName = "particles\\oylsister\\env_fire_large.vpcf"; + particle.DataCP = 1; + particle.DataCPValue.X = position.X; + particle.DataCPValue.Y = position.Y; + particle.DataCPValue.Z = position.Z; + + particle.DissolveStartTime = Server.CurrentTime + duration; + + particle.DispatchSpawn(); + + particle.Teleport(position, null, null); + particle.AcceptInput("SetParent", playerPawn, null, "!activator"); + + PlayerData.PlayerBurnData[client] = particle; + + CounterStrikeSharp.API.Modules.Timers.Timer? timer = null; + + timer = _core.AddTimer(0.3f, () => + { + //Server.PrintToChatAll($"Dissolve = {PlayerData.PlayerBurnData[client].DissolveStartTime} | Current = {Server.CurrentTime}"); + + if(client == null) + { + timer?.Kill(); + return; + } + + if(PlayerData.PlayerBurnData == null) + { + _logger.LogError("[IgnitePawn] PlayerBurnData is null!"); + timer?.Kill(); + return; + } + + if(!PlayerData.PlayerBurnData.ContainsKey(client)) + { + _logger.LogError("[IgnitePawn] PlayerBurnData doesn't has this client"); + timer?.Kill(); + return; + } + + if(PlayerData.PlayerBurnData[client] == null) + { + timer?.Kill(); + return; + } + + if(PlayerData.PlayerBurnData[client]?.DesignerName != "info_particle_system") + { + _logger.LogError("[IgnitePawn] Found unexpected entity type: {0}", particle.DesignerName); + timer?.Kill(); + PlayerData.PlayerBurnData[client] = null; + return; + } + + if(PlayerData.PlayerBurnData[client]?.DissolveStartTime < Server.CurrentTime || !Utils.IsPlayerAlive(client)) + { + PlayerData.PlayerBurnData[client]?.AcceptInput("Stop"); + PlayerData.PlayerBurnData[client]?.AddEntityIOEvent("Kill", PlayerData.PlayerBurnData[client], null, "", 0.1f); + timer?.Kill(); + PlayerData.PlayerBurnData[client] = null; + return; + } + + if(damage > 0) + Utils.TakeDamage(client, attacker, damage); + + Utils.SetStamina(client, 40.0f); + + }, TimerFlags.REPEAT|TimerFlags.STOP_ON_MAPCHANGE); + + return true; + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/Respawn.cs b/ZombieSharp/Plugin/Respawn.cs index 4be02f1..1168406 100644 --- a/ZombieSharp/Plugin/Respawn.cs +++ b/ZombieSharp/Plugin/Respawn.cs @@ -1,76 +1,76 @@ -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; - -namespace ZombieSharp.Plugin; - -public class Respawn(ZombieSharp core, ILogger logger) -{ - private readonly ZombieSharp _core = core; - private readonly ILogger _logger = logger; - - public void RespawnOnLoad() - { - _core.AddCommand("css_zspawn", "Zspawn command obviously", ZSpawnCommand); - } - - [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] - public void ZSpawnCommand(CCSPlayerController? client, CommandInfo info) - { - if(client == null) - return; - - if(client.Team != CsTeam.Terrorist && client.Team != CsTeam.CounterTerrorist) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeInTeam"]}"); - return; - } - - if(Utils.IsPlayerAlive(client)) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeDead"]}"); - return; - } - - if(!GameSettings.Settings?.RespawnEnable ?? true) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Respawn.Disabled"]}"); - return; - } - - RespawnClient(client); - } - - public void RespawnOnPlayerDeath(CCSPlayerController? client) - { - if(client == null) - { - _logger.LogCritical("[RespawnOnPlayerDeath] client {0} is null or alive", client?.PlayerName ?? "Unnamed"); - return; - } - - // if not enable then no respawn. - if(!GameSettings.Settings?.RespawnEnable ?? true) - return; - - _core.AddTimer(GameSettings.Settings?.RespawnDelay ?? 5.0f, () => RespawnClient(client)); - } - - public static void RespawnClient(CCSPlayerController? client) - { - if(client == null || client.Handle == IntPtr.Zero) - return; - - if(Utils.IsPlayerAlive(client)) - return; - - if(client.Team == CsTeam.Spectator || client.Team == CsTeam.None) - return; - - if(!GameSettings.Settings?.RespawnEnable ?? true) - return; - - client.Respawn(); - } +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; + +namespace ZombieSharp.Plugin; + +public class Respawn(ZombieSharp core, ILogger logger) +{ + private readonly ZombieSharp _core = core; + private readonly ILogger _logger = logger; + + public void RespawnOnLoad() + { + _core.AddCommand("css_zspawn", "Zspawn command obviously", ZSpawnCommand); + } + + [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] + public void ZSpawnCommand(CCSPlayerController? client, CommandInfo info) + { + if(client == null) + return; + + if(client.Team != CsTeam.Terrorist && client.Team != CsTeam.CounterTerrorist) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeInTeam"]}"); + return; + } + + if(Utils.IsPlayerAlive(client)) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeDead"]}"); + return; + } + + if(!GameSettings.Settings?.RespawnEnable ?? true) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Respawn.Disabled"]}"); + return; + } + + RespawnClient(client); + } + + public void RespawnOnPlayerDeath(CCSPlayerController? client) + { + if(client == null) + { + _logger.LogCritical("[RespawnOnPlayerDeath] client {0} is null or alive", client?.PlayerName ?? "Unnamed"); + return; + } + + // if not enable then no respawn. + if(!GameSettings.Settings?.RespawnEnable ?? true) + return; + + _core.AddTimer(GameSettings.Settings?.RespawnDelay ?? 5.0f, () => RespawnClient(client)); + } + + public static void RespawnClient(CCSPlayerController? client) + { + if(client == null || client.Handle == IntPtr.Zero) + return; + + if(Utils.IsPlayerAlive(client)) + return; + + if(client.Team == CsTeam.Spectator || client.Team == CsTeam.None) + return; + + if(!GameSettings.Settings?.RespawnEnable ?? true) + return; + + client.Respawn(); + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/RoundEnd.cs b/ZombieSharp/Plugin/RoundEnd.cs index c672442..51cda7a 100644 --- a/ZombieSharp/Plugin/RoundEnd.cs +++ b/ZombieSharp/Plugin/RoundEnd.cs @@ -1,136 +1,136 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; -using CounterStrikeSharp.API.Modules.Entities.Constants; -using CounterStrikeSharp.API.Modules.Timers; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; - -namespace ZombieSharp.Plugin; - -public class RoundEnd -{ - private static ILogger? _logger; - private static ZombieSharp? _core; - - public RoundEnd(ZombieSharp core, ILogger logger) - { - _core = core; - _logger = logger; - } - - public static CounterStrikeSharp.API.Modules.Timers.Timer? TerminateRoundTimer; - - public static void RoundEndKillTimer() - { - if(TerminateRoundTimer != null) - { - TerminateRoundTimer.Kill(); - TerminateRoundTimer = null; - } - } - - public static void RoundEndOnRoundStart() - { - RoundEndKillTimer(); - } - - public static void RoundEndOnRoundEnd() - { - RoundEndKillTimer(); - } - - public static void RoundEndOnRoundFreezeEnd() - { - RoundEndKillTimer(); - - var convar = ConVar.Find("mp_roundtime"); - - if(convar == null) - { - _logger?.LogError("[RoundEndOnRoundFreezeEnd] Cannot find mp_roundtime convar!"); - return; - } - - var timer = convar.GetPrimitiveValue(); - - _logger?.LogInformation($"[RoundEndOnRoundFreezeEnd] Round time: {timer}"); - - if(GameSettings.Settings == null) - { - _logger?.LogError("[RoundEndOnRoundFreezeEnd] Game Settings is null! Cannot terminate round!"); - return; - } - - TerminateRoundTimer = _core?.AddTimer(timer * 60, () => { - - CsTeam winner = CsTeam.None; - - if(GameSettings.Settings.TimeoutWinner == 0) - winner = CsTeam.Terrorist; - - else if(GameSettings.Settings.TimeoutWinner == 1) - winner = CsTeam.CounterTerrorist; - - TerminateRound(winner); - }); - } - - public static void CheckGameStatus() - { - var ct = Utilities.GetPlayers().Where(player => player.TeamNum == 3 && player.PlayerPawn.Value?.LifeState == (byte)LifeState_t.LIFE_ALIVE).Count(); - var t = Utilities.GetPlayers().Where(player => player.TeamNum == 2 && player.PlayerPawn.Value?.LifeState == (byte)LifeState_t.LIFE_ALIVE).Count(); - - // Server.PrintToChatAll($"CT = {ct} | T = {t}"); - - if(ct == 0 && t > 0) - TerminateRound(CsTeam.Terrorist); - - else if(t == 0 && ct > 0) - TerminateRound(CsTeam.CounterTerrorist); - - else if(t == 0 && ct == 0) - TerminateRound(); - } - - public static void TerminateRound(CsTeam team = CsTeam.None) - { - var gameRules = Utilities.FindAllEntitiesByDesignerName("cs_gamerules").First().GameRules; - - if(gameRules == null) - { - _logger?.LogError("[TerminateRound] Gamerules is invalid cannot terminated round!"); - return; - } - - RoundEndReason reason; - - if(team == CsTeam.Terrorist) - reason = RoundEndReason.TerroristsWin; - - else if(team == CsTeam.CounterTerrorist) - reason = RoundEndReason.CTsWin; - - else - reason = RoundEndReason.RoundDraw; - - gameRules.TerminateRound(5f, reason); - - if(team != CsTeam.None && team != CsTeam.Spectator) - UpdateTeamScore(team); - } - - public static void UpdateTeamScore(CsTeam team, int score = 1) - { - var teamManagers = Utilities.FindAllEntitiesByDesignerName("cs_team_manager"); - - foreach (var teamManager in teamManagers) - { - if ((int)team == teamManager.TeamNum) - { - teamManager.Score += score; - Utilities.SetStateChanged(teamManager, "CTeam", "m_iScore"); - } - } - } +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Cvars; +using CounterStrikeSharp.API.Modules.Entities.Constants; +using CounterStrikeSharp.API.Modules.Timers; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; + +namespace ZombieSharp.Plugin; + +public class RoundEnd +{ + private static ILogger? _logger; + private static ZombieSharp? _core; + + public RoundEnd(ZombieSharp core, ILogger logger) + { + _core = core; + _logger = logger; + } + + public static CounterStrikeSharp.API.Modules.Timers.Timer? TerminateRoundTimer; + + public static void RoundEndKillTimer() + { + if(TerminateRoundTimer != null) + { + TerminateRoundTimer.Kill(); + TerminateRoundTimer = null; + } + } + + public static void RoundEndOnRoundStart() + { + RoundEndKillTimer(); + } + + public static void RoundEndOnRoundEnd() + { + RoundEndKillTimer(); + } + + public static void RoundEndOnRoundFreezeEnd() + { + RoundEndKillTimer(); + + var convar = ConVar.Find("mp_roundtime"); + + if(convar == null) + { + _logger?.LogError("[RoundEndOnRoundFreezeEnd] Cannot find mp_roundtime convar!"); + return; + } + + var timer = convar.GetPrimitiveValue(); + + _logger?.LogInformation($"[RoundEndOnRoundFreezeEnd] Round time: {timer}"); + + if(GameSettings.Settings == null) + { + _logger?.LogError("[RoundEndOnRoundFreezeEnd] Game Settings is null! Cannot terminate round!"); + return; + } + + TerminateRoundTimer = _core?.AddTimer(timer * 60, () => { + + CsTeam winner = CsTeam.None; + + if(GameSettings.Settings.TimeoutWinner == 0) + winner = CsTeam.Terrorist; + + else if(GameSettings.Settings.TimeoutWinner == 1) + winner = CsTeam.CounterTerrorist; + + TerminateRound(winner); + }); + } + + public static void CheckGameStatus() + { + var ct = Utilities.GetPlayers().Where(player => player.TeamNum == 3 && player.PlayerPawn.Value?.LifeState == (byte)LifeState_t.LIFE_ALIVE).Count(); + var t = Utilities.GetPlayers().Where(player => player.TeamNum == 2 && player.PlayerPawn.Value?.LifeState == (byte)LifeState_t.LIFE_ALIVE).Count(); + + // Server.PrintToChatAll($"CT = {ct} | T = {t}"); + + if(ct == 0 && t > 0) + TerminateRound(CsTeam.Terrorist); + + else if(t == 0 && ct > 0) + TerminateRound(CsTeam.CounterTerrorist); + + else if(t == 0 && ct == 0) + TerminateRound(); + } + + public static void TerminateRound(CsTeam team = CsTeam.None) + { + var gameRules = Utilities.FindAllEntitiesByDesignerName("cs_gamerules").First().GameRules; + + if(gameRules == null) + { + _logger?.LogError("[TerminateRound] Gamerules is invalid cannot terminated round!"); + return; + } + + RoundEndReason reason; + + if(team == CsTeam.Terrorist) + reason = RoundEndReason.TerroristsWin; + + else if(team == CsTeam.CounterTerrorist) + reason = RoundEndReason.CTsWin; + + else + reason = RoundEndReason.RoundDraw; + + gameRules.TerminateRound(5f, reason); + + if(team != CsTeam.None && team != CsTeam.Spectator) + UpdateTeamScore(team); + } + + public static void UpdateTeamScore(CsTeam team, int score = 1) + { + var teamManagers = Utilities.FindAllEntitiesByDesignerName("cs_team_manager"); + + foreach (var teamManager in teamManagers) + { + if ((int)team == teamManager.TeamNum) + { + teamManager.Score += score; + Utilities.SetStateChanged(teamManager, "CTeam", "m_iScore"); + } + } + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/Teleport.cs b/ZombieSharp/Plugin/Teleport.cs index a601b3e..f443462 100644 --- a/ZombieSharp/Plugin/Teleport.cs +++ b/ZombieSharp/Plugin/Teleport.cs @@ -1,93 +1,93 @@ -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Core.Attributes.Registration; -using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Timers; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class Teleport(ZombieSharp core, ILogger logger) -{ - private readonly ZombieSharp _core = core; - private readonly ILogger _logger = logger; - - public void TeleportOnLoad() - { - _core.AddCommand("css_ztele", "ZTele Command", TeleportCommand); - } - - public void TeleportOnPlayerSpawn(CCSPlayerController client) - { - if(client == null || !Utils.IsPlayerAlive(client)) - return; - - if(PlayerData.PlayerSpawnData == null) - { - _logger.LogCritical("[TeleportCommand] SpawnData is null!"); - return; - } - - if(!PlayerData.PlayerSpawnData.TryGetValue(client, out var data)) - { - _logger.LogCritical("[TeleportCommand] client {0} is not in PlayerSpawnData", client.PlayerName); - return; - } - - var pos = client.PlayerPawn.Value?.AbsOrigin ?? new(0, 0, 0); - var angle = client.PlayerPawn.Value?.AbsRotation ?? new(0, 0, 0); - - // saving dev - data.PlayerPosition = new(pos.X, pos.Y, pos.Z); - data.PlayerAngle = new(angle.X, angle.Y, angle.Z); - } - - [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] - public void TeleportCommand(CCSPlayerController? client, CommandInfo info) - { - if(!GameSettings.Settings?.TeleportAllow ?? false) - { - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.FeatureDisbled"]}"); - return; - } - - if(client == null || !Utils.IsPlayerAlive(client)) - { - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeAlive"]}"); - return; - } - - if(PlayerData.PlayerSpawnData == null) - { - _logger.LogCritical("[TeleportCommand] SpawnData is null!"); - return; - } - - if(!PlayerData.PlayerSpawnData.ContainsKey(client)) - { - _logger.LogCritical("[TeleportCommand] client {0} is not in PlayerSpawnData", client.PlayerName); - return; - } - - var pos = PlayerData.PlayerSpawnData[client].PlayerPosition; - var angle = PlayerData.PlayerSpawnData[client].PlayerAngle; - var timer = 5; - - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Teleport.Countdown", timer]}"); - - _core.AddTimer(timer, () => - { - TeleportClientToSpawn(client, pos, angle); - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Teleport.Success", timer]}"); - }); - } - - public static void TeleportClientToSpawn(CCSPlayerController client, Vector pos, QAngle angle) - { - if(client == null || !Utils.IsPlayerAlive(client)) - return; - - client.PlayerPawn.Value?.Teleport(pos, angle); - } +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Attributes.Registration; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Timers; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class Teleport(ZombieSharp core, ILogger logger) +{ + private readonly ZombieSharp _core = core; + private readonly ILogger _logger = logger; + + public void TeleportOnLoad() + { + _core.AddCommand("css_ztele", "ZTele Command", TeleportCommand); + } + + public void TeleportOnPlayerSpawn(CCSPlayerController client) + { + if(client == null || !Utils.IsPlayerAlive(client)) + return; + + if(PlayerData.PlayerSpawnData == null) + { + _logger.LogCritical("[TeleportCommand] SpawnData is null!"); + return; + } + + if(!PlayerData.PlayerSpawnData.TryGetValue(client, out var data)) + { + _logger.LogCritical("[TeleportCommand] client {0} is not in PlayerSpawnData", client.PlayerName); + return; + } + + var pos = client.PlayerPawn.Value?.AbsOrigin ?? new(0, 0, 0); + var angle = client.PlayerPawn.Value?.AbsRotation ?? new(0, 0, 0); + + // saving dev + data.PlayerPosition = new(pos.X, pos.Y, pos.Z); + data.PlayerAngle = new(angle.X, angle.Y, angle.Z); + } + + [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] + public void TeleportCommand(CCSPlayerController? client, CommandInfo info) + { + if(!GameSettings.Settings?.TeleportAllow ?? false) + { + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.FeatureDisbled"]}"); + return; + } + + if(client == null || !Utils.IsPlayerAlive(client)) + { + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeAlive"]}"); + return; + } + + if(PlayerData.PlayerSpawnData == null) + { + _logger.LogCritical("[TeleportCommand] SpawnData is null!"); + return; + } + + if(!PlayerData.PlayerSpawnData.ContainsKey(client)) + { + _logger.LogCritical("[TeleportCommand] client {0} is not in PlayerSpawnData", client.PlayerName); + return; + } + + var pos = PlayerData.PlayerSpawnData[client].PlayerPosition; + var angle = PlayerData.PlayerSpawnData[client].PlayerAngle; + var timer = 5; + + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Teleport.Countdown", timer]}"); + + _core.AddTimer(timer, () => + { + TeleportClientToSpawn(client, pos, angle); + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Teleport.Success", timer]}"); + }); + } + + public static void TeleportClientToSpawn(CCSPlayerController client, Vector pos, QAngle angle) + { + if(client == null || !Utils.IsPlayerAlive(client)) + return; + + client.PlayerPawn.Value?.Teleport(pos, angle); + } } \ No newline at end of file diff --git a/ZombieSharp/Plugin/Utils.cs b/ZombieSharp/Plugin/Utils.cs index 48acf3e..fb1e3dc 100644 --- a/ZombieSharp/Plugin/Utils.cs +++ b/ZombieSharp/Plugin/Utils.cs @@ -1,359 +1,488 @@ -using System.Runtime.InteropServices; -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Entities.Constants; -using CounterStrikeSharp.API.Modules.Memory; -using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; -using CounterStrikeSharp.API.Modules.Utils; -using Microsoft.Extensions.Logging; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class Utils -{ - private static ZombieSharp? _core; - private static ILogger? _logger; - - public Utils(ZombieSharp core, ILogger logger) - { - _core = core; - _logger = logger; - } - - public static MemoryFunctionVoid CBaseEntity_EmitSoundParamsFunc = new(GameData.GetSignature("CBaseEntity_EmitSoundParams")); - - public static void PrintToCenterAll(string message) - { - if(string.IsNullOrEmpty(message)) - return; - - foreach(var player in Utilities.GetPlayers()) - { - player.PrintToCenter(message); - } - } - - public static void EmitSound(CBaseEntity entity, string soundPath, int pitch = 100, float volume = 1.0f, float deley = 0.0f) - { - if(entity == null || string.IsNullOrEmpty(soundPath)) - return; - - CBaseEntity_EmitSoundParamsFunc.Invoke(entity, soundPath, pitch, volume, deley); - } - - public static CCSPlayerController? GetCCSPlayerController(CEntityInstance? instance) - { - if (instance == null) - return null; - - if (instance.DesignerName != "player") - return null; - - // grab the pawn index - int index = (int)instance.Index; - - // grab player controller from pawn - var pawn = Utilities.GetEntityFromIndex(index); - - // pawn valid - if (pawn == null || !pawn.IsValid) - return null; - - // controller valid - if (pawn.OriginalController == null || !pawn.OriginalController.IsValid) - return null; - - // any further validity is up to the caller - return pawn.OriginalController.Value; - } - - public static string? FindWeaponItemDefinition(CBasePlayerWeapon? weapon) - { - if(weapon == null) - return null; - - var item = (ItemDefinition)weapon.AttributeManager.Item.ItemDefinitionIndex; - - if (weapon.DesignerName == "weapon_m4a1") - { - switch (item) - { - case ItemDefinition.M4A1_S: return "weapon_m4a1_silencer"; - case ItemDefinition.M4A4: return "weapon_m4a1"; - } - } - - else if (weapon.DesignerName == "weapon_hkp2000") - { - switch (item) - { - case ItemDefinition.P2000: return "weapon_hkp2000"; - case ItemDefinition.USP_S: return "weapon_usp_silencer"; - } - } - - else if (weapon.DesignerName == "weapon_mp7") - { - switch (item) - { - case ItemDefinition.MP7: return "weapon_mp7"; - case ItemDefinition.MP5_SD: return "weapon_mp5sd"; - } - } - - return weapon.DesignerName; - } - - public static void DropAllWeapon(CCSPlayerController client, bool remove = false) - { - if(client == null) - return; - - foreach(var weapon in WeaponList) - { - DropWeaponByDesignName(client, weapon, remove); - } - } - - public static void DropWeaponByDesignName(CCSPlayerController client, string weaponName, bool remove = false) - { - if(client == null) - return; - - var matchedWeapon = client.PlayerPawn.Value?.WeaponServices?.MyWeapons.Where(x => x.Value?.DesignerName == weaponName).FirstOrDefault(); - - if (matchedWeapon != null && matchedWeapon.IsValid) - { - client.PlayerPawn.Value!.WeaponServices!.ActiveWeapon.Raw = matchedWeapon.Raw; - - // set timer to remove if remove is true. - if(remove) - { - _core?.AddTimer(1f, () => { - matchedWeapon.Value?.AddEntityIOEvent("Kill", matchedWeapon.Value, null, "", 0.1f); - }); - } - - client.DropActiveWeapon(); - } - } - - public static void RefreshPurchaseCount(CCSPlayerController client) - { - if(PlayerData.PlayerPurchaseCount == null) - { - _logger?.LogError("[RefreshPurchaseCount] Player Purchase count is null"); - return; - } - - if(!PlayerData.PlayerPurchaseCount.ContainsKey(client)) - { - _logger?.LogError("[RefreshPurchaseCount] {name} is not in array data, so create a new one for player.", client.PlayerName); - PlayerData.PlayerPurchaseCount.Add(client, new()); - } - - // clear them all. - PlayerData.PlayerPurchaseCount[client].WeaponCount?.Clear(); - } - - public static bool IsClientInBuyZone(CCSPlayerController client) - { - if(client == null) - return false; - - return client.PlayerPawn.Value?.InBuyZone ?? false; - } - - public static bool IsPlayerAlive(CCSPlayerController client) - { - if(client == null) - return false; - - if(!client.IsValid) - { - _logger?.LogError("[IsPlayerAlive] Client is invalid pointer!"); - return false; - } - - var clientPawn = client.PlayerPawn.Value; - - if(clientPawn == null || !clientPawn.IsValid) - return false; - - return (LifeState_t)clientPawn.LifeState == LifeState_t.LIFE_ALIVE; - } - - public static ClassAttribute? GetRandomPlayerClasses(int team) - { - if(Classes.ClassesConfig == null) - { - _logger?.LogError("[GetRandomPlayerClasses] ClassesConfig is null!"); - return null; - } - - List classes = []; - - foreach(var playerClasses in Classes.ClassesConfig) - { - if(playerClasses.Value.Team == team && playerClasses.Value.Enable) - classes.Add(playerClasses.Value); - } - - if(classes.Count <= 0) - { - _logger?.LogError("[GetRandomPlayerClasses] Can't get one from it since it's empty!"); - return null; - } - - return GetRandomItem(classes); - } - - public static T GetRandomItem(List list) - { - // Create a new Random instance - Random random = new Random(); - // Generate a random index - int index = random.Next(list.Count); - // Return the item at the random index - return list[index]; - } - - public static void TakeDamage(CCSPlayerController? client, CCSPlayerController? attacker = null, int damage = 1) - { - if(client == null) - { - _logger?.LogError("[TakeDamage] Client is null!"); - return; - } - - if(!IsPlayerAlive(client)) - { - return; - } - - var size = Schema.GetClassSize("CTakeDamageInfo"); - var ptr = Marshal.AllocHGlobal(size); - - for (var i = 0; i < size; i++) - Marshal.WriteByte(ptr, i, 0); - - var damageInfo = new CTakeDamageInfo(ptr); - - CAttackerInfo attackerInfo; - - if(attacker == null) - attackerInfo = new CAttackerInfo(client); - - else - attackerInfo = new CAttackerInfo(attacker); - - Marshal.StructureToPtr(attackerInfo, new IntPtr(ptr.ToInt64() + 0x80), false); - - if(attacker == null) - { - Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hInflictor", client.Pawn.Raw); - Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hAttacker", client.Pawn.Raw); - } - - else - { - Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hInflictor", attacker.Pawn.Raw); - Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hAttacker", attacker.Pawn.Raw); - } - - damageInfo.Damage = damage; - - if(client.Pawn.Value == null) - { - _logger?.LogError("[TakeDamage] Client Pawn is null!"); - return; - } - - VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Invoke(client.Pawn.Value, damageInfo); - Marshal.FreeHGlobal(ptr); - } - - public static void UpdatedPlayerCash(CCSPlayerController? client, int damage) - { - if(client == null) - return; - - if(client.InGameMoneyServices == null) - return; - - client.InGameMoneyServices.Account += damage; - Utilities.SetStateChanged(client, "CCSPlayerController", "m_pInGameMoneyServices"); - } - - public static void RemoveRoundObjective() - { - var objectivelist = new List() { "func_bomb_target", "func_hostage_rescue", "hostage_entity", "c4" }; - - foreach (string objectivename in objectivelist) - { - var entityIndex = Utilities.FindAllEntitiesByDesignerName(objectivename); - - foreach (var entity in entityIndex) - { - _logger?.LogInformation("[RemoveRoundObjective]: Removed {entityname}", entity.DesignerName); - entity.AddEntityIOEvent("Kill", entity, null, "", 0.1f); - } - } - } - - public static void SetStamina(CCSPlayerController? client, float stamina) - { - if(client == null) - return; - - if(client.PlayerPawn.Value == null) - return; - - client.PlayerPawn.Value.VelocityModifier = stamina / 100f; - } - - public static List WeaponList = new List - { - "weapon_deagle", - "weapon_elite", - "weapon_fiveseven", - "weapon_glock", - "weapon_ak47", - "weapon_aug", - "weapon_awp", - "weapon_famas", - "weapon_g3sg1", - "weapon_galilar", - "weapon_m249", - "weapon_m4a1", - "weapon_mac10", - "weapon_p90", - "weapon_mp5sd", - "weapon_ump45", - "weapon_xm1014", - "weapon_bizon", - "weapon_mag7", - "weapon_negev", - "weapon_sawedoff", - "weapon_tec9", - "weapon_hkp2000", - "weapon_mp7", - "weapon_mp9", - "weapon_nova", - "weapon_p250", - "weapon_scar20", - "weapon_sg556", - "weapon_ssg08", - "weapon_m4a1_silencer", - "weapon_usp_silencer", - "weapon_cz75a", - "weapon_revolver", - "weapon_hegrenade", - "weapon_incgrenade", - "weapon_decoy", - "weapon_molotov", - "weapon_flashbang", - "weapon_smokegrenade" - }; +using System.Runtime.InteropServices; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Entities.Constants; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; +using CounterStrikeSharp.API.Modules.Utils; +using Microsoft.Extensions.Logging; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class Utils +{ + private static ZombieSharp? _core; + private static ILogger? _logger; + + public Utils(ZombieSharp core, ILogger logger) + { + _core = core; + _logger = logger; + } + + public static MemoryFunctionVoid CBaseEntity_EmitSoundParamsFunc = new(GameData.GetSignature("CBaseEntity_EmitSoundParams")); + + public static void PrintToCenterAll(string message) + { + if(string.IsNullOrEmpty(message)) + return; + + foreach(var player in Utilities.GetPlayers()) + { + player.PrintToCenter(message); + } + } + + public static void EmitSound(CBaseEntity entity, string soundPath, int pitch = 100, float volume = 1.0f, float deley = 0.0f) + { + if(entity == null || string.IsNullOrEmpty(soundPath)) + return; + + CBaseEntity_EmitSoundParamsFunc.Invoke(entity, soundPath, pitch, volume, deley); + } + + public static CCSPlayerController? GetCCSPlayerController(CEntityInstance? instance) + { + if (instance == null) + return null; + + if (instance.DesignerName != "player") + return null; + + // grab the pawn index + int index = (int)instance.Index; + + // grab player controller from pawn + var pawn = Utilities.GetEntityFromIndex(index); + + // pawn valid + if (pawn == null || !pawn.IsValid) + return null; + + // controller valid + if (pawn.OriginalController == null || !pawn.OriginalController.IsValid) + return null; + + // any further validity is up to the caller + return pawn.OriginalController.Value; + } + + public static string? FindWeaponItemDefinition(CBasePlayerWeapon? weapon) + { + if(weapon == null) + return null; + + var item = (ItemDefinition)weapon.AttributeManager.Item.ItemDefinitionIndex; + + if (weapon.DesignerName == "weapon_m4a1") + { + switch (item) + { + case ItemDefinition.M4A1_S: return "weapon_m4a1_silencer"; + case ItemDefinition.M4A4: return "weapon_m4a1"; + } + } + + else if (weapon.DesignerName == "weapon_hkp2000") + { + switch (item) + { + case ItemDefinition.P2000: return "weapon_hkp2000"; + case ItemDefinition.USP_S: return "weapon_usp_silencer"; + } + } + + else if (weapon.DesignerName == "weapon_mp7") + { + switch (item) + { + case ItemDefinition.MP7: return "weapon_mp7"; + case ItemDefinition.MP5_SD: return "weapon_mp5sd"; + } + } + + return weapon.DesignerName; + } + + public static void DropAllWeapon(CCSPlayerController client, bool remove = false) + { + if(client == null) + { + _logger?.LogError("[DropAllWeapon] Client is null!"); + return; + } + + int droppedCount = 0; + + foreach(var weapon in WeaponList) + { + if (DropWeaponByDesignName(client, weapon, remove)) + droppedCount++; + } + + } + + public static void DropAllWeaponsExceptKnife(CCSPlayerController client) + { + if(client == null) + { + _logger?.LogError("[DropAllWeaponsExceptKnife] Client is null!"); + return; + } + + int droppedCount = 0; + + try + { + // Use the safer drop method for all weapons except knives + foreach(var weapon in WeaponList) + { + if (DropWeaponByDesignName(client, weapon, true)) + droppedCount++; + } + } + catch (Exception ex) + { + _logger?.LogError("[DEBUG][DropAllWeaponsExceptKnife] Error dropping weapons for {0}: {1}", client.PlayerName, ex.Message); + return; + } + + // Ensure zombie has a knife after weapons are dropped + _core?.AddTimer(0.1f, () => { + try + { + if (client == null || !client.IsValid || !Utils.IsPlayerAlive(client)) return; + + // Check if player has any knife + bool hasKnife = false; + var weaponServices = client.PlayerPawn.Value?.WeaponServices; + if (weaponServices != null) + { + foreach (var weapon in weaponServices.MyWeapons) + { + if (weapon.Value != null && weapon.IsValid && weapon.Value.DesignerName.Contains("knife")) + { + hasKnife = true; + // Switch to knife as active weapon + weaponServices.ActiveWeapon.Raw = weapon.Raw; + break; + } + } + } + + if (!hasKnife) + { + client.GiveNamedItem("weapon_knife"); + + // Switch to the newly given knife + _core?.AddTimer(0.05f, () => { + try + { + if (client == null || !client.IsValid) return; + var ws = client.PlayerPawn.Value?.WeaponServices; + if (ws != null) + { + foreach (var weapon in ws.MyWeapons) + { + if (weapon.Value != null && weapon.IsValid && weapon.Value.DesignerName.Contains("knife")) + { + ws.ActiveWeapon.Raw = weapon.Raw; + break; + } + } + } + } + catch (Exception ex) + { + _logger?.LogError("[DropAllWeaponsExceptKnife] Error switching to knife for {0}: {1}", client.PlayerName, ex.Message); + } + }); + } + } + catch (Exception ex) + { + _logger?.LogError("[DropAllWeaponsExceptKnife] Error in knife validation for {0}: {1}", client.PlayerName, ex.Message); + } + }); + } + + public static bool DropWeaponByDesignName(CCSPlayerController client, string weaponName, bool remove = false) + { + if(client == null) + { + _logger?.LogError("[DropWeaponByDesignName] Client is null!"); + return false; + } + + var matchedWeapon = client.PlayerPawn.Value?.WeaponServices?.MyWeapons.Where(x => x.Value?.DesignerName == weaponName).FirstOrDefault(); + + if (matchedWeapon != null && matchedWeapon.IsValid) + { + client.PlayerPawn.Value!.WeaponServices!.ActiveWeapon.Raw = matchedWeapon.Raw; + + // set timer to remove if remove is true. + if(remove) + { + _core?.AddTimer(1f, () => { + matchedWeapon.Value?.AddEntityIOEvent("Kill", matchedWeapon.Value, null, "", 0.1f); + }); + } + + client.DropActiveWeapon(); + return true; + } + + return false; + } + + + public static void RefreshPurchaseCount(CCSPlayerController client) + { + if(PlayerData.PlayerPurchaseCount == null) + { + _logger?.LogError("[RefreshPurchaseCount] Player Purchase count is null"); + return; + } + + if(!PlayerData.PlayerPurchaseCount.ContainsKey(client)) + { + _logger?.LogWarning("[RefreshPurchaseCount] {name} is not in array data, creating new entry", client.PlayerName); + PlayerData.PlayerPurchaseCount.Add(client, new()); + } + + // clear them all. + PlayerData.PlayerPurchaseCount[client].WeaponCount?.Clear(); + } + + public static bool IsClientInBuyZone(CCSPlayerController client) + { + if(client == null) + return false; + + return client.PlayerPawn.Value?.InBuyZone ?? false; + } + + public static bool IsPlayerAlive(CCSPlayerController client) + { + if(client == null) + return false; + + if(!client.IsValid) + { + _logger?.LogError("[IsPlayerAlive] Client is invalid pointer!"); + return false; + } + + var clientPawn = client.PlayerPawn.Value; + + if(clientPawn == null || !clientPawn.IsValid) + return false; + + return (LifeState_t)clientPawn.LifeState == LifeState_t.LIFE_ALIVE; + } + + public static ClassAttribute? GetRandomPlayerClasses(int team) + { + if(Classes.ClassesConfig == null) + { + _logger?.LogError("[GetRandomPlayerClasses] ClassesConfig is null!"); + return null; + } + + List classes = []; + + foreach(var playerClasses in Classes.ClassesConfig) + { + if(playerClasses.Value.Team == team && playerClasses.Value.Enable) + classes.Add(playerClasses.Value); + } + + if(classes.Count <= 0) + { + _logger?.LogError("[GetRandomPlayerClasses] Can't get one from it since it's empty!"); + return null; + } + + return GetRandomItem(classes); + } + + public static T GetRandomItem(List list) + { + // Create a new Random instance + Random random = new Random(); + // Generate a random index + int index = random.Next(list.Count); + // Return the item at the random index + return list[index]; + } + + public static void TakeDamage(CCSPlayerController? client, CCSPlayerController? attacker = null, int damage = 1) + { + if(client == null) + { + _logger?.LogError("[TakeDamage] Client is null!"); + return; + } + + if(!IsPlayerAlive(client)) + { + return; + } + + var size = Schema.GetClassSize("CTakeDamageInfo"); + var ptr = Marshal.AllocHGlobal(size); + + for (var i = 0; i < size; i++) + Marshal.WriteByte(ptr, i, 0); + + var damageInfo = new CTakeDamageInfo(ptr); + + CAttackerInfo attackerInfo; + + if(attacker == null) + attackerInfo = new CAttackerInfo(client); + + else + attackerInfo = new CAttackerInfo(attacker); + + Marshal.StructureToPtr(attackerInfo, new IntPtr(ptr.ToInt64() + 0xf0), false); + + if(attacker == null) + { + Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hInflictor", client.PlayerPawn.Raw); + Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hAttacker", client.PlayerPawn.Raw); + } + + else + { + if(attacker.PlayerPawn != null) + { + Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hInflictor", attacker.PlayerPawn.Raw); + Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hAttacker", attacker.PlayerPawn.Raw); + } + else + { + Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hInflictor", client.PlayerPawn.Raw); + Schema.SetSchemaValue(damageInfo.Handle, "CTakeDamageInfo", "m_hAttacker", client.PlayerPawn.Raw); + } + } + + damageInfo.Damage = damage; + + if(client.Pawn.Value == null) + { + _logger?.LogError("[TakeDamage] Client Pawn is null!"); + return; + } + + VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Invoke(client.Pawn.Value, damageInfo); + Marshal.FreeHGlobal(ptr); + } + + public static void EmitSoundToClient(CCSPlayerController client, CBaseEntity entity, string soundName) + { + if(client == null || entity == null || !client.IsValid || !entity.IsValid || string.IsNullOrEmpty(soundName)) + return; + + RecipientFilter filter = [client]; + entity.EmitSound(soundName, filter, 1f, 1); + } + + public static void UpdatedPlayerCash(CCSPlayerController? client, int damage) + { + if (client == null) + return; + + if (client.InGameMoneyServices == null) + return; + + client.InGameMoneyServices.Account += damage; + Utilities.SetStateChanged(client, "CCSPlayerController", "m_pInGameMoneyServices"); + } + + public static void RemoveRoundObjective() + { + var objectivelist = new List() { "func_bomb_target", "func_hostage_rescue", "hostage_entity", "c4" }; + + foreach (string objectivename in objectivelist) + { + var entityIndex = Utilities.FindAllEntitiesByDesignerName(objectivename); + + foreach (var entity in entityIndex) + { + _logger?.LogInformation("[RemoveRoundObjective]: Removed {entityname}", entity.DesignerName); + entity.AddEntityIOEvent("Kill", entity, null, "", 0.1f); + } + } + } + + public static void SetStamina(CCSPlayerController? client, float stamina) + { + if(client == null) + return; + + if(client.PlayerPawn.Value == null) + return; + + client.PlayerPawn.Value.VelocityModifier = stamina / 100f; + } + + public static float? GetPlayerDistance(CCSPlayerController? client, CCSPlayerController? attacker) + { + if (client == null || attacker == null || !attacker.IsValid || !client.IsValid || !IsPlayerAlive(client) || !IsPlayerAlive(attacker)) + return null; + + var clientPos = client.PlayerPawn.Value?.AbsOrigin; + var attackerPos = attacker.PlayerPawn.Value?.AbsOrigin; + + if (clientPos == null || attackerPos == null) + return null; + + return (float)Math.Sqrt(Math.Pow(clientPos.X - attackerPos.X, 2) + Math.Pow(clientPos.Y - attackerPos.Y, 2) + Math.Pow(clientPos.Z - attackerPos.Z, 2)); + } + + public static List WeaponList = new List + { + "weapon_deagle", + "weapon_elite", + "weapon_fiveseven", + "weapon_glock", + "weapon_ak47", + "weapon_aug", + "weapon_awp", + "weapon_famas", + "weapon_g3sg1", + "weapon_galilar", + "weapon_m249", + "weapon_m4a1", + "weapon_mac10", + "weapon_p90", + "weapon_mp5sd", + "weapon_ump45", + "weapon_xm1014", + "weapon_bizon", + "weapon_mag7", + "weapon_negev", + "weapon_sawedoff", + "weapon_tec9", + "weapon_hkp2000", + "weapon_mp7", + "weapon_mp9", + "weapon_nova", + "weapon_p250", + "weapon_scar20", + "weapon_sg556", + "weapon_ssg08", + "weapon_m4a1_silencer", + "weapon_usp_silencer", + "weapon_cz75a", + "weapon_revolver", + "weapon_hegrenade", + "weapon_incgrenade", + "weapon_decoy", + "weapon_molotov", + "weapon_flashbang", + "weapon_smokegrenade" + }; } \ No newline at end of file diff --git a/ZombieSharp/Plugin/Weapons.cs b/ZombieSharp/Plugin/Weapons.cs index 37756a5..529367c 100644 --- a/ZombieSharp/Plugin/Weapons.cs +++ b/ZombieSharp/Plugin/Weapons.cs @@ -1,354 +1,354 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Admin; -using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Menu; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using ZombieSharp.Models; - -namespace ZombieSharp.Plugin; - -public class Weapons(ZombieSharp core, ILogger logger) -{ - private readonly ZombieSharp _core = core; - private readonly ILogger _logger = logger; - public static Dictionary? WeaponsConfig = null; - bool weaponCommandInitialized = false; - - public void WeaponOnLoad() - { - _core.AddCommand("zs_restrict", "Restrict Weapon Command", WeaponRestrictCommand); - _core.AddCommand("zs_unrestrict", "Unrestrict Weapon Command", WeaponUnrestrictCommand); - } - - public void WeaponsOnMapStart() - { - // make sure this one is null. - WeaponsConfig = null; - - // initial weapon config data - WeaponsConfig = new Dictionary(); - - var configPath = Path.Combine(ZombieSharp.ConfigPath, "weapons.jsonc"); - - if(!File.Exists(configPath)) - { - _logger.LogCritical("[WeaponsOnMapStart] Couldn't find a weapons.jsonc file!"); - return; - } - - _logger.LogInformation("[WeaponsOnMapStart] Load Weapon Config file."); - - // we get data from jsonc file. - WeaponsConfig = JsonConvert.DeserializeObject>(File.ReadAllText(configPath)); - - // we create weapon purchase command here. - IntialWeaponPurchaseCommand(); - } - - public void IntialWeaponPurchaseCommand() - { - // if it's not enabled or null then we don't have to. - if(!GameSettings.Settings?.WeaponPurchaseEnable ?? false) - { - // at least tell them that this is disable. - _logger.LogInformation("[IntialWeaponPurchaseCommand] Purchasing is disabled"); - return; - } - - // if this part has been done before don't do it again. - if(weaponCommandInitialized) - return; - - // safety first. - if(WeaponsConfig == null) - { - _logger.LogError("[IntialWeaponPurchaseCommand] Weapon Configs is null!"); - return; - } - - foreach(var weapon in WeaponsConfig.Values) - { - if(weapon.PurchaseCommand == null || weapon.PurchaseCommand.Count <= 0) - continue; - - foreach(var command in weapon.PurchaseCommand) - { - if(string.IsNullOrEmpty(command)) - continue; - - _core.AddCommand(command, $"Weapon {weapon.WeaponName} Purchase Command", WeaponPurchaseCommand); - } - } - - weaponCommandInitialized = true; - } - - [RequiresPermissions("@css/slay")] - public void WeaponRestrictCommand(CCSPlayerController? client, CommandInfo info) - { - if(WeaponsConfig == null) - { - _logger.LogError("[WeaponRestrictCommand] WeaponsConfig is null!"); - return; - } - - if(info.ArgCount > 1) - { - var weaponname = info.GetArg(1); - var weapon = GetWeaponAttributeByName(weaponname); - - if(weapon == null) - { - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.NotFound", weaponname]}"); - return; - } - - Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Restrict.Weapon", weapon.WeaponName!]}"); - weapon.Restrict = true; - return; - } - - if(client == null) - return; - - var menu = new ChatMenu($" {_core.Localizer["Prefix"]} {_core.Localizer["Restrict.MainMenu"]}"); - - foreach(var weapon in WeaponsConfig) - { - menu.AddMenuOption(weapon.Value.WeaponName!, (client, option) => - { - weapon.Value.Restrict = true; - Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Restrict.Weapon", weapon.Value.WeaponName!]}"); - MenuManager.CloseActiveMenu(client); - }, - weapon.Value.Restrict); - } - menu.ExitButton = true; - MenuManager.OpenChatMenu(client, menu); - } - - [RequiresPermissions("@css/slay")] - public void WeaponUnrestrictCommand(CCSPlayerController? client, CommandInfo info) - { - if(WeaponsConfig == null) - { - _logger.LogError("[WeaponRestrictCommand] WeaponsConfig is null!"); - return; - } - - if(info.ArgCount > 1) - { - var weaponname = info.GetArg(1); - var weapon = GetWeaponAttributeByName(weaponname); - - if(weapon == null) - { - info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.NotFound"]}"); - return; - } - - Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Unrestrict.Weapon", weapon.WeaponName!]}"); - weapon.Restrict = false; - return; - } - - if(client == null) - return; - - var menu = new ChatMenu($" {_core.Localizer["Prefix"]} {_core.Localizer["Unrestrict.MainMenu"]}"); - - foreach(var weapon in WeaponsConfig) - { - menu.AddMenuOption(weapon.Value.WeaponName!, (client, option) => - { - weapon.Value.Restrict = false; - Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Unrestrict.Weapon", weapon.Value.WeaponName!]}"); - MenuManager.CloseActiveMenu(client); - }, - !weapon.Value.Restrict); - } - menu.ExitButton = true; - MenuManager.OpenChatMenu(client, menu); - } - - [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] - public void WeaponPurchaseCommand(CCSPlayerController? client, CommandInfo info) - { - // args 0 is basically command string. - var command = info.GetArg(0); - var weaponAttribute = WeaponsConfig?.Where(weapon => weapon.Value.PurchaseCommand!.Contains(command)).FirstOrDefault().Value; - - if(weaponAttribute != null && client != null) - PurchaseWeapon(client, weaponAttribute); - } - - public void PurchaseWeapon(CCSPlayerController client, WeaponAttribute attribute) - { - // if not enable then we don't have to proceed any further. - if(!GameSettings.Settings?.WeaponPurchaseEnable ?? false) - return; - - // double check for possible - if(attribute == null || client == null) - return; - - // if not alive. - if(!Utils.IsPlayerAlive(client)) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeAlive"]}"); - return; - } - - // if is zombie - if(Infect.IsClientInfect(client)) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeHuman"]}"); - return; - } - - // this check for buyzone if enable. - var buyzone = GameSettings.Settings?.WeaponBuyZoneOnly ?? false; - - if(buyzone && !Utils.IsClientInBuyZone(client)) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.BuyZoneOnly"]}"); - return; - } - - // if it's restricted - if(IsRestricted(attribute.WeaponEntity!)) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.IsRestricted", attribute.WeaponName!]}"); - return; - } - - // check their cash in account - if(client.InGameMoneyServices?.Account < attribute.Price) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.NotEnoughCash"]}"); - return; - } - - // Max Purchase section, will be added later. - if(attribute.MaxPurchase != 0) - { - var count = 0; - - if(PlayerData.PlayerPurchaseCount == null) - { - _logger.LogError("[PurchaseWeapon] Player Purchase count is null!"); - return; - } - - if(!PlayerData.PlayerPurchaseCount.ContainsKey(client)) - { - _logger.LogError("[PurchaseWeapon] Player {name} is not in purchase count data, so create a new one", client.PlayerName); - PlayerData.PlayerPurchaseCount.Add(client, new()); - } - - if(PlayerData.PlayerPurchaseCount[client].WeaponCount == null) - { - _logger.LogError("[PurchaseWeapon] Player {name} Purchase data is null", client.PlayerName); - return; - } - - if(PlayerData.PlayerPurchaseCount[client].WeaponCount!.ContainsKey(attribute.WeaponEntity!)) - count = PlayerData.PlayerPurchaseCount[client].WeaponCount![attribute.WeaponEntity!]; - - if(count >= attribute.MaxPurchase) - { - client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.ReachMaxPurchase", attribute.MaxPurchase]}"); - return; - } - } - - // we need to force drop weapon first before make an purchase - var weapons = client.PlayerPawn.Value?.WeaponServices?.MyWeapons; - - if(weapons == null) - { - _logger.LogError("[PurchaseWeapon] {0} Weapon service is somehow null", client.PlayerName); - return; - } - - foreach(var weapon in weapons) - { - var slot = (int)weapon.Value!.GetVData()!.GearSlot; - - // for primary and secondary only. - if(slot > 2) - continue; - - if(slot == attribute.WeaponSlot) - { - // drop this weapon then break - Utils.DropWeaponByDesignName(client, weapon.Value.DesignerName); - break; - } - } - - Server.NextWorldUpdate(() => - { - // we give weapon to them this part can't be null unless server manager fucked it up. - if(attribute.WeaponEntity == "item_kevlar") - { - client.PlayerPawn.Value!.ArmorValue = 100; - Utilities.SetStateChanged(client.PlayerPawn.Value, "CCSPlayerPawn", "m_ArmorValue"); - } - - else - client.GiveNamedItem(attribute.WeaponEntity!); - - // update purchase history - if(PlayerData.PlayerPurchaseCount![client].WeaponCount!.ContainsKey(attribute.WeaponEntity!)) - PlayerData.PlayerPurchaseCount![client].WeaponCount![attribute.WeaponEntity!]++; - - else - PlayerData.PlayerPurchaseCount?[client].WeaponCount?.Add(attribute.WeaponEntity!, 1); - - var purchaseCount = PlayerData.PlayerPurchaseCount![client].WeaponCount![attribute.WeaponEntity!]; - - var message = $" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.PurchaseSuccess", attribute.WeaponName!]}"; - - if(attribute.MaxPurchase > 0) - message += $" {_core.Localizer["Weapon.PurchaseCount", attribute.MaxPurchase - purchaseCount, attribute.MaxPurchase]}"; - - client.PrintToChat($"{message}"); - - // updated their cash. - client.InGameMoneyServices!.Account -= attribute.Price; - Utilities.SetStateChanged(client, "CCSPlayerController", "m_pInGameMoneyServices"); - }); - } - - public static bool IsRestricted(string weaponentity) - { - if(WeaponsConfig == null) - return false; - - var weapon = GetWeaponAttributeByEntityName(weaponentity); - - if(weapon == null) - return false; - - return weapon.Restrict; - } - - public static WeaponAttribute? GetWeaponAttributeByEntityName(string? weaponentity) - { - if(WeaponsConfig == null) - return null; - - return WeaponsConfig.Where(data => data.Value.WeaponEntity == weaponentity).FirstOrDefault().Value; - } - - public static WeaponAttribute? GetWeaponAttributeByName(string? weapon) - { - if(WeaponsConfig == null) - return null; - - return WeaponsConfig.Where(data => string.Equals(data.Value.WeaponName, weapon, StringComparison.OrdinalIgnoreCase)).FirstOrDefault().Value; - } +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Admin; +using CounterStrikeSharp.API.Modules.Commands; +using CounterStrikeSharp.API.Modules.Menu; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using ZombieSharp.Models; + +namespace ZombieSharp.Plugin; + +public class Weapons(ZombieSharp core, ILogger logger) +{ + private readonly ZombieSharp _core = core; + private readonly ILogger _logger = logger; + public static Dictionary? WeaponsConfig = null; + bool weaponCommandInitialized = false; + + public void WeaponOnLoad() + { + _core.AddCommand("zs_restrict", "Restrict Weapon Command", WeaponRestrictCommand); + _core.AddCommand("zs_unrestrict", "Unrestrict Weapon Command", WeaponUnrestrictCommand); + } + + public void WeaponsOnMapStart() + { + // make sure this one is null. + WeaponsConfig = null; + + // initial weapon config data + WeaponsConfig = new Dictionary(); + + var configPath = Path.Combine(ZombieSharp.ConfigPath, "weapons.jsonc"); + + if(!File.Exists(configPath)) + { + _logger.LogCritical("[WeaponsOnMapStart] Couldn't find a weapons.jsonc file!"); + return; + } + + _logger.LogInformation("[WeaponsOnMapStart] Load Weapon Config file."); + + // we get data from jsonc file. + WeaponsConfig = JsonConvert.DeserializeObject>(File.ReadAllText(configPath)); + + // we create weapon purchase command here. + IntialWeaponPurchaseCommand(); + } + + public void IntialWeaponPurchaseCommand() + { + // if it's not enabled or null then we don't have to. + if(!GameSettings.Settings?.WeaponPurchaseEnable ?? false) + { + // at least tell them that this is disable. + _logger.LogInformation("[IntialWeaponPurchaseCommand] Purchasing is disabled"); + return; + } + + // if this part has been done before don't do it again. + if(weaponCommandInitialized) + return; + + // safety first. + if(WeaponsConfig == null) + { + _logger.LogError("[IntialWeaponPurchaseCommand] Weapon Configs is null!"); + return; + } + + foreach(var weapon in WeaponsConfig.Values) + { + if(weapon.PurchaseCommand == null || weapon.PurchaseCommand.Count <= 0) + continue; + + foreach(var command in weapon.PurchaseCommand) + { + if(string.IsNullOrEmpty(command)) + continue; + + _core.AddCommand(command, $"Weapon {weapon.WeaponName} Purchase Command", WeaponPurchaseCommand); + } + } + + weaponCommandInitialized = true; + } + + [RequiresPermissions("@css/slay")] + public void WeaponRestrictCommand(CCSPlayerController? client, CommandInfo info) + { + if(WeaponsConfig == null) + { + _logger.LogError("[WeaponRestrictCommand] WeaponsConfig is null!"); + return; + } + + if(info.ArgCount > 1) + { + var weaponname = info.GetArg(1); + var weapon = GetWeaponAttributeByName(weaponname); + + if(weapon == null) + { + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.NotFound", weaponname]}"); + return; + } + + Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Restrict.Weapon", weapon.WeaponName!]}"); + weapon.Restrict = true; + return; + } + + if(client == null) + return; + + var menu = new ChatMenu($" {_core.Localizer["Prefix"]} {_core.Localizer["Restrict.MainMenu"]}"); + + foreach(var weapon in WeaponsConfig) + { + menu.AddMenuOption(weapon.Value.WeaponName!, (client, option) => + { + weapon.Value.Restrict = true; + Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Restrict.Weapon", weapon.Value.WeaponName!]}"); + MenuManager.CloseActiveMenu(client); + }, + weapon.Value.Restrict); + } + menu.ExitButton = true; + MenuManager.OpenChatMenu(client, menu); + } + + [RequiresPermissions("@css/slay")] + public void WeaponUnrestrictCommand(CCSPlayerController? client, CommandInfo info) + { + if(WeaponsConfig == null) + { + _logger.LogError("[WeaponRestrictCommand] WeaponsConfig is null!"); + return; + } + + if(info.ArgCount > 1) + { + var weaponname = info.GetArg(1); + var weapon = GetWeaponAttributeByName(weaponname); + + if(weapon == null) + { + info.ReplyToCommand($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.NotFound"]}"); + return; + } + + Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Unrestrict.Weapon", weapon.WeaponName!]}"); + weapon.Restrict = false; + return; + } + + if(client == null) + return; + + var menu = new ChatMenu($" {_core.Localizer["Prefix"]} {_core.Localizer["Unrestrict.MainMenu"]}"); + + foreach(var weapon in WeaponsConfig) + { + menu.AddMenuOption(weapon.Value.WeaponName!, (client, option) => + { + weapon.Value.Restrict = false; + Server.PrintToChatAll($" {_core.Localizer["Prefix"]} {_core.Localizer["Unrestrict.Weapon", weapon.Value.WeaponName!]}"); + MenuManager.CloseActiveMenu(client); + }, + !weapon.Value.Restrict); + } + menu.ExitButton = true; + MenuManager.OpenChatMenu(client, menu); + } + + [CommandHelper(0, "", CommandUsage.CLIENT_ONLY)] + public void WeaponPurchaseCommand(CCSPlayerController? client, CommandInfo info) + { + // args 0 is basically command string. + var command = info.GetArg(0); + var weaponAttribute = WeaponsConfig?.Where(weapon => weapon.Value.PurchaseCommand!.Contains(command)).FirstOrDefault().Value; + + if(weaponAttribute != null && client != null) + PurchaseWeapon(client, weaponAttribute); + } + + public void PurchaseWeapon(CCSPlayerController client, WeaponAttribute attribute) + { + // if not enable then we don't have to proceed any further. + if(!GameSettings.Settings?.WeaponPurchaseEnable ?? false) + return; + + // double check for possible + if(attribute == null || client == null) + return; + + // if not alive. + if(!Utils.IsPlayerAlive(client)) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeAlive"]}"); + return; + } + + // if is zombie + if(Infect.IsClientZombie(client)) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Core.MustBeHuman"]}"); + return; + } + + // this check for buyzone if enable. + var buyzone = GameSettings.Settings?.WeaponBuyZoneOnly ?? false; + + if(buyzone && !Utils.IsClientInBuyZone(client)) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.BuyZoneOnly"]}"); + return; + } + + // if it's restricted + if(IsRestricted(attribute.WeaponEntity!)) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.IsRestricted", attribute.WeaponName!]}"); + return; + } + + // check their cash in account + if(client.InGameMoneyServices?.Account < attribute.Price) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.NotEnoughCash"]}"); + return; + } + + // Max Purchase section, will be added later. + if(attribute.MaxPurchase != 0) + { + var count = 0; + + if(PlayerData.PlayerPurchaseCount == null) + { + _logger.LogError("[PurchaseWeapon] Player Purchase count is null!"); + return; + } + + if(!PlayerData.PlayerPurchaseCount.ContainsKey(client)) + { + _logger.LogError("[PurchaseWeapon] Player {name} is not in purchase count data, so create a new one", client.PlayerName); + PlayerData.PlayerPurchaseCount.Add(client, new()); + } + + if(PlayerData.PlayerPurchaseCount[client].WeaponCount == null) + { + _logger.LogError("[PurchaseWeapon] Player {name} Purchase data is null", client.PlayerName); + return; + } + + if(PlayerData.PlayerPurchaseCount[client].WeaponCount!.ContainsKey(attribute.WeaponEntity!)) + count = PlayerData.PlayerPurchaseCount[client].WeaponCount![attribute.WeaponEntity!]; + + if(count >= attribute.MaxPurchase) + { + client.PrintToChat($" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.ReachMaxPurchase", attribute.MaxPurchase]}"); + return; + } + } + + // we need to force drop weapon first before make an purchase + var weapons = client.PlayerPawn.Value?.WeaponServices?.MyWeapons; + + if(weapons == null) + { + _logger.LogError("[PurchaseWeapon] {0} Weapon service is somehow null", client.PlayerName); + return; + } + + foreach(var weapon in weapons) + { + var slot = (int)weapon.Value!.GetVData()!.GearSlot; + + // for primary and secondary only. + if(slot > 2) + continue; + + if(slot == attribute.WeaponSlot) + { + // drop this weapon then break + Utils.DropWeaponByDesignName(client, weapon.Value.DesignerName); + break; + } + } + + Server.NextWorldUpdate(() => + { + // we give weapon to them this part can't be null unless server manager fucked it up. + if(attribute.WeaponEntity == "item_kevlar") + { + client.PlayerPawn.Value!.ArmorValue = 100; + Utilities.SetStateChanged(client.PlayerPawn.Value, "CCSPlayerPawn", "m_ArmorValue"); + } + + else + client.GiveNamedItem(attribute.WeaponEntity!); + + // update purchase history + if(PlayerData.PlayerPurchaseCount![client].WeaponCount!.ContainsKey(attribute.WeaponEntity!)) + PlayerData.PlayerPurchaseCount![client].WeaponCount![attribute.WeaponEntity!]++; + + else + PlayerData.PlayerPurchaseCount?[client].WeaponCount?.Add(attribute.WeaponEntity!, 1); + + var purchaseCount = PlayerData.PlayerPurchaseCount![client].WeaponCount![attribute.WeaponEntity!]; + + var message = $" {_core.Localizer["Prefix"]} {_core.Localizer["Weapon.PurchaseSuccess", attribute.WeaponName!]}"; + + if(attribute.MaxPurchase > 0) + message += $" {_core.Localizer["Weapon.PurchaseCount", attribute.MaxPurchase - purchaseCount, attribute.MaxPurchase]}"; + + client.PrintToChat($"{message}"); + + // updated their cash. + client.InGameMoneyServices!.Account -= attribute.Price; + Utilities.SetStateChanged(client, "CCSPlayerController", "m_pInGameMoneyServices"); + }); + } + + public static bool IsRestricted(string weaponentity) + { + if(WeaponsConfig == null) + return false; + + var weapon = GetWeaponAttributeByEntityName(weaponentity); + + if(weapon == null) + return false; + + return weapon.Restrict; + } + + public static WeaponAttribute? GetWeaponAttributeByEntityName(string? weaponentity) + { + if(WeaponsConfig == null) + return null; + + return WeaponsConfig.Where(data => data.Value.WeaponEntity == weaponentity).FirstOrDefault().Value; + } + + public static WeaponAttribute? GetWeaponAttributeByName(string? weapon) + { + if(WeaponsConfig == null) + return null; + + return WeaponsConfig.Where(data => string.Equals(data.Value.WeaponName, weapon, StringComparison.OrdinalIgnoreCase)).FirstOrDefault().Value; + } } \ No newline at end of file diff --git a/ZombieSharp/ZombieSharp.cs b/ZombieSharp/ZombieSharp.cs index 924fbcd..298529b 100644 --- a/ZombieSharp/ZombieSharp.cs +++ b/ZombieSharp/ZombieSharp.cs @@ -1,118 +1,193 @@ -using CounterStrikeSharp.API; -using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Core.Capabilities; -using Microsoft.Extensions.Logging; -using ZombieSharp.Api; -using ZombieSharp.Database; -using ZombieSharp.Models; -using ZombieSharp.Plugin; -using ZombieSharpAPI; - -namespace ZombieSharp; - -public partial class ZombieSharp : BasePlugin -{ - public override string ModuleName => "ZombieSharp"; - public override string ModuleVersion => "2.2.0"; - public override string ModuleAuthor => "Oylsister"; - public override string ModuleDescription => "Infection/survival style gameplay for CS2 in C#"; - - private Events? _event; - private Infect? _infect; - private Utils? _utils; - private Hook? _hook; - private Classes? _classes; - private GameSettings? _settings; - private Weapons? _weapons; - private Knockback? _knockback; - private Teleport? _teleport; - private Respawn? _respawn; - private DatabaseMain? _database; - private Napalm? _napalm; - private ConVars? _convar; - private HitGroup? _hitgroups; - private RoundEnd? _roundend; - private HealthRegen? _healthregen; - private readonly ILogger _logger; - - // API stuff - ZombieSharpInterface? api { get; set; } - public static PluginCapability APICapability = new("zombiesharp:core"); - - public ZombieSharp(ILogger logger) - { - _logger = logger; - } - - public override void Load(bool hotReload) - { - PlayerData.ZombiePlayerData = []; - PlayerData.PlayerClassesData = []; - PlayerData.PlayerPurchaseCount = []; - PlayerData.PlayerBurnData = []; - PlayerData.PlayerRegenData = []; - - api = new ZombieSharpInterface(); - - Capabilities.RegisterPluginCapability(APICapability, () => api); - - _database = new DatabaseMain(this, _logger); - _classes = new Classes(this, _database, _logger); - _infect = new Infect(this, _logger, _classes, api); - _utils = new Utils(this, _logger); - _settings = new GameSettings(_logger); - _weapons = new Weapons(this, _logger); - _respawn = new Respawn(this, _logger); - _hook = new Hook(this, _weapons, _respawn, _logger); - _teleport = new Teleport(this, _logger); - _napalm = new(this, _logger); - _convar = new ConVars(this, _weapons, _logger); - _hitgroups = new HitGroup(_logger); - _roundend = new RoundEnd(this, _logger); - _event = new Events(this, _infect, _settings, _classes, _weapons, _teleport, _respawn, _napalm, _convar, _hitgroups, _logger); - _knockback = new Knockback(_logger); - _healthregen = new HealthRegen(this, _logger); - - if(hotReload) - { - _logger.LogWarning("[Load] The plugin is hotReloaded! This might cause instability to your server."); - _settings.GameSettingsOnMapStart(); - _classes.ClassesOnMapStart(); - _weapons.WeaponsOnMapStart(); - _hitgroups.HitGroupOnMapStart(); - _convar.ConVarOnLoad(); - _convar.ConVarExecuteOnMapStart(Server.MapName); - } - - Server.ExecuteCommand("sv_predictable_damage_tag_ticks 0"); - Server.ExecuteCommand("mp_ignore_round_win_conditions 1"); - Server.ExecuteCommand("mp_give_player_c4 0"); - - // initial - _infect.InfectOnLoad(); - _weapons.WeaponOnLoad(); - _event.EventOnLoad(); - _hook.HookOnLoad(); - _teleport.TeleportOnLoad(); - _respawn.RespawnOnLoad(); - _classes.ClassesOnLoad(); - _napalm.NapalmOnLoad(); - - _database.DatabaseOnLoad().Wait(); - } - - public override void Unload(bool hotReload) - { - PlayerData.ZombiePlayerData = null; - PlayerData.PlayerClassesData = null; - PlayerData.PlayerPurchaseCount = null; - PlayerData.PlayerRegenData = null; - PlayerData.PlayerBurnData = null; - - _event?.EventOnUnload(); - _hook?.HookOnUnload(); - _database?.DatabaseOnUnload(); - } - - public static string ConfigPath = Path.Combine(Application.RootDirectory, "configs/zombiesharp/"); -} +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Core.Capabilities; +using Microsoft.Extensions.Logging; +using ZombieSharp.Api; +using ZombieSharp.Database; +using ZombieSharp.Models; +using ZombieSharp.Plugin; +using ZombieSharpAPI; + +namespace ZombieSharp; + +public partial class ZombieSharp : BasePlugin +{ + public override string ModuleName => "ZombieSharp"; + public override string ModuleVersion => "2.3.0"; + public override string ModuleAuthor => "Oylsister, +SyntX"; + public override string ModuleDescription => "Infection/survival style gameplay for CS2 in C#"; + + private Events? _event; + private Infect? _infect; + private Utils? _utils; + private Hook? _hook; + private Classes? _classes; + private GameSettings? _settings; + private Weapons? _weapons; + private Knockback? _knockback; + private Teleport? _teleport; + private Respawn? _respawn; + private DatabaseMain? _database; + private Napalm? _napalm; + private ConVars? _convar; + private HitGroup? _hitgroups; + private RoundEnd? _roundend; + private HealthRegen? _healthregen; + private readonly ILogger _logger; + + // API stuff + ZombieSharpInterface? api { get; set; } + public static PluginCapability APICapability = new("zombiesharp:core"); + + public ZombieSharp(ILogger logger) + { + _logger = logger; + } + + public override void Load(bool hotReload) + { + PlayerData.ZombiePlayerData = []; + PlayerData.PlayerClassesData = []; + PlayerData.PlayerPurchaseCount = []; + PlayerData.PlayerBurnData = []; + PlayerData.PlayerRegenData = []; + PlayerData.PlayerSpawnData = []; + + api = new ZombieSharpInterface(); + + Capabilities.RegisterPluginCapability(APICapability, () => api); + + _database = new DatabaseMain(this, _logger); + _classes = new Classes(this, _database, _logger); + _infect = new Infect(this, _logger, _classes, api); + _utils = new Utils(this, _logger); + _settings = new GameSettings(_logger); + _weapons = new Weapons(this, _logger); + _respawn = new Respawn(this, _logger); + _hook = new Hook(this, _weapons, _respawn, _logger); + _teleport = new Teleport(this, _logger); + _napalm = new(this, _logger); + _convar = new ConVars(this, _weapons, _logger); + _hitgroups = new HitGroup(_logger); + _roundend = new RoundEnd(this, _logger); + _event = new Events(this, _infect, _settings, _classes, _weapons, _teleport, _respawn, _napalm, _convar, _hitgroups, _logger); + _knockback = new Knockback(_logger); + _healthregen = new HealthRegen(this, _logger); + + if(hotReload) + { + _logger.LogWarning("[Load] The plugin is hotReloaded! This might cause instability to your server."); + _settings.GameSettingsOnMapStart(); + _classes.ClassesOnMapStart(); + _weapons.WeaponsOnMapStart(); + _hitgroups.HitGroupOnMapStart(); + _convar.ConVarOnLoad(); + _convar.ConVarExecuteOnMapStart(Server.MapName); + } + + Server.ExecuteCommand("sv_predictable_damage_tag_ticks 0"); + Server.ExecuteCommand("mp_ignore_round_win_conditions 1"); + Server.ExecuteCommand("mp_give_player_c4 0"); + Server.ExecuteCommand("mp_autoteambalance 0"); + Server.ExecuteCommand("mp_limitteams 0"); + Server.ExecuteCommand("mp_teammates_are_enemies 0"); + + // initial + _infect.InfectOnLoad(); + _weapons.WeaponOnLoad(); + _event.EventOnLoad(); + _hook.HookOnLoad(); + _teleport.TeleportOnLoad(); + _respawn.RespawnOnLoad(); + _classes.ClassesOnLoad(); + _napalm.NapalmOnLoad(); + + _database.DatabaseOnLoad().Wait(); + } + + public override void Unload(bool hotReload) + { + _logger.LogInformation("[Unload] Starting plugin unload process (hotReload: {hotReload})", hotReload); + + // Dispose of timers first + try + { + _infect?.InfectKillInfectionTimer(); + RoundEnd.RoundEndKillTimer(); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "[Unload] Error disposing timers"); + } + + // Clear dictionaries properly with memory leak prevention + try + { + if (PlayerData.ZombiePlayerData != null) + { + PlayerData.ZombiePlayerData.Clear(); + PlayerData.ZombiePlayerData = null; + } + + if (PlayerData.PlayerClassesData != null) + { + PlayerData.PlayerClassesData.Clear(); + PlayerData.PlayerClassesData = null; + } + + if (PlayerData.PlayerPurchaseCount != null) + { + PlayerData.PlayerPurchaseCount.Clear(); + PlayerData.PlayerPurchaseCount = null; + } + + if (PlayerData.PlayerBurnData != null) + { + PlayerData.PlayerBurnData.Clear(); + PlayerData.PlayerBurnData = null; + } + + if (PlayerData.PlayerRegenData != null) + { + // Dispose any active regen timers to prevent memory leaks + foreach (var regenTimer in PlayerData.PlayerRegenData.Values) + { + regenTimer?.Kill(); + } + PlayerData.PlayerRegenData.Clear(); + PlayerData.PlayerRegenData = null; + } + + if (PlayerData.PlayerSpawnData != null) + { + PlayerData.PlayerSpawnData.Clear(); + PlayerData.PlayerSpawnData = null; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "[Unload] Error clearing player data dictionaries"); + } + + // Unload event handlers and hooks + try + { + _event?.EventOnUnload(); + _hook?.HookOnUnload(); + _database?.DatabaseOnUnload(); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "[Unload] Error unloading modules"); + } + + // Force garbage collection to prevent memory leaks + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + _logger.LogInformation("[Unload] Plugin unload completed successfully"); + } + + public static string ConfigPath = Path.Combine(Application.RootDirectory, "configs/zombiesharp/"); +} diff --git a/ZombieSharp/ZombieSharp.csproj b/ZombieSharp/ZombieSharp.csproj index 0605ad0..7edc73b 100644 --- a/ZombieSharp/ZombieSharp.csproj +++ b/ZombieSharp/ZombieSharp.csproj @@ -1,23 +1,23 @@ - - - - net8.0 - enable - enable - false - false - true - - - - - - - - - - - - - - + + + + net8.0 + enable + enable + false + false + true + + + + + + + + + + + + + + diff --git a/ZombieSharpAPI/ZombieSharpAPI.cs b/ZombieSharpAPI/ZombieSharpAPI.cs index c8a2660..fbd8098 100644 --- a/ZombieSharpAPI/ZombieSharpAPI.cs +++ b/ZombieSharpAPI/ZombieSharpAPI.cs @@ -1,13 +1,15 @@ -using CounterStrikeSharp.API.Core; - -namespace ZombieSharpAPI; - -public interface IZombieSharpAPI -{ - public event Func? OnClientInfect; - public event Func? OnClientHumanize; - - public bool ZS_IsClientHuman(CCSPlayerController client); - public bool ZS_IsClientInfect(CCSPlayerController client); - public void ZS_RespawnClient(CCSPlayerController client); -} +using CounterStrikeSharp.API.Core; + +namespace ZombieSharpAPI; + +public interface IZombieSharpAPI +{ + public event Func? OnClientInfect; + public event Func? OnClientHumanize; + + public void Hook_OnInfectClient(Func handler); + + public bool ZS_IsClientHuman(CCSPlayerController client); + public bool ZS_IsClientZombie(CCSPlayerController controller); + public void ZS_RespawnClient(CCSPlayerController client); +} diff --git a/ZombieSharpAPI/ZombieSharpAPI.csproj b/ZombieSharpAPI/ZombieSharpAPI.csproj index a5c4fac..59dd2b4 100644 --- a/ZombieSharpAPI/ZombieSharpAPI.csproj +++ b/ZombieSharpAPI/ZombieSharpAPI.csproj @@ -1,13 +1,13 @@ - - - - net8.0 - enable - enable - - - - - - - + + + + net8.0 + enable + enable + + + + + + + \ No newline at end of file