feat: add BasePower abstraction
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
using Cs2SuperPowers.Utils;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class AirStrike(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 21;
|
||||
public override string Name => "Air Strike";
|
||||
|
||||
public override string Description => "Decoys thrown by you launch an airstrike (5 second cooldown).";
|
||||
|
||||
public override char ChatColor => ChatColors.Red;
|
||||
|
||||
public override string HtmlColor => "#FF0000";
|
||||
|
||||
private readonly Dictionary<CCSPlayerController, DateTime> _lastUse = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventDecoyStarted>(OnDecoyStarted);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
_lastUse.Clear();
|
||||
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().IsAlive().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NextFrame(() =>
|
||||
{
|
||||
player.GiveNamedItem("weapon_decoy");
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnDecoyStarted(EventDecoyStarted @event, GameEventInfo _)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check cooldown (skip if nodelay is enabled)
|
||||
var now = DateTime.UtcNow;
|
||||
DateTime lastUseTime = default;
|
||||
bool isOnCooldown = !GlobalConfig.NoDelayEnabled && _lastUse.TryGetValue(player!, out lastUseTime) && (now - lastUseTime).TotalSeconds < 5;
|
||||
|
||||
// Always give a new decoy, regardless of cooldown!
|
||||
player!.GiveNamedItem("weapon_decoy");
|
||||
|
||||
if (isOnCooldown)
|
||||
{
|
||||
double secondsRemaining = Math.Ceiling(5 - (now - lastUseTime).TotalSeconds);
|
||||
player.PrintToCenterAlert($"Air Strike is on cooldown! {ChatColors.DarkRed}{secondsRemaining}{ChatColors.Default}s remaining");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
_lastUse[player!] = now;
|
||||
|
||||
var pos = new Vector(@event.X, @event.Y, @event.Z);
|
||||
|
||||
// Airstrike: spawn grenades above the target, spread over an area, falling down
|
||||
var random = new Random();
|
||||
int grenadeCount = GlobalConfig.GetAirStrikeCount(20);
|
||||
float airstrikeHeight = 800.0f; // Height above the detonation point
|
||||
float spreadRadius = 300.0f; // How far from the center grenades can land
|
||||
|
||||
for (int i = 0; i < grenadeCount; i++)
|
||||
{
|
||||
// Random offset in a circle around the target
|
||||
double angle = random.NextDouble() * 2 * Math.PI;
|
||||
double radius = Math.Sqrt(random.NextDouble()) * spreadRadius; // sqrt for uniform distribution
|
||||
|
||||
float offsetX = (float)(Math.Cos(angle) * radius);
|
||||
float offsetY = (float)(Math.Sin(angle) * radius);
|
||||
|
||||
// Spawn position: above the ground, with random spread
|
||||
var spawnPos = new Vector(
|
||||
pos.X + offsetX,
|
||||
pos.Y + offsetY,
|
||||
pos.Z + airstrikeHeight + (float)(random.NextDouble() * 100.0 - 50.0) // small vertical variation
|
||||
);
|
||||
|
||||
// Angle: straight down, maybe with a little randomization
|
||||
float pitch = -90f + (float)(random.NextDouble() * 10.0 - 5.0); // -90 is straight down
|
||||
float yaw = (float)(random.NextDouble() * 360.0);
|
||||
var ang = new QAngle(pitch, yaw, 0);
|
||||
|
||||
// Velocity: mostly downward, with a little horizontal for realism
|
||||
float downwardSpeed = (float)(random.NextDouble() * 200.0 + 800.0); // 800-1000 units/sec down
|
||||
float horizontalSpeed = (float)(random.NextDouble() * 100.0); // up to 100 units/sec sideways
|
||||
|
||||
// Calculate horizontal direction
|
||||
float horizAngle = (float)(random.NextDouble() * 2 * Math.PI);
|
||||
var horizontal = new Vector(
|
||||
(float)Math.Cos(horizAngle) * horizontalSpeed,
|
||||
(float)Math.Sin(horizAngle) * horizontalSpeed,
|
||||
0
|
||||
);
|
||||
|
||||
var velocity = new Vector(
|
||||
horizontal.X,
|
||||
horizontal.Y,
|
||||
-downwardSpeed
|
||||
);
|
||||
|
||||
var createdGrenade = GrenadeSpawner.CreateGrenade(spawnPos, ang, velocity, player.PlayerPawn.Value);
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class ArmBreaker(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 6;
|
||||
public override string Name => "Arms Breaker";
|
||||
|
||||
public override string Description => $"You have 25% chance to disarm victim on hit.";
|
||||
|
||||
public override char ChatColor => ChatColors.LightBlue;
|
||||
|
||||
private readonly Random _random = new();
|
||||
|
||||
public override string HtmlColor => "#D6E6FF";
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (
|
||||
!Ensure.Player(victim).IsValid().IsAlive().Check() ||
|
||||
!Ensure.Player(attacker).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 1 player in the opposing team
|
||||
var attackerTeam = attacker!.Team;
|
||||
var enemyCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator && p.Team != attackerTeam);
|
||||
if (enemyCount < 1)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (_random.NextDouble() <= 0.25)
|
||||
{
|
||||
var weaponServices = victim!.PlayerPawn?.Value?.WeaponServices;
|
||||
if (weaponServices?.MyWeapons == null) return HookResult.Continue;
|
||||
|
||||
foreach (var weapon in weaponServices.MyWeapons)
|
||||
{
|
||||
var weaponName = weapon.Value?.DesignerName;
|
||||
if (weaponName != null && !weaponName.Contains("weapon_knife") && !weaponName.Contains("weapon_c4"))
|
||||
{
|
||||
victim.DropActiveWeapon();
|
||||
}
|
||||
}
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace Cs2SuperPowers.Powers.Assign;
|
||||
|
||||
public interface IPowerAssignLogic
|
||||
{
|
||||
void Initialize(BasePlugin pluginRef, IReadOnlyCollection<ISuperPower> powers);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers.Assign;
|
||||
|
||||
public class RandomAssignLogic : IPowerAssignLogic
|
||||
{
|
||||
// Configuration for special rounds
|
||||
private const double SpecialRoundChance = 0.25;
|
||||
|
||||
// Powers that can be assigned during special rounds (everyone gets the same power)
|
||||
private readonly string[] SpecialRoundPowers =
|
||||
{
|
||||
"Go Fast", "Grenade Launcher", "Air Strike", "Not So Decoy", "I Am Chicken!"
|
||||
};
|
||||
|
||||
private readonly List<Delegate> _registeredHandlers = new();
|
||||
|
||||
public void Initialize(BasePlugin pluginRef, IReadOnlyCollection<ISuperPower> powers)
|
||||
{
|
||||
BasePlugin.GameEventHandler<EventPlayerDisconnect> disconnectHandler = (@event, _) =>
|
||||
{
|
||||
var player = @event.Userid;
|
||||
|
||||
if (!Ensure.Player(player).IsValid().Check()) return HookResult.Continue;
|
||||
PlayerPowers.Instance.RemovePower(player!);
|
||||
|
||||
return HookResult.Continue;
|
||||
};
|
||||
_registeredHandlers.Add(disconnectHandler);
|
||||
pluginRef.RegisterEventHandler(disconnectHandler);
|
||||
|
||||
|
||||
BasePlugin.GameEventHandler<EventRoundStart> roundStartHandler = (_, _) =>
|
||||
{
|
||||
var gameRules = Utilities
|
||||
.FindAllEntitiesByDesignerName<CBaseEntity>("cs_gamerules")
|
||||
.Single()
|
||||
.As<CCSGameRulesProxy>()
|
||||
.GameRules!;
|
||||
|
||||
if (gameRules.WarmupPeriod)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var activePlayerCount = Utilities.GetPlayers().Count(player =>
|
||||
player.IsValid && player.Team != CsTeam.None && player.Team != CsTeam.Spectator);
|
||||
var randomAssignablePowers = powers
|
||||
.Where(p => p.CanBeRandomlyAssigned && p.MinimumPlayersRequired <= activePlayerCount)
|
||||
.ToArray();
|
||||
if (randomAssignablePowers.Length == 0)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var random = new Random();
|
||||
var isSpecialRound = random.NextDouble() < SpecialRoundChance;
|
||||
|
||||
if (isSpecialRound)
|
||||
{
|
||||
// Special round: everyone gets the same power
|
||||
var specialRoundCandidates = randomAssignablePowers
|
||||
.Where(p => SpecialRoundPowers.Contains(p.Name))
|
||||
.ToArray();
|
||||
var specialPower = specialRoundCandidates.Length == 0
|
||||
? null
|
||||
: specialRoundCandidates[random.Next(specialRoundCandidates.Length)];
|
||||
|
||||
if (specialPower != null)
|
||||
{
|
||||
// Assign the same power to all players (without individual messages)
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) continue;
|
||||
PlayerPowers.Instance.AddPower(player!, specialPower, showMessages: false);
|
||||
}
|
||||
|
||||
// Announce special round to all players
|
||||
Server.PrintToChatAll($"{ChatColors.Gold}SPECIAL ROUND!{ChatColors.Default}");
|
||||
Server.PrintToChatAll($"{ChatColors.Yellow}Everyone has the same power: {ChatColors.Default}{specialPower.ChatColor}{specialPower.Name}{ChatColors.Default}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to normal assignment if special power not found
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) continue;
|
||||
var randomPower = randomAssignablePowers.OrderBy(_ => random.Next()).First();
|
||||
PlayerPowers.Instance.AddPower(player!, randomPower, showMessages: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal round: random powers for each player
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (player.Team == CsTeam.Spectator || player.Team == CsTeam.None) continue;
|
||||
var randomPower = randomAssignablePowers.OrderBy(_ => random.Next()).First();
|
||||
PlayerPowers.Instance.AddPower(player!, randomPower, showMessages: true);
|
||||
}
|
||||
|
||||
// Show individual powers to each team
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
var team = player.Team;
|
||||
|
||||
player.PrintToChat($"Your {ChatColors.Blue}Team {ChatColors.Default}powers are:");
|
||||
PlayerPowers.Instance
|
||||
.GetAssignedPowers()
|
||||
.Where(x => x.Player.Team == team)
|
||||
.Select(x => {
|
||||
player.PrintToChat($"{ChatColors.DarkRed} ► {x.Player.PlayerName} {ChatColors.Default}: {x.Power.ChatColor}{x.Power.Name}");
|
||||
return x;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
};
|
||||
_registeredHandlers.Add(roundStartHandler);
|
||||
pluginRef.RegisterEventHandler(roundStartHandler, HookMode.Pre);
|
||||
|
||||
BasePlugin.GameEventHandler<EventPlayerDeath> deathHandler = (@event, _) =>
|
||||
{
|
||||
var victim = @event.Userid;
|
||||
var attacker = @event.Attacker;
|
||||
|
||||
if (victim == null || attacker == null || victim == attacker) return HookResult.Continue;
|
||||
|
||||
if (!PlayerPowers.Instance.TryGetValue(attacker, out var attackerPower))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
victim.PrintToChat($"You have been killed by {ChatColors.DarkRed}{attacker.PlayerName}{ChatColors.Lime}");
|
||||
victim.PrintToChat($"His/her super power was: {attackerPower!.ChatColor}{attackerPower.Name}");
|
||||
|
||||
return HookResult.Continue;
|
||||
};
|
||||
_registeredHandlers.Add(deathHandler);
|
||||
pluginRef.RegisterEventHandler(deathHandler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Text;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Events;
|
||||
using Cs2SuperPowers.Players;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public abstract class BasePower(IPlayerHud playerHud) : ISuperPower
|
||||
{
|
||||
private readonly HashSet<ulong> _playerIds = new();
|
||||
|
||||
protected BasePlugin? Plugin { get; private set; }
|
||||
|
||||
public abstract int Id { get; }
|
||||
public abstract string Name { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract char ChatColor { get; }
|
||||
public abstract string HtmlColor { get; }
|
||||
public virtual bool CanBeRandomlyAssigned => true;
|
||||
public virtual int MinimumPlayersRequired => 0;
|
||||
|
||||
public void Initialize(BasePlugin pluginRef)
|
||||
{
|
||||
Plugin = pluginRef;
|
||||
OnInitialize();
|
||||
}
|
||||
|
||||
protected abstract void OnInitialize();
|
||||
|
||||
public virtual void AssignToPlayer(CCSPlayerController player, bool showMessages)
|
||||
{
|
||||
_playerIds.Add(player.SteamID);
|
||||
|
||||
if (showMessages)
|
||||
{
|
||||
player.PrintToChat($"You have been granted the power of {ChatColor}{Name}");
|
||||
player.PrintToChat(Description);
|
||||
|
||||
var centerMessageBuilder = new StringBuilder();
|
||||
centerMessageBuilder.Append("<font class='fontSize-l' class='fontWeight-Bold' color='#FFFFFF'>Your superpower:</font> <br>");
|
||||
centerMessageBuilder.Append($"<font class='fontSize-l' class='fontWeight-Bold' color='{HtmlColor}'>{Name}</font> <br>");
|
||||
centerMessageBuilder.Append($"<font class='fontSize-s' class='fontWeight-Normal' color='#FFFFFF'>{Description}</font> <br>");
|
||||
playerHud.ShowMessage(player, centerMessageBuilder.ToString(), TimeSpan.FromSeconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void UnassignFromPlayer(CCSPlayerController player)
|
||||
{
|
||||
_playerIds.Remove(player.SteamID);
|
||||
}
|
||||
|
||||
private readonly List<Delegate> _registeredDelegates = new();
|
||||
|
||||
protected void RegisterEventHandler<T>(BasePlugin.GameEventHandler<T> handler, HookMode hookMode = HookMode.Post)
|
||||
where T : GameEvent
|
||||
{
|
||||
_registeredDelegates.Add(handler);
|
||||
Plugin!.RegisterEventHandler(handler, hookMode);
|
||||
}
|
||||
|
||||
protected void RegisterListener<T>(T handler) where T : Delegate
|
||||
{
|
||||
_registeredDelegates.Add(handler);
|
||||
Plugin!.RegisterListener(handler);
|
||||
}
|
||||
|
||||
protected void AddTimer(float seconds, Action callback, bool repeat = false)
|
||||
{
|
||||
if (repeat)
|
||||
{
|
||||
_registeredDelegates.Add(callback);
|
||||
Plugin!.AddTimer(seconds, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
Action? wrappedCallback = null;
|
||||
wrappedCallback = () =>
|
||||
{
|
||||
callback();
|
||||
if (wrappedCallback != null)
|
||||
{
|
||||
_registeredDelegates.Remove(wrappedCallback);
|
||||
}
|
||||
};
|
||||
_registeredDelegates.Add(wrappedCallback);
|
||||
Plugin!.AddTimer(seconds, wrappedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
protected void NextFrame(Action callback)
|
||||
{
|
||||
Action? wrappedCallback = null;
|
||||
wrappedCallback = () =>
|
||||
{
|
||||
callback();
|
||||
if (wrappedCallback != null)
|
||||
{
|
||||
_registeredDelegates.Remove(wrappedCallback);
|
||||
}
|
||||
};
|
||||
_registeredDelegates.Add(wrappedCallback);
|
||||
Server.NextFrame(wrappedCallback);
|
||||
}
|
||||
|
||||
protected bool IsAssignedTo(CCSPlayerController player)
|
||||
{
|
||||
return _playerIds.Contains(player.SteamID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class BlindingLight(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 22;
|
||||
public override string Name => "Blinding Light";
|
||||
|
||||
public override string Description => $"Inflict a blinding flash of light that disorients enemies. Press E (Use) to activate the power.";
|
||||
|
||||
public override char ChatColor => ChatColors.BlueGrey;
|
||||
|
||||
public override string HtmlColor => "#cdcece";
|
||||
|
||||
private readonly List<BlindingLightInfo> _registrations = new();
|
||||
|
||||
private const int SecondsBetweenActivations = 8;
|
||||
|
||||
public override void AssignToPlayer(CCSPlayerController player, bool showMessages)
|
||||
{
|
||||
base.AssignToPlayer(player, showMessages);
|
||||
if (!_registrations.Any(r => r.Player == player))
|
||||
{
|
||||
_registrations.Add(new BlindingLightInfo(player, DateTime.MinValue));
|
||||
}
|
||||
Plugin!.Logger.LogInformation("BlindingLight assigned to {PlayerName} ({SteamID})", player.PlayerName, player.SteamID);
|
||||
}
|
||||
|
||||
public override void UnassignFromPlayer(CCSPlayerController player)
|
||||
{
|
||||
base.UnassignFromPlayer(player);
|
||||
var registration = _registrations.FirstOrDefault(r => r.Player == player);
|
||||
if (registration != null)
|
||||
{
|
||||
_registrations.Remove(registration);
|
||||
}
|
||||
Plugin!.Logger.LogInformation("BlindingLight unassigned from {PlayerName} ({SteamID})", player.PlayerName, player.SteamID);
|
||||
}
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterListener<Listeners.OnTick>(OnTick);
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
|
||||
Plugin!.Logger.LogInformation("BlindingLight initialized!");
|
||||
}
|
||||
|
||||
private HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
var registration = _registrations
|
||||
.FirstOrDefault(r => r.Player == player);
|
||||
if (registration != null)
|
||||
{
|
||||
registration.WasUsePressedLastTick = false;
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
_registrations.Clear();
|
||||
foreach (var player in PlayerPowers.Instance.GetPlayersWithPower(this))
|
||||
{
|
||||
_registrations.Add(new BlindingLightInfo(player, DateTime.MinValue));
|
||||
}
|
||||
Plugin!.Logger.LogInformation("BlindingLight: Round start, registered {Count} players", _registrations.Count);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void OnTick()
|
||||
{
|
||||
foreach (var registration in _registrations.ToList())
|
||||
{
|
||||
if (!Ensure.Player(registration.Player).IsValid().Check())
|
||||
{
|
||||
_registrations.Remove(registration);
|
||||
continue;
|
||||
}
|
||||
|
||||
var usePressed = registration.Player.Buttons.HasFlag(PlayerButtons.Use);
|
||||
var canUsePower = (DateTime.UtcNow - registration.LastUse).TotalSeconds > SecondsBetweenActivations;
|
||||
|
||||
if (usePressed && !registration.WasUsePressedLastTick)
|
||||
{
|
||||
Plugin!.Logger.LogInformation("BlindingLight: {PlayerName} pressed use! CanUse: {CanUse}", registration.Player.PlayerName, canUsePower);
|
||||
if (canUsePower)
|
||||
{
|
||||
ActivatePower(registration);
|
||||
}
|
||||
else
|
||||
{
|
||||
var secondsBeforeNextUse =
|
||||
SecondsBetweenActivations - (int)(DateTime.UtcNow - registration.LastUse).TotalSeconds;
|
||||
registration.Player
|
||||
.PrintToCenterAlert($"You need to wait {ChatColors.DarkRed}{secondsBeforeNextUse} {ChatColors.Default}seconds before using the power.");
|
||||
}
|
||||
}
|
||||
|
||||
registration.WasUsePressedLastTick = usePressed;
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivatePower(BlindingLightInfo registration)
|
||||
{
|
||||
// Check for at least 1 player in the opposing team
|
||||
var playerTeam = registration.Player.Team;
|
||||
var enemyCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator && p.Team != playerTeam);
|
||||
Plugin!.Logger.LogInformation("BlindingLight: Activating for {PlayerName}, enemy count: {EnemyCount}", registration.Player.PlayerName, enemyCount);
|
||||
|
||||
if (enemyCount < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var flashProjectile = Utilities.CreateEntityByName<CFlashbangProjectile>("flashbang_projectile");
|
||||
if (flashProjectile == null || !flashProjectile.IsValid)
|
||||
{
|
||||
Plugin!.Logger.LogError("BlindingLight: Failed to create flashbang projectile!");
|
||||
return;
|
||||
}
|
||||
|
||||
var player = registration.Player;
|
||||
var pos = player.PlayerPawn.Value!.AbsOrigin;
|
||||
pos!.Z += 10;
|
||||
|
||||
flashProjectile.TicksAtZeroVelocity = 100;
|
||||
flashProjectile.TeamNum = player.TeamNum;
|
||||
flashProjectile.Teleport(pos, null, new Vector(0, 0, -10));
|
||||
flashProjectile.DispatchSpawn();
|
||||
flashProjectile.AcceptInput("InitializeSpawnFromWorld", player.PlayerPawn.Value, player.PlayerPawn.Value);
|
||||
flashProjectile.DetonateTime = 0;
|
||||
|
||||
Plugin!.Logger.LogInformation("BlindingLight: Flashbang created and detonated!");
|
||||
registration.UpdateUse();
|
||||
}
|
||||
}
|
||||
|
||||
public record BlindingLightInfo(CCSPlayerController Player, DateTime LastUse)
|
||||
{
|
||||
public DateTime LastUse { get; private set; } = LastUse;
|
||||
public bool WasUsePressedLastTick { get; set; } = false;
|
||||
|
||||
public void UpdateUse()
|
||||
{
|
||||
LastUse = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class BulletStorm(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 13;
|
||||
public override string Name => "Bullet Storm";
|
||||
|
||||
public override string Description => $"Gain effectively unlimited reserve ammo so you can keep reloading without ever running dry.";
|
||||
|
||||
public override char ChatColor => ChatColors.Olive;
|
||||
|
||||
public override string HtmlColor => "#0000FF";
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventWeaponFire>(OnWeaponFire);
|
||||
RegisterEventHandler<EventWeaponReload>(OnWeaponReload);
|
||||
}
|
||||
|
||||
private HookResult OnWeaponFire(EventWeaponFire @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
RefillReserveAmmo(player!);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnWeaponReload(EventWeaponReload @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
RefillReserveAmmo(player!);
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private static void RefillReserveAmmo(CCSPlayerController player)
|
||||
{
|
||||
var activeWeaponHandle = player.PlayerPawn.Value?.WeaponServices?.ActiveWeapon;
|
||||
if (activeWeaponHandle?.Value != null)
|
||||
{
|
||||
activeWeaponHandle.Value.ReserveAmmo[0] = 999;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class DoubleJump(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 28;
|
||||
public override string Name => "Double Jump";
|
||||
public override string Description => "Jump again in mid-air! A classic double jump ability.";
|
||||
public override char ChatColor => ChatColors.Blue;
|
||||
public override string HtmlColor => "#0000FF";
|
||||
|
||||
private readonly Dictionary<CCSPlayerController, bool> _canDoubleJump = new();
|
||||
private readonly Dictionary<CCSPlayerController, bool> _wasOnGround = new();
|
||||
private readonly Dictionary<CCSPlayerController, bool> _requireJumpRelease = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterListener<Listeners.OnTick>(OnTick);
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
_canDoubleJump.Clear();
|
||||
_wasOnGround.Clear();
|
||||
_requireJumpRelease.Clear();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
public override void AssignToPlayer(CCSPlayerController player, bool showMessages)
|
||||
{
|
||||
base.AssignToPlayer(player, showMessages);
|
||||
_canDoubleJump[player] = true;
|
||||
_wasOnGround[player] = true;
|
||||
_requireJumpRelease[player] = false;
|
||||
}
|
||||
|
||||
public override void UnassignFromPlayer(CCSPlayerController player)
|
||||
{
|
||||
base.UnassignFromPlayer(player);
|
||||
_canDoubleJump.Remove(player);
|
||||
_wasOnGround.Remove(player);
|
||||
_requireJumpRelease.Remove(player);
|
||||
}
|
||||
|
||||
private void OnTick()
|
||||
{
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!player.IsValid || !player.PawnIsAlive)
|
||||
continue;
|
||||
|
||||
if (!IsAssignedTo(player))
|
||||
continue;
|
||||
|
||||
var pawn = player.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid)
|
||||
continue;
|
||||
|
||||
var isOnGround = pawn.GroundEntity.IsValid;
|
||||
var jumpPressed = player.Buttons.HasFlag(PlayerButtons.Jump);
|
||||
var wasOnGroundLastTick = _wasOnGround.GetValueOrDefault(player, true);
|
||||
|
||||
if (isOnGround)
|
||||
{
|
||||
_canDoubleJump[player] = true;
|
||||
_requireJumpRelease[player] = false;
|
||||
}
|
||||
else if (wasOnGroundLastTick)
|
||||
{
|
||||
_canDoubleJump[player] = true;
|
||||
_requireJumpRelease[player] = jumpPressed;
|
||||
}
|
||||
|
||||
if (!isOnGround && !jumpPressed)
|
||||
{
|
||||
_requireJumpRelease[player] = false;
|
||||
}
|
||||
|
||||
if (!isOnGround &&
|
||||
jumpPressed &&
|
||||
!_requireJumpRelease.GetValueOrDefault(player, false) &&
|
||||
_canDoubleJump.GetValueOrDefault(player, false))
|
||||
{
|
||||
var velocity = pawn.AbsVelocity;
|
||||
velocity.Z = Math.Max(velocity.Z, 300.0f);
|
||||
pawn.Teleport(pawn.AbsOrigin, null, velocity);
|
||||
_canDoubleJump[player] = false;
|
||||
_requireJumpRelease[player] = true;
|
||||
}
|
||||
|
||||
_wasOnGround[player] = isOnGround;
|
||||
}
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var victim = @event.Userid;
|
||||
if (!Ensure.Player(victim).IsValid().Check() || !IsAssignedTo(victim!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
bool isWorldDamage = !Ensure.Player(@event.Attacker).IsValid().Check();
|
||||
bool hasNoWeapon = string.IsNullOrEmpty(@event.Weapon);
|
||||
|
||||
if (!isWorldDamage || !hasNoWeapon)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var pawn = victim!.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
pawn.Health = Math.Min(pawn.MaxHealth, pawn.Health + @event.DmgHealth);
|
||||
return HookResult.Stop;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class DroppedNades(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
private readonly Dictionary<CCSPlayerController, DateTime> _lastUse = new();
|
||||
|
||||
public override int Id => 34;
|
||||
public override string Name => "Dropped Nades";
|
||||
public override string Description => "MAG-7 shots drop 5 grenades on the ground. This power is force-only.";
|
||||
public override char ChatColor => ChatColors.DarkRed;
|
||||
public override string HtmlColor => "#AA0000";
|
||||
public override bool CanBeRandomlyAssigned => false;
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventWeaponFire>(OnWeaponFire);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
_lastUse.Clear();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnWeaponFire(EventWeaponFire @event, GameEventInfo info)
|
||||
{
|
||||
var controller = @event.Userid;
|
||||
if (!Ensure.Player(controller).IsValid().Check() || !IsAssignedTo(controller!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var pawn = controller!.PlayerPawn.Value;
|
||||
var weapon = pawn?.WeaponServices?.ActiveWeapon.Value;
|
||||
if (pawn == null || !pawn.IsValid || weapon == null || weapon.DesignerName != "weapon_mag7")
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
if (!GlobalConfig.NoDelayEnabled &&
|
||||
_lastUse.TryGetValue(controller, out var lastUseTime) &&
|
||||
(now - lastUseTime).TotalSeconds < 0.35)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
_lastUse[controller] = now;
|
||||
|
||||
var origin = pawn.AbsOrigin;
|
||||
if (origin == null)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
int grenadeCount = GlobalConfig.GetDroppedNadesCount(5);
|
||||
var random = new Random();
|
||||
var viewAngle = pawn.V_angle;
|
||||
float baseYawRad = viewAngle.Y * MathF.PI / 180f;
|
||||
var forward = new Vector(MathF.Cos(baseYawRad), MathF.Sin(baseYawRad), 0.0f);
|
||||
var right = new Vector(-forward.Y, forward.X, 0.0f);
|
||||
|
||||
int droppedGrenades = 0;
|
||||
int attempts = 0;
|
||||
int maxAttempts = grenadeCount + 8;
|
||||
|
||||
while (droppedGrenades < grenadeCount && attempts < maxAttempts)
|
||||
{
|
||||
int i = attempts;
|
||||
attempts++;
|
||||
float lateralOffset = (float)(random.NextDouble() * 18.0 - 9.0);
|
||||
float forwardOffset = (float)(random.NextDouble() * 12.0 + 16.0);
|
||||
var spawnPos = new Vector(
|
||||
origin.X + forward.X * forwardOffset + right.X * lateralOffset,
|
||||
origin.Y + forward.Y * forwardOffset + right.Y * lateralOffset,
|
||||
origin.Z + 10.0f
|
||||
);
|
||||
|
||||
float yaw = viewAngle.Y + (float)(random.NextDouble() * 16.0 - 8.0);
|
||||
var angle = new QAngle(8.0f, yaw, 0.0f);
|
||||
var velocity = new Vector(
|
||||
forward.X * (float)(random.NextDouble() * 40.0 + 45.0) + right.X * (float)(random.NextDouble() * 16.0 - 8.0),
|
||||
forward.Y * (float)(random.NextDouble() * 40.0 + 45.0) + right.Y * (float)(random.NextDouble() * 16.0 - 8.0),
|
||||
(float)(random.NextDouble() * 12.0 + 8.0)
|
||||
);
|
||||
|
||||
var grenadeWeapon = controller.GiveNamedItem<CHEGrenade>("weapon_hegrenade");
|
||||
if (grenadeWeapon == null || !grenadeWeapon.IsValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (grenadeWeapon.IsHeldByPlayer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
grenadeWeapon.Teleport(spawnPos, angle, velocity);
|
||||
droppedGrenades++;
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Utils;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class ExplosiveBullets(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 30;
|
||||
public override string Name => "Explosive Bullets";
|
||||
public override string Description => "Every bullet hit causes a small explosion.";
|
||||
public override char ChatColor => ChatColors.Orange;
|
||||
public override string HtmlColor => "#FF4500";
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (!Ensure.Player(attacker).IsValid().Check() ||
|
||||
!Ensure.Player(victim).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(@event.Weapon) || @event.Weapon.Contains("grenade", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var attackerPawn = attacker!.PlayerPawn.Value;
|
||||
var victimPawn = victim!.PlayerPawn.Value;
|
||||
if (victimPawn == null || attackerPawn == null)
|
||||
return HookResult.Continue;
|
||||
|
||||
var pos = victimPawn.AbsOrigin;
|
||||
if (pos == null)
|
||||
return HookResult.Continue;
|
||||
|
||||
pos.Z += 20;
|
||||
|
||||
// Create explosion with a small grenade at victim's position
|
||||
var ang = new QAngle(0, 0, 0);
|
||||
var velocity = new Vector(0, 0, 0);
|
||||
|
||||
var grenade = GrenadeSpawner.CreateGrenade(pos, ang, velocity, attackerPawn);
|
||||
|
||||
// Detonate it immediately
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
if (grenade != null && grenade.IsValid)
|
||||
{
|
||||
grenade.AcceptInput("InitializeSpawnFromWorld", attackerPawn, attackerPawn);
|
||||
grenade.DetonateTime = 0;
|
||||
}
|
||||
});
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class FinalMission(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 8;
|
||||
public override string Name => "Final Mission";
|
||||
|
||||
public override string Description => $"Upon death, unleash a powerful explosion that eliminates all players within a specified radius, turning your sacrifice into a final act of devastation.";
|
||||
|
||||
public override char ChatColor => ChatColors.Purple;
|
||||
|
||||
public override string HtmlColor => "#F5CB42";
|
||||
public override int MinimumPlayersRequired => 4;
|
||||
|
||||
private const float ExplosionRadius = 500.0f;
|
||||
private const int ExplosionDamage = 999;
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 4 valid (non-spectator, non-none) players total
|
||||
var validPlayerCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator);
|
||||
if (validPlayerCount < 4)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var playerPawn = player!.PlayerPawn.Value;
|
||||
var origin = playerPawn?.AbsOrigin;
|
||||
if (playerPawn == null || origin == null)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var explosionCenter = new Vector(origin.X, origin.Y, origin.Z);
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
foreach (var target in Utilities.GetPlayers())
|
||||
{
|
||||
if (!Ensure.Player(target).IsValid().IsAlive().Check())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetPawn = target.PlayerPawn.Value;
|
||||
var targetOrigin = targetPawn?.AbsOrigin;
|
||||
if (targetPawn == null || targetOrigin == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var deltaX = targetOrigin.X - explosionCenter.X;
|
||||
var deltaY = targetOrigin.Y - explosionCenter.Y;
|
||||
var deltaZ = targetOrigin.Z - explosionCenter.Z;
|
||||
var distanceSquared = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distanceSquared > ExplosionRadius * ExplosionRadius)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
targetPawn.Health = Math.Max(0, targetPawn.Health - ExplosionDamage);
|
||||
Utilities.SetStateChanged(targetPawn, "CBaseEntity", "m_iHealth");
|
||||
}
|
||||
});
|
||||
|
||||
Server.PrintToChatAll($"{ChatColors.DarkRed}► {ChatColors.Green} {player.PlayerName}: {ChatColors.Lime}explodes!!!");
|
||||
|
||||
var fileNames = new[] { "radiobotfallback01", "radiobotfallback02", "radiobotfallback04" };
|
||||
AddTimer(0.1f, () =>
|
||||
{
|
||||
var randomFile = fileNames[new Random().Next(fileNames.Length)];
|
||||
player.ExecuteClientCommand($"play vo/agents/balkan/{randomFile}.vsnd");
|
||||
});
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class FlashKing(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 17;
|
||||
public override string Name => "Flash King";
|
||||
|
||||
public override string Description => $"With this superpower, your flashbangs blind enemies for three times as long.";
|
||||
|
||||
public override char ChatColor => ChatColors.LightBlue;
|
||||
|
||||
public override string HtmlColor => "#D6E6FF";
|
||||
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventPlayerBlind>(OnPlayerBlinded);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().IsAlive().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NextFrame(() =>
|
||||
{
|
||||
player.GiveNamedItem("weapon_flashbang");
|
||||
player.GiveNamedItem("weapon_flashbang");
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnPlayerBlinded(EventPlayerBlind @event, GameEventInfo info)
|
||||
{
|
||||
var victim = @event.Userid;
|
||||
var attacker = @event.Attacker;
|
||||
|
||||
if (!Ensure.Player(victim).IsValid().Check() || !IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var playerPawn = victim!.PlayerPawn.Value;
|
||||
|
||||
if (attacker != victim && attacker!.TeamNum != playerPawn!.TeamNum)
|
||||
{
|
||||
playerPawn!.FlashDuration = playerPawn!.FlashDuration * 3;
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class FlashProof(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 3;
|
||||
public override string Name => "Flash Proof";
|
||||
|
||||
public override string Description => $"You cannot be blind by flashes. You will gain new flash every time your flash explodes.";
|
||||
|
||||
public override char ChatColor => ChatColors.LightBlue;
|
||||
|
||||
public override string HtmlColor => "#D6E6FF";
|
||||
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventPlayerBlind>(OnPlayerBlinded);
|
||||
RegisterEventHandler<EventFlashbangDetonate>(OnFlashbangDetonate);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().IsAlive().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NextFrame(() =>
|
||||
{
|
||||
player.GiveNamedItem("weapon_flashbang");
|
||||
player.GiveNamedItem("weapon_flashbang");
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnPlayerBlinded(EventPlayerBlind @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var playerPawn = player!.PlayerPawn.Value;
|
||||
playerPawn!.FlashDuration = 0.0f;
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnFlashbangDetonate(EventFlashbangDetonate @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
|
||||
if (!Ensure.Player(player).IsValid().IsAlive().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
player!.GiveNamedItem("weapon_flashbang");
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class Ghost(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 7;
|
||||
public override string Name => "The Ghost";
|
||||
|
||||
public override string Description => $"You are invisible to other players - but will show up for few seconds after you will take/give damage.";
|
||||
|
||||
public override char ChatColor => ChatColors.White;
|
||||
|
||||
private readonly Random _random = new Random();
|
||||
|
||||
public override string HtmlColor => "#FFFFFF";
|
||||
public override int MinimumPlayersRequired => 4;
|
||||
|
||||
private readonly Dictionary<ulong, int> _visibilityTokens = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
RegisterEventHandler<EventRoundEnd>(OnRoundEnd);
|
||||
}
|
||||
|
||||
private HookResult OnRoundEnd(EventRoundEnd @event, GameEventInfo info)
|
||||
{
|
||||
foreach (var player in PlayerPowers.Instance.GetPlayersWithPower(this))
|
||||
{
|
||||
_visibilityTokens[player.SteamID] = _visibilityTokens.GetValueOrDefault(player.SteamID) + 1;
|
||||
NextFrame(() =>
|
||||
{
|
||||
SetPlayerVisibility(player, true);
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
var validPlayersWithPower = PlayerPowers.Instance
|
||||
.GetPlayersWithPower(this)
|
||||
.Where(player => Ensure.Player(player).IsValid().Check());
|
||||
|
||||
foreach (var player in validPlayersWithPower)
|
||||
{
|
||||
NextFrame(() =>
|
||||
{
|
||||
SetPlayerVisibility(player, false);
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
public override void UnassignFromPlayer(CCSPlayerController player)
|
||||
{
|
||||
base.UnassignFromPlayer(player);
|
||||
NextFrame(() =>
|
||||
{
|
||||
SetPlayerVisibility(player, true);
|
||||
});
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (!Ensure.Player(victim).IsValid().IsAlive().Check() ||
|
||||
!Ensure.Player(attacker).IsValid().Check())
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 4 valid (non-spectator, non-none) players total
|
||||
var validPlayerCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator);
|
||||
if (validPlayerCount < 4)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (IsAssignedTo(victim!))
|
||||
{
|
||||
RevealGhostTemporarily(victim!);
|
||||
}
|
||||
else if (IsAssignedTo(attacker!))
|
||||
{
|
||||
RevealGhostTemporarily(attacker!);
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void RevealGhostTemporarily(CCSPlayerController player)
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().IsAlive().Check())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var token = _visibilityTokens.GetValueOrDefault(player.SteamID) + 1;
|
||||
_visibilityTokens[player.SteamID] = token;
|
||||
SetPlayerVisibility(player, true);
|
||||
|
||||
AddTimer(_random.Next(1, 5), () =>
|
||||
{
|
||||
if (!IsAssignedTo(player) || _visibilityTokens.GetValueOrDefault(player.SteamID) != token)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetPlayerVisibility(player, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void SetPlayerVisibility(CCSPlayerController player, bool visible)
|
||||
{
|
||||
if (!IsAssignedTo(player) && !visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var playerPawn = player.PlayerPawn.Value;
|
||||
if (playerPawn != null)
|
||||
{
|
||||
var color = visible
|
||||
? Color.FromArgb(255, 255, 255, 255)
|
||||
: Color.FromArgb(0, 255, 255, 255);
|
||||
var shadowStrength = visible ? 1.0f : 0.0f;
|
||||
|
||||
playerPawn.Render = color;
|
||||
playerPawn.ShadowStrength = shadowStrength;
|
||||
Utilities.SetStateChanged(playerPawn, "CBaseModelEntity", "m_clrRender");
|
||||
|
||||
var activeWeapon = playerPawn.WeaponServices?.ActiveWeapon.Value;
|
||||
if (activeWeapon != null && activeWeapon.IsValid)
|
||||
{
|
||||
activeWeapon.Render = color;
|
||||
activeWeapon.ShadowStrength = shadowStrength;
|
||||
Utilities.SetStateChanged(activeWeapon, "CBaseModelEntity", "m_clrRender");
|
||||
}
|
||||
|
||||
var skeleton = playerPawn.CBodyComponent?.SceneNode?.GetSkeletonInstance();
|
||||
if (skeleton != null)
|
||||
{
|
||||
skeleton.MaterialGroup.Value = visible ? (uint)0 : 1;
|
||||
}
|
||||
|
||||
var myWeapons = playerPawn.WeaponServices?.MyWeapons;
|
||||
if (myWeapons != null)
|
||||
{
|
||||
foreach (var gun in myWeapons)
|
||||
{
|
||||
var weapon = gun.Value;
|
||||
if (weapon != null)
|
||||
{
|
||||
weapon.Render = color;
|
||||
weapon.ShadowStrength = shadowStrength;
|
||||
Utilities.SetStateChanged(weapon, "CBaseModelEntity", "m_clrRender");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var myWearables = playerPawn.MyWearables;
|
||||
if (myWearables != null)
|
||||
{
|
||||
foreach (var wearableHandle in myWearables)
|
||||
{
|
||||
var wearable = wearableHandle.Value;
|
||||
if (wearable == null || !wearable.IsValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
wearable.Render = color;
|
||||
wearable.ShadowStrength = shadowStrength;
|
||||
Utilities.SetStateChanged(wearable, "CBaseModelEntity", "m_clrRender");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class GoFast(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 19;
|
||||
public override string Name => "Go Fast";
|
||||
|
||||
public override string Description => $"Your movement speed is increased by 50%";
|
||||
|
||||
public override char ChatColor => ChatColors.Green;
|
||||
|
||||
public override string HtmlColor => "#009905";
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
foreach (var player in PlayerPowers.Instance.GetPlayersWithPower(this))
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().Check())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NextFrame(() =>
|
||||
{
|
||||
player.SetNewSpeed(player.GetSpeed() * 1.5f);
|
||||
player.GravityScale += 5;
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
using Cs2SuperPowers.Utils;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class GrenadeLauncher(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 11;
|
||||
public override string Name => "Grenade Launcher";
|
||||
|
||||
public override string Description => "Every shot from your pistol launches a grenade (0.5s cooldown).";
|
||||
|
||||
public override char ChatColor => ChatColors.Red;
|
||||
|
||||
public override string HtmlColor => "#FF0000";
|
||||
|
||||
private readonly Dictionary<CCSPlayerController, DateTime> _lastUse = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventWeaponFire>(OnWeaponFire);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
_lastUse.Clear();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnWeaponFire(EventWeaponFire @event, GameEventInfo info)
|
||||
{
|
||||
var controller = @event.Userid;
|
||||
if (!Ensure.Player(controller).IsValid().Check() || !IsAssignedTo(controller!))
|
||||
return HookResult.Continue;
|
||||
|
||||
// Only pistols
|
||||
var weapon = controller!.PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value;
|
||||
if (weapon == null)
|
||||
return HookResult.Continue;
|
||||
|
||||
// List of pistol weapon names (classnames)
|
||||
var pistolClassNames = new[]
|
||||
{
|
||||
"weapon_glock", "weapon_usp_silencer", "weapon_hkp2000", "weapon_p250",
|
||||
"weapon_fiveseven", "weapon_tec9", "weapon_cz75a", "weapon_deagle",
|
||||
"weapon_revolver", "weapon_elite"
|
||||
};
|
||||
|
||||
if (!pistolClassNames.Contains(weapon.DesignerName))
|
||||
return HookResult.Continue;
|
||||
|
||||
// Check cooldown (skip if nodelay is enabled)
|
||||
var now = DateTime.UtcNow;
|
||||
if (!GlobalConfig.NoDelayEnabled && _lastUse.TryGetValue(controller!, out var lastUseTime) && (now - lastUseTime).TotalSeconds < 0.5)
|
||||
return HookResult.Continue;
|
||||
_lastUse[controller!] = now;
|
||||
|
||||
var pawn = controller!.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid)
|
||||
return HookResult.Continue;
|
||||
|
||||
var pos = pawn.AbsOrigin;
|
||||
if (pos == null) return HookResult.Continue;
|
||||
pos = new Vector(pos.X, pos.Y, pos.Z + 64f);
|
||||
|
||||
// Forward vector from the player's view angles
|
||||
var ang = pawn.V_angle;
|
||||
float yaw = ang.Y * MathF.PI / 180f;
|
||||
float pitch = ang.X * MathF.PI / 180f;
|
||||
var forward = new Vector(
|
||||
MathF.Cos(yaw) * MathF.Cos(pitch),
|
||||
MathF.Sin(yaw) * MathF.Cos(pitch),
|
||||
-MathF.Sin(pitch)
|
||||
);
|
||||
|
||||
// Spawn farther in front of the player so the grenade does not collide with the shooter.
|
||||
const float spawnAhead = 72f;
|
||||
pos = new Vector(
|
||||
pos.X + forward.X * spawnAhead,
|
||||
pos.Y + forward.Y * spawnAhead,
|
||||
pos.Z + forward.Z * 24f
|
||||
);
|
||||
|
||||
const float speed = 1100f;
|
||||
|
||||
int grenadeCount = GlobalConfig.GetGrenadeLauncherCount(1);
|
||||
for (int i = 0; i < grenadeCount; i++)
|
||||
{
|
||||
// Add slight random spread for multiple grenades
|
||||
var random = new Random();
|
||||
var spreadAng = new QAngle(
|
||||
ang.X + (float)(random.NextDouble() * 10.0 - 5.0), // ±5° pitch
|
||||
ang.Y + (float)(random.NextDouble() * 10.0 - 5.0), // ±5° yaw
|
||||
ang.Z
|
||||
);
|
||||
|
||||
float spreadYaw = spreadAng.Y * MathF.PI / 180f;
|
||||
float spreadPitch = spreadAng.X * MathF.PI / 180f;
|
||||
var spreadForward = new Vector(
|
||||
MathF.Cos(spreadYaw) * MathF.Cos(spreadPitch),
|
||||
MathF.Sin(spreadYaw) * MathF.Cos(spreadPitch),
|
||||
-MathF.Sin(spreadPitch)
|
||||
);
|
||||
|
||||
var velocity = new Vector(
|
||||
spreadForward.X * speed,
|
||||
spreadForward.Y * speed,
|
||||
spreadForward.Z * speed + 100f
|
||||
);
|
||||
|
||||
GrenadeSpawner.CreateGrenade(pos, spreadAng, velocity, pawn);
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class IAmChicken(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 10;
|
||||
public override string Name => "I Am Chicken!";
|
||||
|
||||
public override string Description => $"You become Chicken! You're harder to hit but have 30% of HP.";
|
||||
|
||||
public override char ChatColor => ChatColors.Orange;
|
||||
|
||||
public override string HtmlColor => "#FF8B42";
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
var random = new Random();
|
||||
foreach (var player in PlayerPowers.Instance.GetPlayersWithPower(this))
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().Check())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var playerPawn = player.PlayerPawn?.Value;
|
||||
if (playerPawn == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NextFrame(() =>
|
||||
{
|
||||
SetPlayerModel(player, "models/chicken/chicken.vmdl");
|
||||
playerPawn!.CBodyComponent!.SceneNode!.GetSkeletonInstance().MaterialGroup.Value =
|
||||
(uint)random.Next(1, 4);
|
||||
|
||||
playerPawn.MaxHealth = 30;
|
||||
playerPawn.Health = 30;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerPawn", "m_iMaxHealth");
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void SetPlayerModel(CCSPlayerController player, string model)
|
||||
{
|
||||
var pawn = player.PlayerPawn?.Value;
|
||||
if (pawn == null) return;
|
||||
|
||||
NextFrame(() =>
|
||||
{
|
||||
pawn.SetModel(model);
|
||||
var originalRender = pawn.Render;
|
||||
pawn.Render = Color.FromArgb(255, originalRender.R, originalRender.G, originalRender.B);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class IronBody(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 18;
|
||||
public override string Name => "Iron Body";
|
||||
|
||||
public override string Description => $"You can only be killed by headshots.";
|
||||
|
||||
public override char ChatColor => ChatColors.LightPurple;
|
||||
|
||||
public override string HtmlColor => "#8B4513";
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
if (@event.Hitgroup == (int)HitGroup_t.HITGROUP_HEAD)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!Ensure.Player(victim).IsValid().Check() ||
|
||||
!Ensure.Player(attacker).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(victim!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var playerPawn = victim!.PlayerPawn.Value;
|
||||
var newHealth = playerPawn!.Health + @event.DmgHealth;
|
||||
|
||||
if (newHealth > 100)
|
||||
newHealth = 100;
|
||||
|
||||
playerPawn.Health = newHealth;
|
||||
return HookResult.Stop;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class IronHead(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 4;
|
||||
public override string Name => "Iron Head";
|
||||
|
||||
public override string Description => $"You cannot be killed by head shots.";
|
||||
|
||||
public override char ChatColor => ChatColors.LightPurple;
|
||||
|
||||
public override string HtmlColor => "#8B4513";
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
if (@event.Hitgroup != (int)HitGroup_t.HITGROUP_HEAD)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!Ensure.Player(victim).IsValid().Check() ||
|
||||
!Ensure.Player(attacker).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(victim!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var playerPawn = victim!.PlayerPawn.Value;
|
||||
var newHealth = playerPawn!.Health + @event.DmgHealth;
|
||||
|
||||
if (newHealth > 100)
|
||||
newHealth = 100;
|
||||
|
||||
playerPawn.Health = newHealth;
|
||||
return HookResult.Stop;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class Jetpack(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
private const float MaxFuel = 180.0f;
|
||||
private const float FuelBurnPerTick = 0.8f;
|
||||
private const float FuelRechargePerTick = 0.9f;
|
||||
private const float LiftPerTick = 14.0f;
|
||||
private const float MaxVerticalSpeed = 260.0f;
|
||||
private static readonly TimeSpan HudRefreshInterval = TimeSpan.FromMilliseconds(125);
|
||||
|
||||
public override int Id => 33;
|
||||
public override string Name => "Jetpack";
|
||||
public override string Description => "Hold Jump (Space) to fly with a fuel-based jetpack.";
|
||||
public override char ChatColor => ChatColors.Orange;
|
||||
public override string HtmlColor => "#FF8C00";
|
||||
|
||||
private readonly Dictionary<CCSPlayerController, float> _fuelByPlayer = new();
|
||||
private readonly Dictionary<CCSPlayerController, DateTime> _lastHudUpdate = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterListener<Listeners.OnTick>(OnTick);
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
_fuelByPlayer.Clear();
|
||||
_lastHudUpdate.Clear();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
public override void AssignToPlayer(CCSPlayerController player, bool showMessages)
|
||||
{
|
||||
base.AssignToPlayer(player, showMessages);
|
||||
_fuelByPlayer[player] = MaxFuel;
|
||||
_lastHudUpdate.Remove(player);
|
||||
}
|
||||
|
||||
public override void UnassignFromPlayer(CCSPlayerController player)
|
||||
{
|
||||
base.UnassignFromPlayer(player);
|
||||
_fuelByPlayer.Remove(player);
|
||||
_lastHudUpdate.Remove(player);
|
||||
}
|
||||
|
||||
private void OnTick()
|
||||
{
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!player.IsValid || !player.PawnIsAlive)
|
||||
continue;
|
||||
|
||||
if (!IsAssignedTo(player))
|
||||
continue;
|
||||
|
||||
var pawn = player.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid)
|
||||
continue;
|
||||
|
||||
float fuel = _fuelByPlayer.GetValueOrDefault(player, MaxFuel);
|
||||
bool isJumpPressed = player.Buttons.HasFlag(PlayerButtons.Jump);
|
||||
|
||||
if (isJumpPressed && fuel > 0.0f)
|
||||
{
|
||||
var currentVelocity = pawn.AbsVelocity;
|
||||
currentVelocity.Z = Math.Min(currentVelocity.Z + LiftPerTick, MaxVerticalSpeed);
|
||||
pawn.Teleport(pawn.AbsOrigin, null, currentVelocity);
|
||||
fuel = Math.Max(0.0f, fuel - FuelBurnPerTick);
|
||||
}
|
||||
else
|
||||
{
|
||||
fuel = Math.Min(MaxFuel, fuel + FuelRechargePerTick);
|
||||
}
|
||||
|
||||
_fuelByPlayer[player] = fuel;
|
||||
|
||||
if (isJumpPressed)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (!_lastHudUpdate.TryGetValue(player, out var lastHudUpdate) ||
|
||||
now - lastHudUpdate >= HudRefreshInterval)
|
||||
{
|
||||
_lastHudUpdate[player] = now;
|
||||
player.PrintToCenterHtml(BuildFuelMessage(fuel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildFuelMessage(float fuel)
|
||||
{
|
||||
int percent = (int)MathF.Round((fuel / MaxFuel) * 100.0f);
|
||||
int filledBars = (int)MathF.Round((fuel / MaxFuel) * 10.0f);
|
||||
filledBars = Math.Clamp(filledBars, 0, 10);
|
||||
string bar = new string('|', filledBars).PadRight(10, '.');
|
||||
string color = percent > 60 ? "#4CAF50" : percent > 30 ? "#FFC107" : "#F44336";
|
||||
return $"<font color='#FFFFFF'>Jetpack Fuel:</font> <font color='{color}'>{percent}%</font><br><font color='{color}'>{bar}</font>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
using Cs2SuperPowers.Utils;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class NotSoDecoy(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 24;
|
||||
public override string Name => "Not So Decoy";
|
||||
|
||||
public override string Description => "Decoys thrown by you spawn 20 grenades when thrown (5 second cooldown).";
|
||||
|
||||
public override char ChatColor => ChatColors.Red;
|
||||
|
||||
public override string HtmlColor => "#FF0000";
|
||||
|
||||
private readonly Dictionary<CCSPlayerController, DateTime> _lastUse = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventDecoyStarted>(OnDecoyStarted);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
_lastUse.Clear();
|
||||
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().IsAlive().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
player.GiveNamedItem("weapon_decoy");
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnDecoyStarted(EventDecoyStarted @event, GameEventInfo _)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check cooldown (skip if nodelay is enabled)
|
||||
var now = DateTime.UtcNow;
|
||||
DateTime lastUseTime = default;
|
||||
bool isOnCooldown = !GlobalConfig.NoDelayEnabled && _lastUse.TryGetValue(player!, out lastUseTime) && (now - lastUseTime).TotalSeconds < 5;
|
||||
|
||||
// Always give a new decoy, regardless of cooldown!
|
||||
player!.GiveNamedItem("weapon_decoy");
|
||||
|
||||
if (isOnCooldown)
|
||||
{
|
||||
double secondsRemaining = Math.Ceiling(5 - (now - lastUseTime).TotalSeconds);
|
||||
player.PrintToCenterAlert($"Not So Decoy is on cooldown! {ChatColors.DarkRed}{secondsRemaining}{ChatColors.Default}s remaining");
|
||||
return HookResult.Continue;
|
||||
}
|
||||
_lastUse[player!] = now;
|
||||
|
||||
var pos = new Vector(@event.X, @event.Y, @event.Z);
|
||||
|
||||
// Spawn grenades (with boost level) in a loop with random angles and velocities
|
||||
var random = new Random();
|
||||
int grenadeCount = GlobalConfig.GetNotSoDecoyCount(20);
|
||||
for (int i = 0; i < grenadeCount; i++)
|
||||
{
|
||||
// Random pitch between -45 and 45 degrees, yaw between 0 and 360
|
||||
float pitch = (float)(random.NextDouble() * 90.0 - 45.0);
|
||||
float yaw = (float)(random.NextDouble() * 360.0);
|
||||
var ang = new QAngle(pitch, yaw, 0);
|
||||
|
||||
// Random speed between 400 and 900 units
|
||||
float speed = (float)(random.NextDouble() * 500.0 + 400.0);
|
||||
|
||||
// Convert angles to a forward vector
|
||||
float pitchRad = pitch * MathF.PI / 180f;
|
||||
float yawRad = yaw * MathF.PI / 180f;
|
||||
var forward = new Vector(
|
||||
MathF.Cos(yawRad) * MathF.Cos(pitchRad),
|
||||
MathF.Sin(yawRad) * MathF.Cos(pitchRad),
|
||||
-MathF.Sin(pitchRad));
|
||||
|
||||
// Add some random upward velocity for the "jump"
|
||||
float upward = (float)(random.NextDouble() * 200.0 + 200.0);
|
||||
|
||||
var velocity = new Vector(
|
||||
forward.X * speed,
|
||||
forward.Y * speed,
|
||||
forward.Z * speed + upward);
|
||||
|
||||
var createdGrenade = GrenadeSpawner.CreateGrenade(pos, ang, velocity, player.PlayerPawn.Value);
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class OneShot(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
private const double InstantKillChance = 0.25;
|
||||
private readonly Random _random = new();
|
||||
|
||||
public override int Id => 23;
|
||||
public override string Name => "One Shot";
|
||||
|
||||
public override string Description => $"Each damaging hit has a {(int)(InstantKillChance * 100)}% chance to instantly eliminate the enemy.";
|
||||
|
||||
public override char ChatColor => ChatColors.Magenta;
|
||||
|
||||
public override string HtmlColor => "#ff5CD9";
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (!Ensure.Player(attacker).IsValid().IsAlive().Check() ||
|
||||
!Ensure.Player(victim).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 1 player in the opposing team
|
||||
var attackerTeam = attacker!.Team;
|
||||
var enemyCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator && p.Team != attackerTeam);
|
||||
if (enemyCount < 1)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var victimPawn = victim!.PlayerPawn.Value;
|
||||
if (victimPawn == null)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (_random.NextDouble() > InstantKillChance)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
victimPawn.Health = 0;
|
||||
Utilities.SetStateChanged(victimPawn, "CBaseEntity", "m_iHealth");
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class PanicTeleport(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 16;
|
||||
public override string Name => "Panic Teleport";
|
||||
|
||||
public override string Description => $"Save a single location with the use key, then teleport back to that position whenever you press the use key again.";
|
||||
|
||||
public override char ChatColor => ChatColors.BlueGrey;
|
||||
|
||||
public override string HtmlColor => "#cdcece";
|
||||
|
||||
private readonly List<PanicTeleportInfo> _registrations = new();
|
||||
|
||||
public override void AssignToPlayer(CCSPlayerController player, bool showMessages)
|
||||
{
|
||||
base.AssignToPlayer(player, showMessages);
|
||||
if (_registrations.All(registration => registration.Player != player))
|
||||
{
|
||||
_registrations.Add(new PanicTeleportInfo(player, null));
|
||||
}
|
||||
}
|
||||
|
||||
public override void UnassignFromPlayer(CCSPlayerController player)
|
||||
{
|
||||
base.UnassignFromPlayer(player);
|
||||
var registration = _registrations.FirstOrDefault(r => r.Player == player);
|
||||
if (registration != null)
|
||||
{
|
||||
_registrations.Remove(registration);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterListener<Listeners.OnTick>(OnTick);
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
var registration = _registrations
|
||||
.FirstOrDefault(r => r.Player == player);
|
||||
if (registration != null)
|
||||
{
|
||||
registration.SavedLocation = null;
|
||||
registration.WasUsePressedLastTick = false;
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
|
||||
{
|
||||
_registrations.Clear();
|
||||
foreach (var player in PlayerPowers.Instance.GetPlayersWithPower(this))
|
||||
{
|
||||
_registrations.Add(new PanicTeleportInfo(player, null));
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void OnTick()
|
||||
{
|
||||
foreach (var registration in _registrations)
|
||||
{
|
||||
var usePressed = registration.Player.Buttons.HasFlag(PlayerButtons.Use);
|
||||
if (usePressed && !registration.WasUsePressedLastTick)
|
||||
{
|
||||
// Key was just pressed (not held)
|
||||
HandleUseKeyPress(registration);
|
||||
}
|
||||
registration.WasUsePressedLastTick = usePressed;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleUseKeyPress(PanicTeleportInfo registration)
|
||||
{
|
||||
var player = registration.Player;
|
||||
|
||||
if (!Ensure.Player(player).IsValid().Check() || player.PlayerPawn?.Value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (registration.SavedLocation == null)
|
||||
{
|
||||
// Save current location
|
||||
var currentPos = player.PlayerPawn.Value.AbsOrigin;
|
||||
if (currentPos != null)
|
||||
{
|
||||
registration.SavedLocation = new Vector(currentPos.X, currentPos.Y, currentPos.Z);
|
||||
registration.SavedLocation.Z += 10;
|
||||
player.PrintToCenterAlert($"{ChatColor}Location saved! Press use key again to teleport back.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Teleport to saved location
|
||||
var savedPos = registration.SavedLocation;
|
||||
player.PlayerPawn.Value.Teleport(savedPos, null, null);
|
||||
registration.SavedLocation = null;
|
||||
player.PrintToCenterAlert($"{ChatColor}Teleported. Press use to save a new location.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record PanicTeleportInfo(CCSPlayerController Player, Vector? SavedLocation)
|
||||
{
|
||||
public Vector? SavedLocation { get; set; } = SavedLocation;
|
||||
public bool WasUsePressedLastTick { get; set; } = false;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class Phoenix(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 2;
|
||||
public override string Name => "Phoenix Rising";
|
||||
|
||||
public override string Description => $"You will be reborn after your first death like Phoenix from the ashes.";
|
||||
|
||||
public override char ChatColor => ChatColors.DarkRed;
|
||||
|
||||
public override string HtmlColor => "#ff5C0A";
|
||||
public override int MinimumPlayersRequired => 4;
|
||||
|
||||
private HashSet<CCSPlayerController> _phoenixUses = new HashSet<CCSPlayerController>();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo _)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 4 valid (non-spectator, non-none) players total
|
||||
var validPlayerCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator);
|
||||
if (validPlayerCount < 4)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (_phoenixUses.Contains(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
var newHealth = 120;
|
||||
player!.SetNewHealth(newHealth);
|
||||
player!.Respawn();
|
||||
_phoenixUses.Add(player);
|
||||
|
||||
player.PrintToChat($"{ChatColors.DarkRed}Phoenix {ChatColors.Green}rises.");
|
||||
});
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
_phoenixUses = new HashSet<CCSPlayerController>();
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
player.SetNewHealth(50);
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class PowerThief(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 15;
|
||||
public override string Name => "Power Thief";
|
||||
|
||||
public override string Description => $"With a 25% chance on hit, you can permanently strip your enemies of their current superpower.";
|
||||
|
||||
public override char ChatColor => ChatColors.LightYellow;
|
||||
|
||||
public override string HtmlColor => "#f8e1ab";
|
||||
|
||||
private readonly Random _random = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (
|
||||
!Ensure.Player(victim).IsValid().Check() ||
|
||||
!Ensure.Player(attacker).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 1 player in the opposing team
|
||||
var attackerTeam = attacker!.Team;
|
||||
var enemyCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator && p.Team != attackerTeam);
|
||||
if (enemyCount < 1)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (_random.NextDouble() <= 0.25)
|
||||
{
|
||||
StealSuperpower(victim!, attacker!);
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void StealSuperpower(CCSPlayerController victim, CCSPlayerController attacker)
|
||||
{
|
||||
if (!PlayerPowers.Instance.TryGetValue(victim, out var power)) return;
|
||||
|
||||
PlayerPowers.Instance.RemovePower(victim);
|
||||
victim.SetMaxHealth(100);
|
||||
attacker.PrintToChat($"You have stolen {ChatColors.Red}{victim.PlayerName} {ChatColors.Default}superpower {power!.ChatColor}{power.Name}.");
|
||||
victim.PrintToChat($"Your superpower {power.ChatColor}{power.Name} {ChatColors.Red}was stolen! You're a regular guy/gal now.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class Rambo(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 5;
|
||||
public override string Name => "John Rambo";
|
||||
|
||||
public override string Description => $"You start with additional random health, negev, kevlar and hand grenades. Enemy team gets 5000 cash.";
|
||||
|
||||
public override char ChatColor => ChatColors.Green;
|
||||
|
||||
public override string HtmlColor => "#009905";
|
||||
public override int MinimumPlayersRequired => 4;
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
var random = new Random();
|
||||
var ramboPlayers = PlayerPowers.Instance.GetPlayersWithPower(this).ToList();
|
||||
var validPlayerCount = Utilities.GetPlayers().Count(player =>
|
||||
player.IsValid && player.Team != CsTeam.None && player.Team != CsTeam.Spectator);
|
||||
if (validPlayerCount < MinimumPlayersRequired)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// If there are any Rambo players, give enemy team 5000 cash
|
||||
if (ramboPlayers.Count > 0)
|
||||
{
|
||||
var ramboTeam = ramboPlayers.First().Team;
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().Check())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player.Team != ramboTeam && player.Team != CsTeam.None && player.Team != CsTeam.Spectator)
|
||||
{
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
if (player.InGameMoneyServices != null)
|
||||
{
|
||||
player.InGameMoneyServices.Account += 5000;
|
||||
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var player in ramboPlayers)
|
||||
{
|
||||
if (!Ensure.Player(player).IsValid().Check())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
var newHealth = 100 + random.Next(150, 400);
|
||||
|
||||
player.GiveNamedItem("weapon_negev");
|
||||
player.GiveNamedItem("item_assaultsuit");
|
||||
player.GiveNamedItem("weapon_hegrenade");
|
||||
|
||||
player.SetNewHealth(newHealth);
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class RandomTeleport(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 29;
|
||||
public override string Name => "Random Teleport";
|
||||
public override string Description => "Teleport to a random position on the map every time you get a kill!";
|
||||
public override char ChatColor => ChatColors.Purple;
|
||||
public override string HtmlColor => "#800080";
|
||||
|
||||
private readonly Random _random = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
if (!Ensure.Player(attacker).IsValid().IsAlive().Check() || !IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var pawn = attacker!.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid)
|
||||
return HookResult.Continue;
|
||||
|
||||
// Find random valid player to teleport near
|
||||
var validPlayers = Utilities.GetPlayers()
|
||||
.Where(p => p.IsValid && p.PawnIsAlive && p != attacker)
|
||||
.ToList();
|
||||
|
||||
if (validPlayers.Count > 0)
|
||||
{
|
||||
// Teleport near a random player
|
||||
var targetPlayer = validPlayers[_random.Next(validPlayers.Count)];
|
||||
var targetPos = targetPlayer.PlayerPawn.Value!.AbsOrigin!;
|
||||
|
||||
// Random offset of 100-300 units
|
||||
var offset = new Vector(
|
||||
(float)(_random.NextDouble() * 400 - 200),
|
||||
(float)(_random.NextDouble() * 400 - 200),
|
||||
50
|
||||
);
|
||||
|
||||
pawn.Teleport(targetPos + offset, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no other players, teleport in a random direction from current position
|
||||
var currentPos = pawn.AbsOrigin!;
|
||||
var offset = new Vector(
|
||||
(float)(_random.NextDouble() * 600 - 300),
|
||||
(float)(_random.NextDouble() * 600 - 300),
|
||||
100
|
||||
);
|
||||
pawn.Teleport(currentPos + offset, null, null);
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class RiseFromTheDead(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 1;
|
||||
public override string Name => "Rise From the Dead";
|
||||
|
||||
public override string Description => $"You have {(int)(ReviveChance * 100)}% chance to rise from the dead.";
|
||||
|
||||
public override char ChatColor => ChatColors.Red;
|
||||
|
||||
public override string HtmlColor => "#ff5C0A";
|
||||
public override int MinimumPlayersRequired => 4;
|
||||
|
||||
private const double ReviveChance = 0.35;
|
||||
|
||||
private readonly Random _random = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo _)
|
||||
{
|
||||
var player = @event.Userid;
|
||||
if (!Ensure.Player(player).IsValid().Check() || !IsAssignedTo(player!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 4 valid (non-spectator, non-none) players total
|
||||
var validPlayerCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator);
|
||||
if (validPlayerCount < 4)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (_random.NextDouble() <= ReviveChance)
|
||||
{
|
||||
Server.NextFrame(() =>
|
||||
{
|
||||
player!.MaxHealth = 70;
|
||||
player.Health = 70;
|
||||
player.Respawn();
|
||||
player.PrintToChat($"You have been raised from {ChatColors.DarkRed}the dead.");
|
||||
});
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class Shield(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 32;
|
||||
public override string Name => "Shield";
|
||||
public override string Description => "Hold E (Use) to activate a shield that blocks all damage! Shield has 100 energy points (regenerates 10 per second when not in use).";
|
||||
public override char ChatColor => ChatColors.Yellow;
|
||||
public override string HtmlColor => "#FFFF00";
|
||||
|
||||
private readonly Dictionary<CCSPlayerController, float> _shieldEnergy = new();
|
||||
private readonly Dictionary<CCSPlayerController, bool> _shieldActive = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterListener<Listeners.OnTick>(OnTick);
|
||||
RegisterEventHandler<EventRoundStart>(OnRoundStart);
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnRoundStart(EventRoundStart @event, GameEventInfo _)
|
||||
{
|
||||
_shieldEnergy.Clear();
|
||||
_shieldActive.Clear();
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
public override void AssignToPlayer(CCSPlayerController player, bool showMessages)
|
||||
{
|
||||
base.AssignToPlayer(player, showMessages);
|
||||
_shieldEnergy[player] = 100.0f;
|
||||
_shieldActive[player] = false;
|
||||
}
|
||||
|
||||
public override void UnassignFromPlayer(CCSPlayerController player)
|
||||
{
|
||||
base.UnassignFromPlayer(player);
|
||||
_shieldEnergy.Remove(player);
|
||||
_shieldActive.Remove(player);
|
||||
}
|
||||
|
||||
private void OnTick()
|
||||
{
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (!player.IsValid || !player.PawnIsAlive)
|
||||
continue;
|
||||
|
||||
if (!IsAssignedTo(player))
|
||||
continue;
|
||||
|
||||
if (!_shieldEnergy.ContainsKey(player))
|
||||
continue;
|
||||
|
||||
var usePressed = player.Buttons.HasFlag(PlayerButtons.Use);
|
||||
|
||||
if (usePressed && _shieldEnergy[player] > 0)
|
||||
{
|
||||
// Shield is active!
|
||||
_shieldActive[player] = true;
|
||||
_shieldEnergy[player] -= 0.5f; // Consume 0.5 energy per tick
|
||||
|
||||
// Update HUD (center alert) every second
|
||||
if (Math.Abs(_shieldEnergy[player] % 20) < 1)
|
||||
{
|
||||
player.PrintToCenterAlert($"{ChatColor}Shield: {Math.Ceiling(_shieldEnergy[player])}%");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shield not active, regenerate
|
||||
_shieldActive[player] = false;
|
||||
if (_shieldEnergy[player] < 100.0f)
|
||||
{
|
||||
_shieldEnergy[player] += 0.3f; // Regenerate 0.3 per tick
|
||||
if (_shieldEnergy[player] > 100.0f)
|
||||
_shieldEnergy[player] = 100.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (!Ensure.Player(victim).IsValid().Check() || !IsAssignedTo(victim!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var validatedVictim = victim!;
|
||||
if (_shieldActive.GetValueOrDefault(validatedVictim, false))
|
||||
{
|
||||
var pawn = validatedVictim.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
pawn.Health = Math.Min(pawn.MaxHealth, pawn.Health + @event.DmgHealth);
|
||||
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
|
||||
return HookResult.Handled;
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class TeleportOnKill(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 21;
|
||||
public override string Name => "Teleport On Kill";
|
||||
|
||||
public override string Description => $"When you kill an enemy, you teleport to their location (needs at least 4 players)";
|
||||
|
||||
public override char ChatColor => ChatColors.Red;
|
||||
|
||||
public override string HtmlColor => "#FF0000";
|
||||
public override int MinimumPlayersRequired => 4;
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerDeath>(OnPlayerDeath);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (
|
||||
!Ensure.Player(victim).IsValid().Check() ||
|
||||
!Ensure.Player(attacker).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 4 valid (non-spectator, non-none) players total
|
||||
var validPlayerCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator);
|
||||
if (validPlayerCount < 4)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (victim!.PlayerPawn?.Value != null && attacker!.PlayerPawn?.Value != null)
|
||||
{
|
||||
var victimPos = victim.PlayerPawn.Value.AbsOrigin;
|
||||
// raise Z a bit to avoid getting stuck in the ground
|
||||
if (victimPos != null)
|
||||
{
|
||||
victimPos.Z += 10;
|
||||
attacker.PlayerPawn.Value.Teleport(victimPos, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class Twister(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 14;
|
||||
public override string Name => "Twister";
|
||||
|
||||
public override string Description => $"Inflict disorienting spins upon your enemies with a 25% chance on each hit, disrupting their aim and movement.";
|
||||
|
||||
public override char ChatColor => ChatColors.Silver;
|
||||
|
||||
public override string HtmlColor => "#00FF00";
|
||||
|
||||
private readonly Random _random = new();
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (
|
||||
!Ensure.Player(victim).IsValid().Check() ||
|
||||
!Ensure.Player(attacker).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 1 player in the opposing team
|
||||
var attackerTeam = attacker!.Team;
|
||||
var enemyCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator && p.Team != attackerTeam);
|
||||
if (enemyCount < 1)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
if (_random.NextDouble() <= 0.25)
|
||||
{
|
||||
RotateEnemy(victim!);
|
||||
}
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
private void RotateEnemy(CCSPlayerController player)
|
||||
{
|
||||
var currentPosition = player.PlayerPawn.Value!.AbsOrigin;
|
||||
var currentAngles = player.PlayerPawn.Value.V_angle;
|
||||
|
||||
var newAngles = new QAngle(
|
||||
currentAngles.X,
|
||||
currentAngles.Y + _random.Next(-180, 180),
|
||||
currentAngles.Z
|
||||
);
|
||||
player.PlayerPawn.Value.Teleport(currentPosition, newAngles, new Vector(0, 0, 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Cs2SuperPowers.Players;
|
||||
using Cs2SuperPowers.Players.PlayerValidations;
|
||||
|
||||
namespace Cs2SuperPowers.Powers;
|
||||
|
||||
public class Vampire(IPlayerHud playerHud) : BasePower(playerHud)
|
||||
{
|
||||
public override int Id => 12;
|
||||
public override string Name => "Vampire";
|
||||
|
||||
public override string Description => $"Drain the life force of your enemies, healing yourself for {(int)(HealModifier * 100)}% of the damage dealt with each shot.";
|
||||
|
||||
public override char ChatColor => ChatColors.Magenta;
|
||||
|
||||
public override string HtmlColor => "#ff5CD9";
|
||||
public override int MinimumPlayersRequired => 4;
|
||||
|
||||
private const double HealModifier = 0.7;
|
||||
|
||||
protected override void OnInitialize()
|
||||
{
|
||||
RegisterEventHandler<EventPlayerHurt>(OnPlayerHurt);
|
||||
}
|
||||
|
||||
private HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
||||
{
|
||||
var attacker = @event.Attacker;
|
||||
var victim = @event.Userid;
|
||||
|
||||
if (
|
||||
!Ensure.Player(victim).IsValid().IsAlive().Check() ||
|
||||
!Ensure.Player(attacker).IsValid().Check() ||
|
||||
attacker == victim ||
|
||||
!IsAssignedTo(attacker!))
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
// Check for at least 4 valid (non-spectator, non-none) players total
|
||||
var validPlayerCount = Utilities.GetPlayers().Count(p =>
|
||||
p.IsValid && p.Team != CsTeam.None && p.Team != CsTeam.Spectator);
|
||||
if (validPlayerCount < 4)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var attackerPawn = attacker!.PlayerPawn.Value;
|
||||
if (attackerPawn == null)
|
||||
{
|
||||
return HookResult.Continue;
|
||||
}
|
||||
|
||||
var healing = (int)(@event.DmgHealth * HealModifier);
|
||||
var newHealth = Math.Min(attackerPawn.Health + healing, attackerPawn.MaxHealth);
|
||||
attackerPawn.Health = newHealth;
|
||||
Utilities.SetStateChanged(attackerPawn, "CBaseEntity", "m_iHealth");
|
||||
|
||||
return HookResult.Continue;
|
||||
}
|
||||
}
|
||||
@@ -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