Fix skill bugs and dying-entity transmit, add radar and bomb-timer handling
This commit is contained in:
parent
4b42670804
commit
1dda45c977
17 changed files with 207 additions and 137 deletions
|
|
@ -75,6 +75,10 @@ namespace src.player.skills
|
||||||
SkillUtils.SetPlayerInvisibility(player, 0);
|
SkillUtils.SetPlayerInvisibility(player, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckTransmit hides the model but the radar blip comes from spotted state, so clear it every tick.
|
||||||
|
if (invisiblePlayers.ContainsKey(player.Index))
|
||||||
|
ClearSpottedState(player);
|
||||||
|
|
||||||
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
||||||
if (playerInfo?.Skill != skillName) continue;
|
if (playerInfo?.Skill != skillName) continue;
|
||||||
|
|
||||||
|
|
@ -100,14 +104,6 @@ namespace src.player.skills
|
||||||
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
||||||
|
|
||||||
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
|
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
|
||||||
bool isObservingC4Camouflage = false;
|
|
||||||
|
|
||||||
if (targetHandle != nint.Zero)
|
|
||||||
{
|
|
||||||
var target = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == targetHandle);
|
|
||||||
var targetInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(target)?.Index);
|
|
||||||
if (targetInfo?.Skill == skillName) isObservingC4Camouflage = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var playerIndex in invisiblePlayers.Keys)
|
foreach (var playerIndex in invisiblePlayers.Keys)
|
||||||
{
|
{
|
||||||
|
|
@ -118,11 +114,13 @@ namespace src.player.skills
|
||||||
if (player.Team == playerController.Team)
|
if (player.Team == playerController.Team)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!isObservingC4Camouflage)
|
|
||||||
{
|
|
||||||
var playerPawn = playerController.PlayerPawn.Value;
|
var playerPawn = playerController.PlayerPawn.Value;
|
||||||
if (playerPawn == null || !playerPawn.IsValid) continue;
|
if (playerPawn == null || !playerPawn.IsValid) continue;
|
||||||
|
|
||||||
|
// Only the actively spectated pawn stays transmitted; hiding it breaks the camera.
|
||||||
|
if (targetHandle != nint.Zero && playerPawn.Handle == targetHandle)
|
||||||
|
continue;
|
||||||
|
|
||||||
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
|
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
|
||||||
if (entity == null || !entity.IsValid) continue;
|
if (entity == null || !entity.IsValid) continue;
|
||||||
|
|
||||||
|
|
@ -140,7 +138,6 @@ namespace src.player.skills
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void EnableSkill(CCSPlayerController player)
|
public static void EnableSkill(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
|
|
@ -162,8 +159,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
invisiblePlayers.TryAdd(player.Index, 0);
|
invisiblePlayers.TryAdd(player.Index, 0);
|
||||||
SkillUtils.SetPlayerInvisibility(player, .5f);
|
SkillUtils.SetPlayerInvisibility(player, .5f);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisableSkill(CCSPlayerController player)
|
public static void DisableSkill(CCSPlayerController player)
|
||||||
|
|
@ -171,8 +166,6 @@ namespace src.player.skills
|
||||||
invisiblePlayers.TryRemove(player.Index, out _);
|
invisiblePlayers.TryRemove(player.Index, out _);
|
||||||
SkillUtils.SetPlayerInvisibility(player, 0);
|
SkillUtils.SetPlayerInvisibility(player, 0);
|
||||||
EntityManager.DestroyPlayerEntities(player.Index);
|
EntityManager.DestroyPlayerEntities(player.Index);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreatePlayerPosProp(CCSPlayerController player)
|
private static void CreatePlayerPosProp(CCSPlayerController player)
|
||||||
|
|
@ -228,6 +221,26 @@ namespace src.player.skills
|
||||||
return bomb.Index;
|
return bomb.Index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wipe spotted state (pawn + carried bomb) so a disguised carrier produces no radar blip.
|
||||||
|
private static void ClearSpottedState(CCSPlayerController player)
|
||||||
|
{
|
||||||
|
var pawn = player.PlayerPawn?.Value;
|
||||||
|
if (pawn != null && pawn.IsValid)
|
||||||
|
{
|
||||||
|
pawn.EntitySpottedState.Spotted = false;
|
||||||
|
pawn.EntitySpottedState.SpottedByMask[0] = 0;
|
||||||
|
pawn.EntitySpottedState.SpottedByMask[1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bomb = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
|
||||||
|
if (bomb != null && bomb.IsValid && bomb.OwnerEntity?.Index == player.Index)
|
||||||
|
{
|
||||||
|
bomb.EntitySpottedState.Spotted = false;
|
||||||
|
bomb.EntitySpottedState.SpottedByMask[0] = 0;
|
||||||
|
bomb.EntitySpottedState.SpottedByMask[1] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#00911f", CsTeam onlyTeam = CsTeam.Terrorist, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int maxPerServer = -1, Rarity rarity = Rarity.Uncommon) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, maxPerServer, rarity)
|
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#00911f", CsTeam onlyTeam = CsTeam.Terrorist, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int maxPerServer = -1, Rarity rarity = Rarity.Uncommon) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, maxPerServer, rarity)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace src.player.skills
|
||||||
{
|
{
|
||||||
private const Skills skillName = Skills.Ghost;
|
private const Skills skillName = Skills.Ghost;
|
||||||
private static readonly string[] allowedWeapons = [
|
private static readonly string[] allowedWeapons = [
|
||||||
"weapon_molotov", "weapon_incgrenade", "weapon_flashbang", "weapon_smokegrenade", "weapon_decoy", "weapon_hegrenade", "weapon_knife", "weapon_bayonet"
|
"weapon_molotov", "weapon_incgrenade", "weapon_flashbang", "weapon_smokegrenade", "weapon_decoy", "weapon_hegrenade", "weapon_knife", "weapon_bayonet", "weapon_c4"
|
||||||
];
|
];
|
||||||
private static readonly ConcurrentDictionary<uint, byte> invisiblePlayers = [];
|
private static readonly ConcurrentDictionary<uint, byte> invisiblePlayers = [];
|
||||||
private const string bloodParticle = "particles/blood_impact/blood_impact_high.vpcf";
|
private const string bloodParticle = "particles/blood_impact/blood_impact_high.vpcf";
|
||||||
|
|
@ -61,14 +61,6 @@ namespace src.player.skills
|
||||||
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
||||||
|
|
||||||
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
|
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
|
||||||
bool isObservingGhost = false;
|
|
||||||
|
|
||||||
if (targetHandle != nint.Zero)
|
|
||||||
{
|
|
||||||
var target = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == targetHandle);
|
|
||||||
var targetInfo = PlayerManager.GetPlayerByIndex(target?.Index);
|
|
||||||
if (targetInfo?.Skill == skillName) isObservingGhost = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var playerIndex in invisiblePlayers.Keys)
|
foreach (var playerIndex in invisiblePlayers.Keys)
|
||||||
{
|
{
|
||||||
|
|
@ -79,11 +71,13 @@ namespace src.player.skills
|
||||||
if (player.Team == playerController.Team)
|
if (player.Team == playerController.Team)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!isObservingGhost)
|
|
||||||
{
|
|
||||||
var playerPawn = playerController.PlayerPawn.Value;
|
var playerPawn = playerController.PlayerPawn.Value;
|
||||||
if (playerPawn == null || !playerPawn.IsValid) continue;
|
if (playerPawn == null || !playerPawn.IsValid) continue;
|
||||||
|
|
||||||
|
// Only the actively spectated pawn stays transmitted; hiding it breaks the camera.
|
||||||
|
if (targetHandle != nint.Zero && playerPawn.Handle == targetHandle)
|
||||||
|
continue;
|
||||||
|
|
||||||
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
|
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
|
||||||
if (entity == null || !entity.IsValid) continue;
|
if (entity == null || !entity.IsValid) continue;
|
||||||
|
|
||||||
|
|
@ -101,7 +95,6 @@ namespace src.player.skills
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void EnableSkill(CCSPlayerController player)
|
public static void EnableSkill(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
|
|
@ -113,8 +106,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
if (EntityManager.GetPlayerEntities(player.Index, "empty_prop").Count == 0)
|
if (EntityManager.GetPlayerEntities(player.Index, "empty_prop").Count == 0)
|
||||||
CreatePlayerPosProp(player);
|
CreatePlayerPosProp(player);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreatePlayerPosProp(CCSPlayerController player)
|
private static void CreatePlayerPosProp(CCSPlayerController player)
|
||||||
|
|
@ -150,8 +141,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
invisiblePlayers.TryRemove(player.Index, out _);
|
invisiblePlayers.TryRemove(player.Index, out _);
|
||||||
EntityManager.DestroyPlayerEntities(player.Index);
|
EntityManager.DestroyPlayerEntities(player.Index);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OnTick()
|
public static void OnTick()
|
||||||
|
|
|
||||||
|
|
@ -111,8 +111,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
SkillUtils.TryGiveWeapon(player, CsItem.SmokeGrenade);
|
SkillUtils.TryGiveWeapon(player, CsItem.SmokeGrenade);
|
||||||
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, grenadeLimit);
|
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, grenadeLimit);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisableSkill(CCSPlayerController player)
|
public static void DisableSkill(CCSPlayerController player)
|
||||||
|
|
@ -121,8 +119,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
playersWithSkill.TryRemove(player.Index, out _);
|
playersWithSkill.TryRemove(player.Index, out _);
|
||||||
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
|
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5d00ff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int maxPerServer = -1, Rarity rarity = Rarity.Common, int grenadeLimit = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, maxPerServer, rarity)
|
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#5d00ff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int maxPerServer = -1, Rarity rarity = Rarity.Common, int grenadeLimit = 2) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, maxPerServer, rarity)
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,18 @@ namespace src.player.skills
|
||||||
|
|
||||||
public static void WeaponPickup(EventItemPickup @event)
|
public static void WeaponPickup(EventItemPickup @event)
|
||||||
{
|
{
|
||||||
var player = PlayerManager.GetPlayerEvent(@event.Userid);
|
|
||||||
if (player == null || !player.IsValid) return;
|
|
||||||
|
|
||||||
var weapon = @event.Item;
|
var weapon = @event.Item;
|
||||||
if (string.IsNullOrEmpty(weapon) || weapon != "c4") return;
|
if (string.IsNullOrEmpty(weapon) || weapon != "c4") return;
|
||||||
|
|
||||||
|
// Gate on the bomb being hot (red), not on `players`: the fresh round's C4 is
|
||||||
|
// handed out before NewRound clears `players`, but a new C4 spawns white, so colour is race-free.
|
||||||
|
var bomb = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
|
||||||
|
if (bomb == null || !bomb.IsValid) return;
|
||||||
|
if (bomb.Render.R != 255 || bomb.Render.G != 0 || bomb.Render.B != 0) return;
|
||||||
|
|
||||||
|
var player = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||||
|
if (player == null || !player.IsValid) return;
|
||||||
|
|
||||||
player.PrintToCenterAlert(player.GetTranslation("hotbomb_hint"));
|
player.PrintToCenterAlert(player.GetTranslation("hotbomb_hint"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +106,7 @@ namespace src.player.skills
|
||||||
{
|
{
|
||||||
ChangeC4Color(Color.White);
|
ChangeC4Color(Color.White);
|
||||||
foreach (var enemy in Utilities.GetPlayers().Where(p => p.IsValid && p.Team == CsTeam.Terrorist && p.PlayerPawn?.Value?.Health > 0))
|
foreach (var enemy in Utilities.GetPlayers().Where(p => p.IsValid && p.Team == CsTeam.Terrorist && p.PlayerPawn?.Value?.Health > 0))
|
||||||
enemy.PrintToCenterAlert(enemy.GetTranslation("hotbomb_disable"));
|
enemy.PrintToCenterAlert(enemy.GetTranslation("hotbomb_disable_info"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,9 @@ namespace src.player.skills
|
||||||
var relay = EntityManager.CreateTrackedPhysicsProp(player.Index);
|
var relay = EntityManager.CreateTrackedPhysicsProp(player.Index);
|
||||||
if (relay == null || !relay.IsValid) return;
|
if (relay == null || !relay.IsValid) return;
|
||||||
|
|
||||||
|
// Register before parenting so the trail is filtered from its first snapshot.
|
||||||
|
activeTrails[player.Index] = relay.Index;
|
||||||
|
|
||||||
CBaseEntity target = playerPawn;
|
CBaseEntity target = playerPawn;
|
||||||
var entities = EntityManager.GetPlayerEntities(player.Index, "empty_prop");
|
var entities = EntityManager.GetPlayerEntities(player.Index, "empty_prop");
|
||||||
|
|
||||||
|
|
@ -123,8 +126,6 @@ namespace src.player.skills
|
||||||
particle.AcceptInput("SetParent", relay, particle, "!activator");
|
particle.AcceptInput("SetParent", relay, particle, "!activator");
|
||||||
particle.AcceptInput("Start");
|
particle.AcceptInput("Start");
|
||||||
}
|
}
|
||||||
|
|
||||||
activeTrails[player.Index] = relay.Index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void EnableSkill(CCSPlayerController player)
|
public static void EnableSkill(CCSPlayerController player)
|
||||||
|
|
@ -144,8 +145,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
mainSkillTimer ??= Instance.AddTimer(2.5f, () => UpdateAllTrails(),
|
mainSkillTimer ??= Instance.AddTimer(2.5f, () => UpdateAllTrails(),
|
||||||
CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT | CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
CounterStrikeSharp.API.Modules.Timers.TimerFlags.REPEAT | CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateAllTrails()
|
private static void UpdateAllTrails()
|
||||||
|
|
@ -168,8 +167,6 @@ namespace src.player.skills
|
||||||
mainSkillTimer.Kill();
|
mainSkillTimer.Kill();
|
||||||
mainSkillTimer = null;
|
mainSkillTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#f542ef", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int maxPerServer = 1, Rarity rarity = Rarity.Common, string particleName = "particles/ui/hud/ui_map_def_utility_trail.vpcf") : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, maxPerServer, rarity)
|
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#f542ef", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", int maxPerServer = 1, Rarity rarity = Rarity.Common, string particleName = "particles/ui/hud/ui_map_def_utility_trail.vpcf") : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, maxPerServer, rarity)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,13 @@ namespace src.player.skills
|
||||||
private static bool hooked = false;
|
private static bool hooked = false;
|
||||||
private const int actionCode = 503;
|
private const int actionCode = 503;
|
||||||
private static readonly ConcurrentDictionary<uint, byte> playersInAction = [];
|
private static readonly ConcurrentDictionary<uint, byte> playersInAction = [];
|
||||||
private static readonly MemoryFunctionVoid<IntPtr, short> Shoot_Secondary = new(GameData.GetSignature("Shoot_Secondary"));
|
private static readonly MemoryFunctionVoid<IntPtr, short>? Shoot_Secondary = ResolveShootSecondary();
|
||||||
|
|
||||||
|
private static MemoryFunctionVoid<IntPtr, short>? ResolveShootSecondary()
|
||||||
|
{
|
||||||
|
try { return new(GameData.GetSignature("Shoot_Secondary")); }
|
||||||
|
catch (Exception ex) { Server.PrintToConsole($"[jRandomSkills] Shoot_Secondary signature unresolved: {ex.Message}"); return null; }
|
||||||
|
}
|
||||||
|
|
||||||
public static void LoadSkill()
|
public static void LoadSkill()
|
||||||
{
|
{
|
||||||
|
|
@ -26,12 +32,12 @@ namespace src.player.skills
|
||||||
{
|
{
|
||||||
playersInAction.Clear();
|
playersInAction.Clear();
|
||||||
hooked = false;
|
hooked = false;
|
||||||
Shoot_Secondary.Unhook(ShootSecondary, HookMode.Pre);
|
Shoot_Secondary?.Unhook(ShootSecondary, HookMode.Pre);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void EnableSkill(CCSPlayerController player)
|
public static void EnableSkill(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
if (hooked) return;
|
if (hooked || Shoot_Secondary == null) return;
|
||||||
hooked = true;
|
hooked = true;
|
||||||
Shoot_Secondary.Hook(ShootSecondary, HookMode.Pre);
|
Shoot_Secondary.Hook(ShootSecondary, HookMode.Pre);
|
||||||
playersInAction.TryAdd(player.Index, 0);
|
playersInAction.TryAdd(player.Index, 0);
|
||||||
|
|
@ -42,7 +48,7 @@ namespace src.player.skills
|
||||||
playersInAction.TryRemove(player.Index, out _);
|
playersInAction.TryRemove(player.Index, out _);
|
||||||
if (playersInAction.IsEmpty)
|
if (playersInAction.IsEmpty)
|
||||||
{
|
{
|
||||||
Shoot_Secondary.Unhook(ShootSecondary, HookMode.Pre);
|
Shoot_Secondary?.Unhook(ShootSecondary, HookMode.Pre);
|
||||||
hooked = false;
|
hooked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,14 +63,6 @@ namespace src.player.skills
|
||||||
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
||||||
|
|
||||||
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
|
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget.Value?.Handle ?? nint.Zero;
|
||||||
bool isObservingNinja = false;
|
|
||||||
|
|
||||||
if (targetHandle != nint.Zero)
|
|
||||||
{
|
|
||||||
var target = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == targetHandle);
|
|
||||||
var targetInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(target)?.Index);
|
|
||||||
if (targetInfo?.Skill == skillName) isObservingNinja = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var playerIndex in invisiblePlayers.Keys)
|
foreach (var playerIndex in invisiblePlayers.Keys)
|
||||||
{
|
{
|
||||||
|
|
@ -81,11 +73,13 @@ namespace src.player.skills
|
||||||
if (player.Team == playerController.Team)
|
if (player.Team == playerController.Team)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!isObservingNinja)
|
|
||||||
{
|
|
||||||
var playerPawn = playerController.PlayerPawn.Value;
|
var playerPawn = playerController.PlayerPawn.Value;
|
||||||
if (playerPawn == null || !playerPawn.IsValid) continue;
|
if (playerPawn == null || !playerPawn.IsValid) continue;
|
||||||
|
|
||||||
|
// Only the actively spectated pawn stays transmitted; hiding it breaks the camera.
|
||||||
|
if (targetHandle != nint.Zero && playerPawn.Handle == targetHandle)
|
||||||
|
continue;
|
||||||
|
|
||||||
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
|
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)playerPawn.Index);
|
||||||
if (entity == null || !entity.IsValid) continue;
|
if (entity == null || !entity.IsValid) continue;
|
||||||
|
|
||||||
|
|
@ -102,7 +96,6 @@ namespace src.player.skills
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void OnTick()
|
public static void OnTick()
|
||||||
{
|
{
|
||||||
|
|
@ -139,8 +132,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
if (EntityManager.GetPlayerEntities(player.Index, "empty_prop").Count == 0)
|
if (EntityManager.GetPlayerEntities(player.Index, "empty_prop").Count == 0)
|
||||||
CreatePlayerPosProp(player);
|
CreatePlayerPosProp(player);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisableSkill(CCSPlayerController player)
|
public static void DisableSkill(CCSPlayerController player)
|
||||||
|
|
@ -148,8 +139,6 @@ namespace src.player.skills
|
||||||
SkillUtils.SetPlayerInvisibility(player, 0);
|
SkillUtils.SetPlayerInvisibility(player, 0);
|
||||||
invisiblePlayers.TryRemove(player.Index, out _);
|
invisiblePlayers.TryRemove(player.Index, out _);
|
||||||
EntityManager.DestroyPlayerEntities(player.Index);
|
EntityManager.DestroyPlayerEntities(player.Index);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreatePlayerPosProp(CCSPlayerController player)
|
private static void CreatePlayerPosProp(CCSPlayerController player)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using CounterStrikeSharp.API;
|
using CounterStrikeSharp.API;
|
||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
|
using CounterStrikeSharp.API.Modules.Cvars;
|
||||||
using CounterStrikeSharp.API.Modules.Memory;
|
using CounterStrikeSharp.API.Modules.Memory;
|
||||||
using CounterStrikeSharp.API.Modules.Utils;
|
using CounterStrikeSharp.API.Modules.Utils;
|
||||||
using src.utils;
|
using src.utils;
|
||||||
|
|
@ -12,10 +13,19 @@ namespace src.player.skills
|
||||||
{
|
{
|
||||||
private const Skills skillName = Skills.Planter;
|
private const Skills skillName = Skills.Planter;
|
||||||
private static readonly ConcurrentDictionary<uint, float> plantingPlayers = [];
|
private static readonly ConcurrentDictionary<uint, float> plantingPlayers = [];
|
||||||
|
// mp_c4timer is an Int32 cvar; captured at load so restore never picks up another skill's override.
|
||||||
|
private static int defaultC4Timer = 40;
|
||||||
|
|
||||||
public static void LoadSkill()
|
public static void LoadSkill()
|
||||||
{
|
{
|
||||||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||||
|
defaultC4Timer = ConVar.Find("mp_c4timer")?.GetPrimitiveValue<int>() ?? 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnableSkill(CCSPlayerController player)
|
||||||
|
{
|
||||||
|
// At round start (not at plant) so the client HUD/alert countdown is right before the plant completes.
|
||||||
|
Server.ExecuteCommand($"mp_c4timer {SkillsInfo.GetValue<int>(skillName, "extraC4BlowTime")}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void BombBeginplant(EventBombBeginplant @event)
|
public static void BombBeginplant(EventBombBeginplant @event)
|
||||||
|
|
@ -59,6 +69,8 @@ namespace src.player.skills
|
||||||
foreach (var player in Utilities.GetPlayers())
|
foreach (var player in Utilities.GetPlayers())
|
||||||
DisableSkill(player);
|
DisableSkill(player);
|
||||||
plantingPlayers.Clear();
|
plantingPlayers.Clear();
|
||||||
|
|
||||||
|
Server.ExecuteCommand($"mp_c4timer {defaultC4Timer}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisableSkill(CCSPlayerController player)
|
public static void DisableSkill(CCSPlayerController player)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,9 @@ namespace src.player.skills
|
||||||
private static void SetEnemiesVisibleOnRadar(CCSPlayerController player)
|
private static void SetEnemiesVisibleOnRadar(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
if (player == null || !player.IsValid || player.PlayerPawn?.Value == null) return;
|
if (player == null || !player.IsValid || player.PlayerPawn?.Value == null) return;
|
||||||
int playerIndex = (int)player.Index - 1;
|
|
||||||
|
// SpottedByMask is indexed by player slot (0-63), not entity index.
|
||||||
|
int slot = player.Slot;
|
||||||
|
|
||||||
foreach (var enemy in Utilities.GetPlayers().FindAll(p => p.Team != player.Team))
|
foreach (var enemy in Utilities.GetPlayers().FindAll(p => p.Team != player.Team))
|
||||||
{
|
{
|
||||||
|
|
@ -39,9 +41,13 @@ namespace src.player.skills
|
||||||
if (enemyEvent == null || !enemyEvent.IsValid) continue;
|
if (enemyEvent == null || !enemyEvent.IsValid) continue;
|
||||||
|
|
||||||
var enemyPawn = enemyEvent.PlayerPawn.Value;
|
var enemyPawn = enemyEvent.PlayerPawn.Value;
|
||||||
if (enemyPawn == null) continue;
|
if (enemyPawn == null || !enemyPawn.IsValid) continue;
|
||||||
|
|
||||||
enemyPawn.EntitySpottedState.SpottedByMask[0] |= (1u << (int)(playerIndex % 32));
|
// Invisibility (low render alpha) beats the radar hack.
|
||||||
|
if (enemyPawn.Render.A < 200) continue;
|
||||||
|
|
||||||
|
// Only the observer's slot bit — the Spotted bool would reveal to the whole team.
|
||||||
|
enemyPawn.EntitySpottedState.SpottedByMask[0] |= (1u << (slot % 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
var bombEntities = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").ToList();
|
var bombEntities = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").ToList();
|
||||||
|
|
@ -49,7 +55,7 @@ namespace src.player.skills
|
||||||
{
|
{
|
||||||
var bomb = bombEntities.FirstOrDefault();
|
var bomb = bombEntities.FirstOrDefault();
|
||||||
if (bomb != null && bomb.IsValid)
|
if (bomb != null && bomb.IsValid)
|
||||||
bomb.EntitySpottedState.SpottedByMask[0] |= (1u << (int)(playerIndex % 32));
|
bomb.EntitySpottedState.SpottedByMask[0] |= (1u << (slot % 32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using CounterStrikeSharp.API;
|
using CounterStrikeSharp.API;
|
||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
|
using CounterStrikeSharp.API.Modules.Cvars;
|
||||||
using CounterStrikeSharp.API.Modules.Utils;
|
using CounterStrikeSharp.API.Modules.Utils;
|
||||||
using src.utils;
|
using src.utils;
|
||||||
using static src.jRandomSkills;
|
using static src.jRandomSkills;
|
||||||
|
|
@ -15,6 +16,8 @@ namespace src.player.skills
|
||||||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000;
|
||||||
|
|
||||||
public static void EnableSkill(CCSPlayerController player)
|
public static void EnableSkill(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
||||||
|
|
@ -24,7 +27,7 @@ namespace src.player.skills
|
||||||
var moneyServices = player.InGameMoneyServices;
|
var moneyServices = player.InGameMoneyServices;
|
||||||
if (moneyServices == null) return;
|
if (moneyServices == null) return;
|
||||||
|
|
||||||
moneyBonus = Math.Min(moneyBonus, 16000 - moneyServices.Account);
|
moneyBonus = Math.Min(moneyBonus, GetMaxMoney() - moneyServices.Account);
|
||||||
|
|
||||||
playerInfo.SkillChance = moneyBonus;
|
playerInfo.SkillChance = moneyBonus;
|
||||||
AddMoney(player, moneyBonus);
|
AddMoney(player, moneyBonus);
|
||||||
|
|
@ -48,7 +51,7 @@ namespace src.player.skills
|
||||||
var moneyServices = player.InGameMoneyServices;
|
var moneyServices = player.InGameMoneyServices;
|
||||||
if (moneyServices == null) return;
|
if (moneyServices == null) return;
|
||||||
|
|
||||||
moneyServices.Account = Math.Clamp(moneyServices.Account + money, minimum, 16000);
|
moneyServices.Account = Math.Clamp(moneyServices.Account + money, minimum, GetMaxMoney());
|
||||||
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
|
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using CounterStrikeSharp.API;
|
using CounterStrikeSharp.API;
|
||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
|
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||||
using CounterStrikeSharp.API.Modules.Utils;
|
using CounterStrikeSharp.API.Modules.Utils;
|
||||||
using static src.jRandomSkills;
|
using static src.jRandomSkills;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
@ -10,7 +11,7 @@ namespace src.player.skills
|
||||||
public class SecondLife : ISkill
|
public class SecondLife : ISkill
|
||||||
{
|
{
|
||||||
private const Skills skillName = Skills.SecondLife;
|
private const Skills skillName = Skills.SecondLife;
|
||||||
private static readonly ConcurrentDictionary<nint, int> secondLifePlayers = [];
|
private static readonly ConcurrentDictionary<nint, byte> usedThisRound = [];
|
||||||
private static readonly object setLock = new();
|
private static readonly object setLock = new();
|
||||||
|
|
||||||
public static void LoadSkill()
|
public static void LoadSkill()
|
||||||
|
|
@ -20,30 +21,53 @@ namespace src.player.skills
|
||||||
|
|
||||||
public static void NewRound()
|
public static void NewRound()
|
||||||
{
|
{
|
||||||
secondLifePlayers.Clear();
|
usedThisRound.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void PlayerHurt(EventPlayerHurt @event)
|
// Must intercept pre-damage: player_hurt fires after the death is committed, so a
|
||||||
|
// lethal hit (esp. fall damage) can't be undone there. Cancelling the blow here keeps the respawn clean.
|
||||||
|
public static void OnTakeDamage(DynamicHook h)
|
||||||
{
|
{
|
||||||
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
|
var victimEntity = h.GetParam<CEntityInstance>(0);
|
||||||
|
var info = h.GetParam<CTakeDamageInfo>(1);
|
||||||
|
if (victimEntity == null || !victimEntity.IsValid || info == null) return;
|
||||||
|
|
||||||
if (!Instance.IsPlayerValid(victim)) return;
|
var victimPawn = victimEntity.As<CCSPlayerPawn>();
|
||||||
var victimInfo = PlayerManager.GetPlayerByIndex(victim!.Index);
|
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
|
||||||
|
|
||||||
|
var victimController = victimPawn.Controller.Value;
|
||||||
|
if (victimController == null || !victimController.IsValid) return;
|
||||||
|
|
||||||
|
var victim = victimController.As<CCSPlayerController>();
|
||||||
|
if (victim == null || !victim.IsValid || !victim.PawnIsAlive) return;
|
||||||
|
|
||||||
|
var victimInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index));
|
||||||
if (victimInfo == null || victimInfo.Skill != skillName) return;
|
if (victimInfo == null || victimInfo.Skill != skillName) return;
|
||||||
|
|
||||||
var victimPawn = victim!.PlayerPawn.Value;
|
if (info.Damage < victimPawn.Health) return;
|
||||||
if (victimPawn!.Health > 0 || (secondLifePlayers.TryGetValue(victim.Handle, out int tick) && tick + 4 < Server.TickCount))
|
if (usedThisRound.ContainsKey(victim.Handle)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
lock (setLock)
|
lock (setLock)
|
||||||
{
|
{
|
||||||
SetHealth(victim, SkillsInfo.GetValue<int>(skillName, "startHealth"));
|
if (usedThisRound.ContainsKey(victim.Handle)) return;
|
||||||
|
|
||||||
var spawnpoint = SkillUtils.GetSpawnPointVector(victim);
|
var spawnpoint = SkillUtils.GetSpawnPointVector(victim);
|
||||||
if (spawnpoint == null) return;
|
if (spawnpoint == null) return; // no clean respawn point -> let the normal death happen
|
||||||
|
|
||||||
victimPawn.Teleport(spawnpoint, null, null);
|
usedThisRound.TryAdd(victim.Handle, 0);
|
||||||
secondLifePlayers.TryAdd(victim.Handle, Server.TickCount);
|
info.Damage = 0;
|
||||||
|
|
||||||
|
int startHealth = SkillsInfo.GetValue<int>(skillName, "startHealth");
|
||||||
|
Server.NextFrame(() =>
|
||||||
|
{
|
||||||
|
if (victim == null || !victim.IsValid) return;
|
||||||
|
var pawn = victim.PlayerPawn.Value;
|
||||||
|
if (pawn == null || !pawn.IsValid) return;
|
||||||
|
|
||||||
|
pawn.Health = startHealth;
|
||||||
|
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
|
||||||
|
pawn.Teleport(spawnpoint, null, new Vector(0, 0, 0));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +78,7 @@ namespace src.player.skills
|
||||||
|
|
||||||
public static void DisableSkill(CCSPlayerController player)
|
public static void DisableSkill(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
secondLifePlayers.TryRemove(player.Handle, out _);
|
usedThisRound.TryRemove(player.Handle, out _);
|
||||||
if (player.PlayerPawn.Value == null) return;
|
if (player.PlayerPawn.Value == null) return;
|
||||||
SetHealth(player, Math.Min(player.PlayerPawn.Value.Health + SkillsInfo.GetValue<int>(skillName, "startHealth"), 100));
|
SetHealth(player, Math.Min(player.PlayerPawn.Value.Health + SkillsInfo.GetValue<int>(skillName, "startHealth"), 100));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ namespace src.player.skills
|
||||||
var attackerPawn = attacker.PlayerPawn.Value;
|
var attackerPawn = attacker.PlayerPawn.Value;
|
||||||
if (attackerPawn == null || !attackerPawn.IsValid) return false;
|
if (attackerPawn == null || !attackerPawn.IsValid) return false;
|
||||||
|
|
||||||
var victimPawn = attacker.PlayerPawn.Value;
|
var victimPawn = victim.PlayerPawn.Value;
|
||||||
if (victimPawn == null || !victimPawn.IsValid) return false;
|
if (victimPawn == null || !victimPawn.IsValid) return false;
|
||||||
|
|
||||||
var result = RayTrace.TraceHullShape(
|
var result = RayTrace.TraceHullShape(
|
||||||
|
|
@ -123,7 +123,11 @@ namespace src.player.skills
|
||||||
Vector direction = SkillUtils.GetForwardVector(targetAngle);
|
Vector direction = SkillUtils.GetForwardVector(targetAngle);
|
||||||
Vector targetPos = victimPos - (direction * distance);
|
Vector targetPos = victimPos - (direction * distance);
|
||||||
|
|
||||||
if (CheckTeleport(attacker, victim, victimPos, targetPos, targetAngle))
|
// Trace can only ignore one entity (victim); start a step out so the attacker's
|
||||||
|
// own body in melee range doesn't clip the hull and report a false "no space".
|
||||||
|
Vector traceStart = victimPos - (direction * 20f);
|
||||||
|
|
||||||
|
if (CheckTeleport(attacker, victim, traceStart, targetPos, targetAngle))
|
||||||
{
|
{
|
||||||
attackerPawn.Teleport(targetPos, targetAngle, Vector.Zero);
|
attackerPawn.Teleport(targetPos, targetAngle, Vector.Zero);
|
||||||
teleported = true;
|
teleported = true;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using CounterStrikeSharp.API;
|
using CounterStrikeSharp.API;
|
||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
|
using CounterStrikeSharp.API.Modules.Cvars;
|
||||||
using CounterStrikeSharp.API.Modules.Utils;
|
using CounterStrikeSharp.API.Modules.Utils;
|
||||||
using src.utils;
|
using src.utils;
|
||||||
using static src.jRandomSkills;
|
using static src.jRandomSkills;
|
||||||
|
|
@ -9,10 +10,24 @@ namespace src.player.skills
|
||||||
public class ShortBomb : ISkill
|
public class ShortBomb : ISkill
|
||||||
{
|
{
|
||||||
private const Skills skillName = Skills.ShortBomb;
|
private const Skills skillName = Skills.ShortBomb;
|
||||||
|
// mp_c4timer is an Int32 cvar; captured at load so restore never picks up another skill's override.
|
||||||
|
private static int defaultC4Timer = 40;
|
||||||
|
|
||||||
public static void LoadSkill()
|
public static void LoadSkill()
|
||||||
{
|
{
|
||||||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||||
|
defaultC4Timer = ConVar.Find("mp_c4timer")?.GetPrimitiveValue<int>() ?? 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EnableSkill(CCSPlayerController player)
|
||||||
|
{
|
||||||
|
// At round start (not at plant) so the client HUD/alert countdown is right before the plant completes.
|
||||||
|
Server.ExecuteCommand($"mp_c4timer {SkillsInfo.GetValue<int>(skillName, "detonationTime")}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NewRound()
|
||||||
|
{
|
||||||
|
Server.ExecuteCommand($"mp_c4timer {defaultC4Timer}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void BombPlanted(EventBombPlanted @event)
|
public static void BombPlanted(EventBombPlanted @event)
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,10 @@ namespace src.player.skills
|
||||||
|
|
||||||
public static bool OnWeaponCanAcquire(DynamicHook hook, CCSPlayerController player, CEconItemView econItem, CCSWeaponBaseVData vdata)
|
public static bool OnWeaponCanAcquire(DynamicHook hook, CCSPlayerController player, CEconItemView econItem, CCSWeaponBaseVData vdata)
|
||||||
{
|
{
|
||||||
|
// vdata can be non-null yet point at freed/invalid schema memory; reading
|
||||||
|
// .Name then throws NullReferenceException inside get_Name(). Guard the handle.
|
||||||
|
if (vdata == null || vdata.Handle == IntPtr.Zero) return false;
|
||||||
|
|
||||||
string weaponName = vdata.Name;
|
string weaponName = vdata.Name;
|
||||||
if (string.IsNullOrEmpty(weaponName)) return false;
|
if (string.IsNullOrEmpty(weaponName)) return false;
|
||||||
|
|
||||||
|
|
@ -123,15 +127,12 @@ namespace src.player.skills
|
||||||
public static void EnableSkill(CCSPlayerController _)
|
public static void EnableSkill(CCSPlayerController _)
|
||||||
{
|
{
|
||||||
Event.EnableTransmit();
|
Event.EnableTransmit();
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisableSkill(CCSPlayerController player)
|
public static void DisableSkill(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
if (knivesInfo.TryRemove(player.Index, out KnifeInfo? knifeInfo) && knifeInfo != null)
|
if (knivesInfo.TryRemove(player.Index, out KnifeInfo? knifeInfo) && knifeInfo != null)
|
||||||
DisableKnifeSkill(knifeInfo);
|
DisableKnifeSkill(knifeInfo);
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisableKnifeSkill(KnifeInfo knifeInfo)
|
public static void DisableKnifeSkill(KnifeInfo knifeInfo)
|
||||||
|
|
|
||||||
|
|
@ -28,19 +28,30 @@ namespace src.player.skills
|
||||||
temporaryBlockList.TryRemove(kvp.Key, out _);
|
temporaryBlockList.TryRemove(kvp.Key, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One pawn->info map per frame instead of a GetPlayers scan per client.
|
||||||
|
var infoByPawnHandle = new Dictionary<nint, jSkill_PlayerInfo?>();
|
||||||
|
foreach (var p in Utilities.GetPlayers())
|
||||||
|
{
|
||||||
|
var h = p?.Pawn?.Value?.Handle;
|
||||||
|
if (p != null && p.IsValid && h != null)
|
||||||
|
infoByPawnHandle[h.Value] = PlayerManager.GetPlayerByIndex(p.Index);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var (info, player) in infoList)
|
foreach (var (info, player) in infoList)
|
||||||
{
|
{
|
||||||
if (player == null || !player.IsValid) continue;
|
if (player == null || !player.IsValid) continue;
|
||||||
|
|
||||||
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
|
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
|
||||||
var observedPlayer = Utilities.GetPlayers().FirstOrDefault(p => p?.Pawn?.Value?.Handle == player?.Pawn?.Value?.ObserverServices?.ObserverTarget?.Value?.Handle);
|
var targetHandle = player.Pawn.Value?.ObserverServices?.ObserverTarget?.Value?.Handle ?? nint.Zero;
|
||||||
var observerInfo = observedPlayer != null ? PlayerManager.GetPlayerByIndex(observedPlayer.Index) : null;
|
jSkill_PlayerInfo? observerInfo = null;
|
||||||
|
if (targetHandle != nint.Zero)
|
||||||
|
infoByPawnHandle.TryGetValue(targetHandle, out observerInfo);
|
||||||
|
|
||||||
foreach (var kvp in glows)
|
foreach (var kvp in glows)
|
||||||
{
|
{
|
||||||
var enemyIndex = kvp.Key;
|
var enemyIndex = kvp.Key;
|
||||||
var glowInfo = kvp.Value;
|
var glowInfo = kvp.Value;
|
||||||
var enemy = Utilities.GetPlayers().FirstOrDefault(e => e != null && e.IsValid && e.Index == enemyIndex);
|
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
|
||||||
|
|
||||||
bool hasSkill = playerInfo?.Skill == skillName;
|
bool hasSkill = playerInfo?.Skill == skillName;
|
||||||
bool observerHasSkill = observerInfo != null && observerInfo?.Skill == skillName;
|
bool observerHasSkill = observerInfo != null && observerInfo?.Skill == skillName;
|
||||||
|
|
@ -85,8 +96,9 @@ namespace src.player.skills
|
||||||
var relayIndex = kvp.Value.RelayIndex;
|
var relayIndex = kvp.Value.RelayIndex;
|
||||||
var glowIndex = kvp.Value.GlowIndex;
|
var glowIndex = kvp.Value.GlowIndex;
|
||||||
|
|
||||||
SkillUtils.SafeKillEntity<CDynamicProp>(relayIndex);
|
// The glow follows the relay, so it dies first.
|
||||||
SkillUtils.SafeKillEntity<CDynamicProp>(glowIndex);
|
SkillUtils.SafeKillEntity<CDynamicProp>(glowIndex);
|
||||||
|
SkillUtils.SafeKillEntity<CDynamicProp>(relayIndex);
|
||||||
|
|
||||||
temporaryBlockList.TryAdd(relayIndex, DateTime.Now.AddSeconds(2));
|
temporaryBlockList.TryAdd(relayIndex, DateTime.Now.AddSeconds(2));
|
||||||
temporaryBlockList.TryAdd(glowIndex, DateTime.Now.AddSeconds(2));
|
temporaryBlockList.TryAdd(glowIndex, DateTime.Now.AddSeconds(2));
|
||||||
|
|
@ -103,8 +115,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
if (glows.IsEmpty)
|
if (glows.IsEmpty)
|
||||||
SetGlowEffectForAll();
|
SetGlowEffectForAll();
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisableSkill(CCSPlayerController player)
|
public static void DisableSkill(CCSPlayerController player)
|
||||||
|
|
@ -116,8 +126,6 @@ namespace src.player.skills
|
||||||
NewRound();
|
NewRound();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkillUtils.ForceFullUpdateToAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetGlowEffectForAll()
|
private static void SetGlowEffectForAll()
|
||||||
|
|
@ -149,6 +157,9 @@ namespace src.player.skills
|
||||||
if (modelGlow == null || !modelGlow.IsValid || modelRelay == null || !modelRelay.IsValid)
|
if (modelGlow == null || !modelGlow.IsValid || modelRelay == null || !modelRelay.IsValid)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Register before spawn so the glow is filtered from its first snapshot.
|
||||||
|
glows.TryAdd(enemy.Index, (modelRelay.Index, modelGlow.Index, enemy.Team));
|
||||||
|
|
||||||
var relayEntity = modelRelay.CBodyComponent?.SceneNode?.Owner?.Entity;
|
var relayEntity = modelRelay.CBodyComponent?.SceneNode?.Owner?.Entity;
|
||||||
if (relayEntity != null)
|
if (relayEntity != null)
|
||||||
relayEntity.Flags = (uint)(relayEntity.Flags & ~(1 << 2));
|
relayEntity.Flags = (uint)(relayEntity.Flags & ~(1 << 2));
|
||||||
|
|
@ -180,8 +191,6 @@ namespace src.player.skills
|
||||||
|
|
||||||
modelRelay.AcceptInput("FollowEntity", enemyPawn, modelRelay, "!activator");
|
modelRelay.AcceptInput("FollowEntity", enemyPawn, modelRelay, "!activator");
|
||||||
modelGlow.AcceptInput("FollowEntity", modelRelay, modelGlow, "!activator");
|
modelGlow.AcceptInput("FollowEntity", modelRelay, modelGlow, "!activator");
|
||||||
|
|
||||||
glows.TryAdd(enemy.Index, (modelRelay.Index, modelGlow.Index, enemy.Team));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue