v1.2.3.b1

Revive skills (Second Chance / Phoenix / Re-Zombie) — unified "on death" logic:
- Intercept the lethal hit before it is applied. Previously they reacted after the death was already committed, so one-shots, fall damage and skill-based kills were missed (Phoenix and Re-Zombie were rewritten from a post-death handler to pre-damage interception).

Killer Flash:
- Kills through real damage instead of forcing a suicide, so revive skills can intercept.

Baseball:
- Fixed a rare server crash from decoy grenades: a freed decoy's entity index could be reused by another entity and written to (use-after-free). Decoy tracking is now validated per tick (DesignerName gate) and cleared every round.

Native crash hardening:
- Aimbot: guard the hit-group native pointer before writing (avoids writing to freed/null memory).
- Wallhack: detach the glow when the target dies or disconnects (stale FollowEntity on a freed pawn).
- Spectator / Cypher / FalconEye / ThirdEye: null-guard the camera SceneNode walk and re-validate the target before teleport/parent (cross-frame freed-entity deref).
- CServerSideClient.ForceFullUpdate now bails on a null client handle.

Performance:
- Added a per-tick player/bomb cache (PlayerManager.GetTickPlayers / GetTickBomb).
- Migrated 82 skills from per-skill Utilities.GetPlayers() to the one shared per-tick scan, and the HUD loop reuses it. Much lower per-tick cost at high player counts (verified 10v10).

RayTrace:
- Prints a single notice when the module is not installed; dependent skills (Long Zeus, Long Knife, Iana, Cypher, Noclip, Shade + the skill-use aim check) no-op instead of failing silently (#43).
This commit is contained in:
ByDexter 2026-07-26 17:49:36 +03:00
parent c7ee500957
commit 0e86f54e0a
113 changed files with 717 additions and 437 deletions

View file

@ -28,7 +28,7 @@ namespace src.player.skills
{
foreach (var knifeInfo in knivesInfo.Values.ToArray())
DisableKnifeSkill(knifeInfo);
knivesInfo.Clear();
}
@ -50,18 +50,19 @@ namespace src.player.skills
float y = BitConverter.ToSingle(packedParams, 11);
float z = BitConverter.ToSingle(packedParams, 15);
soundPos = new Vector(x, y, z);
} catch { }
}
catch { }
}
if (soundPos == null) return;
(var knife, var knifeInfo) = GetClosetKnife(soundPos);
if (knife == null || !knife.IsValid || knife.AbsRotation == null)
return;
if (knifeInfo == null || knifeInfo.InitialLook == null || knife.Collision == null)
return;
knifeInfo.IsDropped = false;
QAngle rotation = new(knifeInfo.InitialLook?.X, knifeInfo.InitialLook?.Y, knife.AbsRotation.Z);
Vector offset = SkillUtils.GetForwardVector(new QAngle(rotation.X - 90, rotation.Y, rotation.Z)) * 2;
@ -74,11 +75,11 @@ namespace src.player.skills
knife.Collision.CollisionAttribute.InteractsWith = knifeInfo.OriginalCollisionMask;
Utilities.SetStateChanged(knife, "CCollisionProperty", "m_collisionAttribute");
var world = Utilities.GetEntityFromIndex<CBaseEntity>(0);
if (world != null && world.IsValid)
knife.AcceptInput("SetParent", world, world, "!activator");
if (knifeInfo.Timer != null)
knifeInfo.Timer?.Kill();
@ -136,6 +137,12 @@ namespace src.player.skills
DisableKnifeSkill(knifeInfo);
}
public static void PlayerDisconnect(uint playerIndex)
{
if (knivesInfo.TryRemove(playerIndex, out KnifeInfo? knifeInfo) && knifeInfo != null)
DisableKnifeSkill(knifeInfo);
}
public static void DisableKnifeSkill(KnifeInfo knifeInfo)
{
if (knifeInfo.Timer != null)
@ -185,7 +192,8 @@ namespace src.player.skills
if (weapon == null || !weapon.IsValid || (!weapon.DesignerName.Contains("knife") && !weapon.DesignerName.Contains("bayonet"))) return;
player.DropActiveWeapon();
Server.NextFrame(() => {
Server.NextFrame(() =>
{
if (player == null || !player.IsValid) return;
if (weapon == null || !weapon.IsValid) return;
ThrowKnife(player, weapon);
@ -243,10 +251,10 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) return null;
if (knife == null || !knife.IsValid || knife.AbsOrigin == null) return null;
var trigger = SkillUtils.CreateTrigger($"trowingknife", 10, knife.AbsOrigin);
if (trigger == null || !trigger.IsValid) return null;
trigger.AcceptInput("SetParent", knife, knife, "!activator");
return trigger.Index;
}
@ -312,8 +320,14 @@ namespace src.player.skills
if (victimPawn.Index == throwerPawn.Index)
return;
var victimController = victimPawn.Controller.Value?.As<CCSPlayerController>();
bool friendlyFire = SkillsInfo.GetValue<bool>(skillName, "friendlyFire");
if (!friendlyFire && thrower.TeamNum == victimPawn.TeamNum) return;
if (!friendlyFire)
{
if (thrower.TeamNum == victimPawn.TeamNum) return;
if (victimController != null && victimController.IsValid && thrower.Team == victimController.Team) return;
}
if (CheckHasKnife(thrower!)) return;
@ -371,7 +385,7 @@ namespace src.player.skills
var obs = playerPawn.ObserverServices?.ObserverTarget?.Value;
if (obs != null && obs.IsValid)
{
var observed = Utilities.GetPlayers().FirstOrDefault(p =>
var observed = PlayerManager.GetTickPlayers().FirstOrDefault(p =>
p != null && p.IsValid &&
p.Pawn?.Value?.Handle == obs.Handle);
if (observed != null)