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 _lastUse = new(); protected override void OnInitialize() { RegisterEventHandler(OnRoundStart); RegisterEventHandler(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; } }