#### Compatibility * **CounterStrikeSharp v1.0.375** - after the CS2 update, RayTraceImpl stopped working. All traces now use CounterStrikeSharp's native `Trace` API, so **RayTrace-CSS-API and RayTrace-MM are no longer required**. `RayTraceApi.dll` was removed from the project and the Ray-Trace step was removed from the README install guide. * **Gamedata** - CS2 1.41.8.3 broke the Windows `HEGrenadeProjectile_CreateFunc`, `CMolotovProjectile_CreateFunc` and `SnapViewAngles` signatures, and the Linux `SmokeGrenadeProjectile_CreateFunc` signature. `INetworkGameServer_Slots` moved from 584 to 616 on both platforms; the old value read a garbage size and pointer without throwing. All signatures now match exactly one function on both platforms, and the offsets were confirmed at runtime. #### Skills * **No Recoil** - the client received `weapon_accuracy_nospread`, but the server still applied spread, so the crosshair looked straight while bullets spread. The weapon's spread and inaccuracy values are now also zeroed on the server for that tick. Weapon data is shared per weapon type, so a weapon type is only patched while no other living player holds the same type; the values are restored on the next tick, at map end and on unload. * **Long Zeus** - kills went through suicide, so they did not count as Zeus kills and the ragdoll was marked `world`. The suicide damage is now rewritten to the attacker's Zeus with shock damage, so the engine credits the kill natively (killfeed, kill sound, scoreboard, ragdoll). The victim's body also gets the electric shock effect, which the engine only plays on real Taser hits. * **Long Knife** - long-range slashes now draw a tracer to where they hit. * **Fragile Bomb** - the bomb position was only stored by the `bomb_planted` event, which is sent only to skills active at that moment, so receiving the skill after the plant never detected hits. The planted bomb is now looked up when needed. Hits were also measured from the impact point, so shots passing through the bomb and landing behind it were missed; the bullet's path is now checked instead. * **Tripwire** - wires stayed visible after the skill changed. Beams were hidden from clients in the same tick they were cleared, so the "hide" update never reached the client. Beams are no longer hidden from clients before removal. #### Reliability * **Skill distribution** - an exception thrown during skill assignment aborted the round's distribution silently. It is now caught, logged and retried. Spectators without a pawn no longer count as connected players, which had caused unnecessary retries.
257 lines
9.1 KiB
C#
257 lines
9.1 KiB
C#
using CounterStrikeSharp.API;
|
|
using CounterStrikeSharp.API.Core;
|
|
using CounterStrikeSharp.API.Core.Attributes;
|
|
using CounterStrikeSharp.API.Modules.Memory;
|
|
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
|
using src.player.skills;
|
|
using src.utils;
|
|
using static CounterStrikeSharp.API.Core.Listeners;
|
|
using static src.jRandomSkills;
|
|
|
|
namespace src.player
|
|
{
|
|
public static partial class Event
|
|
{
|
|
private static HookResult BombBeginplant(EventBombBeginplant @event, GameEventInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("BombBeginplant", @event);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult BombAbortplant(EventBombAbortplant @event, GameEventInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("BombAbortplant", @event);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult BombPlanted(EventBombPlanted @event, GameEventInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("BombPlanted", @event);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult BombBegindefuse(EventBombBegindefuse @event, GameEventInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("BombBegindefuse", @event);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult DecoyStarted(EventDecoyStarted @event, GameEventInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("DecoyStarted", @event);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult DecoyDetonate(EventDecoyDetonate @event, GameEventInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("DecoyDetonate", @event);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult SmokegrenadeDetonate(EventSmokegrenadeDetonate @event, GameEventInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("SmokegrenadeDetonate", @event);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult SmokegrenadeExpired(EventSmokegrenadeExpired @event, GameEventInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("SmokegrenadeExpired", @event);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult OnEntityTakeDamagePre(CBaseEntity entity, CTakeDamageInfo info)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
CountDamageResolution(info);
|
|
SkillUtils.TrackHealthBeforeHit(entity, info);
|
|
|
|
object[] args = [entity, info];
|
|
DispatchOnTakeDamage(entity, info, args);
|
|
|
|
if (Fortnite.skillInThisRound == true &&
|
|
!Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite))
|
|
Instance.SkillAction("Fortnite", "OnTakeDamage", args);
|
|
|
|
SkillUtils.ApplyNativeKill(entity, info);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static int dmgTotal, dmgAttackerOk, dmgAbilityOk, dmgSameTeam;
|
|
private static DateTime dmgWindowStart = DateTime.Now;
|
|
|
|
private static void CountDamageResolution(CTakeDamageInfo info)
|
|
{
|
|
if (!PerfLog.Enabled || info == null) return;
|
|
|
|
dmgTotal++;
|
|
|
|
var attacker = info.Attacker?.Value;
|
|
if (attacker != null && attacker.IsValid)
|
|
{
|
|
dmgAttackerOk++;
|
|
|
|
var attackerPawn = new CCSPlayerPawn(attacker.Handle);
|
|
if (attackerPawn.IsValid && attackerPawn.DesignerName == "player") dmgSameTeam++;
|
|
}
|
|
|
|
var ability = info.Ability?.Value;
|
|
if (ability != null && ability.IsValid) dmgAbilityOk++;
|
|
|
|
if ((DateTime.Now - dmgWindowStart).TotalSeconds < 60) return;
|
|
|
|
if (dmgTotal > 0)
|
|
PerfLog.Info($"DMGHOOK total={dmgTotal} attacker={dmgAttackerOk} ability={dmgAbilityOk} playerAttacker={dmgSameTeam}");
|
|
|
|
dmgTotal = dmgAttackerOk = dmgAbilityOk = dmgSameTeam = 0;
|
|
dmgWindowStart = DateTime.Now;
|
|
}
|
|
|
|
private static void OnEntityTakeDamagePost(CBaseEntity entity, CTakeDamageInfo info, CTakeDamageResult result)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchOnTakeDamage(entity, info, [entity, info, result], true);
|
|
}
|
|
}
|
|
|
|
private static HookResult OnTriggerEnter(DynamicHook hook)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
CBaseTrigger trigger = hook.GetParam<CBaseTrigger>(0);
|
|
CBaseEntity entity = hook.GetParam<CBaseEntity>(1);
|
|
|
|
DispatchToActiveSkills("OnTriggerEnter", trigger, entity);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult OnTriggerExit(DynamicHook hook)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
CBaseTrigger trigger = hook.GetParam<CBaseTrigger>(0);
|
|
CBaseEntity entity = hook.GetParam<CBaseEntity>(1);
|
|
|
|
DispatchToActiveSkills("OnTriggerExit", trigger, entity);
|
|
return HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static HookResult OnWeaponCanAcquire(DynamicHook hook)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
CCSPlayer_ItemServices itemServices = hook.GetParam<CCSPlayer_ItemServices>(0);
|
|
if (itemServices == null || itemServices.Pawn.Value == null || !itemServices.Pawn.Value.IsValid) return HookResult.Continue;
|
|
|
|
CEconItemView econItem = hook.GetParam<CEconItemView>(1);
|
|
if (econItem == null) return HookResult.Continue;
|
|
|
|
CBasePlayerPawn pawn = itemServices.Pawn.Value;
|
|
if (pawn == null || !pawn.IsValid || pawn.Controller.Value == null || !pawn.Controller.Value.IsValid) return HookResult.Continue;
|
|
|
|
CCSPlayerController player = pawn.Controller.Value.As<CCSPlayerController>();
|
|
if (player == null || !player.IsValid) return HookResult.Continue;
|
|
|
|
var playerInfo = PlayerManager.GetPlayerByIndex(player.Index);
|
|
if (playerInfo == null) return HookResult.Continue;
|
|
|
|
CCSWeaponBaseVData vdata = VirtualFunctions.GetCSWeaponDataFromKeyFunc.Invoke(-1, econItem.ItemDefinitionIndex.ToString());
|
|
if (vdata == null || vdata.Handle == IntPtr.Zero) return HookResult.Continue;
|
|
|
|
var activeSkills = Instance.SkillPlayer
|
|
.Where(p => !p.IsDrawing)
|
|
.Select(p => p.Skill.ToString())
|
|
.Distinct();
|
|
|
|
bool block = false;
|
|
foreach (string skillName in activeSkills)
|
|
{
|
|
bool? result = (bool?)Instance.SkillAction(skillName, "OnWeaponCanAcquire", [hook, player, econItem, vdata]);
|
|
if (result == true)
|
|
{
|
|
block = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return block ? HookResult.Handled : HookResult.Continue;
|
|
}
|
|
}
|
|
|
|
private static void EntitySpawned(CEntityInstance entity)
|
|
{
|
|
lock (setLock)
|
|
{
|
|
DispatchToActiveSkills("OnEntitySpawned", entity);
|
|
}
|
|
}
|
|
|
|
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList)
|
|
{
|
|
long perfStart = PerfLog.Start();
|
|
lock (setLock)
|
|
{
|
|
try
|
|
{
|
|
// Keep dying entities out of snapshots until the engine processes the kill.
|
|
var dying = EntityManager.GetRecentlyDestroyedSnapshot();
|
|
if (dying.Count > 0)
|
|
{
|
|
foreach (var (info, player) in infoList)
|
|
{
|
|
if (player == null || !player.IsValid) continue;
|
|
foreach (var entityIndex in dying)
|
|
if (info.TransmitEntities.Contains(entityIndex))
|
|
info.TransmitEntities.Remove(entityIndex);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Server.PrintToConsole($"[jRandomSkills] CheckTransmit dying-filter failed: {ex.Message}");
|
|
}
|
|
|
|
DispatchCheckTransmit([infoList]);
|
|
}
|
|
PerfLog.Sample("CheckTransmit", perfStart);
|
|
}
|
|
|
|
public static void EnableTransmit()
|
|
{
|
|
if (!isTransmitRegistered)
|
|
{
|
|
Instance?.RegisterListener<CheckTransmit>(CheckTransmit);
|
|
isTransmitRegistered = true;
|
|
}
|
|
}
|
|
}
|
|
}
|