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 System.Diagnostics.CodeAnalysis;
using WASDSharedAPI;
namespace WASDMenuAPI.Classes;
public class WasdMenu : IWasdMenu
{
public string Title { get; set; } = "";
public string ItemText { get; set; } = "";
public string ItemHoverText { get; set; } = "";
public string ControlText { get; set; } = "";
[AllowNull] public string Title { get; set; } = "";
[AllowNull] public string ItemText { get; set; } = "";
[AllowNull] public string ItemHoverText { get; set; } = "";
[AllowNull] public string ControlText { get; set; } = "";
public LinkedList<IWasdMenuOption>? Options { get; set; } = new();
public LinkedListNode<IWasdMenuOption>? Prev { get; set; } = null;
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>? MenuStart = null;
public string CenterHtml = "";
public int VisibleOptions = 3;
public static int DefaultVisibleOptions = 3;
public int VisibleOptions => DefaultVisibleOptions;
public PlayerButtons Buttons { get; set; }
public bool Paused = false;
@ -245,10 +246,11 @@ public class WasdMenuPlayer
return;
}
if (MainMenu == null || MainMenu.Options == null)
var options = MainMenu?.Options;
if (MainMenu == null || options == null)
return;
MainMenu.Options.Clear();
options.Clear();
bool choiceExists = false;
foreach (var item in list)
@ -257,19 +259,19 @@ public class WasdMenuPlayer
{
OptionDisplay = item.Key,
OnChoose = item.Value,
Index = MainMenu.Options.Count,
Index = options.Count,
Parent = MainMenu
};
MainMenu.Options.AddLast(newOption);
options.AddLast(newOption);
if (CurrentChoice?.Value?.OptionDisplay == item.Key)
{
CurrentChoice = MainMenu.Options?.Last;
CurrentChoice = options.Last;
choiceExists = true;
}
}
if (!choiceExists)
CurrentChoice = MainMenu.Options?.First;
CurrentChoice = options.First;
if (CurrentChoice != null)
{

View file

@ -31,7 +31,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]";
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 ModuleVersion => "1.2.3.b7";
public override string ModuleVersion => "1.2.3.b8";
public override void Load(bool hotReload)
{
@ -165,7 +165,7 @@ namespace src
long perfStart = PerfLog.Start();
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;
}
@ -176,7 +176,7 @@ namespace src
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;
}

View file

@ -31,7 +31,7 @@ namespace src.player
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
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;
});
@ -39,7 +39,7 @@ namespace src.player
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
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;
});
@ -101,7 +101,7 @@ namespace src.player
if (Config.DebugEnabled(DebugCategory.Damage))
{
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre);
Instance.RegisterListener<OnEntityTakeDamagePre>(OnTakeDamage);
damageHooked = true;
}
}
@ -110,7 +110,7 @@ namespace src.player
{
if (damageHooked)
{
try { VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre); }
try { Instance.RemoveListener<OnEntityTakeDamagePre>(OnTakeDamage); }
catch { }
damageHooked = false;
}
@ -118,16 +118,13 @@ namespace src.player
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return HookResult.Continue;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return HookResult.Continue;
@ -141,10 +138,10 @@ namespace src.player
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
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}. " +
$"[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);
return HookResult.Continue;
}
@ -163,11 +160,14 @@ namespace src.player
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))
return;
if (!ignoreIdle && category != DebugCategory.Core && PlayerManager.IsServerIdle())
return;
lock (_writeLock)
{
_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)
{
DispatchOnTakeDamage(h);
CountDamageResolution(info);
object[] args = [entity, info];
DispatchOnTakeDamage(entity, info, args);
if (Fortnite.skillInThisRound == true &&
!Instance.SkillPlayer.Any(p => !p.IsDrawing && p.Skill == Skills.Fortnite))
Instance.SkillAction("Fortnite", "OnTakeDamage", [h]);
Instance.SkillAction("Fortnite", "OnTakeDamage", args);
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)
{
DispatchOnTakeDamage(h, true);
return HookResult.Continue;
DispatchOnTakeDamage(entity, info, [entity, info, result], true);
}
}

View file

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

View file

@ -47,39 +47,98 @@ namespace src.player
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
{
public double TotalMs;
public double MaxMs;
public int Count;
public readonly int[] Buckets = new int[bucketBoundsMs.Length];
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();
// 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.
public static void Sample(string label, long startTimestamp, double reportSeconds = 5.0, double maxThresholdMs = 0.5)
{
if (startTimestamp == 0 || !Enabled) return;
double ms = (Stopwatch.GetTimestamp() - startTimestamp) * 1000.0 / Stopwatch.Frequency;
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)
{
agg.TotalMs += ms;
agg.Count++;
if (ms > agg.MaxMs) agg.MaxMs = ms;
agg.Add(ms);
if ((DateTime.Now - agg.WindowStart).TotalSeconds < reportSeconds) return;
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.MaxMs = 0;
agg.Count = 0;
agg.WindowStart = DateTime.Now;
agg.Reset();
}
}
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.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Events;
@ -80,8 +80,10 @@ namespace src.player
Instance.HookUserMessage(208, PlayerMakeSound);
Instance.HookUserMessage(207, GetPrintToCenterHtml);
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamage, HookMode.Pre);
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook(OnTakeDamagePost, HookMode.Post);
Instance.RegisterListener<OnEntityTakeDamagePre>(OnEntityTakeDamagePre);
Instance.RegisterListener<OnEntityTakeDamagePost>(OnEntityTakeDamagePost);
PerfLog.ContextProvider = PerfContext;
Instance.RegisterEventHandler<EventBulletImpact>(BulletImpact);
@ -96,8 +98,8 @@ namespace src.player
public static void Unload()
{
TryUnhook(() => VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamage, HookMode.Pre));
TryUnhook(() => VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Unhook(OnTakeDamagePost, HookMode.Post));
TryUnhook(() => Instance.RemoveListener<OnEntityTakeDamagePre>(OnEntityTakeDamagePre));
TryUnhook(() => Instance.RemoveListener<OnEntityTakeDamagePost>(OnEntityTakeDamagePost));
TryUnhook(() => VirtualFunctions.CBaseTrigger_StartTouchFunc.Unhook(OnTriggerEnter, HookMode.Post));
TryUnhook(() => VirtualFunctions.CBaseTrigger_EndTouchFunc.Unhook(OnTriggerExit, 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>();
List<Skills>? deferred = null;
@ -186,15 +187,15 @@ namespace src.player
continue;
}
InvokeOnTakeDamage(p.Skill, h, args, post);
InvokeOnTakeDamage(p.Skill, damagedEntity, damageInfo, args, post);
}
if (deferred == null) return;
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))
{
@ -202,24 +203,22 @@ namespace src.player
return;
}
var info = h.GetParam<CTakeDamageInfo>(1);
float before = info == null ? 0f : info.Damage;
float before = damageInfo == null ? 0f : damageInfo.Damage;
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)
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
{
var victimEntity = h.GetParam<CEntityInstance>(0);
if (victimEntity == null || !victimEntity.IsValid) return string.Empty;
if (damagedEntity == null || !damagedEntity.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;
var controller = pawn.Controller.Value?.As<CCSPlayerController>();
@ -445,8 +444,61 @@ namespace src.player
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()
{
ReportStall();
long perfStart = PerfLog.Start();
lock (setLock)
{

View file

@ -17,11 +17,11 @@ namespace src.player
UpdateGameRules();
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);
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();
@ -108,7 +108,7 @@ namespace src.player
}
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");
skillLine = $"<font color='{randomSkill.Color}'>{player.GetSkillName(randomSkill.Skill)}</font>";
@ -148,9 +148,7 @@ namespace src.player
var observerTarget = pawn.ObserverServices.ObserverTarget?.Value;
if (observerTarget == null || !observerTarget.IsValid) return;
var observedPlayer = PlayerManager.GetTickPlayers().FirstOrDefault(p =>
p != null && p.IsValid && p.Pawn?.Value?.Handle == observerTarget.Handle);
var observedPlayer = PlayerManager.GetControllerByPawn(observerTarget.Handle);
if (observedPlayer == null) return;
var observedEvent = PlayerManager.GetPlayerEvent(observedPlayer);

View file

@ -17,15 +17,12 @@ namespace src.player.skills
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null || damageInfo.Handle == nint.Zero) return;
if (damageInfo.Attacker == null || !damageInfo.Attacker.IsValid || damageInfo.Attacker.Value == null) return;
if (param == null || !param.IsValid || param2 == null || param2.Handle == nint.Zero) return;
if (param2.Attacker == null || !param2.Attacker.IsValid || param2.Attacker.Value == null) return;
var attackerEnt = param2.Attacker.Value;
var attackerEnt = damageInfo.Attacker.Value;
if (attackerEnt == null || !attackerEnt.IsValid) return;
var attackerPawn = attackerEnt.As<CCSPlayerPawn>();
@ -40,12 +37,12 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo == null || playerInfo.Skill != skillName) return;
if (!SkillUtils.IsBulletDamage(param2)) return;
if (!SkillUtils.IsBulletDamage(damageInfo)) return;
int offset = GameData.GetOffset("CTakeDamageInfo_HitGroup");
if (offset <= 0) return;
nint hitGroupPointer = Marshal.ReadIntPtr(param2.Handle, offset);
nint hitGroupPointer = Marshal.ReadIntPtr(damageInfo.Handle, offset);
if (hitGroupPointer == nint.Zero) return;
nint hitGroupData = Marshal.ReadIntPtr(hitGroupPointer, 16);
@ -60,14 +57,12 @@ namespace src.player.skills
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 (info == null || info.Handle == nint.Zero)
if (damageInfo == null || damageInfo.Handle == nint.Zero)
return;
if (!SkillUtils.IsBulletDamage(info)) return;
if (!SkillUtils.IsBulletDamage(damageInfo)) return;
if (_restoreStack.Value!.Count > 0)
{

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
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");
}
public static void OnTakeDamage(DynamicHook h)
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
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;
var victimController = victimPawn.Controller.Value;
@ -53,7 +50,7 @@ namespace src.player.skills
if (playerInfo.Skill == skillName && victim.PawnIsAlive)
{
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.Utils;
using src.utils;
@ -17,20 +17,19 @@ namespace src.player.skills
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn.Controller?.Value == null || victimPawn.Controller?.Value == null)
return;
@ -42,12 +41,12 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo?.Skill != skillName) return;
var weapon = param2.Ability?.Value;
var weapon = damageInfo.Ability?.Value;
if (weapon == null || !weapon.IsValid) return;
if (IsNade(weapon.DesignerName)) return;
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)
@ -81,10 +80,11 @@ namespace src.player.skills
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 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;
public static readonly ConcurrentDictionary<uint, int> jumpedPlayers = [];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -35,20 +36,19 @@ namespace src.player.skills
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null)
return;
@ -60,7 +60,7 @@ namespace src.player.skills
if (playerInfo.Skill == skillName)
{
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()
{
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;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
var playerPawn = player.PlayerPawn?.Value;
if (playerPawn == null || playerPawn.VelocityModifier == 0) continue;
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))
playerPawn.VelocityModifier = newSpeedVelocity;
@ -109,15 +111,18 @@ namespace src.player.skills
if (jumpedPlayers.TryGetValue(player.Index, out var time) && time > Server.TickCount)
continue;
if (playerPawn.MoveType == MoveType_t.MOVETYPE_NOCLIP) continue;
if (!((PlayerFlags)player.Flags).HasFlag(PlayerFlags.FL_ONGROUND))
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 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 static readonly string[] noReflectionWeapon = ["inferno", "flashbang", "smokegrenade", "decoy", "hegrenade", "knife", "taser", "bayonet"];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -20,13 +21,11 @@ namespace src.player.skills
{
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;
if (playerPawn == null || !playerPawn.IsValid || playerPawn.VelocityModifier == 0) continue;

View file

@ -50,6 +50,8 @@ namespace src.player.skills
public static void OnTick()
{
if (SkillPlayerInfo.IsEmpty) return;
float cooldown = SkillsInfo.GetValue<float>(skillName, "cooldown");
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null) return;
if (param == null || param.Entity == null || param2 == null) return;
var nade = param2.Attacker.Value;
var nade = damageInfo.Attacker.Value;
if (nade == null || !nade.IsValid) 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 (!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.Controller?.Value == null) return;
@ -170,9 +169,17 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
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);
}
@ -185,13 +192,14 @@ namespace src.player.skills
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 int ExplosionDamage { get; set; } = explosionDamage;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
public float Cooldown { get; set; } = cooldown;
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 static readonly ConcurrentDictionary<uint, int> playersLastJump = [];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void NewRound()
{
@ -28,13 +29,15 @@ namespace src.player.skills
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;
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.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
@ -174,16 +174,13 @@ namespace src.player.skills
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return;

View file

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

View file

@ -14,20 +14,19 @@ namespace src.player.skills
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn.Controller?.Value == null || victimPawn.Controller?.Value == null)
return;
@ -38,15 +37,16 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(attacker.Index);
if (playerInfo?.Skill != skillName) return;
var weapon = param2.Ability?.Value;
var weapon = damageInfo.Ability?.Value;
if (weapon == null || !weapon.IsValid) 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.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
@ -292,18 +292,15 @@ namespace src.player.skills
return camera;
}
public static void OnTakeDamage(DynamicHook h)
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
if (string.IsNullOrEmpty(param.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("CypherCamera_")) return;
if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) 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);
if (userIndex == 0) return;

View file

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

View file

@ -36,7 +36,6 @@ namespace src.player.skills
Server.NextWorldUpdate(() =>
{
if (player == null || !player.IsValid || player.Team != lastTeam) return;
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null) return;
if (param == null || param.Entity == null || param2 == null) return;
var nade = param2.Attacker.Value;
var nade = damageInfo.Attacker.Value;
if (nade == null || !nade.IsValid) 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 (!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.Controller?.Value == null) return;
@ -122,9 +118,17 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
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);
}
@ -135,12 +139,13 @@ namespace src.player.skills
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 ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage;
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 static readonly Skills[] hidingSkills = [Skills.Ghost, Skills.Ninja, Skills.C4Camouflage];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -24,14 +25,14 @@ namespace src.player.skills
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;
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()
{
RestoreAll();
jammedUntilTick.Clear();
}
public static void RoundEnd()
{
RestoreAll();
jammedUntilTick.Clear();
}
public static void PlayerDisconnect(uint playerIndex)
@ -89,6 +87,8 @@ namespace src.player.skills
{
foreach (var playerIndex in jammedUntilTick.Keys)
SetHud(playerIndex, false);
jammedUntilTick.Clear();
}
private static void SetHud(uint playerIndex, bool jam)
@ -142,6 +142,10 @@ namespace src.player.skills
if (player == null || !player.IsValid) return;
playersWithSkill.TryRemove(player.Index, out _);
if (playersWithSkill.IsEmpty)
RestoreAll();
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.Collision.SolidType = SolidType_t.SOLID_VPHYSICS;
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(() =>
{
if (barrel == null || !barrel.IsValid) return;
barrel.SetModel(SkillsInfo.GetValue<string>(skillName, "propModel"));
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null) return;
if (param == null || param.Entity == null || param2 == null) return;
if (HandleBarrelHit(param)) return;
HandleNadeDamage(param, param2);
if (HandleBarrelHit(damagedEntity)) return;
HandleNadeDamage(damagedEntity, damageInfo);
}
private static bool HandleBarrelHit(CEntityInstance param)
private static bool HandleBarrelHit(CEntityInstance damagedEntity)
{
if (string.IsNullOrEmpty(param.Entity!.Name)) return false;
if (!param.Entity.Name.StartsWith("Barrel_")) return false;
if (string.IsNullOrEmpty(damagedEntity.Entity!.Name)) 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 (!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 (!int.TryParse(nameParts[2], out int teamNum)) return true;
if (!uint.TryParse(nameParts[3], out uint ownerIndex)) return true;
@ -207,9 +208,9 @@ namespace src.player.skills
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.DesignerName != "hegrenade_projectile") 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 (!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;
var victim = victimPawn.Controller.Value.As<CCSPlayerController>();
@ -229,9 +230,17 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
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);
}
@ -244,13 +253,14 @@ namespace src.player.skills
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 ExplosionRadius { get; set; } = explosionRadius;
public int ExplosionDamage { get; set; } = explosionDamage;
public string PropModel { get; set; } = propModel;
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null) return;
if (param == null || param.Entity == null || param2 == null) return;
var nade = param2.Attacker?.Value;
var nade = damageInfo.Attacker?.Value;
if (nade == null || !nade.IsValid) 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 (!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.Controller?.Value == null) return;
@ -102,12 +99,20 @@ namespace src.player.skills
if (victim == null || !victim.IsValid || victim.Index == ownerIndex) return;
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);
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);
}
@ -129,13 +134,14 @@ namespace src.player.skills
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 DamageRadius { get; set; } = damageRadius;
public float ChanceFrom { get; set; } = chanceFrom;
public float ChanceTo { get; set; } = chanceTo;
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()
{
if (cameras.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers())
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.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills;
namespace src.player.skills
@ -9,12 +10,75 @@ namespace src.player.skills
public class FastReload : ISkill
{
private const Skills skillName = Skills.FastReload;
private static readonly ConcurrentDictionary<uint, PlayerSkillInfo> SkillPlayerInfo = [];
public static void LoadSkill()
{
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)
{
var playerPawn = player.PlayerPawn.Value;
@ -24,24 +88,40 @@ namespace src.player.skills
if (playerInfo?.Skill != skillName) 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;
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;
if (activeWeapon == null || !activeWeapon.IsValid || activeWeapon.VData == null) return;
if (activeWeapon == null || !activeWeapon.IsValid || activeWeapon.VData == null) return false;
activeWeapon.Clip1 = activeWeapon.VData.MaxClip1;
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;
public static readonly ConcurrentDictionary<uint, int> jumpedPlayers = [];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -75,13 +76,14 @@ namespace src.player.skills
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;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
var playerPawn = player.PlayerPawn?.Value;
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)
continue;
if (playerPawn.MoveType == MoveType_t.MOVETYPE_NOCLIP) continue;
if (!((PlayerFlags)player.Flags).HasFlag(PlayerFlags.FL_ONGROUND))
playerPawn.AbsVelocity.Z = Math.Min(playerPawn.AbsVelocity.Z, 10);
}

View file

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

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
if (string.IsNullOrEmpty(param.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("FortniteWall")) return;
if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) return;
if (!damagedEntity.Entity.Name.StartsWith("FortniteWall")) return;
var box = param.As<CDynamicProp>();
var box = damagedEntity.As<CDynamicProp>();
if (box == null || !box.IsValid) return;
box.EmitSound("Wood_Plank.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
if (barricades.TryGetValue(box.Index, out int health))
{
int newHealth = health - (int)param2.Damage;
int newHealth = health - (int)damageInfo.Damage;
if (newHealth <= 0)
{

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
@ -29,19 +29,16 @@ namespace src.player.skills
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
if (param2.AmmoType == 255)
if (damageInfo.AmmoType == 255)
return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn == null || !attackerPawn.IsValid || victimPawn == null || !victimPawn.IsValid)
return;
@ -67,8 +64,8 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(attacker)?.Index ?? attacker.Index));
if (playerInfo?.Skill != skillName || attacker!.Team != victim!.Team) return;
float damage = param2.Damage;
param2.Damage = 0;
float damage = damageInfo.Damage;
damageInfo.Damage = 0;
if (!autoKickOverridden)
{

View file

@ -46,9 +46,15 @@ namespace src.player.skills
return;
}
if (skill.Skill == skillName && !TakeMoney(player))
if (skill.Skill == skillName)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("gambler_no_money"));
if (!TakeMoney(player))
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("gambler_no_money"));
return;
}
EnableSkill(player);
return;
}

View file

@ -11,11 +11,9 @@ namespace src.player.skills
{
private const Skills skillName = Skills.GravityDecoy;
private const float defaultGravity = 1f;
private static readonly ConcurrentDictionary<uint, int> playersWithSkill = [];
private static readonly ConcurrentDictionary<uint, byte> affected = [];
private static readonly ConcurrentDictionary<uint, byte> restoreOnRespawn = [];
private static readonly ConcurrentDictionary<uint, float> affected = [];
private static readonly ConcurrentDictionary<uint, float> restoreOnRespawn = [];
public static void LoadSkill()
{
@ -78,8 +76,8 @@ namespace src.player.skills
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) continue;
if (pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) continue;
if (restoreOnRespawn.TryRemove(eventPlayer.Index, out _))
pawn.ActualGravityScale = defaultGravity;
if (restoreOnRespawn.TryRemove(eventPlayer.Index, out float respawnGravity))
pawn.ActualGravityScale = respawnGravity;
bool inside = false;
foreach (var decoyPos in decoyPositions)
@ -87,11 +85,11 @@ namespace src.player.skills
if (inside)
{
if (!affected.TryAdd(eventPlayer.Index, 0)) continue;
if (!affected.TryAdd(eventPlayer.Index, pawn.ActualGravityScale)) continue;
pawn.ActualGravityScale = gravity;
}
else if (affected.TryRemove(eventPlayer.Index, out _))
pawn.ActualGravityScale = defaultGravity;
else if (affected.TryRemove(eventPlayer.Index, out float ownGravity))
pawn.ActualGravityScale = ownGravity;
}
}
@ -100,10 +98,10 @@ namespace src.player.skills
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
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);
restoreOnRespawn[victim.Index] = 0;
Restore(victim.Index, ownGravity);
restoreOnRespawn[victim.Index] = ownGravity;
}
public static void PlayerDisconnect(uint playerIndex)
@ -115,23 +113,23 @@ namespace src.player.skills
private static void RestoreAll()
{
foreach (uint playerIndex in affected.Keys)
Restore(playerIndex);
foreach (var entry in affected)
Restore(entry.Key, entry.Value);
foreach (uint playerIndex in restoreOnRespawn.Keys)
Restore(playerIndex);
foreach (var entry in restoreOnRespawn)
Restore(entry.Key, entry.Value);
affected.Clear();
restoreOnRespawn.Clear();
}
private static void Restore(uint playerIndex)
private static void Restore(uint playerIndex, float gravity)
{
var player = Utilities.GetPlayerFromIndex((int)playerIndex);
var pawn = player?.PlayerPawn?.Value;
if (pawn != null && pawn.IsValid)
pawn.ActualGravityScale = defaultGravity;
pawn.ActualGravityScale = gravity;
}
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.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
@ -35,21 +35,18 @@ namespace src.player.skills
chickenOwners.Clear();
}
public static void OnTakeDamage(DynamicHook h)
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (damagedEntity == null || damageInfo == null) return;
if (param == null || param2 == null) return;
if (!chickenOwners.TryGetValue(param.Index, out var ownerData)) return;
if (!chickenOwners.TryGetValue(damagedEntity.Index, out var ownerData)) return;
uint ownerIndex = ownerData.ownerIndex;
int health = ownerData.health;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner == null || !owner.IsValid) return;
var attackerEnt = param2.Attacker?.Value;
var attackerEnt = damageInfo.Attacker?.Value;
if (attackerEnt == null || !attackerEnt.IsValid) return;
var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle);
@ -60,15 +57,15 @@ namespace src.player.skills
if (attackerPawn.Index != ownerPawn.Index && attackerPawn.TeamNum == owner.TeamNum)
{
param2.Damage = 0;
damageInfo.Damage = 0;
return;
}
int newHealth = Math.Max(0, health - (int)param2.Damage);
chickenOwners[param.Index] = (ownerIndex, newHealth);
int newHealth = Math.Max(0, health - (int)damageInfo.Damage);
chickenOwners[damagedEntity.Index] = (ownerIndex, newHealth);
if (newHealth > 0)
CreateHitParticles(param.Index);
CreateHitParticles(damagedEntity.Index);
}
private static void CreateHitParticles(uint chickenIndex)

View file

@ -10,7 +10,14 @@ namespace src.player.skills
public class HealingSmoke : ISkill
{
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 = [];
public static void LoadSkill()
@ -31,7 +38,7 @@ namespace src.player.skills
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
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)
@ -39,9 +46,6 @@ namespace src.player.skills
var player = PlayerManager.GetPlayerEvent(@event.Userid);
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))
smokes.TryRemove(smoke, out _);
}
@ -153,6 +157,7 @@ namespace src.player.skills
if (player == null || !player.IsValid) return;
playersWithSkill.TryRemove(player.Index, out _);
RemoveOwnerSmokes(player.Index);
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
}

View file

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

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Timers;
@ -65,7 +65,6 @@ namespace src.player.skills
{
UpdateWeapons(player, playerSkill);
CreateClone(playerSkill);
SkillUtils.SetPlayerCollisions(player, false);
}
else if (playerSkill.CloneProp != null)
KillClone(playerSkill);
@ -138,8 +137,6 @@ namespace src.player.skills
if (eventTarget != null && eventTarget.IsValid)
SkillUtils.ApplyScreenColor(eventTarget, 0, 0, 0, 0, 10, 0, 2);
SkillUtils.SetPlayerCollisions(player, true);
BlockWeapon(player, false);
playerSkill.NextUse = Server.TickCount + SkillsInfo.GetValue<float>(skillName, "Cooldown") * 64;
playerSkill.UseTime = 0;
@ -305,25 +302,22 @@ namespace src.player.skills
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null)
return;
if (string.IsNullOrEmpty(param.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("IanaClone_")) return;
if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) 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);
if (userIndex == 0) return;
var player = Utilities.GetPlayerFromIndex((int)userIndex);
if (player == null || !player.IsValid) return;
float dealDamage = param2.Damage;
float dealDamage = damageInfo.Damage;
if (playersInfo.TryGetValue(player.Index, out var playerSkill))
{
@ -333,7 +327,7 @@ namespace src.player.skills
if (cloneProp != null && cloneProp.IsValid && cloneProp.AbsOrigin != null)
{
Vector pos = param2.DamagePosition;
Vector pos = damageInfo.DamagePosition;
Vector posCl = cloneProp.AbsOrigin;
bool isHead = false;
@ -343,8 +337,8 @@ namespace src.player.skills
if (pos.Z >= posCl.Z + viewOffset - diff)
isHead = true;
float damage = param2.Damage;
string? weapon = param2.Ability?.Value?.DesignerName;
float damage = damageInfo.Damage;
string? weapon = damageInfo.Ability?.Value?.DesignerName;
if (weapon != null)
dealDamage = CalculateDamage(player, weapon, damage, isHead);
}
@ -357,8 +351,8 @@ namespace src.player.skills
var playerPawn = player.PlayerPawn.Value;
if (playerPawn != null)
{
KillfeedIcons? killfeed = KillfeedIconsExtensions.FromWeaponName(param2.Ability?.Value?.DesignerName);
SkillUtils.TakeHealth(playerPawn, (int)dealDamage, GetShooter(param2), killfeed ?? KillfeedIcons.Leg);
KillfeedIcons? killfeed = KillfeedIconsExtensions.FromWeaponName(damageInfo.Ability?.Value?.DesignerName);
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.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Timers;
@ -183,18 +183,15 @@ namespace src.player.skills
}, 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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
if (string.IsNullOrEmpty(param.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("Illusionist_")) return;
if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) 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 (!consumedReplicas.TryAdd(replica.Index, 0)) return;
@ -205,7 +202,7 @@ namespace src.player.skills
var owner = GetReplicaOwner(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;
var attackerTeam = attackerPawn.TeamNum;

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
@ -58,7 +58,7 @@ namespace src.player.skills
if (playerInfo == null || playerInfo.Skill != skillName) 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())));
SkillUtils.UpdateMenu(player, menuItems);
@ -117,7 +117,7 @@ namespace src.player.skills
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
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)
{

View file

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

View file

@ -33,7 +33,7 @@ namespace src.player.skills
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);
}
}

View file

@ -125,15 +125,13 @@ namespace src.player.skills
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;
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return;
if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
var victimPawn = param.As<CCSPlayerPawn>();
var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value;
@ -145,12 +143,15 @@ namespace src.player.skills
uint victimIndex = PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index;
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 bool FriendlyFire { get; set; } = friendlyFire;
}
}
}

View file

@ -15,20 +15,19 @@ namespace src.player.skills
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);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null)
return;
@ -39,11 +38,12 @@ namespace src.player.skills
if (playerInfo == null) return;
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 victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return false;
if ((HitGroup_t)@event.Hitgroup == HitGroup_t.HITGROUP_HEAD) return false;
if (!Instance.IsPlayerValid(victim)) 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);
return true;
}

View file

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

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
@ -35,13 +35,11 @@ namespace src.player.skills
phoenixTicks.TryRemove(playerIndex, out _);
}
public static void OnTakeDamage(DynamicHook h)
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
var victimEntity = h.GetParam<CEntityInstance>(0);
var info = h.GetParam<CTakeDamageInfo>(1);
if (victimEntity == null || !victimEntity.IsValid || info == null) return;
if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
var victimPawn = victimEntity.As<CCSPlayerPawn>();
var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value;
@ -53,16 +51,16 @@ namespace src.player.skills
var victimInfo = PlayerManager.GetPlayerByIndex(PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index);
if (victimInfo == null || victimInfo.Skill != skillName) return;
if (SkillUtils.IsFriendlyFireBlocked(info, victimPawn)) return;
if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return;
float effectiveDamage = info.Damage;
if (SkillUtils.GetHitGroup(info) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(info);
float effectiveDamage = damageInfo.Damage;
if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(damageInfo);
if (effectiveDamage < victimPawn.Health) return;
if (TryConsumeRevive(victim, victimPawn))
info.Damage = 0;
damageInfo.Damage = 0;
}
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 static readonly ConcurrentDictionary<uint, Pilot_PlayerInfo> PlayerPilotInfo = [];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -40,12 +41,11 @@ namespace src.player.skills
public static void OnTick()
{
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill == skillName)
HandlePilot(player);
}
PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
foreach (var player in holderBuffer)
HandlePilot(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> targetToPlayer = [];
private static readonly object setLock = new();
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -49,9 +50,9 @@ namespace src.player.skills
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;
@ -75,11 +76,12 @@ namespace src.player.skills
}
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;
var enemies = SkillUtils.GetSelectableEnemies(player, true);

View file

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

View file

@ -50,15 +50,13 @@ namespace src.player.skills
storedDamage[victim.Index] = 0f;
}
public static void OnTakeDamage(DynamicHook h)
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
if (storedDamage.IsEmpty) return;
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return;
if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
var victimPawn = param.As<CCSPlayerPawn>();
var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value;
@ -67,9 +65,11 @@ namespace src.player.skills
var victim = victimController.As<CCSPlayerController>();
if (victim == null || !victim.IsValid) return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
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>();
uint? attackerIndex = attackerController != null && attackerController.IsValid
? 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)
{
storedDamage[attackerIndex.Value] = 0f;
param2.Damage += banked;
damageInfo.Damage += banked;
var puncher = CounterStrikeSharp.API.Utilities.GetPlayerFromIndex((int)attackerIndex.Value);
var puncherPawn = puncher?.PlayerPawn?.Value;
@ -97,14 +97,15 @@ namespace src.player.skills
float ratio = SkillsInfo.GetValue<float>(skillName, "storePercent");
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);
}
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 MaxStored { get; set; } = maxStored;
public bool FriendlyFire { get; set; } = friendlyFire;
}
}
}

View file

@ -1,4 +1,5 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
@ -9,6 +10,7 @@ namespace src.player.skills
public class QuickShot : ISkill
{
private const Skills skillName = Skills.QuickShot;
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -17,14 +19,16 @@ namespace src.player.skills
public static void OnTick()
{
foreach (var player in PlayerManager.GetTickPlayers())
{
if (player == null) continue;
PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill != skillName) continue;
foreach (var player in holderBuffer)
{
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;
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 static readonly Skills[] hidingSkills = [Skills.Ghost, Skills.Ninja, Skills.C4Camouflage];
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -18,14 +19,14 @@ namespace src.player.skills
public static void OnTick()
{
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerEvent = PlayerManager.GetPlayerEvent(player);
if (playerEvent == null) continue;
PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
if (PlayerManager.GetPlayerByIndex(playerEvent.Index)?.Skill != skillName) continue;
foreach (var playerEvent in holderBuffer)
{
if (!Instance.IsPlayerValid(playerEvent)) continue;
var player = PlayerManager.GetPlayerFromEvent(playerEvent) ?? playerEvent;
SetEnemiesVisibleOnRadar(player);
}
}

View file

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

View file

@ -40,9 +40,6 @@ namespace src.player.skills
var victim = @event.Userid;
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);
if (victimEvent == null || !victimEvent.IsValid) return false;

View file

@ -1,4 +1,5 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using static src.jRandomSkills;
@ -8,6 +9,7 @@ namespace src.player.skills
public class Regeneration : ISkill
{
private const Skills skillName = Skills.Regeneration;
private static readonly List<CCSPlayerController> holderBuffer = [];
public static void LoadSkill()
{
@ -18,11 +20,11 @@ namespace src.player.skills
{
int cooldown = Math.Max(1, (int)(64 * SkillsInfo.GetValue<float>(skillName, "cooldown")));
if (Server.TickCount % cooldown != 0) return;
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) continue;
PlayerManager.FillSkillHolders(skillName, holderBuffer);
if (holderBuffer.Count == 0) return;
foreach (var player in holderBuffer)
{
var pawn = player.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid) continue;
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.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
@ -136,18 +136,15 @@ namespace src.player.skills
replica.DispatchSpawn();
}
public static void OnTakeDamage(DynamicHook h)
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
if (string.IsNullOrEmpty(param.Entity.Name)) return;
if (!param.Entity.Name.StartsWith("Replica_")) return;
if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) 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 (!consumedReplicas.TryAdd(replica.Index, 0)) return;
@ -157,7 +154,7 @@ namespace src.player.skills
var owner = GetReplicaOwner(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;

View file

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

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
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
// 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);
var info = h.GetParam<CTakeDamageInfo>(1);
if (victimEntity == null || !victimEntity.IsValid || info == null) return;
if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
var victimPawn = victimEntity.As<CCSPlayerPawn>();
var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller.Value;
@ -45,23 +43,23 @@ namespace src.player.skills
var victimInfo = PlayerManager.GetPlayerByIndex((PlayerManager.GetPlayerEvent(victim)?.Index ?? victim.Index));
if (victimInfo == null || victimInfo.Skill != skillName) return;
if (SkillUtils.IsFriendlyFireBlocked(info, victimPawn)) return;
if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return;
float effectiveDamage = info.Damage;
if (SkillUtils.GetHitGroup(info) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(info);
float effectiveDamage = damageInfo.Damage;
if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(damageInfo);
if (effectiveDamage < victimPawn.Health) return;
if (usedThisRound.TryGetValue(victim.Handle, out int savedTick))
{
if (savedTick == Server.TickCount)
info.Damage = 0;
damageInfo.Damage = 0;
return;
}
if (TryConsumeRevive(victim, victimPawn))
info.Damage = 0;
damageInfo.Damage = 0;
}
private static float GetHeadshotMultiplier(CTakeDamageInfo info)

View file

@ -61,6 +61,8 @@ namespace src.player.skills
public static void OnTick()
{
if (noSpace.IsEmpty) return;
foreach (var (playerIndex, time) in noSpace)
{
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");
}
public static void OnTakeDamage(DynamicHook h)
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null || damageInfo.Attacker == null || damageInfo.Attacker.Value == null)
return;
CCSPlayerPawn attackerPawn = new(param2.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(param.Handle);
CCSPlayerPawn attackerPawn = new(damageInfo.Attacker.Value.Handle);
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (attackerPawn.DesignerName != "player" || victimPawn.DesignerName != "player")
return;
if (SkillUtils.IsFriendlyFireBlocked(skillName, damageInfo, victimPawn)) return;
if (attackerPawn == null || attackerPawn.Controller?.Value == null || victimPawn == null || victimPawn.Controller?.Value == null)
return;
@ -54,14 +53,15 @@ namespace src.player.skills
if (playerInfo.Skill == skillName)
{
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 ChanceTo { get; set; } = chanceTo;
public bool FriendlyFire { get; set; } = friendlyFire;
}
}
}

View file

@ -74,6 +74,8 @@ namespace src.player.skills
public static void OnTick()
{
if (cameras.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers())
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()
{
if (SkillPlayerInfo.IsEmpty) return;
var players = PlayerManager.GetTickPlayers().ToArray();
foreach (var player in players)
{

View file

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

View file

@ -63,6 +63,8 @@ namespace src.player.skills
public static void OnTick()
{
if (cameras.IsEmpty) return;
foreach (var player in PlayerManager.GetTickPlayers())
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)
{
if (knivesInfo.TryRemove(player.Index, out KnifeInfo? knifeInfo) && knifeInfo != null)
{
bool wasDropped = knifeInfo.IsDropped;
DisableKnifeSkill(knifeInfo);
if (wasDropped && player != null && player.IsValid && player.PawnIsAlive)
player.GiveNamedItem("weapon_knife");
}
}
public static void PlayerDisconnect(uint playerIndex)

View file

@ -12,6 +12,13 @@ namespace src.player.skills
{
private const Skills skillName = Skills.ToxicSmoke;
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 = [];
public static void LoadSkill()
@ -40,9 +47,6 @@ namespace src.player.skills
var player = PlayerManager.GetPlayerEvent(@event.Userid);
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))
smokes.TryRemove(smoke, out _);
}
@ -100,6 +104,8 @@ namespace src.player.skills
int damage = smokeDamage;
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));
if (damage <= 0) continue;
}
@ -167,10 +173,11 @@ namespace src.player.skills
if (player == null || !player.IsValid) return;
playersWithSkill.TryRemove(player.Index, out _);
RemoveOwnerSmokes(player.Index);
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 int SmokeDamage { get; set; } = smokeDamage;
@ -178,6 +185,7 @@ namespace src.player.skills
public int TickCooldown { get; set; } = tickCooldown;
public int GrenadeLimit { get; set; } = grenadeLimit;
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.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
@ -43,11 +43,11 @@ namespace src.player.skills
pending.TryRemove(playerIndex, out _);
}
public static void OnTakeDamage(DynamicHook h)
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
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;
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);
}
public static void OnTakeDamagePost(DynamicHook h)
public static void OnTakeDamagePost(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, CTakeDamageResult damageResult)
{
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;
victimPawn!.ArmorValue = restored;
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;
pawn = null;
info = null;
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || !param.IsValid || param2 == null) return false;
if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return false;
var victimPawn = param.As<CCSPlayerPawn>();
var victimPawn = damagedEntity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.DesignerName != "player") return false;
var controller = victimPawn.Controller.Value;
@ -104,7 +102,7 @@ namespace src.player.skills
var routed = PlayerManager.GetPlayerEvent(resolved);
victim = routed != null && routed.IsValid ? routed : resolved;
pawn = victimPawn;
info = param2;
info = damageInfo;
return true;
}

View file

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

View file

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

View file

@ -63,10 +63,7 @@ namespace src.utils
var current = Newtonsoft.Json.Linq.JObject.Parse(json);
var expected = Newtonsoft.Json.Linq.JObject.FromObject(new SettingsModel());
foreach (var property in expected.Properties())
if (current[property.Name] == null) return true;
return false;
return HasMissingKeys(current, expected);
}
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)
{
try
@ -227,6 +239,7 @@ namespace src.utils
WSADMenuItemLineColor = "white",
WSADMenuItemHoverLineColor = "orange",
WSADMenuItemLineSize = "sm",
WSADMenuVisibleItems = 3,
WSADMenuControllsLineSize = "sm",
WSADMenuControllsLineColor1 = "cyan",
WSADMenuControllsLineColor2 = "white",
@ -308,6 +321,7 @@ namespace src.utils
public required string WSADMenuItemLineColor { get; set; }
public required string WSADMenuItemHoverLineColor { get; set; }
public required string WSADMenuItemLineSize { get; set; }
public required int WSADMenuVisibleItems { get; set; }
public required string WSADMenuControllsLineSize { get; set; }
public required string WSADMenuControllsLineColor1 { get; set; }
public required string WSADMenuControllsLineColor2 { get; set; }

View file

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

View file

@ -91,6 +91,21 @@ namespace src.utils
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)
{

View file

@ -18,6 +18,7 @@ namespace src.utils
private static readonly string playersLanguageFileName = "playersLanguage.json";
private static readonly string configsFolderPath = Path.Combine(jRandomSkills.Instance.ModuleDirectory, "configs");
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 string defaultLangCode = "en";
@ -47,6 +48,7 @@ namespace src.utils
{
_translations.Clear();
_playersLanguage.Clear();
_lastSavedPlayersJson = string.Empty;
_skillNameCache.Clear();
_skillDescCache.Clear();
SetLangCode();
@ -324,16 +326,78 @@ namespace src.utils
return;
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()
{
var json = BuildPlayersJson();
if (json == null || json == _lastSavedPlayersJson)
return;
Directory.CreateDirectory(configsFolderPath);
string filePath = Path.Combine(configsFolderPath, playersLanguageFileName);
var json = JsonConvert.SerializeObject(_playersLanguage);
if (json != null)
File.WriteAllText(filePath, json);
File.WriteAllText(Path.Combine(configsFolderPath, playersLanguageFileName), json);
_lastSavedPlayersJson = json;
}
private static string? GetLangCodeFromFile(ulong? playerSteamID)
@ -346,7 +410,9 @@ namespace src.utils
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);
SavePlayersLanguage();
}

View file

@ -16,6 +16,35 @@ namespace src.utils
private static int cachedBombTick = int.MinValue;
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()
{
int tick = Server.TickCount;
@ -24,6 +53,28 @@ namespace src.utils
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()
{
EnsureTickCache();
@ -69,12 +120,8 @@ namespace src.utils
if (!player.ControllingBot)
return player;
return GetTickPlayers().FirstOrDefault(p =>
p != null &&
p.IsValid &&
p.IsBot &&
p.OriginalControllerOfCurrentPawn.Value != null && p.OriginalControllerOfCurrentPawn.Value == player)
?? player;
EnsureBotMapCache();
return botsByController.TryGetValue(player.Handle, out var bot) ? bot : player;
}
public static CCSPlayerController? GetPlayerFromEvent(CCSPlayerController? player)
@ -85,12 +132,48 @@ namespace src.utils
if (!player.IsBot)
return player;
return GetTickPlayers().FirstOrDefault(p =>
p != null &&
p.IsValid &&
!p.IsBot &&
player.OriginalControllerOfCurrentPawn.Value != null && player.OriginalControllerOfCurrentPawn.Value == p)
?? player;
var original = player.OriginalControllerOfCurrentPawn.Value;
if (original == null || !original.IsValid || original.IsBot) return player;
EnsureBotMapCache();
return tickPlayerHandles.Contains(original.Handle) ? original : 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()

View file

@ -220,27 +220,6 @@ namespace src.utils
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)
//{
// 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
}
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)
{
float reduction = SkillsInfo.GetValue<float>(skill, "dmgReductionForTeamates");
@ -945,9 +948,17 @@ namespace src.utils
{
if (jRandomSkills.Instance.MenuManager == null)
jRandomSkills.Instance.MenuManager = new WasdManager();
ApplyMenuVisibleItems();
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)
{
var manager = GetMenuManager();
@ -1092,11 +1103,20 @@ namespace src.utils
if (lastElement != null)
{
string encodedLastElement = isIlliterate
? System.Net.WebUtility.HtmlEncode(Illiterate.GetRandomText(lastElement.Value.Item1)!)
: System.Net.WebUtility.HtmlEncode(lastElement.Value.Item1);
string lastText = lastElement.Value.Item1;
string lastColor = string.Empty;
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 }]);
if (lastElement.Value.Item3)
@ -1131,7 +1151,7 @@ namespace src.utils
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;
var teams = Utilities.FindAllEntitiesByDesignerName<CCSTeam>("cs_team_manager");
@ -1144,6 +1164,40 @@ namespace src.utils
UpdateServerTeamScores(ctScore, tScore);
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)

View file

@ -44,6 +44,9 @@ namespace src.utils
json = sr.ReadToEnd();
var root = JsonConvert.DeserializeObject<JArray>(json);
bool needsRewrite = root == null;
var present = new HashSet<string>(StringComparer.Ordinal);
if (root != null)
foreach (var skillObj in root)
{
@ -51,8 +54,24 @@ namespace src.utils
if (string.IsNullOrEmpty(name)) continue;
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
{
@ -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)
{
lock (fileLock)

View file

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

View file

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

View file

@ -487,7 +487,7 @@
"returntosender": "Zwrot do Nadawcy",
"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_pending_info": "POWRÓT: {0}",