feat: add BasePower abstraction
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
// GlowData class
|
||||
public class GlowData
|
||||
{
|
||||
public CDynamicProp GlowEnt { get; set; } = null!;
|
||||
public CDynamicProp ModelRelay { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class Wallhack(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 26;
|
||||
public override string Name => "Wallhack";
|
||||
public override string Description => "See enemies through walls with a glow!";
|
||||
public override char ChatColor => ChatColors.Purple;
|
||||
public override string HtmlColor => "#AB4BD1";
|
||||
|
||||
// Static storage
|
||||
public static List<CCSPlayerController> Wallhackers = [];
|
||||
public static Dictionary<CCSPlayerController, GlowData> GlowData = [];
|
||||
private static BasePlugin? _plugin; // Static reference to plugin!
|
||||
private static readonly List<Delegate> _staticRegisteredDelegates = []; // Store delegates to prevent GC!
|
||||
|
||||
// Static references to event handler delegates to prevent GC!
|
||||
private static readonly BasePlugin.GameEventHandler<EventPlayerDeath> _onPlayerDeathHandler = OnPlayerDeath;
|
||||
private static readonly BasePlugin.GameEventHandler<EventPlayerDisconnect> _onPlayerDisconnectHandler = OnPlayerDisconnect;
|
||||
private static readonly BasePlugin.GameEventHandler<EventPlayerSpawn> _onPlayerSpawnHandler = OnPlayerSpawn;
|
||||
private static readonly BasePlugin.GameEventHandler<EventPlayerTeam> _onPlayerChangeTeamHandler = OnPlayerChangeTeam;
|
||||
// Also store a static reference to OnPlayerTransmit!
|
||||
public static readonly Action<CCheckTransmitInfo, CCSPlayerController> _onPlayerTransmitHandler = OnPlayerTransmit;
|
||||
|
||||
public override void AssignToPlayer(CCSPlayerController player, bool showMessages)
|
||||
{
|
||||
base.AssignToPlayer(player, showMessages);
|
||||
AddWallhacker(player);
|
||||
_plugin?.Logger.LogInformation("Wallhack assigned to {PlayerName}", player.PlayerName);
|
||||
|
||||
// Ensure all alive players have glows (delay slightly just in case!)
|
||||
NextFrame(() =>
|
||||
{
|
||||
foreach (var existingPlayer in Utilities.GetPlayers())
|
||||
{
|
||||
if (IsPlayerValid(existingPlayer) && existingPlayer.PawnIsAlive && existingPlayer.Team >= CsTeam.Terrorist)
|
||||
{
|
||||
if (!GlowData.ContainsKey(existingPlayer))
|
||||
{
|
||||
_plugin?.Logger.LogInformation("Creating glow for {PlayerName}", existingPlayer.PlayerName);
|
||||
Glow(existingPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
_plugin?.Logger.LogInformation("Wallhackers: {Count}, GlowData: {GlowCount}", Wallhackers.Count, GlowData.Count);
|
||||
});
|
||||
}
|
||||
|
||||
public override void UnassignFromPlayer(CCSPlayerController player)
|
||||
{
|
||||
base.UnassignFromPlayer(player);
|
||||
RemoveWallhacker(player);
|
||||
_plugin?.Logger.LogInformation("Wallhack unassigned from {PlayerName}", player.PlayerName);
|
||||
}
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
_plugin = Plugin; // Set static plugin reference!
|
||||
RegisterEventHandler<EventPlayerDeath>(_onPlayerDeathHandler);
|
||||
RegisterEventHandler<EventPlayerDisconnect>(_onPlayerDisconnectHandler);
|
||||
RegisterEventHandler<EventPlayerSpawn>(_onPlayerSpawnHandler, HookMode.Post);
|
||||
RegisterEventHandler<EventPlayerTeam>(_onPlayerChangeTeamHandler, HookMode.Post);
|
||||
_plugin?.Logger.LogInformation("Wallhack initialized!");
|
||||
|
||||
// Delay creating initial glows until global variables are initialized!
|
||||
NextFrame(() =>
|
||||
{
|
||||
_plugin?.Logger.LogInformation("Creating initial glows for existing players!");
|
||||
foreach (var existingPlayer in Utilities.GetPlayers())
|
||||
{
|
||||
if (IsPlayerValid(existingPlayer) && existingPlayer.PawnIsAlive && existingPlayer.Team >= CsTeam.Terrorist)
|
||||
{
|
||||
if (!GlowData.ContainsKey(existingPlayer))
|
||||
{
|
||||
_plugin?.Logger.LogInformation("Creating glow for {PlayerName} during init", existingPlayer.PlayerName);
|
||||
Glow(existingPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static HookResult OnPlayerSpawn(EventPlayerSpawn @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
_plugin?.Logger.LogInformation("Wallhack: OnPlayerSpawn called for {PlayerName}", player?.PlayerName ?? "NULL");
|
||||
|
||||
if (!IsPlayerValid(player))
|
||||
{
|
||||
_plugin?.Logger.LogInformation("Wallhack: Player invalid, skipping");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
if (player!.Team < CsTeam.Terrorist)
|
||||
{
|
||||
_plugin?.Logger.LogInformation("Wallhack: Player team invalid, skipping");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var gameRules = Utilities.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules").FirstOrDefault();
|
||||
if (gameRules?.GameRules?.WarmupPeriod == true)
|
||||
{
|
||||
_plugin?.Logger.LogInformation("Wallhack: Warmup period, skipping");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
_plugin?.Logger.LogInformation("Wallhack: Calling Glow for {PlayerName}", player.PlayerName);
|
||||
Glow(player!);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
public static HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!IsPlayerValid(player)) return HookResult.Continue;
|
||||
|
||||
if (GlowData.TryGetValue(player!, out var glowData))
|
||||
{
|
||||
if (glowData.GlowEnt.IsValid)
|
||||
glowData.GlowEnt.Remove();
|
||||
if (glowData.ModelRelay.IsValid)
|
||||
glowData.ModelRelay.Remove();
|
||||
|
||||
GlowData.Remove(player!);
|
||||
}
|
||||
|
||||
Wallhackers.Remove(player!);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
public static HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!IsPlayerValid(player)) return HookResult.Continue;
|
||||
if (!GlowData.TryGetValue(player!, out var glowData)) return HookResult.Continue;
|
||||
|
||||
if (glowData.GlowEnt.IsValid)
|
||||
glowData.GlowEnt.Remove();
|
||||
if (glowData.ModelRelay.IsValid)
|
||||
glowData.ModelRelay.Remove();
|
||||
GlowData.Remove(player!);
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
public static HookResult OnPlayerChangeTeam(EventPlayerTeam @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!IsPlayerValid(player)) return HookResult.Continue;
|
||||
if (!GlowData.TryGetValue(player!, out var glowData)) return HookResult.Continue;
|
||||
if (!glowData.GlowEnt.IsValid || !glowData.ModelRelay.IsValid) return HookResult.Continue;
|
||||
|
||||
NextWorldUpdate(() =>
|
||||
{
|
||||
glowData.GlowEnt.SetModel(GetPlayerModel(player!));
|
||||
glowData.ModelRelay.SetModel(GetPlayerModel(player!));
|
||||
});
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private static void Glow(CCSPlayerController player)
|
||||
{
|
||||
_plugin?.Logger.LogInformation("Wallhack: Glow() called for {PlayerName}", player.PlayerName);
|
||||
|
||||
// First remove existing glow for this player if it exists
|
||||
if (GlowData.TryGetValue(player, out var existingGlow))
|
||||
{
|
||||
if (existingGlow.GlowEnt.IsValid)
|
||||
existingGlow.GlowEnt.Remove();
|
||||
if (existingGlow.ModelRelay.IsValid)
|
||||
existingGlow.ModelRelay.Remove();
|
||||
GlowData.Remove(player);
|
||||
}
|
||||
|
||||
var glowEntity = Utilities.CreateEntityByName<CDynamicProp>("prop_dynamic");
|
||||
var modelRelay = Utilities.CreateEntityByName<CDynamicProp>("prop_dynamic");
|
||||
|
||||
if (glowEntity == null || modelRelay == null)
|
||||
{
|
||||
_plugin?.Logger.LogError("Wallhack: Failed to create glow entities for {PlayerName}", player.PlayerName);
|
||||
return;
|
||||
}
|
||||
|
||||
modelRelay.Spawnflags = 256;
|
||||
modelRelay.Render = Color.Transparent;
|
||||
modelRelay.RenderMode = RenderMode_t.kRenderNone;
|
||||
modelRelay.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags &= ~(1u << 2);
|
||||
modelRelay.SetModel(GetPlayerModel(player));
|
||||
|
||||
glowEntity.Spawnflags = 256;
|
||||
glowEntity.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags &= ~(1u << 2);
|
||||
glowEntity.Render = Color.FromArgb(1, 0, 0, 0);
|
||||
glowEntity.SetModel(GetPlayerModel(player));
|
||||
|
||||
glowEntity.DispatchSpawn();
|
||||
modelRelay.DispatchSpawn();
|
||||
|
||||
glowEntity.Glow.GlowRange = 5000;
|
||||
glowEntity.Glow.GlowRangeMin = 0;
|
||||
glowEntity.Glow.GlowColorOverride = Color.FromArgb(255, 171, 75, 209);
|
||||
glowEntity.Glow.GlowTeam = -1;
|
||||
glowEntity.Glow.GlowType = 3;
|
||||
|
||||
modelRelay.AcceptInput("FollowEntity", player.Pawn.Value, null, "!activator");
|
||||
glowEntity.AcceptInput("FollowEntity", modelRelay, null, "!activator");
|
||||
|
||||
GlowData.Add(player, new GlowData
|
||||
{
|
||||
GlowEnt = glowEntity,
|
||||
ModelRelay = modelRelay
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetPlayerModel(CCSPlayerController player)
|
||||
{
|
||||
return player.Pawn.Value!.CBodyComponent!.SceneNode!.GetSkeletonInstance().ModelState.ModelName;
|
||||
}
|
||||
|
||||
private static bool IsPlayerValid(CCSPlayerController? plr) => plr != null &&
|
||||
plr.IsValid &&
|
||||
plr.PlayerPawn != null &&
|
||||
plr.PlayerPawn.IsValid &&
|
||||
plr.Connected == PlayerConnectedState.PlayerConnected &&
|
||||
!plr.IsHLTV;
|
||||
|
||||
public static void OnPlayerTransmit(CCheckTransmitInfo info, CCSPlayerController player)
|
||||
{
|
||||
foreach (var entity in GlowData.ToList())
|
||||
{
|
||||
if (Wallhackers.Contains(player!))
|
||||
{
|
||||
if (!IsPlayerValid(entity.Key) || !IsPlayerValid(player)) continue;
|
||||
|
||||
if (entity.Key.Team != player!.Team && player!.Team != CsTeam.Spectator && entity.Key.Team != CsTeam.Spectator)
|
||||
{
|
||||
info.TransmitEntities.Add(entity.Value.ModelRelay);
|
||||
info.TransmitEntities.Add(entity.Value.GlowEnt);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
info.TransmitEntities.Remove(entity.Value.ModelRelay);
|
||||
info.TransmitEntities.Remove(entity.Value.GlowEnt);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddWallhacker(CCSPlayerController player)
|
||||
{
|
||||
if (!Wallhackers.Contains(player))
|
||||
{
|
||||
Wallhackers.Add(player);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveWallhacker(CCSPlayerController player)
|
||||
{
|
||||
Wallhackers.Remove(player);
|
||||
}
|
||||
|
||||
// Helper to wrap Server.NextWorldUpdate and store delegate to prevent GC!
|
||||
private static void NextWorldUpdate(Action callback)
|
||||
{
|
||||
Action? wrappedCallback = null;
|
||||
wrappedCallback = () =>
|
||||
{
|
||||
callback();
|
||||
if (wrappedCallback != null)
|
||||
{
|
||||
_staticRegisteredDelegates.Remove(wrappedCallback);
|
||||
}
|
||||
};
|
||||
_staticRegisteredDelegates.Add(wrappedCallback);
|
||||
Server.NextWorldUpdate(wrappedCallback);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user