feat: add BasePower abstraction

This commit is contained in:
admin
2026-07-26 23:47:38 +02:00
commit 6dbbfc0dde
83 changed files with 7587 additions and 0 deletions
+291
View File
@@ -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);
}
}