commit
5fc3e8db66
34 changed files with 316 additions and 102 deletions
|
|
@ -26,6 +26,7 @@ namespace src
|
|||
public IWasdMenuManager? MenuManager;
|
||||
// Skills that were enabled at least once this round; used to reset only those on round change (not all 124).
|
||||
public static readonly ConcurrentDictionary<string, byte> ActiveSkillsThisRound = new();
|
||||
public static readonly ConcurrentDictionary<string, byte> SkillsUsedThisMap = new();
|
||||
|
||||
public override string ModuleName => "[CS2] [ jRandomSkills ]";
|
||||
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)";
|
||||
|
|
@ -95,7 +96,10 @@ namespace src
|
|||
return null;
|
||||
|
||||
if (methodName == "EnableSkill")
|
||||
{
|
||||
ActiveSkillsThisRound.TryAdd(skill, 0);
|
||||
SkillsUsedThisMap.TryAdd(skill, 0);
|
||||
}
|
||||
|
||||
var method = _skillMethodCache.GetOrAdd((skill, methodName), key =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -138,22 +138,54 @@ namespace src.player
|
|||
return candidates[Random.Shared.Next(candidates.Count)];
|
||||
}
|
||||
|
||||
private static readonly Skills[] lateDamageSkills = [Skills.SecondLife];
|
||||
|
||||
private static readonly HashSet<Skills> tickFailuresLogged = [];
|
||||
|
||||
private static void InvokeSkill(Skills skill, string methodName, object[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Instance.SkillAction(skill.ToString(), methodName, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Server.PrintToConsole($"[jRandomSkills] {skill}.{methodName} failed: {ex.InnerException?.Message ?? ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DispatchToActiveSkills(string methodName, params object[] args)
|
||||
{
|
||||
var seen = new HashSet<Skills>();
|
||||
foreach (var p in Instance.SkillPlayer)
|
||||
{
|
||||
if (p.IsDrawing || !seen.Add(p.Skill)) continue;
|
||||
try
|
||||
InvokeSkill(p.Skill, methodName, args);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DispatchOnTakeDamage(DynamicHook h)
|
||||
{
|
||||
Instance.SkillAction(p.Skill.ToString(), methodName, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
object[] args = [h];
|
||||
var seen = new HashSet<Skills>();
|
||||
List<Skills>? deferred = null;
|
||||
|
||||
foreach (var p in Instance.SkillPlayer)
|
||||
{
|
||||
// One skill's failure must not break the dispatch chain.
|
||||
Server.PrintToConsole($"[jRandomSkills] {p.Skill}.{methodName} failed: {ex.InnerException?.Message ?? ex.Message}");
|
||||
if (p.IsDrawing || !seen.Add(p.Skill)) continue;
|
||||
|
||||
if (Array.IndexOf(lateDamageSkills, p.Skill) >= 0)
|
||||
{
|
||||
(deferred ??= []).Add(p.Skill);
|
||||
continue;
|
||||
}
|
||||
|
||||
InvokeSkill(p.Skill, "OnTakeDamage", args);
|
||||
}
|
||||
|
||||
if (deferred == null) return;
|
||||
foreach (var skill in deferred)
|
||||
InvokeSkill(skill, "OnTakeDamage", args);
|
||||
}
|
||||
|
||||
private static HookResult PlayerMakeSound(UserMessage um)
|
||||
|
|
@ -322,7 +354,7 @@ namespace src.player
|
|||
{
|
||||
lock (setLock)
|
||||
{
|
||||
DispatchToActiveSkills("OnTakeDamage", h);
|
||||
DispatchOnTakeDamage(h);
|
||||
|
||||
if (Fortnite.skillInThisRound == true &&
|
||||
!Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite))
|
||||
|
|
@ -468,8 +500,18 @@ namespace src.player
|
|||
foreach (var skill in _activeSkillsList)
|
||||
{
|
||||
if (freeze && _freezeDisabledSkills.Contains(skill)) continue;
|
||||
try
|
||||
{
|
||||
Instance.SkillAction(_skillNames[skill], "OnTick");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Without this one throwing skill cancels every later skill's tick, every frame.
|
||||
// Logged once per skill per round; at 64 ticks a repeat would flood the console.
|
||||
if (tickFailuresLogged.Add(skill))
|
||||
Server.PrintToConsole($"[jRandomSkills] {skill}.OnTick failed: {ex.InnerException?.Message ?? ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
PerfLog.Sample("OnTick(skills)", perfStart);
|
||||
}
|
||||
|
|
@ -541,6 +583,10 @@ namespace src.player
|
|||
|
||||
Instance.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]);
|
||||
|
||||
uint leavingIndex = player.Index;
|
||||
foreach (var skill in SkillData.Skills)
|
||||
Instance.SkillAction(skill.Skill.ToString(), "PlayerDisconnect", [leavingIndex]);
|
||||
|
||||
PlayerManager.UnregisterPlayer(player.Index);
|
||||
EntityManager.DestroyPlayerEntities(player.Index);
|
||||
|
||||
|
|
@ -660,8 +706,12 @@ namespace src.player
|
|||
if (playerInfo == null) continue;
|
||||
|
||||
ActiveSkillsThisRound.TryAdd(playerInfo.Skill.ToString(), 0);
|
||||
SkillsUsedThisMap.TryAdd(playerInfo.Skill.ToString(), 0);
|
||||
if (playerInfo.SpecialSkill != noneSkill.Skill)
|
||||
{
|
||||
ActiveSkillsThisRound.TryAdd(playerInfo.SpecialSkill.ToString(), 0);
|
||||
SkillsUsedThisMap.TryAdd(playerInfo.SpecialSkill.ToString(), 0);
|
||||
}
|
||||
|
||||
Instance.SkillAction(playerInfo.Skill.ToString(), "DisableSkill", [player]);
|
||||
|
||||
|
|
@ -674,9 +724,13 @@ namespace src.player
|
|||
RestorePlayer(player);
|
||||
}
|
||||
|
||||
foreach (var skillName in ActiveSkillsThisRound.Keys)
|
||||
// Reset every skill used so far on this map, not only the ones held this round: a skill
|
||||
// nobody drew now would otherwise never clear state left over from an earlier round.
|
||||
// Skills that never ran cannot hold state, so they stay out of the sweep.
|
||||
foreach (var skillName in SkillsUsedThisMap.Keys)
|
||||
Instance.SkillAction(skillName, "NewRound");
|
||||
ActiveSkillsThisRound.Clear();
|
||||
tickFailuresLogged.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -712,6 +766,7 @@ namespace src.player
|
|||
EntityManager.SuppressKills = false;
|
||||
|
||||
ActiveSkillsThisRound.Clear();
|
||||
SkillsUsedThisMap.Clear();
|
||||
nextRoundPicks.Clear();
|
||||
|
||||
playersSkills.Clear();
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ namespace src.player.skills
|
|||
Marshal.WriteInt32(hitGroupOffset, 56, hitGroup);
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
hitGroups.Clear();
|
||||
}
|
||||
|
||||
public static void DisableSkill(CCSPlayerController _)
|
||||
{
|
||||
foreach (var hit in hitGroups)
|
||||
|
|
@ -65,6 +70,8 @@ namespace src.player.skills
|
|||
if (hit.Key != nint.Zero)
|
||||
Marshal.WriteInt32(hit.Key, 56, hit.Value);
|
||||
}
|
||||
|
||||
hitGroups.Clear();
|
||||
}
|
||||
|
||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff0000", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ namespace src.player.skills
|
|||
{
|
||||
if (Server.TickCount % 2 != 0) return;
|
||||
|
||||
var bomb = invisiblePlayers.IsEmpty
|
||||
? null
|
||||
: Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
|
||||
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
if (player.PlayerPawn?.Value?.Health <= 0 && invisiblePlayers.ContainsKey(player.Index))
|
||||
|
|
@ -77,7 +81,7 @@ namespace src.player.skills
|
|||
|
||||
// CheckTransmit hides the model but the radar blip comes from spotted state, so clear it every tick.
|
||||
if (invisiblePlayers.ContainsKey(player.Index))
|
||||
ClearSpottedState(player);
|
||||
ClearSpottedState(player, bomb);
|
||||
|
||||
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
||||
if (playerInfo?.Skill != skillName) continue;
|
||||
|
|
@ -99,6 +103,11 @@ namespace src.player.skills
|
|||
|
||||
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList)
|
||||
{
|
||||
if (invisiblePlayers.IsEmpty) return;
|
||||
|
||||
var bomb = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
|
||||
if (bomb != null && !bomb.IsValid) bomb = null;
|
||||
|
||||
foreach (var (info, player) in infoList)
|
||||
{
|
||||
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
||||
|
|
@ -127,14 +136,10 @@ namespace src.player.skills
|
|||
if (info.TransmitEntities.Contains(entity.Index))
|
||||
info.TransmitEntities.Remove(entity.Index);
|
||||
|
||||
var bombIndex = GetBombIndex();
|
||||
if (bombIndex == null) continue;
|
||||
if (bomb == null) continue;
|
||||
|
||||
var bombEntity = Utilities.GetEntityFromIndex<CBaseEntity>((int)bombIndex);
|
||||
if (bombEntity == null || !bombEntity.IsValid) continue;
|
||||
|
||||
if (info.TransmitEntities.Contains(bombEntity.Index))
|
||||
info.TransmitEntities.Remove(bombEntity.Index);
|
||||
if (info.TransmitEntities.Contains(bomb.Index))
|
||||
info.TransmitEntities.Remove(bomb.Index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -212,19 +217,8 @@ namespace src.player.skills
|
|||
particle.AcceptInput("Start");
|
||||
}
|
||||
|
||||
private static uint? GetBombIndex()
|
||||
{
|
||||
var bombEntities = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").ToList();
|
||||
if (bombEntities.Count == 0) return null;
|
||||
|
||||
var bomb = bombEntities.FirstOrDefault();
|
||||
if (bomb == null) return null;
|
||||
|
||||
return bomb.Index;
|
||||
}
|
||||
|
||||
// Wipe spotted state (pawn + carried bomb) so a disguised carrier produces no radar blip.
|
||||
private static void ClearSpottedState(CCSPlayerController player)
|
||||
private static void ClearSpottedState(CCSPlayerController player, CC4? bomb)
|
||||
{
|
||||
var pawn = player.PlayerPawn?.Value;
|
||||
if (pawn != null && pawn.IsValid)
|
||||
|
|
@ -234,7 +228,6 @@ namespace src.player.skills
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,19 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
playersInDark.TryRemove(playerIndex, out _);
|
||||
playersToTarget.TryRemove(playerIndex, out _);
|
||||
|
||||
foreach (var kvp in playersToTarget)
|
||||
if (kvp.Value == playerIndex)
|
||||
playersToTarget.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
lock (setLock)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,16 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
deafPlayers.TryRemove(playerIndex, out _);
|
||||
playersToTarget.TryRemove(playerIndex, out _);
|
||||
|
||||
foreach (var kvp in playersToTarget)
|
||||
if (kvp.Value == playerIndex)
|
||||
playersToTarget.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
foreach (var playerIndex in deafPlayers.Keys)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using src.utils;
|
||||
|
|
@ -9,10 +10,23 @@ namespace src.player.skills
|
|||
public class FriendlyFire : ISkill
|
||||
{
|
||||
private const Skills skillName = Skills.FriendlyFire;
|
||||
private static bool defaultAutoKick = true;
|
||||
private static bool autoKickOverridden;
|
||||
|
||||
public static void LoadSkill()
|
||||
{
|
||||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
|
||||
try { defaultAutoKick = ConVar.Find("mp_autokick")?.GetPrimitiveValue<bool>() ?? true; }
|
||||
catch { defaultAutoKick = true; }
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
if (!autoKickOverridden) return;
|
||||
|
||||
autoKickOverridden = false;
|
||||
Server.ExecuteCommand($"mp_autokick {(defaultAutoKick ? 1 : 0)}");
|
||||
}
|
||||
|
||||
public static void OnTakeDamage(DynamicHook h)
|
||||
|
|
@ -56,7 +70,11 @@ namespace src.player.skills
|
|||
float damage = param2.Damage;
|
||||
param2.Damage = 0;
|
||||
|
||||
if (!autoKickOverridden)
|
||||
{
|
||||
autoKickOverridden = true;
|
||||
Server.ExecuteCommand("mp_autokick 0");
|
||||
}
|
||||
|
||||
SkillUtils.AddHealth(
|
||||
victimPawn,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ namespace src.player.skills
|
|||
|
||||
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList)
|
||||
{
|
||||
if (invisiblePlayers.IsEmpty) return;
|
||||
|
||||
var bomb = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
|
||||
uint? bombOwnerIndex = bomb != null && bomb.IsValid ? bomb.OwnerEntity?.Index : null;
|
||||
|
||||
foreach (var (info, player) in infoList)
|
||||
{
|
||||
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
||||
|
|
@ -84,14 +89,11 @@ namespace src.player.skills
|
|||
if (info.TransmitEntities.Contains(entity.Index))
|
||||
info.TransmitEntities.Remove(entity.Index);
|
||||
|
||||
var bombIndex = GetBombIndex(playerController);
|
||||
if (bombIndex == null) continue;
|
||||
// Hide the bomb as well, but only while this hidden player is the one holding it.
|
||||
if (bomb == null || bombOwnerIndex != playerController.Index) continue;
|
||||
|
||||
var bombEntity = Utilities.GetEntityFromIndex<CBaseEntity>((int)bombIndex);
|
||||
if (bombEntity == null || !bombEntity.IsValid) continue;
|
||||
|
||||
if (info.TransmitEntities.Contains(bombEntity.Index))
|
||||
info.TransmitEntities.Remove(bombEntity.Index);
|
||||
if (info.TransmitEntities.Contains(bomb.Index))
|
||||
info.TransmitEntities.Remove(bomb.Index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -232,18 +234,6 @@ namespace src.player.skills
|
|||
playerInfo.PrintHTML = $"<font color='#FF0000'>{player.GetTranslation("disabled_weapon")}</font>";
|
||||
}
|
||||
|
||||
private static uint? GetBombIndex(CCSPlayerController player)
|
||||
{
|
||||
var bombEntities = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").ToList();
|
||||
if (bombEntities.Count == 0) return null;
|
||||
|
||||
var bomb = bombEntities.FirstOrDefault();
|
||||
if (bomb == null) return null;
|
||||
|
||||
if (bomb.OwnerEntity.Index != player.Index) return null;
|
||||
return bomb.Index;
|
||||
}
|
||||
|
||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#FFFFFF", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Epic) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,19 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
glitchedPlayers.TryRemove(playerIndex, out _);
|
||||
playersToTarget.TryRemove(playerIndex, out _);
|
||||
|
||||
foreach (var kvp in playersToTarget)
|
||||
if (kvp.Value == playerIndex)
|
||||
playersToTarget.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
lock (setLock)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace src.player.skills
|
|||
|
||||
public static void OnTick()
|
||||
{
|
||||
int tick = SkillsInfo.GetValue<int>(skillName, "tickCooldown");
|
||||
int tick = Math.Max(1, SkillsInfo.GetValue<int>(skillName, "tickCooldown"));
|
||||
if (Server.TickCount % tick != 0) return;
|
||||
|
||||
float smokeRadius = SkillsInfo.GetValue<float>(skillName, "smokeRadius");
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace src.player.skills
|
|||
|
||||
public static void OnTick()
|
||||
{
|
||||
int cooldown = (int)(SkillsInfo.GetValue<float>(skillName, "cooldown") * 64);
|
||||
int cooldown = Math.Max(1, (int)(SkillsInfo.GetValue<float>(skillName, "cooldown") * 64));
|
||||
if (Server.TickCount % cooldown != 0) return;
|
||||
|
||||
if (players.IsEmpty || jRandomSkills.Instance.GameRules?.FreezePeriod == true) return;
|
||||
|
|
|
|||
|
|
@ -340,6 +340,8 @@ namespace src.player.skills
|
|||
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||
if (victim == null || !victim.IsValid) return;
|
||||
|
||||
if (PlayerManager.GetPlayerByIndex(victim.Index)?.Skill != skillName) return;
|
||||
|
||||
if (playersInfo.TryGetValue(victim.Index, out var playerSkill))
|
||||
{
|
||||
if (playerSkill.CloneProp != null)
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ namespace src.player.skills
|
|||
replica.Teleport(nexPos, null, null);
|
||||
}, TimerFlags.REPEAT | TimerFlags.STOP_ON_MAPCHANGE);
|
||||
ActiveTimers.TryAdd(replicaIndex, moveTimer);
|
||||
});
|
||||
}, TimerFlags.STOP_ON_MAPCHANGE);
|
||||
|
||||
float duration = SkillsInfo.GetValue<float>(skillName, ducking ? "durationCrouch" : "durationRun");
|
||||
Instance.AddTimer(duration, () =>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,19 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"), false);
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
jammedPlayers.TryRemove(playerIndex, out _);
|
||||
jammerToTarget.TryRemove(playerIndex, out _);
|
||||
|
||||
foreach (var kvp in jammerToTarget)
|
||||
if (kvp.Value == playerIndex)
|
||||
jammerToTarget.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
lock (setLock)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,16 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
if (!jesters.TryRemove(playerIndex, out var jester)) return;
|
||||
|
||||
jester.Generation++;
|
||||
jester.Active = false;
|
||||
jester.Timer?.Kill();
|
||||
jester.Timer = null;
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
Server.NextWorldUpdate(() =>
|
||||
|
|
@ -37,7 +47,7 @@ namespace src.player.skills
|
|||
SkillUtils.ResetPrintHTML(player);
|
||||
|
||||
var pawn = player.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
|
||||
if (pawn == null || !pawn.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) continue;
|
||||
|
||||
var color = Color.FromArgb(255, 255, 255, 255);
|
||||
pawn.Render = color;
|
||||
|
|
@ -114,11 +124,10 @@ namespace src.player.skills
|
|||
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
|
||||
|
||||
if (!Instance.IsPlayerValid(victim)) return;
|
||||
var jesterVictim = GetJesterInfo(victim!.Index);
|
||||
|
||||
if (!Instance.IsPlayerValid(attacker))
|
||||
{
|
||||
if (jesterVictim != null && jesterVictim.Active)
|
||||
if (IsActiveJester(victim!.Index))
|
||||
{
|
||||
SkillUtils.RestoreHealth(victim);
|
||||
RestoreArmor(victim, @event.DmgArmor);
|
||||
|
|
@ -126,14 +135,19 @@ namespace src.player.skills
|
|||
return;
|
||||
}
|
||||
|
||||
var jesterAttacker = GetJesterInfo(attacker!.Index);
|
||||
if ((jesterVictim != null && jesterVictim.Active) || (jesterAttacker != null && jesterAttacker.Active))
|
||||
if (IsActiveJester(victim!.Index) || IsActiveJester(attacker!.Index))
|
||||
{
|
||||
SkillUtils.RestoreHealth(victim);
|
||||
RestoreArmor(victim, @event.DmgArmor);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsActiveJester(uint playerIndex)
|
||||
{
|
||||
if (GetJesterInfo(playerIndex)?.Active != true) return false;
|
||||
return PlayerManager.GetPlayerByIndex(playerIndex)?.Skill == skillName;
|
||||
}
|
||||
|
||||
private static void RestoreArmor(CCSPlayerController? victim, int dmgArmor)
|
||||
{
|
||||
if (victim == null || !victim.IsValid || dmgArmor <= 0) return;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,16 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
bannedPlayers.TryRemove(playerIndex, out _);
|
||||
playersToTarget.TryRemove(playerIndex, out _);
|
||||
|
||||
foreach (var kvp in playersToTarget)
|
||||
if (kvp.Value == playerIndex)
|
||||
playersToTarget.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
bannedPlayers.Clear();
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ namespace src.player.skills
|
|||
public static void NewRound()
|
||||
{
|
||||
playersInAction.Clear();
|
||||
|
||||
if (!hooked) return;
|
||||
|
||||
hooked = false;
|
||||
Shoot_Secondary?.Unhook(ShootSecondary, HookMode.Pre);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,16 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
playersFOV.TryRemove(playerIndex, out _);
|
||||
playersToTarget.TryRemove(playerIndex, out _);
|
||||
|
||||
foreach (var kvp in playersToTarget)
|
||||
if (kvp.Value == playerIndex)
|
||||
playersToTarget.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
foreach (var playerIndex in playersFOV.Keys)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ namespace src.player.skills
|
|||
|
||||
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList)
|
||||
{
|
||||
if (invisiblePlayers.IsEmpty) return;
|
||||
|
||||
var bomb = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").FirstOrDefault();
|
||||
uint? bombOwnerIndex = bomb != null && bomb.IsValid ? bomb.OwnerEntity?.Index : null;
|
||||
|
||||
foreach (var (info, player) in infoList)
|
||||
{
|
||||
if (player == null || !player.IsValid || player.Team == CsTeam.Spectator) continue;
|
||||
|
|
@ -86,13 +91,10 @@ namespace src.player.skills
|
|||
if (info.TransmitEntities.Contains(entity.Index))
|
||||
info.TransmitEntities.Remove(entity.Index);
|
||||
|
||||
var bombIndex = GetBombIndex(playerController);
|
||||
if (bombIndex == null) continue;
|
||||
var bombEntity = Utilities.GetEntityFromIndex<CBaseEntity>((int)bombIndex);
|
||||
if (bombEntity == null || !bombEntity.IsValid) continue;
|
||||
if (bomb == null || bombOwnerIndex != playerController.Index) continue;
|
||||
|
||||
if (info.TransmitEntities.Contains(bombEntity.Index))
|
||||
info.TransmitEntities.Remove(bombEntity.Index);
|
||||
if (info.TransmitEntities.Contains(bomb.Index))
|
||||
info.TransmitEntities.Remove(bomb.Index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -229,18 +231,6 @@ namespace src.player.skills
|
|||
particle.AcceptInput("Start");
|
||||
}
|
||||
|
||||
private static uint? GetBombIndex(CCSPlayerController player)
|
||||
{
|
||||
var bombEntities = Utilities.FindAllEntitiesByDesignerName<CC4>("weapon_c4").ToList();
|
||||
if (bombEntities.Count == 0) return null;
|
||||
|
||||
var bomb = bombEntities.FirstOrDefault();
|
||||
if (bomb == null) return null;
|
||||
|
||||
if (bomb.OwnerEntity.Index != player.Index) return null;
|
||||
return bomb.Index;
|
||||
}
|
||||
|
||||
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#dedede", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float idlePercentInvisibility = .3f, float duckPercentInvisibility = .3f, float knifePercentInvisibility = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
|
||||
{
|
||||
public float IdlePercentInvisibility { get; set; } = idlePercentInvisibility;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using CounterStrikeSharp.API;
|
|||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using src.utils;
|
||||
using System.Collections.Concurrent;
|
||||
using static src.jRandomSkills;
|
||||
|
||||
namespace src.player.skills
|
||||
|
|
@ -10,6 +11,9 @@ namespace src.player.skills
|
|||
{
|
||||
private const Skills skillName = Skills.NoRecoil;
|
||||
|
||||
private static readonly ConcurrentDictionary<uint, byte> holders = [];
|
||||
private static bool noSpreadActive;
|
||||
|
||||
public static void LoadSkill()
|
||||
{
|
||||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
|
|
@ -17,21 +21,33 @@ namespace src.player.skills
|
|||
|
||||
public static void NewRound()
|
||||
{
|
||||
var players = Utilities.GetPlayers();
|
||||
foreach (var player in players)
|
||||
DisableSkill(player);
|
||||
holders.Clear();
|
||||
ApplyNoSpread(false);
|
||||
}
|
||||
|
||||
public static void EnableSkill(CCSPlayerController player)
|
||||
{
|
||||
if (player == null || !player.IsValid) return;
|
||||
Server.ExecuteCommand("weapon_accuracy_nospread 1");
|
||||
|
||||
holders.TryAdd(player.Index, 0);
|
||||
ApplyNoSpread(true);
|
||||
}
|
||||
|
||||
public static void DisableSkill(CCSPlayerController player)
|
||||
{
|
||||
if (player == null || !player.IsValid) return;
|
||||
Server.ExecuteCommand("weapon_accuracy_nospread 0");
|
||||
|
||||
holders.TryRemove(player.Index, out _);
|
||||
if (holders.IsEmpty)
|
||||
ApplyNoSpread(false);
|
||||
}
|
||||
|
||||
private static void ApplyNoSpread(bool enabled)
|
||||
{
|
||||
if (noSpreadActive == enabled) return;
|
||||
|
||||
noSpreadActive = enabled;
|
||||
Server.ExecuteCommand($"weapon_accuracy_nospread {(enabled ? 1 : 0)}");
|
||||
}
|
||||
|
||||
public static void OnTick()
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace src.player.skills
|
|||
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;
|
||||
private static bool c4TimerOverridden;
|
||||
|
||||
public static void LoadSkill()
|
||||
{
|
||||
|
|
@ -25,6 +26,7 @@ namespace src.player.skills
|
|||
public static void EnableSkill(CCSPlayerController player)
|
||||
{
|
||||
// At round start (not at plant) so the client HUD/alert countdown is right before the plant completes.
|
||||
c4TimerOverridden = true;
|
||||
Server.ExecuteCommand($"mp_c4timer {SkillsInfo.GetValue<int>(skillName, "extraC4BlowTime")}");
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +72,9 @@ namespace src.player.skills
|
|||
DisableSkill(player);
|
||||
plantingPlayers.Clear();
|
||||
|
||||
if (!c4TimerOverridden) return;
|
||||
|
||||
c4TimerOverridden = false;
|
||||
Server.ExecuteCommand($"mp_c4timer {defaultC4Timer}");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,19 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"), false);
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
poisonedPlayers.TryRemove(playerIndex, out _);
|
||||
playersToTarget.TryRemove(playerIndex, out _);
|
||||
|
||||
foreach (var kvp in playersToTarget)
|
||||
if (kvp.Value == playerIndex)
|
||||
playersToTarget.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
lock (setLock)
|
||||
|
|
@ -29,7 +42,7 @@ namespace src.player.skills
|
|||
|
||||
public static void OnTick()
|
||||
{
|
||||
int cooldown = (int)(64 * SkillsInfo.GetValue<float>(skillName, "Cooldown"));
|
||||
int cooldown = Math.Max(1, (int)(64 * SkillsInfo.GetValue<float>(skillName, "Cooldown")));
|
||||
|
||||
if (Server.TickCount % cooldown == 0)
|
||||
{
|
||||
|
|
@ -43,7 +56,7 @@ namespace src.player.skills
|
|||
var pawn = player.PlayerPawn.Value;
|
||||
if (pawn == null || !pawn.IsValid) continue;
|
||||
|
||||
if (Jester.GetJesterInfo(playerIndex)?.Active == true) continue;
|
||||
if (Jester.IsActiveJester(playerIndex)) continue;
|
||||
|
||||
if (pawn.Health <= SkillsInfo.GetValue<int>(skillName, "MinHealth")) continue;
|
||||
SkillUtils.TakeHealth(pawn, SkillsInfo.GetValue<int>(skillName, "Damage"));
|
||||
|
|
|
|||
|
|
@ -28,6 +28,19 @@ namespace src.player.skills
|
|||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
lock (setLock)
|
||||
{
|
||||
bannedPlayers.TryRemove(playerIndex, out _);
|
||||
playersToTarget.TryRemove(playerIndex, out _);
|
||||
|
||||
foreach (var kvp in playersToTarget)
|
||||
if (kvp.Value == playerIndex)
|
||||
playersToTarget.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
lock (setLock)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace src.player.skills
|
|||
public class RadarHack : ISkill
|
||||
{
|
||||
private const Skills skillName = Skills.RadarHack;
|
||||
private static readonly Skills[] hidingSkills = [Skills.Ghost, Skills.Ninja, Skills.C4Camouflage];
|
||||
|
||||
public static void LoadSkill()
|
||||
{
|
||||
|
|
@ -43,8 +44,9 @@ namespace src.player.skills
|
|||
var enemyPawn = enemyEvent.PlayerPawn.Value;
|
||||
if (enemyPawn == null || !enemyPawn.IsValid) continue;
|
||||
|
||||
// Invisibility (low render alpha) beats the radar hack.
|
||||
if (enemyPawn.Render.A < 200) continue;
|
||||
var enemyInfo = PlayerManager.GetPlayerByIndex(enemyEvent.Index);
|
||||
if (enemyInfo != null && Array.IndexOf(hidingSkills, enemyInfo.Skill) >= 0 && 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));
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ namespace src.player.skills
|
|||
if (attacker == null || !attacker.IsValid) return;
|
||||
if (victimEvent == null || !victimEvent.IsValid || !victim.PawnIsAlive) return;
|
||||
|
||||
if (PlayerManager.GetPlayerByIndex(victimEvent!.Index)?.Skill != skillName) return;
|
||||
|
||||
if (SkillPlayerInfo.TryGetValue(victimEvent!.Index, out var skillInfo))
|
||||
{
|
||||
if (!skillInfo.CanUse) return;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ namespace src.player.skills
|
|||
|
||||
public static void OnTick()
|
||||
{
|
||||
if (Server.TickCount % (int)(64 * SkillsInfo.GetValue<float>(skillName, "cooldown")) != 0) return;
|
||||
int cooldown = Math.Max(1, (int)(64 * SkillsInfo.GetValue<float>(skillName, "cooldown")));
|
||||
if (Server.TickCount % cooldown != 0) return;
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
{
|
||||
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace src.player.skills
|
|||
public class SecondLife : ISkill
|
||||
{
|
||||
private const Skills skillName = Skills.SecondLife;
|
||||
private static readonly ConcurrentDictionary<nint, byte> usedThisRound = [];
|
||||
private static readonly ConcurrentDictionary<nint, int> usedThisRound = [];
|
||||
private static readonly object setLock = new();
|
||||
|
||||
public static void LoadSkill()
|
||||
|
|
@ -45,7 +45,13 @@ namespace src.player.skills
|
|||
if (victimInfo == null || victimInfo.Skill != skillName) return;
|
||||
|
||||
if (info.Damage < victimPawn.Health) return;
|
||||
if (usedThisRound.ContainsKey(victim.Handle)) return;
|
||||
|
||||
if (usedThisRound.TryGetValue(victim.Handle, out int savedTick))
|
||||
{
|
||||
if (savedTick == Server.TickCount)
|
||||
info.Damage = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
lock (setLock)
|
||||
{
|
||||
|
|
@ -54,18 +60,18 @@ namespace src.player.skills
|
|||
var spawnpoint = SkillUtils.GetSpawnPointVector(victim);
|
||||
if (spawnpoint == null) return; // no clean respawn point -> let the normal death happen
|
||||
|
||||
usedThisRound.TryAdd(victim.Handle, 0);
|
||||
usedThisRound.TryAdd(victim.Handle, Server.TickCount);
|
||||
info.Damage = 0;
|
||||
|
||||
int startHealth = SkillsInfo.GetValue<int>(skillName, "startHealth");
|
||||
victimPawn.Health = SkillsInfo.GetValue<int>(skillName, "startHealth");
|
||||
Utilities.SetStateChanged(victimPawn, "CBaseEntity", "m_iHealth");
|
||||
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ namespace src.player.skills
|
|||
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;
|
||||
private static bool c4TimerOverridden;
|
||||
|
||||
public static void LoadSkill()
|
||||
{
|
||||
|
|
@ -22,11 +23,15 @@ namespace src.player.skills
|
|||
public static void EnableSkill(CCSPlayerController player)
|
||||
{
|
||||
// At round start (not at plant) so the client HUD/alert countdown is right before the plant completes.
|
||||
c4TimerOverridden = true;
|
||||
Server.ExecuteCommand($"mp_c4timer {SkillsInfo.GetValue<int>(skillName, "detonationTime")}");
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
if (!c4TimerOverridden) return;
|
||||
|
||||
c4TimerOverridden = false;
|
||||
Server.ExecuteCommand($"mp_c4timer {defaultC4Timer}");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,20 +59,20 @@ namespace src.player.skills
|
|||
foreach (var player in Utilities.GetPlayers())
|
||||
if (cameras.TryGetValue(player.Index, out var cameraInfo) && cameraInfo.Item2 != 0)
|
||||
{
|
||||
if (player == null || !player.IsValid) return;
|
||||
if (player == null || !player.IsValid) continue;
|
||||
|
||||
var enemy = Utilities.GetPlayerFromIndex((int)cameraInfo.Item3);
|
||||
if (enemy == null || !enemy.IsValid || enemy.PlayerPawn == null)
|
||||
{
|
||||
ChangeCamera(player, true);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
var enemyPawn = enemy.PlayerPawn.Value;
|
||||
if (enemyPawn == null || !enemyPawn.IsValid)
|
||||
{
|
||||
ChangeCamera(player, true);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (enemyPawn.Health <= 0 || (player.PlayerPawn?.Value != null && player.PlayerPawn.Value.Health <= 0))
|
||||
|
|
@ -117,6 +117,10 @@ namespace src.player.skills
|
|||
pawn.CameraServices.ViewEntity.Raw = orginalCameraRaw;
|
||||
|
||||
Utilities.SetStateChanged(pawn, "CBasePlayerPawn", "m_pCameraServices");
|
||||
|
||||
if (forceToDefault && cameras.TryGetValue(player.Index, out var current) && current.Item2 != 0)
|
||||
cameras[player.Index] = (current.Item1, 0, current.Item3);
|
||||
|
||||
BlockWeapon(player, !defaultCam);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ namespace src.player.skills
|
|||
|
||||
public static void OnTick()
|
||||
{
|
||||
int tick = SkillsInfo.GetValue<int>(skillName, "tickCooldown");
|
||||
int tick = Math.Max(1, SkillsInfo.GetValue<int>(skillName, "tickCooldown"));
|
||||
if (Server.TickCount % tick != 0) return;
|
||||
|
||||
float smokeRadius = SkillsInfo.GetValue<float>(skillName, "smokeRadius");
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ namespace src.player.skills
|
|||
{
|
||||
lock (setLock)
|
||||
{
|
||||
if (infectedPlayers.IsEmpty && playersToTarget.IsEmpty) return;
|
||||
|
||||
foreach (var player in Utilities.GetPlayers())
|
||||
DisableSkill(player);
|
||||
|
||||
|
|
|
|||
|
|
@ -296,8 +296,8 @@ namespace src.utils
|
|||
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
||||
if (playerInfo == null) return false;
|
||||
|
||||
var jester = Jester.GetJesterInfo(player.Index);
|
||||
if (jester?.Active == true) return false;
|
||||
if (playerInfo.Skill == Skills.Jester && Jester.GetJesterInfo(player.Index)?.Active == true)
|
||||
return false;
|
||||
|
||||
if (playerInfo.Skill == Skills.GodMode && GodMode.HaveHodMode(player.Index))
|
||||
return false;
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue