v1.2.3.b8

- #### Shared Systems
    - **PlayerManager.FillSkillHolders** - Skill holders are resolved from the player index instead of scanning every controller each tick, replacing the per-skill full player loop used by ten skills.
    - **PlayerManager.GetControllerByPawn** - Tick-cached pawn to controller map, replacing the linear observer-target scan.
    - **PlayerManager.IsServerIdle** - Tick-cached idle check shared by the debug and perf writers.
    - **SkillUtils.IsFriendlyFireBlocked** - Now takes the skill, so the new per-skill `FriendlyFire` key and the server convars are resolved in one place. The convar-only overload remains for victim-side skills.
    - **SkillUtils.TerminateRound** - Accepts the player who ended the round and pays out the round-end money that the engine skips on plugin-terminated rounds.

- #### Performance
    - **OnTick(hud)** - Average cost down 17% against the previous build.
    - **CheckTransmit** - Average cost down 25%, peak down 39%.
    - **OnTick(skills)** - Peak cost down 62%.
    - **Host stalls** - Real freezes measured with players connected dropped from six to two across 785 rounds.
    - **DecoyRing** - Beam creation is spread across four frames instead of spawning the whole ring in one tick.
    - **Fourteen skills** - `OnTick` now returns immediately when the skill holds no state.
    - **Berserker, BladeMaster, BunnyHop, DemonEye, Flash, PawelJumper, Pilot, Poison, QuickShot, RadarHack, Regeneration** - Switched to the shared skill-holder list; config reads hoisted out of per-player loops.

- #### Fixes
    - **GravityDecoy** - Restored a hardcoded gravity of 1.0 when leaving the field, permanently breaking Astronaut. Each pawn's own scale is now captured and restored.
    - **PsychicDefusing** - The bomb location stayed unset when the skill was assigned after the plant, the stored position pointed into the entity and dangled once the bomb was killed, and setup was silently skipped when the pawn was not ready.
    - **PsychicDefusing, FragileBomb** - Winning teams were paid nothing when the plugin ended the round. Winners are now paid from the matching `cash_*` convars, with the defuser bonus on top.
    - **ToxicSmoke, HealingSmoke** - Smoke kept damaging and healing after its owner lost the skill, and expiry left behind smokes whose owner had already lost it. HealingSmoke never tracked an owner at all.
    - **EmpGrenade** - Crosshair and radar stayed jammed forever, because `OnTick` stops being dispatched once nobody holds the skill and the jam timer never expired.
    - **ThrowingKnife** - Losing the skill while the knife was thrown destroyed it without giving one back.
    - **OnlyHead, ReactiveArmor** - Both ignored fall and world damage, which has no player attacker.
    - **Gambler** - Refreshing consumed the one-shot instead of rolling two new skills, and the refresh row lost its colour prefix to the text-direction wrapper.
    - **Berserker, Flash** - The falling-speed clamp fired while noclipping.
    - **ExplodingBarrel** - The prop spawned without a model and received it a frame later; the model is now passed through `CEntityKeyValues` at spawn.
    - **FastReload** - Now has a cooldown, five seconds by default, with a HUD countdown. The cooldown only starts when a magazine is actually refilled.
    - **Config** - Missing-key migration now descends into nested sections, so keys under `HtmlHudCustomisation` are detected.

- #### Damage Hook
    - **OnEntityTakeDamagePre / OnEntityTakeDamagePost** - Replaced the deprecated `CBaseEntity_TakeDamageOldFunc` hook across 27 files.
    - **Teammate damage** - The new hook runs before the engine zeroes friendly fire, so attacker-side modifiers need explicit guards. Added to OneShot, Cutter, Assassin, Soldier, Berserker, Nemesis and Punisher.
    - **FriendlyFire** - New per-skill key on twelve skills, defaulting to `true`. Setting it to `false` stops teammates being affected, the way LongKnife and KillerFlash already allow. Covers both the damage amplifiers and the explosives that previously only reduced teammate damage.

- #### Menu
    - **WSADMenuVisibleItems** - New setting replacing the hardcoded three visible menu rows, clamped between one and ten.
    - **WSAD menu** - The final menu row keeps its `#RRGGBB` colour prefix instead of losing it to the text-direction wrapper.

- #### Config Migration
    - **skillsInfo.json** - Missing keys are written back to the file on load instead of only being defaulted in memory. Existing values are preserved.
    - **playersLanguage.json** - Now grouped by language instead of one row per player, which cuts a ten thousand player file by 20%. Old files are converted on load and invalid SteamIDs are dropped.

- #### Debug
    - **Perf samples** - Report `p50`, `p95` and `p99` alongside the average and maximum.
    - **Per-skill sampling** - Every skill is now sampled, instead of only logging the calls that exceeded two milliseconds. The measurement was already running; only the reporting threshold discarded it.
    - **Load context** - Aggregate lines carry the player, alive, bot, active-skill and round counts, so sessions can be compared under different loads.
    - **ENTITIES** - Tracked entities are broken down by type.
    - **STALL** - Lines carry the tick, tracked entity count and load context, which separates map-load pauses from genuine freezes.
    - **DMGHOOK** - Reports how often the attacker and ability resolve on the damage hook.
    - **Idle server** - Nothing is written while the server sits empty, apart from lifecycle lines and player connects and disconnects.

- #### Cleanup
    - **Compiler warnings** - All twelve resolved; the build is clean.
    - **SkillUtils.SetPlayerCollisions** - Removed, its body started with an unconditional `return`.
    - **Damage hook aliases** - Dropped the `param` and `param2` locals the migration left behind across 27 files.
This commit is contained in:
ByDexter 2026-08-20 18:23:13 +03:00
parent b98842212f
commit d016d42780
82 changed files with 1140 additions and 544 deletions

View file

@ -1,14 +1,15 @@
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using System.Diagnostics.CodeAnalysis;
using WASDSharedAPI; using WASDSharedAPI;
namespace WASDMenuAPI.Classes; namespace WASDMenuAPI.Classes;
public class WasdMenu : IWasdMenu public class WasdMenu : IWasdMenu
{ {
public string Title { get; set; } = ""; [AllowNull] public string Title { get; set; } = "";
public string ItemText { get; set; } = ""; [AllowNull] public string ItemText { get; set; } = "";
public string ItemHoverText { get; set; } = ""; [AllowNull] public string ItemHoverText { get; set; } = "";
public string ControlText { get; set; } = ""; [AllowNull] public string ControlText { get; set; } = "";
public LinkedList<IWasdMenuOption>? Options { get; set; } = new(); public LinkedList<IWasdMenuOption>? Options { get; set; } = new();
public LinkedListNode<IWasdMenuOption>? Prev { get; set; } = null; public LinkedListNode<IWasdMenuOption>? Prev { get; set; } = null;
public LinkedListNode<IWasdMenuOption> Add(string display, Action<CCSPlayerController, IWasdMenuOption> onChoice) public LinkedListNode<IWasdMenuOption> Add(string display, Action<CCSPlayerController, IWasdMenuOption> onChoice)

View file

@ -13,7 +13,8 @@ public class WasdMenuPlayer
public LinkedListNode<IWasdMenuOption>? CurrentChoice = null; public LinkedListNode<IWasdMenuOption>? CurrentChoice = null;
public LinkedListNode<IWasdMenuOption>? MenuStart = null; public LinkedListNode<IWasdMenuOption>? MenuStart = null;
public string CenterHtml = ""; public string CenterHtml = "";
public int VisibleOptions = 3; public static int DefaultVisibleOptions = 3;
public int VisibleOptions => DefaultVisibleOptions;
public PlayerButtons Buttons { get; set; } public PlayerButtons Buttons { get; set; }
public bool Paused = false; public bool Paused = false;
@ -245,10 +246,11 @@ public class WasdMenuPlayer
return; return;
} }
if (MainMenu == null || MainMenu.Options == null) var options = MainMenu?.Options;
if (MainMenu == null || options == null)
return; return;
MainMenu.Options.Clear(); options.Clear();
bool choiceExists = false; bool choiceExists = false;
foreach (var item in list) foreach (var item in list)
@ -257,19 +259,19 @@ public class WasdMenuPlayer
{ {
OptionDisplay = item.Key, OptionDisplay = item.Key,
OnChoose = item.Value, OnChoose = item.Value,
Index = MainMenu.Options.Count, Index = options.Count,
Parent = MainMenu Parent = MainMenu
}; };
MainMenu.Options.AddLast(newOption); options.AddLast(newOption);
if (CurrentChoice?.Value?.OptionDisplay == item.Key) if (CurrentChoice?.Value?.OptionDisplay == item.Key)
{ {
CurrentChoice = MainMenu.Options?.Last; CurrentChoice = options.Last;
choiceExists = true; choiceExists = true;
} }
} }
if (!choiceExists) if (!choiceExists)
CurrentChoice = MainMenu.Options?.First; CurrentChoice = options.First;
if (CurrentChoice != null) if (CurrentChoice != null)
{ {

View file

@ -31,7 +31,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]"; public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)"; public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus."; public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
public override string ModuleVersion => "1.2.3.b7"; public override string ModuleVersion => "1.2.3.b8";
public override void Load(bool hotReload) public override void Load(bool hotReload)
{ {
@ -165,7 +165,7 @@ namespace src
long perfStart = PerfLog.Start(); long perfStart = PerfLog.Start();
var result = method.Invoke(null, param); var result = method.Invoke(null, param);
PerfLog.End($"SkillAction {skill}.{methodName}", perfStart, 2.0); PerfLog.Sample($"SkillAction {skill}.{methodName}", perfStart, 30.0, 1.0);
return result; return result;
} }
@ -176,7 +176,7 @@ namespace src
CommandManager.RegisterCommand(definition); CommandManager.RegisterCommand(definition);
} }
internal bool IsPlayerValid(CCSPlayerController? player) internal bool IsPlayerValid([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] CCSPlayerController? player)
{ {
return player != null && player.IsValid && player.PlayerPawn?.Value != null && player.PlayerPawn.Value.IsValid && player.PlayerPawn.Value.LifeState == (byte)LifeState_t.LIFE_ALIVE; return player != null && player.IsValid && player.PlayerPawn?.Value != null && player.PlayerPawn.Value.IsValid && player.PlayerPawn.Value.LifeState == (byte)LifeState_t.LIFE_ALIVE;
} }

View file

@ -31,7 +31,7 @@ namespace src.player
{ {
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return HookResult.Continue; if (player == null || !player.IsValid) return HookResult.Continue;
WriteToDebug($"{(player.IsBot ? "Bot" : "Player")} {player.PlayerName} joined the game.", DebugCategory.Round); WriteToDebug($"{(player.IsBot ? "Bot" : "Player")} {player.PlayerName} joined the game.", DebugCategory.Round, ignoreIdle: true);
return HookResult.Continue; return HookResult.Continue;
}); });
@ -39,7 +39,7 @@ namespace src.player
{ {
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return HookResult.Continue; if (player == null || !player.IsValid) return HookResult.Continue;
WriteToDebug($"{(player.IsBot ? "Bot" : "Player")} {player.PlayerName} disconnected.", DebugCategory.Round); WriteToDebug($"{(player.IsBot ? "Bot" : "Player")} {player.PlayerName} disconnected.", DebugCategory.Round, ignoreIdle: true);
return HookResult.Continue; return HookResult.Continue;
}); });
@ -101,7 +101,7 @@ namespace src.player
if (Config.DebugEnabled(DebugCategory.Damage)) if (Config.DebugEnabled(DebugCategory.Damage))
{ {
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre); Instance.RegisterListener<OnEntityTakeDamagePre>(OnTakeDamage);
damageHooked = true; damageHooked = true;
} }
} }
@ -110,7 +110,7 @@ namespace src.player
{ {
if (damageHooked) if (damageHooked)
{ {
try { VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre); } try { Instance.RemoveListener<OnEntityTakeDamagePre>(OnTakeDamage); }
catch { } catch { }
damageHooked = false; damageHooked = false;
} }
@ -118,16 +118,13 @@ namespace src.player
lock (_writeLock) { _writer?.Dispose(); _writer = null; } lock (_writeLock) { _writer?.Dispose(); _writer = null; }
} }
private static HookResult OnTakeDamage(DynamicHook h) private static HookResult OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return HookResult.Continue; return HookResult.Continue;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player") if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return HookResult.Continue; return HookResult.Continue;
@ -141,10 +138,10 @@ namespace src.player
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (playerInfo == null) return HookResult.Continue; if (playerInfo == null) return HookResult.Continue;
var nativeHitGroup = SkillUtils.GetHitGroup(param2); var nativeHitGroup = SkillUtils.GetHitGroup(damageInfo);
WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} took damage from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}. " + WriteToDebug($"{(victim.IsBot ? "Bot" : "Player")} {victim.PlayerName} took damage from {(attacker.IsBot ? "bot" : "player")} {attacker.PlayerName}. " +
$"[dmg={param2.Damage:0.#} hp={victimPawn.Health}/{victimPawn.MaxHealth} armor={victimPawn.ArmorValue} hitgroup={nativeHitGroup} " + $"[dmg={damageInfo.Damage:0.#} hp={victimPawn.Health}/{victimPawn.MaxHealth} armor={victimPawn.ArmorValue} hitgroup={nativeHitGroup} " +
$"takes={victimPawn.TakesDamage} vskill={PlayerManager.GetPlayerByIndex(victim.Index)?.Skill} askill={playerInfo.Skill}]", DebugCategory.Damage); $"takes={victimPawn.TakesDamage} vskill={PlayerManager.GetPlayerByIndex(victim.Index)?.Skill} askill={playerInfo.Skill}]", DebugCategory.Damage);
return HookResult.Continue; return HookResult.Continue;
} }
@ -163,11 +160,14 @@ namespace src.player
return gameRules.WarmupPeriod ? " [WARMUP]" : string.Empty; return gameRules.WarmupPeriod ? " [WARMUP]" : string.Empty;
} }
public static void WriteToDebug(string message, DebugCategory category = DebugCategory.Core) public static void WriteToDebug(string message, DebugCategory category = DebugCategory.Core, bool ignoreIdle = false)
{ {
if (!Config.DebugEnabled(category)) if (!Config.DebugEnabled(category))
return; return;
if (!ignoreIdle && category != DebugCategory.Core && PlayerManager.IsServerIdle())
return;
lock (_writeLock) lock (_writeLock)
{ {
_writer ??= CreateWriter(); _writer ??= CreateWriter();

View file

@ -84,26 +84,58 @@ namespace src.player
} }
} }
private static HookResult OnTakeDamage(DynamicHook h) private static HookResult OnEntityTakeDamagePre(CBaseEntity entity, CTakeDamageInfo info)
{ {
lock (setLock) lock (setLock)
{ {
DispatchOnTakeDamage(h); CountDamageResolution(info);
object[] args = [entity, info];
DispatchOnTakeDamage(entity, info, args);
if (Fortnite.skillInThisRound == true && if (Fortnite.skillInThisRound == true &&
!Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite)) !Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite))
Instance.SkillAction("Fortnite", "OnTakeDamage", [h]); Instance.SkillAction("Fortnite", "OnTakeDamage", args);
return HookResult.Continue; return HookResult.Continue;
} }
} }
private static HookResult OnTakeDamagePost(DynamicHook h) 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) lock (setLock)
{ {
DispatchOnTakeDamage(h, true); DispatchOnTakeDamage(entity, info, [entity, info, result], true);
return HookResult.Continue;
} }
} }

View file

@ -13,8 +13,8 @@ public interface ISkill
public static void UseSkill(CCSPlayerController _) { } public static void UseSkill(CCSPlayerController _) { }
public static void TypeSkill(CCSPlayerController _, string[] __) { } public static void TypeSkill(CCSPlayerController _, string[] __) { }
public static void OnTakeDamage(DynamicHook _) { } public static void OnTakeDamage(CBaseEntity _, CTakeDamageInfo __) { }
public static void OnTakeDamagePost(DynamicHook _) { } public static void OnTakeDamagePost(CBaseEntity _, CTakeDamageInfo __, CTakeDamageResult ___) { }
public static void OnEntitySpawned(CEntityInstance _) { } public static void OnEntitySpawned(CEntityInstance _) { }
public static void OnTick() { } public static void OnTick() { }
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList _) { } public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList _) { }

View file

@ -47,39 +47,98 @@ namespace src.player
Write($"{label} took {ms:F2}ms"); Write($"{label} took {ms:F2}ms");
} }
private static readonly double[] bucketBoundsMs = [0.05, 0.1, 0.25, 0.5, 1, 2, 4, 8, 16, 32, double.MaxValue];
private sealed class Aggregate private sealed class Aggregate
{ {
public double TotalMs; public double TotalMs;
public double MaxMs; public double MaxMs;
public int Count; public int Count;
public readonly int[] Buckets = new int[bucketBoundsMs.Length];
public DateTime WindowStart = DateTime.Now; public DateTime WindowStart = DateTime.Now;
public void Add(double ms)
{
TotalMs += ms;
Count++;
if (ms > MaxMs) MaxMs = ms;
for (int i = 0; i < bucketBoundsMs.Length; i++)
if (ms <= bucketBoundsMs[i]) { Buckets[i]++; break; }
}
public double Percentile(double fraction)
{
int target = (int)Math.Ceiling(Count * fraction);
if (target < 1) target = 1;
int seen = 0;
for (int i = 0; i < bucketBoundsMs.Length; i++)
{
seen += Buckets[i];
if (seen >= target)
return bucketBoundsMs[i] == double.MaxValue ? MaxMs : bucketBoundsMs[i];
}
return MaxMs;
}
public void Reset()
{
TotalMs = 0;
MaxMs = 0;
Count = 0;
Array.Clear(Buckets);
WindowStart = DateTime.Now;
}
} }
private static readonly ConcurrentDictionary<string, Aggregate> _aggregates = new(); private static readonly ConcurrentDictionary<string, Aggregate> _aggregates = new();
// Per-tick measurement: accumulates and logs an avg/max summary every few seconds, // Per-tick measurement: accumulates and logs an avg/percentile summary every few seconds,
// so tick paths do not produce one log line per tick. // so tick paths do not produce one log line per tick.
public static void Sample(string label, long startTimestamp, double reportSeconds = 5.0, double maxThresholdMs = 0.5) public static void Sample(string label, long startTimestamp, double reportSeconds = 5.0, double maxThresholdMs = 0.5)
{ {
if (startTimestamp == 0 || !Enabled) return; if (startTimestamp == 0 || !Enabled) return;
double ms = (Stopwatch.GetTimestamp() - startTimestamp) * 1000.0 / Stopwatch.Frequency;
var agg = _aggregates.GetOrAdd(label, _ => new Aggregate()); var agg = _aggregates.GetOrAdd(label, _ => new Aggregate());
if (PlayerManager.IsServerIdle())
{
lock (agg) agg.Reset();
return;
}
double ms = (Stopwatch.GetTimestamp() - startTimestamp) * 1000.0 / Stopwatch.Frequency;
lock (agg) lock (agg)
{ {
agg.TotalMs += ms; agg.Add(ms);
agg.Count++;
if (ms > agg.MaxMs) agg.MaxMs = ms;
if ((DateTime.Now - agg.WindowStart).TotalSeconds < reportSeconds) return; if ((DateTime.Now - agg.WindowStart).TotalSeconds < reportSeconds) return;
if (agg.MaxMs >= maxThresholdMs) if (agg.MaxMs >= maxThresholdMs)
Write($"{label} avg={agg.TotalMs / agg.Count:F2}ms max={agg.MaxMs:F2}ms samples={agg.Count}"); Write($"{label} avg={agg.TotalMs / agg.Count:F2}ms p50={agg.Percentile(.5):F2}ms " +
$"p95={agg.Percentile(.95):F2}ms p99={agg.Percentile(.99):F2}ms max={agg.MaxMs:F2}ms " +
$"samples={agg.Count}{ContextSuffix()}");
agg.TotalMs = 0; agg.Reset();
agg.MaxMs = 0; }
agg.Count = 0; }
agg.WindowStart = DateTime.Now;
public static Func<string>? ContextProvider;
private static string ContextSuffix()
{
var provider = ContextProvider;
if (provider == null) return string.Empty;
try
{
string context = provider();
return string.IsNullOrEmpty(context) ? string.Empty : $" {context}";
}
catch
{
return string.Empty;
} }
} }

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Events; using CounterStrikeSharp.API.Modules.Events;
@ -80,8 +80,10 @@ namespace src.player
Instance.HookUserMessage(208, PlayerMakeSound); Instance.HookUserMessage(208, PlayerMakeSound);
Instance.HookUserMessage(207, GetPrintToCenterHtml); Instance.HookUserMessage(207, GetPrintToCenterHtml);
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre); Instance.RegisterListener<OnEntityTakeDamagePre>(OnEntityTakeDamagePre);
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamagePost, HookMode.Post); Instance.RegisterListener<OnEntityTakeDamagePost>(OnEntityTakeDamagePost);
PerfLog.ContextProvider = PerfContext;
Instance.RegisterEventHandler<EventBulletImpact>(BulletImpact); Instance.RegisterEventHandler<EventBulletImpact>(BulletImpact);
@ -96,8 +98,8 @@ namespace src.player
public static void Unload() public static void Unload()
{ {
TryUnhook(() => VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre)); TryUnhook(() => Instance.RemoveListener<OnEntityTakeDamagePre>(OnEntityTakeDamagePre));
TryUnhook(() => VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamagePost, HookMode.Post)); TryUnhook(() => Instance.RemoveListener<OnEntityTakeDamagePost>(OnEntityTakeDamagePost));
TryUnhook(() => VirtualFunctions.CBaseTrigger_StartTouchFunc.Unhook(OnTriggerEnter, HookMode.Post)); TryUnhook(() => VirtualFunctions.CBaseTrigger_StartTouchFunc.Unhook(OnTriggerEnter, HookMode.Post));
TryUnhook(() => VirtualFunctions.CBaseTrigger_EndTouchFunc.Unhook(OnTriggerExit, HookMode.Pre)); TryUnhook(() => VirtualFunctions.CBaseTrigger_EndTouchFunc.Unhook(OnTriggerExit, HookMode.Pre));
TryUnhook(() => VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnWeaponCanAcquire, HookMode.Pre)); TryUnhook(() => VirtualFunctions.CCSPlayer_ItemServices_CanAcquireFunc.Unhook(OnWeaponCanAcquire, HookMode.Pre));
@ -170,9 +172,8 @@ namespace src.player
} }
} }
private static void DispatchOnTakeDamage(DynamicHook h, bool post = false) private static void DispatchOnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, object[] args, bool post = false)
{ {
object[] args = [h];
var seen = new HashSet<Skills>(); var seen = new HashSet<Skills>();
List<Skills>? deferred = null; List<Skills>? deferred = null;
@ -186,15 +187,15 @@ namespace src.player
continue; continue;
} }
InvokeOnTakeDamage(p.Skill, h, args, post); InvokeOnTakeDamage(p.Skill, damagedEntity, damageInfo, args, post);
} }
if (deferred == null) return; if (deferred == null) return;
foreach (var skill in deferred) foreach (var skill in deferred)
InvokeOnTakeDamage(skill, h, args, post); InvokeOnTakeDamage(skill, damagedEntity, damageInfo, args, post);
} }
private static void InvokeOnTakeDamage(Skills skill, DynamicHook h, object[] args, bool post) private static void InvokeOnTakeDamage(Skills skill, CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, object[] args, bool post)
{ {
if (!Config.DebugEnabled(DebugCategory.Damage)) if (!Config.DebugEnabled(DebugCategory.Damage))
{ {
@ -202,24 +203,22 @@ namespace src.player
return; return;
} }
var info = h.GetParam<CTakeDamageInfo>(1); float before = damageInfo == null ? 0f : damageInfo.Damage;
float before = info == null ? 0f : info.Damage;
InvokeSkill(skill, post ? "OnTakeDamagePost" : "OnTakeDamage", args); InvokeSkill(skill, post ? "OnTakeDamagePost" : "OnTakeDamage", args);
float after = info == null ? 0f : info.Damage; float after = damageInfo == null ? 0f : damageInfo.Damage;
if (Math.Abs(before - after) > 0.01f) if (Math.Abs(before - after) > 0.01f)
Debug.WriteToDebug($"[DMG] {skill} changed damage {before:0.#} -> {after:0.#}{DescribeDamageTarget(h)}", DebugCategory.Damage); Debug.WriteToDebug($"[DMG] {skill} changed damage {before:0.#} -> {after:0.#}{DescribeDamageTarget(damagedEntity)}", DebugCategory.Damage);
} }
private static string DescribeDamageTarget(DynamicHook h) private static string DescribeDamageTarget(CBaseEntity damagedEntity)
{ {
try try
{ {
var victimEntity = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid) return string.Empty;
if (victimEntity == null || !victimEntity.IsValid) return string.Empty;
var pawn = victimEntity.As<CCSPlayerPawn>(); var pawn = damagedEntity.As<CCSPlayerPawn>();
if (pawn == null || !pawn.IsValid || pawn.DesignerName != "player") return string.Empty; if (pawn == null || !pawn.IsValid || pawn.DesignerName != "player") return string.Empty;
var controller = pawn.Controller.Value?.As<CCSPlayerController>(); var controller = pawn.Controller.Value?.As<CCSPlayerController>();
@ -445,8 +444,61 @@ namespace src.player
public static void InvalidateFreezeDisabledCache() => _freezeDisabledSkills = null; public static void InvalidateFreezeDisabledCache() => _freezeDisabledSkills = null;
private const double stallThresholdMs = 100.0;
private static long lastTickTimestamp;
private static void ReportStall()
{
if (!PerfLog.Enabled)
{
lastTickTimestamp = 0;
return;
}
long now = System.Diagnostics.Stopwatch.GetTimestamp();
long previous = lastTickTimestamp;
lastTickTimestamp = now;
if (previous == 0) return;
double gapMs = (now - previous) * 1000.0 / System.Diagnostics.Stopwatch.Frequency;
if (gapMs >= stallThresholdMs && !PlayerManager.IsServerIdle())
{
var (tracked, owners) = EntityManager.GetStatistics();
PerfLog.Info($"STALL gap={gapMs:F2}ms tick={Server.TickCount} tracked={tracked} owners={owners}{PerfContext()}");
}
}
public static string PerfContext()
{
try
{
int players = 0, alive = 0, bots = 0;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (player == null || !player.IsValid || player.IsHLTV) continue;
if (player.Team != CsTeam.Terrorist && player.Team != CsTeam.CounterTerrorist) continue;
players++;
if (player.IsBot) bots++;
if (player.PawnIsAlive) alive++;
}
var gameRules = jRandomSkills.Instance?.GameRules;
int round = gameRules == null || gameRules.Handle == IntPtr.Zero ? -1 : gameRules.TotalRoundsPlayed;
return $" players={players} alive={alive} bots={bots} skills={_activeSkillsList.Count} round={round}";
}
catch
{
return string.Empty;
}
}
private static void OnTick() private static void OnTick()
{ {
ReportStall();
long perfStart = PerfLog.Start(); long perfStart = PerfLog.Start();
lock (setLock) lock (setLock)
{ {

View file

@ -17,11 +17,11 @@ namespace src.player
UpdateGameRules(); UpdateGameRules();
if (!SkillUtils.IsHudFrame()) return; if (!SkillUtils.IsHudFrame()) return;
if (PerfLog.Enabled && Server.TickCount % 1920 == 0) if (PerfLog.Enabled && Server.TickCount % 1920 == 0 && !PlayerManager.IsServerIdle())
{ {
int server = Utilities.GetAllEntities().Count(e => e != null && e.IsValid); int server = Utilities.GetAllEntities().Count(e => e != null && e.IsValid);
var (tracked, owners) = EntityManager.GetStatistics(); var (tracked, owners) = EntityManager.GetStatistics();
PerfLog.Info($"ENTITIES server={server} tracked={tracked} owners={owners}"); PerfLog.Info($"ENTITIES server={server} tracked={tracked} owners={owners}{EntityManager.DescribeTracked()}{Event.PerfContext()}");
} }
long perfStart = PerfLog.Start(); long perfStart = PerfLog.Start();
@ -108,7 +108,7 @@ namespace src.player
} }
else if (skillPlayer.IsDrawing && player.PawnIsAlive) else if (skillPlayer.IsDrawing && player.PawnIsAlive)
{ {
var randomSkill = skills[Instance.Random.Next(skills.Length)]; var randomSkill = skills[Random.Shared.Next(skills.Length)];
infoLine = player.GetTranslationWithoutIlliterate("drawing_skill"); infoLine = player.GetTranslationWithoutIlliterate("drawing_skill");
skillLine = $"<font color='{randomSkill.Color}'>{player.GetSkillName(randomSkill.Skill)}</font>"; skillLine = $"<font color='{randomSkill.Color}'>{player.GetSkillName(randomSkill.Skill)}</font>";
@ -148,9 +148,7 @@ namespace src.player
var observerTarget = pawn.ObserverServices.ObserverTarget?.Value; var observerTarget = pawn.ObserverServices.ObserverTarget?.Value;
if (observerTarget == null || !observerTarget.IsValid) return; if (observerTarget == null || !observerTarget.IsValid) return;
var observedPlayer = PlayerManager.GetTickPlayers().FirstOrDefault(p => var observedPlayer = PlayerManager.GetControllerByPawn(observerTarget.Handle);
p != null && p.IsValid && p.Pawn?.Value?.Handle == observerTarget.Handle);
if (observedPlayer == null) return; if (observedPlayer == null) return;
var observedEvent = PlayerManager.GetPlayerEvent(observedPlayer); var observedEvent = PlayerManager.GetPlayerEvent(observedPlayer);

View file

@ -17,15 +17,12 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null || damageInfo.Handle == nint.Zero) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1); if (damageInfo.Attacker == null || !damageInfo.Attacker.IsValid || damageInfo.Attacker.Value == null) return;
if (param == null || !param.IsValid || param2 == null || param2.Handle == nint.Zero) return; var attackerEnt = damageInfo.Attacker.Value;
if (param2.Attacker == null || !param2.Attacker.IsValid || param2.Attacker.Value == null) return;
var attackerEnt = param2.Attacker.Value;
if (attackerEnt == null || !attackerEnt.IsValid) return; if (attackerEnt == null || !attackerEnt.IsValid) return;
var attackerPawn = attackerEnt.As<CCSPlayerPawn>(); var attackerPawn = attackerEnt.As<CCSPlayerPawn>();
@ -40,12 +37,12 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index); var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo == null || playerInfo.Skill != skillName) return; if (playerInfo == null || playerInfo.Skill != skillName) return;
if (!SkillUtils.IsBulletDamage(param2)) return; if (!SkillUtils.IsBulletDamage(damageInfo)) return;
int offset = GameData.GetOffset("CTakeDamageInfo_HitGroup"); int offset = GameData.GetOffset("CTakeDamageInfo_HitGroup");
if (offset <= 0) return; if (offset <= 0) return;
nint hitGroupPointer = Marshal.ReadIntPtr(param2.Handle, offset); nint hitGroupPointer = Marshal.ReadIntPtr(damageInfo.Handle, offset);
if (hitGroupPointer == nint.Zero) return; if (hitGroupPointer == nint.Zero) return;
nint hitGroupData = Marshal.ReadIntPtr(hitGroupPointer, 16); nint hitGroupData = Marshal.ReadIntPtr(hitGroupPointer, 16);
@ -60,14 +57,12 @@ namespace src.player.skills
Marshal.WriteInt32(address, (int)HitGroup_t.HITGROUP_HEAD); Marshal.WriteInt32(address, (int)HitGroup_t.HITGROUP_HEAD);
} }
public static void OnTakeDamagePost(DynamicHook h) public static void OnTakeDamagePost(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, CTakeDamageResult damageResult)
{ {
var info = h.GetParam<CTakeDamageInfo>(1); if (damageInfo == null || damageInfo.Handle == nint.Zero)
if (info == null || info.Handle == nint.Zero)
return; return;
if (!SkillUtils.IsBulletDamage(info)) return; if (!SkillUtils.IsBulletDamage(damageInfo)) return;
if (_restoreStack.Value!.Count > 0) if (_restoreStack.Value!.Count > 0)
{ {

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -31,14 +31,11 @@ namespace src.player.skills
border: !PlayerManager.GetTickPlayers().Any(p => p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t"); border: !PlayerManager.GetTickPlayers().Any(p => p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t");
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return; var victimPawn = damagedEntity.As<CCSPlayerPawn>();
var victimPawn = param.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return; if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value; var victimController = victimPawn.Controller.Value;
@ -53,7 +50,7 @@ namespace src.player.skills
if (playerInfo.Skill == skillName && victim.PawnIsAlive) if (playerInfo.Skill == skillName && victim.PawnIsAlive)
{ {
float? skillChance = playerInfo.SkillChance; float? skillChance = playerInfo.SkillChance;
param2.Damage *= skillChance ?? 1f; damageInfo.Damage *= skillChance ?? 1f;
} }
} }

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
@ -17,20 +17,19 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player") if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return; return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn.Controller?.Value == null || victimPawn.Controller?.Value == null) if (attackerPawn.Controller?.Value == null || victimPawn.Controller?.Value == null)
return; return;
@ -42,12 +41,12 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index); var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo?.Skill != skillName) return; if (playerInfo?.Skill != skillName) return;
var weapon = param2.Ability?.Value; var weapon = damageInfo.Ability?.Value;
if (weapon == null || !weapon.IsValid) return; if (weapon == null || !weapon.IsValid) return;
if (IsNade(weapon.DesignerName)) return; if (IsNade(weapon.DesignerName)) return;
if (IsBehind(attacker, victim)) if (IsBehind(attacker, victim))
param2.Damage *= SkillsInfo.GetValue<float>(skillName, "damageMultiplier"); damageInfo.Damage *= SkillsInfo.GetValue<float>(skillName, "damageMultiplier");
} }
private static bool IsBehind(CCSPlayerController attacker, CCSPlayerController victim) private static bool IsBehind(CCSPlayerController attacker, CCSPlayerController victim)
@ -81,10 +80,11 @@ namespace src.player.skills
return (target >= a || target <= b); return (target >= a || target <= b);
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#d9d9d9", 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 damageMultiplier = 2f, float toleranceDeg = 45f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#d9d9d9", 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 damageMultiplier = 2f, float toleranceDeg = 45f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float DamageMultiplier { get; set; } = damageMultiplier; public float DamageMultiplier { get; set; } = damageMultiplier;
public float ToleranceDeg { get; set; } = toleranceDeg; public float ToleranceDeg { get; set; } = toleranceDeg;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -12,6 +12,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.Berserker; private const Skills skillName = Skills.Berserker;
public static readonly ConcurrentDictionary<uint, int> jumpedPlayers = []; public static readonly ConcurrentDictionary<uint, int> jumpedPlayers = [];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -35,20 +36,19 @@ namespace src.player.skills
return Math.Max(1, Math.Min(maxValue, newValue)); return Math.Max(1, Math.Min(maxValue, newValue));
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player") if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return; return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null) if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null)
return; return;
@ -60,7 +60,7 @@ namespace src.player.skills
if (playerInfo.Skill == skillName) if (playerInfo.Skill == skillName)
{ {
float damageMultiplier = CalculateNewVelocity(attackerPawn, SkillsInfo.GetValue<float>(skillName, "maxDamageVelocity")); float damageMultiplier = CalculateNewVelocity(attackerPawn, SkillsInfo.GetValue<float>(skillName, "maxDamageVelocity"));
param2.Damage *= damageMultiplier; damageInfo.Damage *= damageMultiplier;
} }
} }
@ -90,18 +90,20 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
float maxSpeedVelocity = SkillsInfo.GetValue<float>(skillName, "maxSpeedVelocity");
foreach (var player in holderBuffer)
{ {
if (!Instance.IsPlayerValid(player)) continue; if (!Instance.IsPlayerValid(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
var playerPawn = player.PlayerPawn?.Value; var playerPawn = player.PlayerPawn?.Value;
if (playerPawn == null || playerPawn.VelocityModifier == 0) continue; if (playerPawn == null || playerPawn.VelocityModifier == 0) continue;
var buttons = player.Buttons; var buttons = player.Buttons;
float newSpeedVelocity = CalculateNewVelocity(playerPawn, SkillsInfo.GetValue<float>(skillName, "maxSpeedVelocity")); float newSpeedVelocity = CalculateNewVelocity(playerPawn, maxSpeedVelocity);
if (buttons.HasFlag(PlayerButtons.Moveleft) || buttons.HasFlag(PlayerButtons.Moveright) || buttons.HasFlag(PlayerButtons.Forward) || buttons.HasFlag(PlayerButtons.Back)) if (buttons.HasFlag(PlayerButtons.Moveleft) || buttons.HasFlag(PlayerButtons.Moveright) || buttons.HasFlag(PlayerButtons.Forward) || buttons.HasFlag(PlayerButtons.Back))
playerPawn.VelocityModifier = newSpeedVelocity; playerPawn.VelocityModifier = newSpeedVelocity;
@ -109,15 +111,18 @@ namespace src.player.skills
if (jumpedPlayers.TryGetValue(player.Index, out var time) && time > Server.TickCount) if (jumpedPlayers.TryGetValue(player.Index, out var time) && time > Server.TickCount)
continue; continue;
if (playerPawn.MoveType == MoveType_t.MOVETYPE_NOCLIP) continue;
if (!((PlayerFlags)player.Flags).HasFlag(PlayerFlags.FL_ONGROUND)) if (!((PlayerFlags)player.Flags).HasFlag(PlayerFlags.FL_ONGROUND))
playerPawn.AbsVelocity.Z = Math.Min(playerPawn.AbsVelocity.Z, 10); playerPawn.AbsVelocity.Z = Math.Min(playerPawn.AbsVelocity.Z, 10);
} }
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#cc2929", 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 maxSpeedVelocity = 2f, float maxDamageVelocity = 2f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#cc2929", 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 maxSpeedVelocity = 2f, float maxDamageVelocity = 2f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float MaxSpeedVelocity { get; set; } = maxSpeedVelocity; public float MaxSpeedVelocity { get; set; } = maxSpeedVelocity;
public float MaxDamageVelocity { get; set; } = maxDamageVelocity; public float MaxDamageVelocity { get; set; } = maxDamageVelocity;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -10,6 +10,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.BladeMaster; private const Skills skillName = Skills.BladeMaster;
private static readonly string[] noReflectionWeapon = ["inferno", "flashbang", "smokegrenade", "decoy", "hegrenade", "knife", "taser", "bayonet"]; private static readonly string[] noReflectionWeapon = ["inferno", "flashbang", "smokegrenade", "decoy", "hegrenade", "knife", "taser", "bayonet"];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -20,13 +21,11 @@ namespace src.player.skills
{ {
var modifier = SkillsInfo.GetValue<float>(skillName, "velocityModifier"); var modifier = SkillsInfo.GetValue<float>(skillName, "velocityModifier");
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
foreach (var player in holderBuffer)
{ {
if (player == null || !player.IsValid) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
var playerPawn = player.PlayerPawn?.Value; var playerPawn = player.PlayerPawn?.Value;
if (playerPawn == null || !playerPawn.IsValid || playerPawn.VelocityModifier == 0) continue; if (playerPawn == null || !playerPawn.IsValid || playerPawn.VelocityModifier == 0) continue;

View file

@ -50,6 +50,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (SkillPlayerInfo.IsEmpty) return;
float cooldown = SkillsInfo.GetValue<float>(skillName, "cooldown"); float cooldown = SkillsInfo.GetValue<float>(skillName, "cooldown");
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
@ -140,14 +142,11 @@ namespace src.player.skills
}); });
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null) return; var nade = damageInfo.Attacker.Value;
var nade = param2.Attacker.Value;
if (nade == null || !nade.IsValid) return; if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return; if (nade.DesignerName != "hegrenade_projectile") return;
@ -158,7 +157,7 @@ namespace src.player.skills
if (!int.TryParse(parts[2], out int nadeTeam)) return; if (!int.TryParse(parts[2], out int nadeTeam)) return;
if (!uint.TryParse(parts[3], out uint ownerIndex)) return; if (!uint.TryParse(parts[3], out uint ownerIndex)) return;
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (victimPawn.DesignerName != "player") return; if (victimPawn.DesignerName != "player") return;
if (victimPawn.Controller?.Value == null) return; if (victimPawn.Controller?.Value == null) return;
@ -170,9 +169,17 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null; if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam) if (victimPawn.TeamNum == nadeTeam)
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName); {
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire"))
{
damageInfo.Damage = 0;
return;
}
if (owner != null && param2.Damage >= victimPawn.Health) damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
}
if (owner != null && damageInfo.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion); SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
} }
@ -185,13 +192,14 @@ namespace src.player.skills
public DateTime Cooldown { get; set; } public DateTime Cooldown { get; set; }
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#7740c9", 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 explosionRadius = 400.0f, int explosionDamage = 60, float dmgReductionForTeamates = 0.5f, float cooldown = 10f, float force = 1000f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#7740c9", 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 explosionRadius = 400.0f, int explosionDamage = 60, float dmgReductionForTeamates = 0.5f, float cooldown = 10f, float force = 1000f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float ExplosionRadius { get; set; } = explosionRadius; public float ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage; public int ExplosionDamage { get; set; } = explosionDamage;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates; public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
public float Cooldown { get; set; } = cooldown; public float Cooldown { get; set; } = cooldown;
public float Force { get; set; } = force; public float Force { get; set; } = force;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -10,6 +10,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.BunnyHop; private const Skills skillName = Skills.BunnyHop;
private static readonly ConcurrentDictionary<uint, int> playersLastJump = []; private static readonly ConcurrentDictionary<uint, int> playersLastJump = [];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void NewRound() public static void NewRound()
{ {
@ -28,12 +29,14 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
foreach (var playerEvent in holderBuffer)
{ {
var player = PlayerManager.GetPlayerFromEvent(playerEvent) ?? playerEvent;
if (player == null || !player.IsValid) continue; if (player == null || !player.IsValid) continue;
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index));
if (playerInfo?.Skill == skillName)
GiveBunnyHop(player); GiveBunnyHop(player);
} }
} }

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -174,16 +174,13 @@ namespace src.player.skills
return owner != null && owner.IsValid ? owner : null; return owner != null && owner.IsValid ? owner : null;
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player") if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return; return;

View file

@ -142,6 +142,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (chickens.IsEmpty) return;
var pairs = chickens.ToArray(); var pairs = chickens.ToArray();
foreach (var valuePair in pairs) foreach (var valuePair in pairs)
{ {

View file

@ -14,20 +14,19 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player") if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return; return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn.Controller?.Value == null || victimPawn.Controller?.Value == null) if (attackerPawn.Controller?.Value == null || victimPawn.Controller?.Value == null)
return; return;
@ -38,15 +37,16 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index); var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo?.Skill != skillName) return; if (playerInfo?.Skill != skillName) return;
var weapon = param2.Ability?.Value; var weapon = damageInfo.Ability?.Value;
if (weapon == null || !weapon.IsValid) return; if (weapon == null || !weapon.IsValid) return;
if (weapon.DesignerName != "weapon_knife" && weapon.DesignerName != "weapon_bayonet") return; if (weapon.DesignerName != "weapon_knife" && weapon.DesignerName != "weapon_bayonet") return;
param2.Damage = 9999f; damageInfo.Damage = 9999f;
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#88a31a", 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) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#88a31a", 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, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Commands.Targeting; using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
@ -292,18 +292,15 @@ namespace src.player.skills
return camera; return camera;
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
if (string.IsNullOrEmpty(param.Entity.Name)) return; if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("CypherCamera_")) return; if (!damagedEntity.Entity.Name.StartsWith("CypherCamera_")) return;
var nameParams = param.Entity.Name.Split('_')[2]; var nameParams = damagedEntity.Entity.Name.Split('_')[2];
_ = uint.TryParse(nameParams, out uint userIndex); _ = uint.TryParse(nameParams, out uint userIndex);
if (userIndex == 0) return; if (userIndex == 0) return;

View file

@ -67,6 +67,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (SkillPlayerInfo.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
{ {
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;

View file

@ -36,7 +36,6 @@ namespace src.player.skills
Server.NextWorldUpdate(() => Server.NextWorldUpdate(() =>
{ {
if (player == null || !player.IsValid || player.Team != lastTeam) return; if (player == null || !player.IsValid || player.Team != lastTeam) return;
var pawn = player.PlayerPawn.Value; var pawn = player.PlayerPawn.Value;
@ -92,14 +91,11 @@ namespace src.player.skills
}); });
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null) return; var nade = damageInfo.Attacker.Value;
var nade = param2.Attacker.Value;
if (nade == null || !nade.IsValid) return; if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return; if (nade.DesignerName != "hegrenade_projectile") return;
@ -110,7 +106,7 @@ namespace src.player.skills
if (!int.TryParse(parts[2], out int nadeTeam)) return; if (!int.TryParse(parts[2], out int nadeTeam)) return;
if (!uint.TryParse(parts[3], out uint ownerIndex)) return; if (!uint.TryParse(parts[3], out uint ownerIndex)) return;
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (victimPawn.DesignerName != "player") return; if (victimPawn.DesignerName != "player") return;
if (victimPawn.Controller?.Value == null) return; if (victimPawn.Controller?.Value == null) return;
@ -122,9 +118,17 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null; if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam) if (victimPawn.TeamNum == nadeTeam)
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName); {
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire"))
{
damageInfo.Damage = 0;
return;
}
if (owner != null && param2.Damage >= victimPawn.Health) damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
}
if (owner != null && damageInfo.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion); SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
} }
@ -135,12 +139,13 @@ namespace src.player.skills
return player != null && player.IsValid && player.PlayerPawn?.Value != null; return player != null && player.IsValid && player.PlayerPawn?.Value != null;
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#F5CB42", 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 explosionRadius = 500.0f, int explosionDamage = 999, float dmgReductionForTeamates = 0.5f, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#F5CB42", 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 explosionRadius = 500.0f, int explosionDamage = 999, float dmgReductionForTeamates = 0.5f, float soundVolume = 1f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float SoundVolume { get; set; } = soundVolume; public float SoundVolume { get; set; } = soundVolume;
public float ExplosionRadius { get; set; } = explosionRadius; public float ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage; public int ExplosionDamage { get; set; } = explosionDamage;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates; public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -11,6 +11,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.DemonEye; private const Skills skillName = Skills.DemonEye;
private static readonly Skills[] hidingSkills = [Skills.Ghost, Skills.Ninja, Skills.C4Camouflage]; private static readonly Skills[] hidingSkills = [Skills.Ghost, Skills.Ninja, Skills.C4Camouflage];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -24,13 +25,13 @@ namespace src.player.skills
int damage = SkillsInfo.GetValue<int>(skillName, "damage"); int damage = SkillsInfo.GetValue<int>(skillName, "damage");
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
foreach (var playerEvent in holderBuffer)
{ {
var playerEvent = PlayerManager.GetPlayerEvent(player);
if (!Instance.IsPlayerValid(playerEvent)) continue; if (!Instance.IsPlayerValid(playerEvent)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(playerEvent!.Index);
if (playerInfo?.Skill == skillName)
HitEnemies(playerEvent, damage); HitEnemies(playerEvent, damage);
} }
} }

View file

@ -24,13 +24,11 @@ namespace src.player.skills
public static void NewRound() public static void NewRound()
{ {
RestoreAll(); RestoreAll();
jammedUntilTick.Clear();
} }
public static void RoundEnd() public static void RoundEnd()
{ {
RestoreAll(); RestoreAll();
jammedUntilTick.Clear();
} }
public static void PlayerDisconnect(uint playerIndex) public static void PlayerDisconnect(uint playerIndex)
@ -89,6 +87,8 @@ namespace src.player.skills
{ {
foreach (var playerIndex in jammedUntilTick.Keys) foreach (var playerIndex in jammedUntilTick.Keys)
SetHud(playerIndex, false); SetHud(playerIndex, false);
jammedUntilTick.Clear();
} }
private static void SetHud(uint playerIndex, bool jam) private static void SetHud(uint playerIndex, bool jam)
@ -142,6 +142,10 @@ namespace src.player.skills
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
playersWithSkill.TryRemove(player.Index, out _); playersWithSkill.TryRemove(player.Index, out _);
if (playersWithSkill.IsEmpty)
RestoreAll();
SkillUtils.UpdateGrenadeCount(player, CsItem.HEGrenade, 1); SkillUtils.UpdateGrenadeCount(player, CsItem.HEGrenade, 1);
} }

View file

@ -130,12 +130,16 @@ namespace src.player.skills
barrel.Entity!.Name = barrel.Globalname = $"Barrel_{Server.TickCount}_{player.TeamNum}_{player.Index}"; barrel.Entity!.Name = barrel.Globalname = $"Barrel_{Server.TickCount}_{player.TeamNum}_{player.Index}";
barrel.Collision.SolidType = SolidType_t.SOLID_VPHYSICS; barrel.Collision.SolidType = SolidType_t.SOLID_VPHYSICS;
barrel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(barrel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2)); barrel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(barrel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
barrel.DispatchSpawn();
using (var keys = new CEntityKeyValues())
{
keys.SetString("model", SkillsInfo.GetValue<string>(skillName, "propModel"));
barrel.DispatchSpawn(keys);
}
Server.NextFrame(() => Server.NextFrame(() =>
{ {
if (barrel == null || !barrel.IsValid) return; if (barrel == null || !barrel.IsValid) return;
barrel.SetModel(SkillsInfo.GetValue<string>(skillName, "propModel"));
barrel.Teleport(pos, rotation, null); barrel.Teleport(pos, rotation, null);
}); });
} }
@ -165,27 +169,24 @@ namespace src.player.skills
}); });
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null) return; if (HandleBarrelHit(damagedEntity)) return;
HandleNadeDamage(damagedEntity, damageInfo);
if (HandleBarrelHit(param)) return;
HandleNadeDamage(param, param2);
} }
private static bool HandleBarrelHit(CEntityInstance param) private static bool HandleBarrelHit(CEntityInstance damagedEntity)
{ {
if (string.IsNullOrEmpty(param.Entity!.Name)) return false; if (string.IsNullOrEmpty(damagedEntity.Entity!.Name)) return false;
if (!param.Entity.Name.StartsWith("Barrel_")) return false; if (!damagedEntity.Entity.Name.StartsWith("Barrel_")) return false;
var barrel = param.As<CDynamicProp>(); var barrel = damagedEntity.As<CDynamicProp>();
if (barrel == null || !barrel.IsValid) return true; if (barrel == null || !barrel.IsValid) return true;
if (!consumedBarrels.TryAdd(barrel.Index, 0)) return true; if (!consumedBarrels.TryAdd(barrel.Index, 0)) return true;
var nameParts = param.Entity.Name.Split('_'); var nameParts = damagedEntity.Entity.Name.Split('_');
if (nameParts.Length < 4) return true; if (nameParts.Length < 4) return true;
if (!int.TryParse(nameParts[2], out int teamNum)) return true; if (!int.TryParse(nameParts[2], out int teamNum)) return true;
if (!uint.TryParse(nameParts[3], out uint ownerIndex)) return true; if (!uint.TryParse(nameParts[3], out uint ownerIndex)) return true;
@ -207,9 +208,9 @@ namespace src.player.skills
return true; return true;
} }
private static void HandleNadeDamage(CEntityInstance param, CTakeDamageInfo param2) private static void HandleNadeDamage(CEntityInstance damagedEntity, CTakeDamageInfo damageInfo)
{ {
var nade = param2.Attacker?.Value; var nade = damageInfo.Attacker?.Value;
if (nade == null || !nade.IsValid) return; if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return; if (nade.DesignerName != "hegrenade_projectile") return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("barrel_team_")) return; if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("barrel_team_")) return;
@ -219,7 +220,7 @@ namespace src.player.skills
if (!int.TryParse(parts[2], out int nadeTeam)) return; if (!int.TryParse(parts[2], out int nadeTeam)) return;
if (!uint.TryParse(parts[3], out uint ownerIndex)) return; if (!uint.TryParse(parts[3], out uint ownerIndex)) return;
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (victimPawn.DesignerName != "player" || victimPawn.Controller?.Value == null) return; if (victimPawn.DesignerName != "player" || victimPawn.Controller?.Value == null) return;
var victim = victimPawn.Controller.Value.As<CCSPlayerController>(); var victim = victimPawn.Controller.Value.As<CCSPlayerController>();
@ -229,9 +230,17 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null; if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam) if (victimPawn.TeamNum == nadeTeam)
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName); {
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire"))
{
damageInfo.Damage = 0;
return;
}
if (owner != null && param2.Damage >= victimPawn.Health) damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
}
if (owner != null && damageInfo.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion); SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
} }
@ -244,13 +253,14 @@ namespace src.player.skills
public DateTime Cooldown { get; set; } public DateTime Cooldown { get; set; }
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#c0392b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Common, float cooldown = 20f, float explosionRadius = 600f, int explosionDamage = 50, string propModel = "models/props/de_train/hr_t/barrel_a/barrel_a.vmdl", float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#c0392b", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Common, float cooldown = 20f, float explosionRadius = 600f, int explosionDamage = 50, string propModel = "models/props/de_train/hr_t/barrel_a/barrel_a.vmdl", float dmgReductionForTeamates = 0.5f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float Cooldown { get; set; } = cooldown; public float Cooldown { get; set; } = cooldown;
public float ExplosionRadius { get; set; } = explosionRadius; public float ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage; public int ExplosionDamage { get; set; } = explosionDamage;
public string PropModel { get; set; } = propModel; public string PropModel { get; set; } = propModel;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates; public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -75,14 +75,11 @@ namespace src.player.skills
}); });
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null) return; var nade = damageInfo.Attacker?.Value;
var nade = param2.Attacker?.Value;
if (nade == null || !nade.IsValid) return; if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return; if (nade.DesignerName != "hegrenade_projectile") return;
@ -93,7 +90,7 @@ namespace src.player.skills
if (!int.TryParse(parts[2], out int nadeTeam)) return; if (!int.TryParse(parts[2], out int nadeTeam)) return;
if (!uint.TryParse(parts[3], out uint ownerIndex)) return; if (!uint.TryParse(parts[3], out uint ownerIndex)) return;
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (victimPawn.DesignerName != "player") return; if (victimPawn.DesignerName != "player") return;
if (victimPawn.Controller?.Value == null) return; if (victimPawn.Controller?.Value == null) return;
@ -102,12 +99,20 @@ namespace src.player.skills
if (victim == null || !victim.IsValid || victim.Index == ownerIndex) return; if (victim == null || !victim.IsValid || victim.Index == ownerIndex) return;
if (victimPawn.TeamNum == nadeTeam) if (victimPawn.TeamNum == nadeTeam)
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName); {
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire"))
{
damageInfo.Damage = 0;
return;
}
damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
}
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex); var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner != null && !owner.IsValid) owner = null; if (owner != null && !owner.IsValid) owner = null;
if (owner != null && param2.Damage >= victimPawn.Health) if (owner != null && damageInfo.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion); SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
} }
@ -129,13 +134,14 @@ namespace src.player.skills
SpawnExplosion(pos, player); SpawnExplosion(pos, player);
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9c0000", 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 damage = 25f, float damageRadius = 210f, float chanceFrom = .15f, float chanceTo = .3f, float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9c0000", 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 damage = 25f, float damageRadius = 210f, float chanceFrom = .15f, float chanceTo = .3f, float dmgReductionForTeamates = 0.5f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float Damage { get; set; } = damage; public float Damage { get; set; } = damage;
public float DamageRadius { get; set; } = damageRadius; public float DamageRadius { get; set; } = damageRadius;
public float ChanceFrom { get; set; } = chanceFrom; public float ChanceFrom { get; set; } = chanceFrom;
public float ChanceTo { get; set; } = chanceTo; public float ChanceTo { get; set; } = chanceTo;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates; public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -60,6 +60,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (cameras.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
if (cameras.TryGetValue(player.Index, out var cameraInfo) && cameraInfo.Item2 != 0) if (cameras.TryGetValue(player.Index, out var cameraInfo) && cameraInfo.Item2 != 0)
{ {

View file

@ -2,6 +2,7 @@ using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills; using static src.jRandomSkills;
namespace src.player.skills namespace src.player.skills
@ -9,12 +10,75 @@ namespace src.player.skills
public class FastReload : ISkill public class FastReload : ISkill
{ {
private const Skills skillName = Skills.FastReload; private const Skills skillName = Skills.FastReload;
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
} }
public static void NewRound()
{
SkillPlayerInfo.Clear();
}
public static void EnableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
SkillPlayerInfo[player.Index] = new PlayerSkillInfo
{
SteamID = player.Index,
CanUse = true,
Cooldown = DateTime.MinValue,
};
}
public static void DisableSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
SkillPlayerInfo.TryRemove(player.Index, out _);
SkillUtils.ResetPrintHTML(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
SkillPlayerInfo.TryRemove(playerIndex, out _);
}
public static void OnTick()
{
if (SkillPlayerInfo.IsEmpty) return;
if (!SkillUtils.IsHudFrame()) return;
if (SkillUtils.IsFreezeTime()) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) continue;
UpdateHUD(player, skillInfo);
}
}
private static void UpdateHUD(CCSPlayerController player, PlayerSkillInfo skillInfo)
{
float time = (int)Math.Ceiling((skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue<float>(skillName, "cooldown")) - DateTime.Now).TotalSeconds);
float cooldown = Math.Max(time, 0);
if (cooldown == 0 && !skillInfo.CanUse)
skillInfo.CanUse = true;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null) return;
playerInfo.PrintHTML = cooldown != 0
? $"{player.GetTranslation("hud_info", $"<font color='#FF0000'>{cooldown}</font>")}"
: null;
}
public static void UseSkill(CCSPlayerController player) public static void UseSkill(CCSPlayerController player)
{ {
var playerPawn = player.PlayerPawn.Value; var playerPawn = player.PlayerPawn.Value;
@ -24,24 +88,40 @@ namespace src.player.skills
if (playerInfo?.Skill != skillName) return; if (playerInfo?.Skill != skillName) return;
if (!player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; if (!player.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
InstaReload(playerPawn); if (!SkillPlayerInfo.TryGetValue(player.Index, out var skillInfo)) return;
if (!skillInfo.CanUse) return;
if (skillInfo.Cooldown.AddSeconds(SkillsInfo.GetValue<float>(skillName, "cooldown")) > DateTime.Now) return;
if (!InstaReload(playerPawn)) return;
skillInfo.CanUse = false;
skillInfo.Cooldown = DateTime.Now;
} }
private static void InstaReload(CCSPlayerPawn pawn) private static bool InstaReload(CCSPlayerPawn pawn)
{ {
if (pawn == null || !pawn.IsValid) return; if (pawn == null || !pawn.IsValid) return false;
var weaponServices = pawn.WeaponServices; var weaponServices = pawn.WeaponServices;
if (weaponServices == null || weaponServices.ActiveWeapon == null || !weaponServices.ActiveWeapon.IsValid) return; if (weaponServices == null || weaponServices.ActiveWeapon == null || !weaponServices.ActiveWeapon.IsValid) return false;
var activeWeapon = weaponServices.ActiveWeapon.Value; var activeWeapon = weaponServices.ActiveWeapon.Value;
if (activeWeapon == null || !activeWeapon.IsValid || activeWeapon.VData == null) return; if (activeWeapon == null || !activeWeapon.IsValid || activeWeapon.VData == null) return false;
activeWeapon.Clip1 = activeWeapon.VData.MaxClip1; activeWeapon.Clip1 = activeWeapon.VData.MaxClip1;
Utilities.SetStateChanged(activeWeapon, "CBasePlayerWeapon", "m_iClip1"); Utilities.SetStateChanged(activeWeapon, "CBasePlayerWeapon", "m_iClip1");
return true;
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffc061", 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) public class PlayerSkillInfo
{ {
public ulong SteamID { get; set; }
public bool CanUse { get; set; }
public DateTime Cooldown { get; set; }
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffc061", 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 cooldown = 5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float Cooldown { get; set; } = cooldown;
} }
} }
} }

View file

@ -12,6 +12,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.Flash; private const Skills skillName = Skills.Flash;
public static readonly ConcurrentDictionary<uint, int> jumpedPlayers = []; public static readonly ConcurrentDictionary<uint, int> jumpedPlayers = [];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -75,13 +76,14 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
foreach (var player in holderBuffer)
{ {
if (!Instance.IsPlayerValid(player)) continue; if (!Instance.IsPlayerValid(player)) continue;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
var playerPawn = player.PlayerPawn?.Value; var playerPawn = player.PlayerPawn?.Value;
if (playerPawn == null || playerPawn.VelocityModifier == 0) continue; if (playerPawn == null || playerPawn.VelocityModifier == 0) continue;
@ -93,6 +95,8 @@ namespace src.player.skills
if (jumpedPlayers.TryGetValue(player.Index, out var time) && time > Server.TickCount) if (jumpedPlayers.TryGetValue(player.Index, out var time) && time > Server.TickCount)
continue; continue;
if (playerPawn.MoveType == MoveType_t.MOVETYPE_NOCLIP) continue;
if (!((PlayerFlags)player.Flags).HasFlag(PlayerFlags.FL_ONGROUND)) if (!((PlayerFlags)player.Flags).HasFlag(PlayerFlags.FL_ONGROUND))
playerPawn.AbsVelocity.Z = Math.Min(playerPawn.AbsVelocity.Z, 10); playerPawn.AbsVelocity.Z = Math.Min(playerPawn.AbsVelocity.Z, 10);
} }

View file

@ -41,6 +41,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (SkillPlayerInfo.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
{ {
if (player == null || !player.IsValid) if (player == null || !player.IsValid)
@ -288,10 +290,14 @@ namespace src.player.skills
if (needEnemySees && !enemySeesPlayer) if (needEnemySees && !enemySeesPlayer)
continue; continue;
var targetOrigin = targetPawn.AbsOrigin;
if (targetOrigin == null)
continue;
Vector targetEyePos = new( Vector targetEyePos = new(
targetPawn.AbsOrigin.X, targetOrigin.X,
targetPawn.AbsOrigin.Y, targetOrigin.Y,
targetPawn.AbsOrigin.Z + targetPawn.ViewOffset.Z targetOrigin.Z + targetPawn.ViewOffset.Z
); );
Vector toTarget = new( Vector toTarget = new(

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -141,24 +141,21 @@ namespace src.player.skills
}); });
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
if (string.IsNullOrEmpty(param.Entity.Name)) return; if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("FortniteWall")) return; if (!damagedEntity.Entity.Name.StartsWith("FortniteWall")) return;
var box = param.As<CDynamicProp>(); var box = damagedEntity.As<CDynamicProp>();
if (box == null || !box.IsValid) return; if (box == null || !box.IsValid) return;
box.EmitSound("Wood_Plank.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume")); box.EmitSound("Wood_Plank.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
if (barricades.TryGetValue(box.Index, out int health)) if (barricades.TryGetValue(box.Index, out int health))
{ {
int newHealth = health - (int)param2.Damage; int newHealth = health - (int)damageInfo.Damage;
if (newHealth <= 0) if (newHealth <= 0)
{ {

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
@ -29,19 +29,16 @@ namespace src.player.skills
Server.ExecuteCommand($"mp_autokick {(defaultAutoKick ? 1 : 0)}"); Server.ExecuteCommand($"mp_autokick {(defaultAutoKick ? 1 : 0)}");
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
if (param2.AmmoType == 255) if (damageInfo.AmmoType == 255)
return; return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn == null || !attackerPawn.IsValid || victimPawn == null || !victimPawn.IsValid) if (attackerPawn == null || !attackerPawn.IsValid || victimPawn == null || !victimPawn.IsValid)
return; return;
@ -67,8 +64,8 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(attacker)?.Index ?? attacker.Index)); var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(attacker)?.Index ?? attacker.Index));
if (playerInfo?.Skill != skillName || attacker!.Team != victim!.Team) return; if (playerInfo?.Skill != skillName || attacker!.Team != victim!.Team) return;
float damage = param2.Damage; float damage = damageInfo.Damage;
param2.Damage = 0; damageInfo.Damage = 0;
if (!autoKickOverridden) if (!autoKickOverridden)
{ {

View file

@ -46,12 +46,18 @@ namespace src.player.skills
return; return;
} }
if (skill.Skill == skillName && !TakeMoney(player)) if (skill.Skill == skillName)
{
if (!TakeMoney(player))
{ {
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("gambler_no_money")); playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("gambler_no_money"));
return; return;
} }
EnableSkill(player);
return;
}
Instance.AddTimer(.1f, () => Instance.AddTimer(.1f, () =>
{ {
playerInfo.Skill = skill.Skill; playerInfo.Skill = skill.Skill;

View file

@ -11,11 +11,9 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.GravityDecoy; private const Skills skillName = Skills.GravityDecoy;
private const float defaultGravity = 1f;
private static readonly ConcurrentDictionary<uint, int> playersWithSkill = []; private static readonly ConcurrentDictionary<uint, int> playersWithSkill = [];
private static readonly ConcurrentDictionary<uint, byte> affected = []; private static readonly ConcurrentDictionary<uint, float> affected = [];
private static readonly ConcurrentDictionary<uint, byte> restoreOnRespawn = []; private static readonly ConcurrentDictionary<uint, float> restoreOnRespawn = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -78,8 +76,8 @@ namespace src.player.skills
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) continue; if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) continue;
if (pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) continue; if (pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) continue;
if (restoreOnRespawn.TryRemove(eventPlayer.Index, out _)) if (restoreOnRespawn.TryRemove(eventPlayer.Index, out float respawnGravity))
pawn.ActualGravityScale = defaultGravity; pawn.ActualGravityScale = respawnGravity;
bool inside = false; bool inside = false;
foreach (var decoyPos in decoyPositions) foreach (var decoyPos in decoyPositions)
@ -87,11 +85,11 @@ namespace src.player.skills
if (inside) if (inside)
{ {
if (!affected.TryAdd(eventPlayer.Index, 0)) continue; if (!affected.TryAdd(eventPlayer.Index, pawn.ActualGravityScale)) continue;
pawn.ActualGravityScale = gravity; pawn.ActualGravityScale = gravity;
} }
else if (affected.TryRemove(eventPlayer.Index, out _)) else if (affected.TryRemove(eventPlayer.Index, out float ownGravity))
pawn.ActualGravityScale = defaultGravity; pawn.ActualGravityScale = ownGravity;
} }
} }
@ -100,10 +98,10 @@ namespace src.player.skills
var victim = PlayerManager.GetPlayerEvent(@event.Userid); var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return; if (victim == null || !victim.IsValid) return;
if (!affected.TryRemove(victim.Index, out _)) return; if (!affected.TryRemove(victim.Index, out float ownGravity)) return;
Restore(victim.Index); Restore(victim.Index, ownGravity);
restoreOnRespawn[victim.Index] = 0; restoreOnRespawn[victim.Index] = ownGravity;
} }
public static void PlayerDisconnect(uint playerIndex) public static void PlayerDisconnect(uint playerIndex)
@ -115,23 +113,23 @@ namespace src.player.skills
private static void RestoreAll() private static void RestoreAll()
{ {
foreach (uint playerIndex in affected.Keys) foreach (var entry in affected)
Restore(playerIndex); Restore(entry.Key, entry.Value);
foreach (uint playerIndex in restoreOnRespawn.Keys) foreach (var entry in restoreOnRespawn)
Restore(playerIndex); Restore(entry.Key, entry.Value);
affected.Clear(); affected.Clear();
restoreOnRespawn.Clear(); restoreOnRespawn.Clear();
} }
private static void Restore(uint playerIndex) private static void Restore(uint playerIndex, float gravity)
{ {
var player = Utilities.GetPlayerFromIndex((int)playerIndex); var player = Utilities.GetPlayerFromIndex((int)playerIndex);
var pawn = player?.PlayerPawn?.Value; var pawn = player?.PlayerPawn?.Value;
if (pawn != null && pawn.IsValid) if (pawn != null && pawn.IsValid)
pawn.ActualGravityScale = defaultGravity; pawn.ActualGravityScale = gravity;
} }
public static void GrenadeThrown(EventGrenadeThrown @event) public static void GrenadeThrown(EventGrenadeThrown @event)

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -35,21 +35,18 @@ namespace src.player.skills
chickenOwners.Clear(); chickenOwners.Clear();
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damageInfo == null) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param2 == null) return; if (!chickenOwners.TryGetValue(damagedEntity.Index, out var ownerData)) return;
if (!chickenOwners.TryGetValue(param.Index, out var ownerData)) return;
uint ownerIndex = ownerData.ownerIndex; uint ownerIndex = ownerData.ownerIndex;
int health = ownerData.health; int health = ownerData.health;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex); var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner == null || !owner.IsValid) return; if (owner == null || !owner.IsValid) return;
var attackerEnt = param2.Attacker?.Value; var attackerEnt = damageInfo.Attacker?.Value;
if (attackerEnt == null || !attackerEnt.IsValid) return; if (attackerEnt == null || !attackerEnt.IsValid) return;
var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle); var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle);
@ -60,15 +57,15 @@ namespace src.player.skills
if (attackerPawn.Index != ownerPawn.Index && attackerPawn.TeamNum == owner.TeamNum) if (attackerPawn.Index != ownerPawn.Index && attackerPawn.TeamNum == owner.TeamNum)
{ {
param2.Damage = 0; damageInfo.Damage = 0;
return; return;
} }
int newHealth = Math.Max(0, health - (int)param2.Damage); int newHealth = Math.Max(0, health - (int)damageInfo.Damage);
chickenOwners[param.Index] = (ownerIndex, newHealth); chickenOwners[damagedEntity.Index] = (ownerIndex, newHealth);
if (newHealth > 0) if (newHealth > 0)
CreateHitParticles(param.Index); CreateHitParticles(damagedEntity.Index);
} }
private static void CreateHitParticles(uint chickenIndex) private static void CreateHitParticles(uint chickenIndex)

View file

@ -10,7 +10,14 @@ namespace src.player.skills
public class HealingSmoke : ISkill public class HealingSmoke : ISkill
{ {
private const Skills skillName = Skills.HealingSmoke; private const Skills skillName = Skills.HealingSmoke;
private static readonly ConcurrentDictionary<Vector, byte> smokes = []; private static readonly ConcurrentDictionary<Vector, uint> smokes = [];
private static void RemoveOwnerSmokes(uint ownerIndex)
{
foreach (var smoke in smokes)
if (smoke.Value == ownerIndex)
smokes.TryRemove(smoke.Key, out _);
}
private readonly static ConcurrentDictionary<uint, int> playersWithSkill = []; private readonly static ConcurrentDictionary<uint, int> playersWithSkill = [];
public static void LoadSkill() public static void LoadSkill()
@ -31,7 +38,7 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return; if (playerInfo?.Skill != skillName) return;
smokes.TryAdd(new Vector(@event.X, @event.Y, @event.Z), 0); smokes.TryAdd(new Vector(@event.X, @event.Y, @event.Z), player.Index);
} }
public static void SmokegrenadeExpired(EventSmokegrenadeExpired @event) public static void SmokegrenadeExpired(EventSmokegrenadeExpired @event)
@ -39,9 +46,6 @@ namespace src.player.skills
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
foreach (var smoke in smokes.Keys.Where(v => v.X == @event.X && v.Y == @event.Y && v.Z == @event.Z)) foreach (var smoke in smokes.Keys.Where(v => v.X == @event.X && v.Y == @event.Y && v.Z == @event.Z))
smokes.TryRemove(smoke, out _); smokes.TryRemove(smoke, out _);
} }
@ -153,6 +157,7 @@ namespace src.player.skills
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
playersWithSkill.TryRemove(player.Index, out _); playersWithSkill.TryRemove(player.Index, out _);
RemoveOwnerSmokes(player.Index);
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1); SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
} }

View file

@ -22,10 +22,10 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (players.IsEmpty || jRandomSkills.Instance.GameRules?.FreezePeriod == true) return;
int cooldown = Math.Max(1, (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 (Server.TickCount % cooldown != 0) return;
if (players.IsEmpty || jRandomSkills.Instance.GameRules?.FreezePeriod == true) return;
int damage = SkillsInfo.GetValue<int>(skillName, "damage"); int damage = SkillsInfo.GetValue<int>(skillName, "damage");
var owner = GetSkillOwner(); var owner = GetSkillOwner();

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Timers; using CounterStrikeSharp.API.Modules.Timers;
@ -65,7 +65,6 @@ namespace src.player.skills
{ {
UpdateWeapons(player, playerSkill); UpdateWeapons(player, playerSkill);
CreateClone(playerSkill); CreateClone(playerSkill);
SkillUtils.SetPlayerCollisions(player, false);
} }
else if (playerSkill.CloneProp != null) else if (playerSkill.CloneProp != null)
KillClone(playerSkill); KillClone(playerSkill);
@ -138,8 +137,6 @@ namespace src.player.skills
if (eventTarget != null && eventTarget.IsValid) if (eventTarget != null && eventTarget.IsValid)
SkillUtils.ApplyScreenColor(eventTarget, 0, 0, 0, 0, 10, 0, 2); SkillUtils.ApplyScreenColor(eventTarget, 0, 0, 0, 0, 10, 0, 2);
SkillUtils.SetPlayerCollisions(player, true);
BlockWeapon(player, false); BlockWeapon(player, false);
playerSkill.NextUse = Server.TickCount + SkillsInfo.GetValue<float>(skillName, "Cooldown") * 64; playerSkill.NextUse = Server.TickCount + SkillsInfo.GetValue<float>(skillName, "Cooldown") * 64;
playerSkill.UseTime = 0; playerSkill.UseTime = 0;
@ -305,25 +302,22 @@ namespace src.player.skills
return shooter != null && shooter.IsValid ? shooter : null; return shooter != null && shooter.IsValid ? shooter : null;
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null)
return; return;
if (string.IsNullOrEmpty(param.Entity.Name)) return; if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("IanaClone_")) return; if (!damagedEntity.Entity.Name.StartsWith("IanaClone_")) return;
var nameParams = param.Entity.Name.Split('_')[2]; var nameParams = damagedEntity.Entity.Name.Split('_')[2];
_ = uint.TryParse(nameParams, out uint userIndex); _ = uint.TryParse(nameParams, out uint userIndex);
if (userIndex == 0) return; if (userIndex == 0) return;
var player = Utilities.GetPlayerFromIndex((int)userIndex); var player = Utilities.GetPlayerFromIndex((int)userIndex);
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
float dealDamage = param2.Damage; float dealDamage = damageInfo.Damage;
if (playersInfo.TryGetValue(player.Index, out var playerSkill)) if (playersInfo.TryGetValue(player.Index, out var playerSkill))
{ {
@ -333,7 +327,7 @@ namespace src.player.skills
if (cloneProp != null && cloneProp.IsValid && cloneProp.AbsOrigin != null) if (cloneProp != null && cloneProp.IsValid && cloneProp.AbsOrigin != null)
{ {
Vector pos = param2.DamagePosition; Vector pos = damageInfo.DamagePosition;
Vector posCl = cloneProp.AbsOrigin; Vector posCl = cloneProp.AbsOrigin;
bool isHead = false; bool isHead = false;
@ -343,8 +337,8 @@ namespace src.player.skills
if (pos.Z >= posCl.Z + viewOffset - diff) if (pos.Z >= posCl.Z + viewOffset - diff)
isHead = true; isHead = true;
float damage = param2.Damage; float damage = damageInfo.Damage;
string? weapon = param2.Ability?.Value?.DesignerName; string? weapon = damageInfo.Ability?.Value?.DesignerName;
if (weapon != null) if (weapon != null)
dealDamage = CalculateDamage(player, weapon, damage, isHead); dealDamage = CalculateDamage(player, weapon, damage, isHead);
} }
@ -357,8 +351,8 @@ namespace src.player.skills
var playerPawn = player.PlayerPawn.Value; var playerPawn = player.PlayerPawn.Value;
if (playerPawn != null) if (playerPawn != null)
{ {
KillfeedIcons? killfeed = KillfeedIconsExtensions.FromWeaponName(param2.Ability?.Value?.DesignerName); KillfeedIcons? killfeed = KillfeedIconsExtensions.FromWeaponName(damageInfo.Ability?.Value?.DesignerName);
SkillUtils.TakeHealth(playerPawn, (int)dealDamage, GetShooter(param2), killfeed ?? KillfeedIcons.Leg); SkillUtils.TakeHealth(playerPawn, (int)dealDamage, GetShooter(damageInfo), killfeed ?? KillfeedIcons.Leg);
} }
} }
} }

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Timers; using CounterStrikeSharp.API.Modules.Timers;
@ -183,18 +183,15 @@ namespace src.player.skills
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE); }, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
if (string.IsNullOrEmpty(param.Entity.Name)) return; if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("Illusionist_")) return; if (!damagedEntity.Entity.Name.StartsWith("Illusionist_")) return;
var replica = param.As<CDynamicProp>(); var replica = damagedEntity.As<CDynamicProp>();
if (replica == null || !replica.IsValid) return; if (replica == null || !replica.IsValid) return;
if (!consumedReplicas.TryAdd(replica.Index, 0)) return; if (!consumedReplicas.TryAdd(replica.Index, 0)) return;
@ -205,7 +202,7 @@ namespace src.player.skills
var owner = GetReplicaOwner(replica.Index); var owner = GetReplicaOwner(replica.Index);
EntityManager.DestroyEntity(replica.Index); EntityManager.DestroyEntity(replica.Index);
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
if (attackerPawn.DesignerName != "player") return; if (attackerPawn.DesignerName != "player") return;
var attackerTeam = attackerPawn.TeamNum; var attackerTeam = attackerPawn.TeamNum;

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
@ -58,7 +58,7 @@ namespace src.player.skills
if (playerInfo == null || playerInfo.Skill != skillName) continue; if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue; if (!SkillUtils.HasMenu(player)) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).OfType<CCSPlayerController>().ToArray();
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.UpdateMenu(player, menuItems); SkillUtils.UpdateMenu(player, menuItems);
@ -117,7 +117,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player); var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !playerEvent.IsValid) return; if (playerEvent == null || !playerEvent.IsValid) return;
var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid).Select(p => PlayerManager.GetPlayerEvent(p)).Where(p => p != null && p.IsValid && p.Team != player.Team && p.PlayerPawn?.Value != null && p.PlayerPawn.Value.IsValid && p.PlayerPawn.Value.Health > 0 && !p.IsHLTV && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).OfType<CCSPlayerController>().ToArray();
if (enemies.Length > 0) if (enemies.Length > 0)
{ {

View file

@ -46,6 +46,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (bannedPlayers.IsEmpty) return;
foreach (var item in bannedPlayers) foreach (var item in bannedPlayers)
{ {
var playerIndex = item.Key; var playerIndex = item.Key;

View file

@ -33,7 +33,7 @@ namespace src.player.skills
var enemies = SkillUtils.GetSelectableEnemies(player, true); var enemies = SkillUtils.GetSelectableEnemies(player, true);
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e?.PlayerPawn?.Value?.Health ?? 0} HP", e.Index.ToString()))); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => ($"\u202A{e.PlayerName}\u202C : {e.PlayerPawn?.Value?.Health ?? 0} HP", e.Index.ToString())));
SkillUtils.UpdateMenu(player, menuItems); SkillUtils.UpdateMenu(player, menuItems);
} }
} }

View file

@ -125,15 +125,13 @@ namespace src.player.skills
enemyEvent.PrintToChat($" {ChatColors.Red}{enemyEvent.GetTranslation("nemesis_enemy_info")}"); enemyEvent.PrintToChat($" {ChatColors.Red}{enemyEvent.GetTranslation("nemesis_enemy_info")}");
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
if (markedPlayers.IsEmpty) return; if (markedPlayers.IsEmpty) return;
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return;
var victimPawn = param.As<CCSPlayerPawn>(); var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return; if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value; var victimController = victimPawn.Controller.Value;
@ -145,12 +143,15 @@ namespace src.player.skills
uint victimIndex = PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index; uint victimIndex = PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index;
if (!markedPlayers.ContainsKey(victimIndex)) return; if (!markedPlayers.ContainsKey(victimIndex)) return;
param2.Damage *= 1f + SkillsInfo.GetValue<float>(skillName, "extraDamagePercent"); if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
damageInfo.Damage *= 1f + SkillsInfo.GetValue<float>(skillName, "extraDamagePercent");
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#b8003a", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Rare, float extraDamagePercent = .25f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#b8003a", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = 2, Rarity rarity = Rarity.Rare, float extraDamagePercent = .25f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float ExtraDamagePercent { get; set; } = extraDamagePercent; public float ExtraDamagePercent { get; set; } = extraDamagePercent;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -15,20 +15,19 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player") if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return; return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null) if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null)
return; return;
@ -39,11 +38,12 @@ namespace src.player.skills
if (playerInfo == null) return; if (playerInfo == null) return;
if (playerInfo.Skill == skillName) if (playerInfo.Skill == skillName)
param2.Damage = 1000f; damageInfo.Damage = 1000f;
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff5CD9", 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) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff5CD9", 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, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -20,10 +20,12 @@ namespace src.player.skills
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker); var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid); var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return false; if (!Instance.IsPlayerValid(victim)) return false;
if ((HitGroup_t)@event.Hitgroup == HitGroup_t.HITGROUP_HEAD) return false;
if (PlayerManager.GetPlayerByIndex(victim!.Index)?.Skill != skillName) return false; if (PlayerManager.GetPlayerByIndex(victim!.Index)?.Skill != skillName) return false;
bool worldDamage = !Instance.IsPlayerValid(attacker) || attacker!.Index == victim.Index;
if (!worldDamage && (HitGroup_t)@event.Hitgroup == HitGroup_t.HITGROUP_HEAD) return false;
SkillUtils.RestoreHealth(victim); SkillUtils.RestoreHealth(victim);
return true; return true;
} }

View file

@ -10,6 +10,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.PawelJumper; private const Skills skillName = Skills.PawelJumper;
private const string jumpSound = "Default.WalkJump"; private const string jumpSound = "Default.WalkJump";
private static readonly List<CCSPlayerController> holderBuffer = [];
private static readonly int?[] J = new int?[64]; private static readonly int?[] J = new int?[64];
private static readonly PlayerButtons[] LB = new PlayerButtons[64]; private static readonly PlayerButtons[] LB = new PlayerButtons[64];
@ -20,13 +21,14 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
{ if (holderBuffer.Count == 0) return;
if (player == null || !player.IsValid) return;
var playerEvent = PlayerManager.GetPlayerEvent(player); foreach (var playerEvent in holderBuffer)
var playerInfo = PlayerManager.GetPlayerByIndex(playerEvent!.Index); {
var player = PlayerManager.GetPlayerFromEvent(playerEvent) ?? playerEvent;
if (player == null || !player.IsValid) continue;
if (playerInfo?.Skill == skillName)
GiveAdditionalJump(player, playerEvent); GiveAdditionalJump(player, playerEvent);
} }
} }

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -35,13 +35,11 @@ namespace src.player.skills
phoenixTicks.TryRemove(playerIndex, out _); phoenixTicks.TryRemove(playerIndex, out _);
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
var victimEntity = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
var info = h.GetParam<CTakeDamageInfo>(1);
if (victimEntity == null || !victimEntity.IsValid || info == null) return;
var victimPawn = victimEntity.As<CCSPlayerPawn>(); var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return; if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value; var victimController = victimPawn.Controller.Value;
@ -53,16 +51,16 @@ namespace src.player.skills
var victimInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index); var victimInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index);
if (victimInfo == null || victimInfo.Skill != skillName) return; if (victimInfo == null || victimInfo.Skill != skillName) return;
if (SkillUtils.IsFriendlyFireBlocked(info, victimPawn)) return; if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return;
float effectiveDamage = info.Damage; float effectiveDamage = damageInfo.Damage;
if (SkillUtils.GetHitGroup(info) == HitGroup_t.HITGROUP_HEAD) if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(info); effectiveDamage *= GetHeadshotMultiplier(damageInfo);
if (effectiveDamage < victimPawn.Health) return; if (effectiveDamage < victimPawn.Health) return;
if (TryConsumeRevive(victim, victimPawn)) if (TryConsumeRevive(victim, victimPawn))
info.Damage = 0; damageInfo.Damage = 0;
} }
public static bool TryConsumeRevive(CCSPlayerController? victim, CCSPlayerPawn? victimPawn) public static bool TryConsumeRevive(CCSPlayerController? victim, CCSPlayerPawn? victimPawn)

View file

@ -11,6 +11,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.Pilot; private const Skills skillName = Skills.Pilot;
private static readonly ConcurrentDictionary<uint, Pilot_PlayerInfo> PlayerPilotInfo = []; private static readonly ConcurrentDictionary<uint, Pilot_PlayerInfo> PlayerPilotInfo = [];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -40,13 +41,12 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
{ if (holderBuffer.Count == 0) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill == skillName) foreach (var player in holderBuffer)
HandlePilot(player); HandlePilot(player);
} }
}
public static void EnableSkill(CCSPlayerController player) public static void EnableSkill(CCSPlayerController player)
{ {

View file

@ -15,6 +15,7 @@ namespace src.player.skills
private static readonly ConcurrentDictionary<uint, uint> playersToTarget = []; private static readonly ConcurrentDictionary<uint, uint> playersToTarget = [];
private static readonly ConcurrentDictionary<uint, uint> targetToPlayer = []; private static readonly ConcurrentDictionary<uint, uint> targetToPlayer = [];
private static readonly object setLock = new(); private static readonly object setLock = new();
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -49,9 +50,9 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
int cooldown = Math.Max(1, (int)(64 * SkillsInfo.GetValue<float>(skillName, "Cooldown"))); int cooldown = poisonedPlayers.IsEmpty ? 0 : Math.Max(1, (int)(64 * SkillsInfo.GetValue<float>(skillName, "Cooldown")));
if (Server.TickCount % cooldown == 0) if (cooldown != 0 && Server.TickCount % cooldown == 0)
{ {
int cooldown2 = cooldown * 2; int cooldown2 = cooldown * 2;
@ -75,11 +76,12 @@ namespace src.player.skills
} }
if (Server.TickCount % 32 != 0) return; if (Server.TickCount % 32 != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo == null || playerInfo.Skill != skillName) continue; PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
foreach (var player in holderBuffer)
{
if (!SkillUtils.HasMenu(player)) continue; if (!SkillUtils.HasMenu(player)) continue;
var enemies = SkillUtils.GetSelectableEnemies(player, true); var enemies = SkillUtils.GetSelectableEnemies(player, true);

View file

@ -52,10 +52,19 @@ namespace src.player.skills
} }
public static void BombPlanted(EventBombPlanted _) public static void BombPlanted(EventBombPlanted _)
{
ResolveBombLocation();
}
private static void ResolveBombLocation()
{ {
var plantedBomb = Utilities.FindAllEntitiesByDesignerName<CPlantedC4>("planted_c4").FirstOrDefault(); var plantedBomb = Utilities.FindAllEntitiesByDesignerName<CPlantedC4>("planted_c4").FirstOrDefault();
if (plantedBomb != null) if (plantedBomb == null || !plantedBomb.IsValid || !plantedBomb.BombTicking || plantedBomb.BombDefused) return;
bombLocation = plantedBomb.AbsOrigin;
var origin = plantedBomb.AbsOrigin;
if (origin == null) return;
bombLocation = new Vector(origin.X, origin.Y, origin.Z);
} }
public static void OnTick() public static void OnTick()
@ -92,7 +101,7 @@ namespace src.player.skills
if (plantedBomb != null && plantedBomb.IsValid && plantedBomb.BombTicking && !plantedBomb.BombDefused) if (plantedBomb != null && plantedBomb.IsValid && plantedBomb.BombTicking && !plantedBomb.BombDefused)
{ {
plantedBomb.AddEntityIOEvent("Kill", plantedBomb, delay: 0.1f); plantedBomb.AddEntityIOEvent("Kill", plantedBomb, delay: 0.1f);
SkillUtils.TerminateRound(CsTeam.CounterTerrorist); SkillUtils.TerminateRound(CsTeam.CounterTerrorist, player);
} }
SkillUtils.ResetPrintHTML(player); SkillUtils.ResetPrintHTML(player);
SkillPlayerInfo.Clear(); SkillPlayerInfo.Clear();
@ -105,8 +114,7 @@ namespace src.player.skills
public static void EnableSkill(CCSPlayerController player) public static void EnableSkill(CCSPlayerController player)
{ {
var pawn = player.PlayerPawn.Value; if (player == null || !player.IsValid) return;
if (pawn == null || !pawn.IsValid) return;
SkillPlayerInfo.TryAdd(player.Index, new PlayerSkillInfo SkillPlayerInfo.TryAdd(player.Index, new PlayerSkillInfo
{ {
@ -114,6 +122,9 @@ namespace src.player.skills
Defusing = false, Defusing = false,
DefusingTime = SkillsInfo.GetValue<float>(skillName, "defusingTime"), DefusingTime = SkillsInfo.GetValue<float>(skillName, "defusingTime"),
}); });
if (!roundEnded && bombLocation == null)
ResolveBombLocation();
} }
public static void DisableSkill(CCSPlayerController player) public static void DisableSkill(CCSPlayerController player)

View file

@ -50,15 +50,13 @@ namespace src.player.skills
storedDamage[victim.Index] = 0f; storedDamage[victim.Index] = 0f;
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
if (storedDamage.IsEmpty) return; if (storedDamage.IsEmpty) return;
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return;
var victimPawn = param.As<CCSPlayerPawn>(); var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return; if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value; var victimController = victimPawn.Controller.Value;
@ -67,9 +65,11 @@ namespace src.player.skills
var victim = victimController.As<CCSPlayerController>(); var victim = victimController.As<CCSPlayerController>();
if (victim == null || !victim.IsValid) return; if (victim == null || !victim.IsValid) return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
uint victimIndex = PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index; uint victimIndex = PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index;
var attacker = param2.Attacker?.Value?.As<CCSPlayerPawn>(); var attacker = damageInfo.Attacker?.Value?.As<CCSPlayerPawn>();
var attackerController = attacker?.Controller?.Value?.As<CCSPlayerController>(); var attackerController = attacker?.Controller?.Value?.As<CCSPlayerController>();
uint? attackerIndex = attackerController != null && attackerController.IsValid uint? attackerIndex = attackerController != null && attackerController.IsValid
? PlayerManager.GetPlayerEvent(attackerController)?.Index ?? attackerController.Index ? PlayerManager.GetPlayerEvent(attackerController)?.Index ?? attackerController.Index
@ -78,7 +78,7 @@ namespace src.player.skills
if (attackerIndex.HasValue && attackerIndex.Value != victimIndex && storedDamage.TryGetValue(attackerIndex.Value, out float banked) && banked > 0f) if (attackerIndex.HasValue && attackerIndex.Value != victimIndex && storedDamage.TryGetValue(attackerIndex.Value, out float banked) && banked > 0f)
{ {
storedDamage[attackerIndex.Value] = 0f; storedDamage[attackerIndex.Value] = 0f;
param2.Damage += banked; damageInfo.Damage += banked;
var puncher = CounterStrikeSharp.API.Utilities.GetPlayerFromIndex((int)attackerIndex.Value); var puncher = CounterStrikeSharp.API.Utilities.GetPlayerFromIndex((int)attackerIndex.Value);
var puncherPawn = puncher?.PlayerPawn?.Value; var puncherPawn = puncher?.PlayerPawn?.Value;
@ -97,14 +97,15 @@ namespace src.player.skills
float ratio = SkillsInfo.GetValue<float>(skillName, "storePercent"); float ratio = SkillsInfo.GetValue<float>(skillName, "storePercent");
float cap = SkillsInfo.GetValue<float>(skillName, "maxStored"); float cap = SkillsInfo.GetValue<float>(skillName, "maxStored");
float stored = storedDamage[victimIndex] + (param2.Damage * ratio); float stored = storedDamage[victimIndex] + (damageInfo.Damage * ratio);
storedDamage[victimIndex] = MathF.Min(stored, cap); storedDamage[victimIndex] = MathF.Min(stored, cap);
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffb347", 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 storePercent = .5f, float maxStored = 100f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ffb347", 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 storePercent = .5f, float maxStored = 100f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float StorePercent { get; set; } = storePercent; public float StorePercent { get; set; } = storePercent;
public float MaxStored { get; set; } = maxStored; public float MaxStored { get; set; } = maxStored;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -1,4 +1,5 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
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;
@ -9,6 +10,7 @@ namespace src.player.skills
public class QuickShot : ISkill public class QuickShot : ISkill
{ {
private const Skills skillName = Skills.QuickShot; private const Skills skillName = Skills.QuickShot;
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -17,14 +19,16 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
{ if (holderBuffer.Count == 0) return;
if (player == null) continue;
if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill != skillName) continue; foreach (var player in holderBuffer)
{
if (!Instance.IsPlayerValid(player)) continue; if (!Instance.IsPlayerValid(player)) continue;
var pawn = player.PlayerPawn.Value!; var pawn = player.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid) continue;
var weaponServices = pawn.WeaponServices; var weaponServices = pawn.WeaponServices;
if (weaponServices == null || weaponServices.ActiveWeapon == null || !weaponServices.ActiveWeapon.IsValid) continue; if (weaponServices == null || weaponServices.ActiveWeapon == null || !weaponServices.ActiveWeapon.IsValid) continue;

View file

@ -10,6 +10,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.RadarHack; private const Skills skillName = Skills.RadarHack;
private static readonly Skills[] hidingSkills = [Skills.Ghost, Skills.Ninja, Skills.C4Camouflage]; private static readonly Skills[] hidingSkills = [Skills.Ghost, Skills.Ninja, Skills.C4Camouflage];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -18,14 +19,14 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
{ if (holderBuffer.Count == 0) return;
var playerEvent = PlayerManager.GetPlayerEvent(player);
if (playerEvent == null) continue;
if (PlayerManager.GetPlayerByIndex(playerEvent.Index)?.Skill != skillName) continue; foreach (var playerEvent in holderBuffer)
{
if (!Instance.IsPlayerValid(playerEvent)) continue; if (!Instance.IsPlayerValid(playerEvent)) continue;
var player = PlayerManager.GetPlayerFromEvent(playerEvent) ?? playerEvent;
SetEnemiesVisibleOnRadar(player); SetEnemiesVisibleOnRadar(player);
} }
} }

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -69,13 +69,11 @@ namespace src.player.skills
zombies.Clear(); zombies.Clear();
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
var victimEntity = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
var info = h.GetParam<CTakeDamageInfo>(1);
if (victimEntity == null || !victimEntity.IsValid || info == null) return;
var victimPawn = victimEntity.As<CCSPlayerPawn>(); var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return; if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value; var victimController = victimPawn.Controller.Value;
@ -88,16 +86,16 @@ namespace src.player.skills
if (victimInfo == null || victimInfo.Skill != skillName) return; if (victimInfo == null || victimInfo.Skill != skillName) return;
// Friendly-fire-off teammate hit deals 0 damage; don't zombify over a hit that never lands. // Friendly-fire-off teammate hit deals 0 damage; don't zombify over a hit that never lands.
if (SkillUtils.IsFriendlyFireBlocked(info, victimPawn)) return; if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return;
float effectiveDamage = info.Damage; float effectiveDamage = damageInfo.Damage;
if (SkillUtils.GetHitGroup(info) == HitGroup_t.HITGROUP_HEAD) if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(info); effectiveDamage *= GetHeadshotMultiplier(damageInfo);
if (effectiveDamage < victimPawn.Health) return; // survivable, let it through if (effectiveDamage < victimPawn.Health) return; // survivable, let it through
if (TryBecomeZombie(victim, victimPawn)) if (TryBecomeZombie(victim, victimPawn))
info.Damage = 0; damageInfo.Damage = 0;
} }
public static bool TryBecomeZombie(CCSPlayerController? victim, CCSPlayerPawn? victimPawn) public static bool TryBecomeZombie(CCSPlayerController? victim, CCSPlayerPawn? victimPawn)

View file

@ -40,9 +40,6 @@ namespace src.player.skills
var victim = @event.Userid; var victim = @event.Userid;
if (victim == null || !victim.IsValid || !victim.PawnIsAlive) return false; if (victim == null || !victim.IsValid || !victim.PawnIsAlive) return false;
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
if (attacker == null || !attacker.IsValid) return false;
var victimEvent = PlayerManager.GetPlayerEvent(victim); var victimEvent = PlayerManager.GetPlayerEvent(victim);
if (victimEvent == null || !victimEvent.IsValid) return false; if (victimEvent == null || !victimEvent.IsValid) return false;

View file

@ -1,4 +1,5 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
using static src.jRandomSkills; using static src.jRandomSkills;
@ -8,6 +9,7 @@ namespace src.player.skills
public class Regeneration : ISkill public class Regeneration : ISkill
{ {
private const Skills skillName = Skills.Regeneration; private const Skills skillName = Skills.Regeneration;
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -18,11 +20,11 @@ namespace src.player.skills
{ {
int cooldown = Math.Max(1, (int)(64 * SkillsInfo.GetValue<float>(skillName, "cooldown"))); int cooldown = Math.Max(1, (int)(64 * SkillsInfo.GetValue<float>(skillName, "cooldown")));
if (Server.TickCount % cooldown != 0) return; if (Server.TickCount % cooldown != 0) return;
foreach (var player in PlayerManager.GetTickPlayers()) PlayerManager.FillSkillHolders(skillName, holderBuffer);
{ if (holderBuffer.Count == 0) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
foreach (var player in holderBuffer)
{
var pawn = player.PlayerPawn.Value; var pawn = player.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid) continue; if (pawn == null || !pawn.IsValid) continue;
SkillUtils.AddHealth(pawn, SkillsInfo.GetValue<int>(skillName, "healthToAdd")); SkillUtils.AddHealth(pawn, SkillsInfo.GetValue<int>(skillName, "healthToAdd"));

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -136,18 +136,15 @@ namespace src.player.skills
replica.DispatchSpawn(); replica.DispatchSpawn();
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
if (string.IsNullOrEmpty(param.Entity.Name)) return; if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("Replica_")) return; if (!damagedEntity.Entity.Name.StartsWith("Replica_")) return;
var replica = param.As<CPhysicsPropMultiplayer>(); var replica = damagedEntity.As<CPhysicsPropMultiplayer>();
if (replica == null || !replica.IsValid) return; if (replica == null || !replica.IsValid) return;
if (!consumedReplicas.TryAdd(replica.Index, 0)) return; if (!consumedReplicas.TryAdd(replica.Index, 0)) return;
@ -157,7 +154,7 @@ namespace src.player.skills
var owner = GetReplicaOwner(replica.Index); var owner = GetReplicaOwner(replica.Index);
EntityManager.DestroyEntity(replica.Index); EntityManager.DestroyEntity(replica.Index);
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
if (attackerPawn.DesignerName != "player") if (attackerPawn.DesignerName != "player")
return; return;

View file

@ -42,6 +42,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (playersToSlow.IsEmpty) return;
foreach (var item in playersToSlow) foreach (var item in playersToSlow)
{ {
var playerIndex = item.Key; var playerIndex = item.Key;

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -27,13 +27,11 @@ namespace src.player.skills
// Must intercept pre-damage: player_hurt fires after the death is committed, so a // 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. // lethal hit (esp. fall damage) can't be undone there. Cancelling the blow here keeps the respawn clean.
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
var victimEntity = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
var info = h.GetParam<CTakeDamageInfo>(1);
if (victimEntity == null || !victimEntity.IsValid || info == null) return;
var victimPawn = victimEntity.As<CCSPlayerPawn>(); var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return; if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value; var victimController = victimPawn.Controller.Value;
@ -45,23 +43,23 @@ namespace src.player.skills
var victimInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index)); var victimInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index));
if (victimInfo == null || victimInfo.Skill != skillName) return; if (victimInfo == null || victimInfo.Skill != skillName) return;
if (SkillUtils.IsFriendlyFireBlocked(info, victimPawn)) return; if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return;
float effectiveDamage = info.Damage; float effectiveDamage = damageInfo.Damage;
if (SkillUtils.GetHitGroup(info) == HitGroup_t.HITGROUP_HEAD) if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(info); effectiveDamage *= GetHeadshotMultiplier(damageInfo);
if (effectiveDamage < victimPawn.Health) return; if (effectiveDamage < victimPawn.Health) return;
if (usedThisRound.TryGetValue(victim.Handle, out int savedTick)) if (usedThisRound.TryGetValue(victim.Handle, out int savedTick))
{ {
if (savedTick == Server.TickCount) if (savedTick == Server.TickCount)
info.Damage = 0; damageInfo.Damage = 0;
return; return;
} }
if (TryConsumeRevive(victim, victimPawn)) if (TryConsumeRevive(victim, victimPawn))
info.Damage = 0; damageInfo.Damage = 0;
} }
private static float GetHeadshotMultiplier(CTakeDamageInfo info) private static float GetHeadshotMultiplier(CTakeDamageInfo info)

View file

@ -61,6 +61,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (noSpace.IsEmpty) return;
foreach (var (playerIndex, time) in noSpace) foreach (var (playerIndex, time) in noSpace)
{ {
var player = Utilities.GetPlayerFromIndex((int)playerIndex); var player = Utilities.GetPlayerFromIndex((int)playerIndex);

View file

@ -29,20 +29,19 @@ namespace src.player.skills
border: !PlayerManager.GetTickPlayers().Any(p => p.Team == player.Team && p != player) ? "tb" : "t"); border: !PlayerManager.GetTickPlayers().Any(p => p.Team == player.Team && p != player) ? "tb" : "t");
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
return; return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle); CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle); CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player") if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return; return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null) if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null)
return; return;
@ -54,14 +53,15 @@ namespace src.player.skills
if (playerInfo.Skill == skillName) if (playerInfo.Skill == skillName)
{ {
float? skillChance = playerInfo.SkillChance; float? skillChance = playerInfo.SkillChance;
param2.Damage *= skillChance ?? 1f; damageInfo.Damage *= skillChance ?? 1f;
} }
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#09ba00", 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 chanceFrom = 1.15f, float chanceTo = 1.35f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#09ba00", 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 chanceFrom = 1.15f, float chanceTo = 1.35f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float ChanceFrom { get; set; } = chanceFrom; public float ChanceFrom { get; set; } = chanceFrom;
public float ChanceTo { get; set; } = chanceTo; public float ChanceTo { get; set; } = chanceTo;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -74,6 +74,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (cameras.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
if (cameras.TryGetValue(player.Index, out var cameraInfo) && cameraInfo.Item2 != 0) if (cameras.TryGetValue(player.Index, out var cameraInfo) && cameraInfo.Item2 != 0)
{ {

View file

@ -81,6 +81,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (SkillPlayerInfo.IsEmpty) return;
var players = PlayerManager.GetTickPlayers().ToArray(); var players = PlayerManager.GetTickPlayers().ToArray();
foreach (var player in players) foreach (var player in players)
{ {

View file

@ -35,6 +35,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (SkillPlayerInfo.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
{ {
if (player == null || !player.IsValid) continue; if (player == null || !player.IsValid) continue;

View file

@ -63,6 +63,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (cameras.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
if (cameras.TryGetValue(player.Index, out var cameraInfo) && cameraInfo.Item2 != 0) if (cameras.TryGetValue(player.Index, out var cameraInfo) && cameraInfo.Item2 != 0)
{ {

View file

@ -156,7 +156,13 @@ namespace src.player.skills
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)
{
bool wasDropped = knifeInfo.IsDropped;
DisableKnifeSkill(knifeInfo); DisableKnifeSkill(knifeInfo);
if (wasDropped && player != null && player.IsValid && player.PawnIsAlive)
player.GiveNamedItem("weapon_knife");
}
} }
public static void PlayerDisconnect(uint playerIndex) public static void PlayerDisconnect(uint playerIndex)

View file

@ -12,6 +12,13 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.ToxicSmoke; private const Skills skillName = Skills.ToxicSmoke;
private static readonly ConcurrentDictionary<Vector, uint> smokes = []; private static readonly ConcurrentDictionary<Vector, uint> smokes = [];
private static void RemoveOwnerSmokes(uint ownerIndex)
{
foreach (var smoke in smokes)
if (smoke.Value == ownerIndex)
smokes.TryRemove(smoke.Key, out _);
}
private readonly static ConcurrentDictionary<uint, int> playersWithSkill = []; private readonly static ConcurrentDictionary<uint, int> playersWithSkill = [];
public static void LoadSkill() public static void LoadSkill()
@ -40,9 +47,6 @@ namespace src.player.skills
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
foreach (var smoke in smokes.Keys.Where(v => v.X == @event.X && v.Y == @event.Y && v.Z == @event.Z)) foreach (var smoke in smokes.Keys.Where(v => v.X == @event.X && v.Y == @event.Y && v.Z == @event.Z))
smokes.TryRemove(smoke, out _); smokes.TryRemove(smoke, out _);
} }
@ -100,6 +104,8 @@ namespace src.player.skills
int damage = smokeDamage; int damage = smokeDamage;
if (thrower != null && eventPlayer.Index != thrower.Index && pawn.TeamNum == thrower.TeamNum) if (thrower != null && eventPlayer.Index != thrower.Index && pawn.TeamNum == thrower.TeamNum)
{ {
if (!SkillsInfo.GetValue<bool>(skillName, "friendlyFire")) continue;
damage = (int)MathF.Round(smokeDamage * SkillUtils.GetTeamDamageMultiplier(skillName)); damage = (int)MathF.Round(smokeDamage * SkillUtils.GetTeamDamageMultiplier(skillName));
if (damage <= 0) continue; if (damage <= 0) continue;
} }
@ -167,10 +173,11 @@ namespace src.player.skills
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
playersWithSkill.TryRemove(player.Index, out _); playersWithSkill.TryRemove(player.Index, out _);
RemoveOwnerSmokes(player.Index);
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1); SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", 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, int smokeDamage = 2, float smokeRadius = 180, int tickCooldown = 17, int grenadeLimit = 1, float dmgReductionForTeamates = 0.5f, float soundVolume = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", 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, int smokeDamage = 2, float smokeRadius = 180, int tickCooldown = 17, int grenadeLimit = 1, float dmgReductionForTeamates = 0.5f, float soundVolume = .3f, bool friendlyFire = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{ {
public float SoundVolume { get; set; } = soundVolume; public float SoundVolume { get; set; } = soundVolume;
public int SmokeDamage { get; set; } = smokeDamage; public int SmokeDamage { get; set; } = smokeDamage;
@ -178,6 +185,7 @@ namespace src.player.skills
public int TickCooldown { get; set; } = tickCooldown; public int TickCooldown { get; set; } = tickCooldown;
public int GrenadeLimit { get; set; } = grenadeLimit; public int GrenadeLimit { get; set; } = grenadeLimit;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates; public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
public bool FriendlyFire { get; set; } = friendlyFire;
} }
} }
} }

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
@ -43,11 +43,11 @@ namespace src.player.skills
pending.TryRemove(playerIndex, out _); pending.TryRemove(playerIndex, out _);
} }
public static void OnTakeDamage(DynamicHook h) public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{ {
if (holders.IsEmpty) return; if (holders.IsEmpty) return;
if (!TryResolveVictim(h, out var victim, out var victimPawn, out var info)) return; if (!TryResolveVictim(damagedEntity, damageInfo, out var victim, out var victimPawn, out var info)) return;
if (!holders.ContainsKey(victim!.Index)) return; if (!holders.ContainsKey(victim!.Index)) return;
int armor = victimPawn!.ArmorValue; int armor = victimPawn!.ArmorValue;
@ -71,28 +71,26 @@ namespace src.player.skills
$"passed={info.Damage:0.#} armor={armor}->{pending[victim.Index]} hp={victimPawn.Health}", DebugCategory.Damage); $"passed={info.Damage:0.#} armor={armor}->{pending[victim.Index]} hp={victimPawn.Health}", DebugCategory.Damage);
} }
public static void OnTakeDamagePost(DynamicHook h) public static void OnTakeDamagePost(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, CTakeDamageResult damageResult)
{ {
if (pending.IsEmpty) return; if (pending.IsEmpty) return;
if (!TryResolveVictim(h, out var victim, out var victimPawn, out _)) return; if (!TryResolveVictim(damagedEntity, damageInfo, out var victim, out var victimPawn, out _)) return;
if (!pending.TryRemove(victim!.Index, out int restored)) return; if (!pending.TryRemove(victim!.Index, out int restored)) return;
victimPawn!.ArmorValue = restored; victimPawn!.ArmorValue = restored;
Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue"); Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue");
} }
private static bool TryResolveVictim(DynamicHook h, out CCSPlayerController? victim, out CCSPlayerPawn? pawn, out CTakeDamageInfo? info) private static bool TryResolveVictim(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, out CCSPlayerController? victim, out CCSPlayerPawn? pawn, out CTakeDamageInfo? info)
{ {
victim = null; victim = null;
pawn = null; pawn = null;
info = null; info = null;
CEntityInstance param = h.GetParam<CEntityInstance>(0); if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return false;
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return false;
var victimPawn = param.As<CCSPlayerPawn>(); var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return false; if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return false;
var controller = victimPawn.Controller.Value; var controller = victimPawn.Controller.Value;
@ -104,7 +102,7 @@ namespace src.player.skills
var routed = PlayerManager.GetPlayerEvent(resolved); var routed = PlayerManager.GetPlayerEvent(resolved);
victim = routed != null && routed.IsValid ? routed : resolved; victim = routed != null && routed.IsValid ? routed : resolved;
pawn = victimPawn; pawn = victimPawn;
info = param2; info = damageInfo;
return true; return true;
} }

View file

@ -47,6 +47,8 @@ namespace src.player.skills
public static void OnTick() public static void OnTick()
{ {
if (SkillPlayerInfo.IsEmpty) return;
var players = PlayerManager.GetTickPlayers().ToArray(); var players = PlayerManager.GetTickPlayers().ToArray();
foreach (var player in players) foreach (var player in players)
{ {

View file

@ -114,7 +114,7 @@ namespace src.player.skills
!p.IsHLTV && !p.IsHLTV &&
p.Team != CsTeam.Spectator p.Team != CsTeam.Spectator
&& p.Team != CsTeam.None && p.Team != CsTeam.None
).ToArray(); ).OfType<CCSPlayerController>().ToArray();
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.UpdateMenu(player, menuItems); SkillUtils.UpdateMenu(player, menuItems);

View file

@ -63,10 +63,7 @@ namespace src.utils
var current = Newtonsoft.Json.Linq.JObject.Parse(json); var current = Newtonsoft.Json.Linq.JObject.Parse(json);
var expected = Newtonsoft.Json.Linq.JObject.FromObject(new SettingsModel()); var expected = Newtonsoft.Json.Linq.JObject.FromObject(new SettingsModel());
foreach (var property in expected.Properties()) return HasMissingKeys(current, expected);
if (current[property.Name] == null) return true;
return false;
} }
catch catch
{ {
@ -74,6 +71,21 @@ namespace src.utils
} }
} }
private static bool HasMissingKeys(Newtonsoft.Json.Linq.JObject current, Newtonsoft.Json.Linq.JObject expected)
{
foreach (var property in expected.Properties())
{
var currentValue = current[property.Name];
if (currentValue == null) return true;
if (property.Value is Newtonsoft.Json.Linq.JObject expectedChild
&& currentValue is Newtonsoft.Json.Linq.JObject currentChild
&& HasMissingKeys(currentChild, expectedChild)) return true;
}
return false;
}
private static bool IsLegacyDebugMode(string json) private static bool IsLegacyDebugMode(string json)
{ {
try try
@ -227,6 +239,7 @@ namespace src.utils
WSADMenuItemLineColor = "white", WSADMenuItemLineColor = "white",
WSADMenuItemHoverLineColor = "orange", WSADMenuItemHoverLineColor = "orange",
WSADMenuItemLineSize = "sm", WSADMenuItemLineSize = "sm",
WSADMenuVisibleItems = 3,
WSADMenuControllsLineSize = "sm", WSADMenuControllsLineSize = "sm",
WSADMenuControllsLineColor1 = "cyan", WSADMenuControllsLineColor1 = "cyan",
WSADMenuControllsLineColor2 = "white", WSADMenuControllsLineColor2 = "white",
@ -308,6 +321,7 @@ namespace src.utils
public required string WSADMenuItemLineColor { get; set; } public required string WSADMenuItemLineColor { get; set; }
public required string WSADMenuItemHoverLineColor { get; set; } public required string WSADMenuItemHoverLineColor { get; set; }
public required string WSADMenuItemLineSize { get; set; } public required string WSADMenuItemLineSize { get; set; }
public required int WSADMenuVisibleItems { get; set; }
public required string WSADMenuControllsLineSize { get; set; } public required string WSADMenuControllsLineSize { get; set; }
public required string WSADMenuControllsLineColor1 { get; set; } public required string WSADMenuControllsLineColor1 { get; set; }
public required string WSADMenuControllsLineColor2 { get; set; } public required string WSADMenuControllsLineColor2 { get; set; }

View file

@ -23,12 +23,15 @@ namespace src.utils
private const float maxLifetime = 25f; private const float maxLifetime = 25f;
private const int spawnFrames = 4;
private static readonly ConcurrentDictionary<string, Ring> rings = []; private static readonly ConcurrentDictionary<string, Ring> rings = [];
private sealed class Ring private sealed class Ring
{ {
public uint? Disc { get; set; } public uint? Disc { get; set; }
public List<uint> Beams { get; } = []; public List<uint> Beams { get; } = [];
public bool Cancelled { get; set; }
} }
public static void PreloadAssets() public static void PreloadAssets()
@ -48,9 +51,11 @@ namespace src.utils
if (!EntityManager.OverBudget()) if (!EntityManager.OverBudget())
{ {
Color color = GetSkillColor(skill); var segments = new List<(Vector From, Vector To)>(outerSegments + innerSegments);
SpawnRing(center, radius, outerSegments, color, ring); CollectRing(center, radius, outerSegments, segments);
SpawnRing(center, radius * .5f, innerSegments, color, ring); CollectRing(center, radius * .5f, innerSegments, segments);
SpawnBeamChunk(ring, segments, 0, GetSkillColor(skill));
} }
jRandomSkills.Instance.AddTimer(maxLifetime, () => Hide(skill, id), TimerFlags.STOP_ON_MAPCHANGE); jRandomSkills.Instance.AddTimer(maxLifetime, () => Hide(skill, id), TimerFlags.STOP_ON_MAPCHANGE);
@ -77,6 +82,8 @@ namespace src.utils
private static void Destroy(Ring ring) private static void Destroy(Ring ring)
{ {
ring.Cancelled = true;
foreach (uint beamIndex in ring.Beams) foreach (uint beamIndex in ring.Beams)
EntityManager.DestroyBeam(beamIndex); EntityManager.DestroyBeam(beamIndex);
@ -166,7 +173,7 @@ namespace src.utils
attribute.InteractsExclude = 0; attribute.InteractsExclude = 0;
} }
private static void SpawnRing(Vector center, float radius, int segments, Color color, Ring ring) private static void CollectRing(Vector center, float radius, int segments, List<(Vector From, Vector To)> output)
{ {
if (segments < 3 || radius <= 0) return; if (segments < 3 || radius <= 0) return;
@ -176,10 +183,23 @@ namespace src.utils
for (int i = 1; i <= segments; i++) for (int i = 1; i <= segments; i++)
{ {
Vector next = PointOn(center, radius, step * i); Vector next = PointOn(center, radius, step * i);
output.Add((previous, next));
previous = next;
}
}
var beam = EntityManager.CreateTrackedBeam(EntityManager.SystemOwnerIndex, previous, next, color); private static void SpawnBeamChunk(Ring ring, List<(Vector From, Vector To)> segments, int start, Color color)
if (beam != null && beam.IsValid)
{ {
if (ring.Cancelled || start >= segments.Count) return;
int perFrame = (segments.Count + spawnFrames - 1) / spawnFrames;
int end = Math.Min(start + perFrame, segments.Count);
for (int i = start; i < end; i++)
{
var beam = EntityManager.CreateTrackedBeam(EntityManager.SystemOwnerIndex, segments[i].From, segments[i].To, color);
if (beam == null || !beam.IsValid) continue;
beam.Width = 1.2f; beam.Width = 1.2f;
beam.EndWidth = 1.2f; beam.EndWidth = 1.2f;
@ -189,8 +209,9 @@ namespace src.utils
ring.Beams.Add(beam.Index); ring.Beams.Add(beam.Index);
} }
previous = next; if (end >= segments.Count) return;
}
Server.NextFrame(() => SpawnBeamChunk(ring, segments, end, color));
} }
private static Vector PointOn(Vector center, float radius, float angle) private static Vector PointOn(Vector center, float radius, float angle)

View file

@ -91,6 +91,21 @@ namespace src.utils
return (trackedEntities.Count, trackedEntities.Values.Select(e => e.PlayerIndex).Distinct().Count()); return (trackedEntities.Count, trackedEntities.Values.Select(e => e.PlayerIndex).Distinct().Count());
} }
public static string DescribeTracked(int topTypes = 4)
{
var byType = new Dictionary<string, int>();
foreach (var data in trackedEntities.Values)
{
string type = string.IsNullOrEmpty(data.EntityType) ? "?" : data.EntityType;
byType[type] = byType.TryGetValue(type, out int count) ? count + 1 : 1;
}
if (byType.Count == 0) return string.Empty;
var parts = byType.OrderByDescending(kvp => kvp.Value).Take(topTypes).Select(kvp => $"{kvp.Key}:{kvp.Value}");
return " types=" + string.Join(",", parts);
}
public static CParticleSystem? CreateTrackedParticleSystem(uint playerIndex, string particleName, float? autoDestroySeconds = null) public static CParticleSystem? CreateTrackedParticleSystem(uint playerIndex, string particleName, float? autoDestroySeconds = null)
{ {

View file

@ -18,6 +18,7 @@ namespace src.utils
private static readonly string playersLanguageFileName = "playersLanguage.json"; private static readonly string playersLanguageFileName = "playersLanguage.json";
private static readonly string configsFolderPath = Path.Combine(jRandomSkills.Instance.ModuleDirectory, "configs"); private static readonly string configsFolderPath = Path.Combine(jRandomSkills.Instance.ModuleDirectory, "configs");
private static ConcurrentDictionary<ulong, string> _playersLanguage = []; private static ConcurrentDictionary<ulong, string> _playersLanguage = [];
private static string _lastSavedPlayersJson = string.Empty;
private static readonly string geoliteFilePath = Path.Combine(jRandomSkills.Instance.ModuleDirectory, "packages", "GeoLite2-Country.mmdb"); private static readonly string geoliteFilePath = Path.Combine(jRandomSkills.Instance.ModuleDirectory, "packages", "GeoLite2-Country.mmdb");
private static string defaultLangCode = "en"; private static string defaultLangCode = "en";
@ -47,6 +48,7 @@ namespace src.utils
{ {
_translations.Clear(); _translations.Clear();
_playersLanguage.Clear(); _playersLanguage.Clear();
_lastSavedPlayersJson = string.Empty;
_skillNameCache.Clear(); _skillNameCache.Clear();
_skillDescCache.Clear(); _skillDescCache.Clear();
SetLangCode(); SetLangCode();
@ -324,16 +326,78 @@ namespace src.utils
return; return;
var jsonText = File.ReadAllText(filePath); var jsonText = File.ReadAllText(filePath);
_playersLanguage = JsonConvert.DeserializeObject<ConcurrentDictionary<ulong, string>>(jsonText) ?? []; if (string.IsNullOrWhiteSpace(jsonText))
return;
bool legacyFormat = false;
var loaded = new ConcurrentDictionary<ulong, string>();
try
{
var root = Newtonsoft.Json.Linq.JObject.Parse(jsonText);
foreach (var property in root.Properties())
{
if (property.Value is Newtonsoft.Json.Linq.JArray steamIds)
{
foreach (var steamId in steamIds)
if (ulong.TryParse(steamId.ToString(), out ulong grouped) && grouped != 0)
loaded[grouped] = property.Name;
}
else
{
legacyFormat = true;
if (ulong.TryParse(property.Name, out ulong flat) && flat != 0)
loaded[flat] = property.Value.ToString();
}
}
}
catch
{
Server.PrintToConsole($"[jRandomSkills] {playersLanguageFileName} could not be parsed, starting empty.");
return;
}
_playersLanguage = loaded;
if (legacyFormat)
{
Server.PrintToConsole($"[jRandomSkills] {playersLanguageFileName} converted to the grouped format ({_playersLanguage.Count} players).");
SavePlayersLanguage();
}
else
_lastSavedPlayersJson = BuildPlayersJson();
}
private static string BuildPlayersJson()
{
var grouped = new SortedDictionary<string, List<string>>(StringComparer.Ordinal);
foreach (var entry in _playersLanguage)
{
if (entry.Key == 0 || string.IsNullOrEmpty(entry.Value)) continue;
if (!grouped.TryGetValue(entry.Value, out var steamIds))
grouped[entry.Value] = steamIds = [];
steamIds.Add(entry.Key.ToString());
}
foreach (var steamIds in grouped.Values)
steamIds.Sort(StringComparer.Ordinal);
return JsonConvert.SerializeObject(grouped);
} }
private static void SavePlayersLanguage() private static void SavePlayersLanguage()
{ {
var json = BuildPlayersJson();
if (json == null || json == _lastSavedPlayersJson)
return;
Directory.CreateDirectory(configsFolderPath); Directory.CreateDirectory(configsFolderPath);
string filePath = Path.Combine(configsFolderPath, playersLanguageFileName); File.WriteAllText(Path.Combine(configsFolderPath, playersLanguageFileName), json);
var json = JsonConvert.SerializeObject(_playersLanguage); _lastSavedPlayersJson = json;
if (json != null)
File.WriteAllText(filePath, json);
} }
private static string? GetLangCodeFromFile(ulong? playerSteamID) private static string? GetLangCodeFromFile(ulong? playerSteamID)
@ -346,7 +410,9 @@ namespace src.utils
public static void ChangePlayerLanguage(CCSPlayerController? player, string language) public static void ChangePlayerLanguage(CCSPlayerController? player, string language)
{ {
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid || player.SteamID == 0) return;
if (string.IsNullOrEmpty(language)) return;
_playersLanguage.AddOrUpdate(player.SteamID, language, (k, v) => language); _playersLanguage.AddOrUpdate(player.SteamID, language, (k, v) => language);
SavePlayersLanguage(); SavePlayersLanguage();
} }

View file

@ -16,6 +16,35 @@ namespace src.utils
private static int cachedBombTick = int.MinValue; private static int cachedBombTick = int.MinValue;
private static CC4? cachedBomb; private static CC4? cachedBomb;
private static int cachedIdleTick = int.MinValue;
private static bool cachedIdle = true;
public static bool IsServerIdle()
{
int tick = Server.TickCount;
if (tick == cachedIdleTick) return cachedIdle;
cachedIdleTick = tick;
cachedIdle = true;
foreach (var player in GetTickPlayers())
{
if (player == null || !player.IsValid) continue;
if (player.IsBot || player.IsHLTV) continue;
cachedIdle = false;
break;
}
return cachedIdle;
}
private static int cachedPawnMapTick = int.MinValue;
private static readonly Dictionary<nint, CCSPlayerController> controllersByPawn = [];
private static int cachedBotMapTick = int.MinValue;
private static readonly Dictionary<nint, CCSPlayerController> botsByController = [];
private static readonly HashSet<nint> tickPlayerHandles = [];
private static void EnsureTickCache() private static void EnsureTickCache()
{ {
int tick = Server.TickCount; int tick = Server.TickCount;
@ -24,6 +53,28 @@ namespace src.utils
cachedControllers = Utilities.GetPlayers(); cachedControllers = Utilities.GetPlayers();
} }
private static void EnsureBotMapCache()
{
int tick = Server.TickCount;
if (tick == cachedBotMapTick) return;
cachedBotMapTick = tick;
botsByController.Clear();
tickPlayerHandles.Clear();
foreach (var player in GetTickPlayers())
{
if (player == null || !player.IsValid) continue;
tickPlayerHandles.Add(player.Handle);
if (!player.IsBot) continue;
var original = player.OriginalControllerOfCurrentPawn.Value;
if (original != null)
botsByController[original.Handle] = player;
}
}
public static List<CCSPlayerController> GetTickPlayers() public static List<CCSPlayerController> GetTickPlayers()
{ {
EnsureTickCache(); EnsureTickCache();
@ -69,12 +120,8 @@ namespace src.utils
if (!player.ControllingBot) if (!player.ControllingBot)
return player; return player;
return GetTickPlayers().FirstOrDefault(p => EnsureBotMapCache();
p != null && return botsByController.TryGetValue(player.Handle, out var bot) ? bot : player;
p.IsValid &&
p.IsBot &&
p.OriginalControllerOfCurrentPawn.Value != null && p.OriginalControllerOfCurrentPawn.Value == player)
?? player;
} }
public static CCSPlayerController? GetPlayerFromEvent(CCSPlayerController? player) public static CCSPlayerController? GetPlayerFromEvent(CCSPlayerController? player)
@ -85,12 +132,48 @@ namespace src.utils
if (!player.IsBot) if (!player.IsBot)
return player; return player;
return GetTickPlayers().FirstOrDefault(p => var original = player.OriginalControllerOfCurrentPawn.Value;
p != null && if (original == null || !original.IsValid || original.IsBot) return player;
p.IsValid &&
!p.IsBot && EnsureBotMapCache();
player.OriginalControllerOfCurrentPawn.Value != null && player.OriginalControllerOfCurrentPawn.Value == p) return tickPlayerHandles.Contains(original.Handle) ? original : player;
?? player; }
public static CCSPlayerController? GetControllerByPawn(nint pawnHandle)
{
if (pawnHandle == nint.Zero) return null;
int tick = Server.TickCount;
if (tick != cachedPawnMapTick)
{
cachedPawnMapTick = tick;
controllersByPawn.Clear();
foreach (var player in GetTickPlayers())
{
if (player == null || !player.IsValid) continue;
var handle = player.Pawn?.Value?.Handle;
if (handle != null && handle.Value != nint.Zero)
controllersByPawn[handle.Value] = player;
}
}
return controllersByPawn.TryGetValue(pawnHandle, out var controller) ? controller : null;
}
public static void FillSkillHolders(Skills skill, List<CCSPlayerController> buffer)
{
buffer.Clear();
foreach (var info in playersByIndex.Values)
{
if (info.Skill != skill) continue;
var controller = Utilities.GetPlayerFromIndex((int)info.PlayerIndex);
if (controller != null && controller.IsValid)
buffer.Add(controller);
}
} }
public static IEnumerable<jSkill_PlayerInfo> GetAllPlayers() public static IEnumerable<jSkill_PlayerInfo> GetAllPlayers()

View file

@ -220,27 +220,6 @@ namespace src.utils
SnapViewAngles.Value?.Invoke(pawn, angle); SnapViewAngles.Value?.Invoke(pawn, angle);
} }
public static void SetPlayerCollisions(CCSPlayerController? player, bool enable)
{
return;
if (player == null || !player.IsValid) return;
var pawn = player.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid || player.LifeState != (byte)LifeState_t.LIFE_ALIVE || pawn.CBodyComponent == null) return;
var collision = pawn.Collision;
if (collision == null) return;
var collisionGroup = (byte)(enable ? CollisionGroup.COLLISION_GROUP_PLAYER : CollisionGroup.COLLISION_GROUP_DEBRIS);
collision.CollisionGroup = collisionGroup;
collision.CollisionAttribute.CollisionGroup = collisionGroup;
Utilities.SetStateChanged(pawn, "CCollisionProperty", "m_collisionAttribute");
// CollisionRulesChanged(pawn);
}
//public static void CollisionRulesChanged(CBaseEntity? entity) //public static void CollisionRulesChanged(CBaseEntity? entity)
//{ //{
// if (entity == null || !entity.IsValid || collisionRulesChangedOffset <= 0) return; // if (entity == null || !entity.IsValid || collisionRulesChangedOffset <= 0) return;
@ -367,6 +346,30 @@ namespace src.utils
return !ff && !tae; // same team + FF off -> engine will zero this damage return !ff && !tae; // same team + FF off -> engine will zero this damage
} }
public static bool IsFriendlyFireBlocked(Skills skill, CTakeDamageInfo? info, CCSPlayerPawn? victimPawn)
{
if (!IsSameTeamHit(info, victimPawn)) return false;
if (!SkillsInfo.GetValue<bool>(skill, "friendlyFire")) return true;
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
public static bool IsSameTeamHit(CTakeDamageInfo? info, CCSPlayerPawn? victimPawn)
{
if (info == null || victimPawn == null || !victimPawn.IsValid) return false;
var attackerEnt = info.Attacker?.Value;
if (attackerEnt == null || !attackerEnt.IsValid) return false;
if (attackerEnt.Handle == victimPawn.Handle) return false;
var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle);
if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return false;
return attackerPawn.TeamNum == victimPawn.TeamNum;
}
public static float GetTeamDamageMultiplier(Skills skill) public static float GetTeamDamageMultiplier(Skills skill)
{ {
float reduction = SkillsInfo.GetValue<float>(skill, "dmgReductionForTeamates"); float reduction = SkillsInfo.GetValue<float>(skill, "dmgReductionForTeamates");
@ -945,9 +948,17 @@ namespace src.utils
{ {
if (jRandomSkills.Instance.MenuManager == null) if (jRandomSkills.Instance.MenuManager == null)
jRandomSkills.Instance.MenuManager = new WasdManager(); jRandomSkills.Instance.MenuManager = new WasdManager();
ApplyMenuVisibleItems();
return jRandomSkills.Instance.MenuManager; return jRandomSkills.Instance.MenuManager;
} }
private static void ApplyMenuVisibleItems()
{
int visibleItems = Config.LoadedConfig.HtmlHudCustomisation.WSADMenuVisibleItems;
WASDMenuAPI.WasdMenuPlayer.DefaultVisibleOptions = visibleItems < 1 ? 3 : Math.Min(visibleItems, 10);
}
public static void CloseMenu(CCSPlayerController? player) public static void CloseMenu(CCSPlayerController? player)
{ {
var manager = GetMenuManager(); var manager = GetMenuManager();
@ -1092,11 +1103,20 @@ namespace src.utils
if (lastElement != null) if (lastElement != null)
{ {
string encodedLastElement = isIlliterate string lastText = lastElement.Value.Item1;
? System.Net.WebUtility.HtmlEncode(Illiterate.GetRandomText(lastElement.Value.Item1)!) string lastColor = string.Empty;
: System.Net.WebUtility.HtmlEncode(lastElement.Value.Item1);
menu.Add($"\u202A{encodedLastElement}\u202C", (p, option) => if (lastText.Length > 8 && lastText[0] == '#' && lastText[7] == '|')
{
lastColor = lastText[..8];
lastText = lastText[8..];
}
string encodedLastElement = isIlliterate
? System.Net.WebUtility.HtmlEncode(Illiterate.GetRandomText(lastText)!)
: System.Net.WebUtility.HtmlEncode(lastText);
menu.Add($"{lastColor}\u202A{encodedLastElement}\u202C", (p, option) =>
{ {
jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [p, new[] { lastElement.Value.Item2 }]); jRandomSkills.Instance.SkillAction(playerInfo.Skill.ToString(), "TypeSkill", [p, new[] { lastElement.Value.Item2 }]);
if (lastElement.Value.Item3) if (lastElement.Value.Item3)
@ -1131,7 +1151,7 @@ namespace src.utils
TerminateRoundFunc.Value?.Invoke(jRandomSkills.Instance.GameRules.Handle, 5f, roundEndReason, 0, 0); TerminateRoundFunc.Value?.Invoke(jRandomSkills.Instance.GameRules.Handle, 5f, roundEndReason, 0, 0);
} }
public static void TerminateRound(CsTeam winnerTeam) public static void TerminateRound(CsTeam winnerTeam, CCSPlayerController? bonusPlayer = null)
{ {
if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return; if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return;
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager"); var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager");
@ -1144,6 +1164,40 @@ namespace src.utils
UpdateServerTeamScores(ctScore, tScore); UpdateServerTeamScores(ctScore, tScore);
jRandomSkills.Instance.GameRules?.TerminateRound(5f, winnerTeam == CsTeam.CounterTerrorist ? RoundEndReason.BombDefused : RoundEndReason.TargetBombed); jRandomSkills.Instance.GameRules?.TerminateRound(5f, winnerTeam == CsTeam.CounterTerrorist ? RoundEndReason.BombDefused : RoundEndReason.TargetBombed);
AwardRoundEndMoney(winnerTeam, bonusPlayer);
}
private static void AwardRoundEndMoney(CsTeam winnerTeam, CCSPlayerController? bonusPlayer = null)
{
int winnerReward = winnerTeam == CsTeam.CounterTerrorist
? ConVar.Find("cash_team_win_by_defusing_bomb")?.GetPrimitiveValue<int>() ?? 3500
: ConVar.Find("cash_team_terrorist_win_bomb")?.GetPrimitiveValue<int>() ?? 3500;
if (winnerReward <= 0) return;
int maxMoney = ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000;
int personalBonus = bonusPlayer == null
? 0
: winnerTeam == CsTeam.CounterTerrorist
? ConVar.Find("cash_player_defused_bomb")?.GetPrimitiveValue<int>() ?? 300
: ConVar.Find("cash_player_bomb_planted")?.GetPrimitiveValue<int>() ?? 300;
foreach (var player in PlayerManager.GetTickPlayers())
{
if (player == null || !player.IsValid) continue;
if (player.Team != winnerTeam) continue;
var moneyServices = player.InGameMoneyServices;
if (moneyServices == null) continue;
int reward = winnerReward;
if (bonusPlayer != null && bonusPlayer.IsValid && player.Index == bonusPlayer.Index)
reward += personalBonus;
moneyServices.Account = Math.Clamp(moneyServices.Account + reward, 0, maxMoney);
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
}
} }
private static void UpdateServerTeamScores(short ctScore, short tScore) private static void UpdateServerTeamScores(short ctScore, short tScore)

View file

@ -44,6 +44,9 @@ namespace src.utils
json = sr.ReadToEnd(); json = sr.ReadToEnd();
var root = JsonConvert.DeserializeObject<JArray>(json); var root = JsonConvert.DeserializeObject<JArray>(json);
bool needsRewrite = root == null;
var present = new HashSet<string>(StringComparer.Ordinal);
if (root != null) if (root != null)
foreach (var skillObj in root) foreach (var skillObj in root)
{ {
@ -51,7 +54,23 @@ namespace src.utils
if (string.IsNullOrEmpty(name)) continue; if (string.IsNullOrEmpty(name)) continue;
var instance = newConfig.FirstOrDefault(x => x.Name == name.ToString()); var instance = newConfig.FirstOrDefault(x => x.Name == name.ToString());
if (instance != null) JsonConvert.PopulateObject(skillObj.ToString(), instance); if (instance == null) continue;
present.Add(name);
if (skillObj is JObject current && HasMissingKeys(current, instance))
needsRewrite = true;
JsonConvert.PopulateObject(skillObj.ToString(), instance);
}
if (newConfig.Any(s => !string.IsNullOrEmpty(s.Name) && !present.Contains(s.Name)))
needsRewrite = true;
if (needsRewrite)
{
SaveConfig(newConfig);
Instance.Logger.LogInformation("skillsInfo.json was missing keys; rewritten with the defaults filled in.");
} }
} }
catch catch
@ -63,6 +82,16 @@ namespace src.utils
} }
} }
private static bool HasMissingKeys(JObject current, DefaultSkillInfo expectedInstance)
{
var expected = JObject.FromObject(expectedInstance);
foreach (var property in expected.Properties())
if (current[property.Name] == null) return true;
return false;
}
public static void SaveConfig(SkillsInfoModel config) public static void SaveConfig(SkillsInfoModel config)
{ {
lock (fileLock) lock (fileLock)

View file

@ -126,6 +126,7 @@
"WSADMenuItemLineColor": "white", "WSADMenuItemLineColor": "white",
"WSADMenuItemHoverLineColor": "orange", "WSADMenuItemHoverLineColor": "orange",
"WSADMenuItemLineSize": "sm", "WSADMenuItemLineSize": "sm",
"WSADMenuVisibleItems": 3,
"WSADMenuControllsLineSize": "sm", "WSADMenuControllsLineSize": "sm",
"WSADMenuControllsLineColor1": "cyan", "WSADMenuControllsLineColor1": "cyan",
"WSADMenuControllsLineColor2": "white", "WSADMenuControllsLineColor2": "white",

View file

@ -1,4 +1,4 @@
[ [
{ {
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
@ -146,6 +146,7 @@
"TickCooldown": 17, "TickCooldown": 17,
"GrenadeLimit": 1, "GrenadeLimit": 1,
"DmgReductionForTeamates": 0.5, "DmgReductionForTeamates": 0.5,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -321,6 +322,7 @@
{ {
"ChanceFrom": 1.15, "ChanceFrom": 1.15,
"ChanceTo": 1.35, "ChanceTo": 1.35,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -725,6 +727,7 @@
{ {
"StorePercent": 0.5, "StorePercent": 0.5,
"MaxStored": 100.0, "MaxStored": 100.0,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -883,6 +886,7 @@
"Rarity": "Common" "Rarity": "Common"
}, },
{ {
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -993,6 +997,7 @@
}, },
{ {
"ExtraDamagePercent": 0.25, "ExtraDamagePercent": 0.25,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -1798,6 +1803,7 @@
"Rarity": "Epic" "Rarity": "Epic"
}, },
{ {
"Cooldown": 5.0,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -1832,6 +1838,7 @@
"ChanceFrom": 0.15, "ChanceFrom": 0.15,
"ChanceTo": 0.3, "ChanceTo": 0.3,
"DmgReductionForTeamates": 0.5, "DmgReductionForTeamates": 0.5,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -1851,6 +1858,7 @@
"ExplosionDamage": 50, "ExplosionDamage": 50,
"PropModel": "models/props/de_train/hr_t/barrel_a/barrel_a.vmdl", "PropModel": "models/props/de_train/hr_t/barrel_a/barrel_a.vmdl",
"DmgReductionForTeamates": 0.5, "DmgReductionForTeamates": 0.5,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": true, "DisableOnFreezeTime": true,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -2008,6 +2016,7 @@
"ExplosionRadius": 500.0, "ExplosionRadius": 500.0,
"ExplosionDamage": 999, "ExplosionDamage": 999,
"DmgReductionForTeamates": 0.5, "DmgReductionForTeamates": 0.5,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -2103,6 +2112,7 @@
"Rarity": "Common" "Rarity": "Common"
}, },
{ {
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -2243,6 +2253,7 @@
"DmgReductionForTeamates": 0.5, "DmgReductionForTeamates": 0.5,
"Cooldown": 10.0, "Cooldown": 10.0,
"Force": 1000.0, "Force": 1000.0,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -2276,6 +2287,7 @@
{ {
"MaxSpeedVelocity": 2.0, "MaxSpeedVelocity": 2.0,
"MaxDamageVelocity": 2.0, "MaxDamageVelocity": 2.0,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,
@ -2356,6 +2368,7 @@
{ {
"DamageMultiplier": 2.0, "DamageMultiplier": 2.0,
"ToleranceDeg": 45.0, "ToleranceDeg": 45.0,
"FriendlyFire": true,
"NeedsTeammates": false, "NeedsTeammates": false,
"DisableOnFreezeTime": false, "DisableOnFreezeTime": false,
"DisableOnPistolRound": false, "DisableOnPistolRound": false,

View file

@ -487,7 +487,7 @@
"returntosender": "Zwrot do Nadawcy", "returntosender": "Zwrot do Nadawcy",
"returntosender_desc": "Pierwsze trafienie wroga powoduje, że wraca on na swój resp", "returntosender_desc": "Pierwsze trafienie wroga powoduje, że wraca on na swój resp",
"rewind": "Cofnięcie", "rewind": "Cofnięcietruearmor",
"rewind_desc": "Naciśnij [css_useSkill], aby zostawić znacznik i wkrótce do niego wrócić", "rewind_desc": "Naciśnij [css_useSkill], aby zostawić znacznik i wkrótce do niego wrócić",
"rewind_pending_info": "POWRÓT: {0}", "rewind_pending_info": "POWRÓT: {0}",