initial commit
This commit is contained in:
commit
d701598350
67 changed files with 9351 additions and 0 deletions
93
Outnumbered/Engine/GrenadeSpawner.cs
Normal file
93
Outnumbered/Engine/GrenadeSpawner.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace Outnumbered.Engine;
|
||||
|
||||
// A real HE blast at a point, attributed to a player. Spawns via the game's NATIVE projectile-create (the same call used
|
||||
// when a player throws) because Valve removed the think-arming from the InitializeSpawnFromWorld input, so a
|
||||
// CreateEntityByName grenade is inert on current CS2. Signature from MatchZy / cs2-executes. Lazy-resolved; if it doesn't
|
||||
// match this build, falls back to a manual radius blast (no engine VFX). Config-agnostic: damage/radius/fuse are passed in;
|
||||
// `log` receives one-time path notes. Args: (position, angle, velocity, velocity, IntPtr.Zero, itemDefIndex).
|
||||
internal static class GrenadeSpawner
|
||||
{
|
||||
private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
private static MemoryFunctionWithReturn<nint, nint, nint, nint, nint, int, CHEGrenadeProjectile>? _heCreate;
|
||||
private static bool _init, _ok, _nativeLogged, _manualLogged;
|
||||
|
||||
public static void Explode(CCSPlayerController attacker, Vector pos, float baseDamage, float radius, float fuseSeconds, Action<string, Exception?> log)
|
||||
{
|
||||
if (attacker is not { IsValid: true } || attacker.PlayerPawn.Value is null) return;
|
||||
if (!TrySpawnHe(attacker, pos, baseDamage, radius, fuseSeconds, log))
|
||||
ManualBlast(attacker, pos, baseDamage, radius, log);
|
||||
}
|
||||
|
||||
private static bool TrySpawnHe(CCSPlayerController attacker, Vector pos, float baseDamage, float radius, float fuseSeconds, Action<string, Exception?> log)
|
||||
{
|
||||
if (!_init)
|
||||
{
|
||||
_init = true;
|
||||
try
|
||||
{
|
||||
_heCreate = new(IsLinux
|
||||
? "55 4C 89 C1 48 89 E5 41 57 49 89 D7"
|
||||
: "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 50 48 8B AC 24 80 00 00 00 49 8B F8");
|
||||
_ok = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log("explode-on-kill: HE native-create signature not found — manual blast fallback", ex);
|
||||
_ok = false;
|
||||
}
|
||||
}
|
||||
if (!_ok || _heCreate is null) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var apawn = attacker.PlayerPawn.Value!;
|
||||
var spawn = new Vector(pos.X, pos.Y, pos.Z + 12f);
|
||||
var ang = new QAngle();
|
||||
var vel = new Vector(0, 0, -10f);
|
||||
var nade = _heCreate.Invoke(spawn.Handle, ang.Handle, vel.Handle, vel.Handle, IntPtr.Zero, EngineNames.HeGrenadeItemDef);
|
||||
if (nade is null || !nade.IsValid) return false;
|
||||
|
||||
nade.Teleport(spawn, ang, vel);
|
||||
nade.Globalname = "custom";
|
||||
nade.TeamNum = apawn.TeamNum; // CT -> only damages bots; survivors safe (ff off)
|
||||
nade.Thrower.Raw = attacker.PlayerPawn.Raw; // kill attribution + killfeed
|
||||
nade.OriginalThrower.Raw = attacker.PlayerPawn.Raw;
|
||||
nade.OwnerEntity.Raw = attacker.PlayerPawn.Raw;
|
||||
nade.Damage = baseDamage; // scaled UP by the offense hook on detonation
|
||||
nade.DmgRadius = radius;
|
||||
nade.DetonateTime = Server.CurrentTime + Math.Max(0f, fuseSeconds); // think is scheduled now -> respected
|
||||
if (!_nativeLogged) { _nativeLogged = true; log("explode-on-kill: native HE grenade spawned + armed OK", null); }
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log("explode-on-kill: native HE invoke failed — manual blast fallback", ex);
|
||||
_ok = false; // don't keep retrying a bad signature this session
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot the list — a lethal Deal fires player_death synchronously, which mutates bot state mid-loop.
|
||||
private static void ManualBlast(CCSPlayerController attacker, Vector center, float baseDamage, float radius, Action<string, Exception?> log)
|
||||
{
|
||||
double r = Math.Max(1.0, radius);
|
||||
foreach (var bot in Utilities.GetPlayers().ToList())
|
||||
{
|
||||
if (bot is not { IsValid: true, IsBot: true, IsHLTV: false } || !bot.PawnIsAlive) continue;
|
||||
var bpos = bot.PlayerPawn.Value?.AbsOrigin;
|
||||
if (bpos is null) continue;
|
||||
double dx = bpos.X - center.X, dy = bpos.Y - center.Y, dz = bpos.Z - center.Z;
|
||||
double dist = Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (dist > r) continue;
|
||||
float dmg = (float)(baseDamage * (1.0 - dist / r)); // linear falloff
|
||||
if (dmg >= 1f) DamageDealer.Deal(bot, attacker, dmg); // non-raw -> scales + attributed + chains
|
||||
}
|
||||
if (!_manualLogged) { _manualLogged = true; log("explode-on-kill: native HE unavailable -> MANUAL radius blast (no engine VFX)", null); }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue