Merge pull request #53 from ByDexterTR/main

v1.2.3.b8
This commit is contained in:
Juzlus 2026-08-22 03:27:07 +02:00 • committed by GitHub
commit 427b8e6d93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
88 changed files with 1251 additions and 582 deletions

1
.gitignore vendored
View file

@ -30,6 +30,7 @@ project.fragment.lock.json
*.userosscache
# Logs, caches, temp
log/
*.log
debug_*.txt
perf_*.txt

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

@ -62,8 +62,9 @@ namespace src.command
foreach (var commandPair in commands)
foreach (var command in commandPair.Key)
{
Instance.AddCommand($"css_{command}", commandPair.Value.description, commandPair.Value.handler);
oldCommands.TryAdd($"css_{command}", commandPair.Value.handler);
string name = $"css_{command}";
if (!oldCommands.TryAdd(name, commandPair.Value.handler)) continue;
Instance.AddCommand(name, commandPair.Value.description, commandPair.Value.handler);
}
}
}

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

@ -1,4 +1,4 @@
using src.utils;
using src.utils;
using System.Collections.Concurrent;
using System.Diagnostics;
using static src.jRandomSkills;
@ -47,39 +47,137 @@ 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 const int maxRawSamples = 4096;
private sealed class Aggregate
{
public double TotalMs;
public double MaxMs;
public int Count;
public readonly int[] Buckets = new int[bucketBoundsMs.Length];
public readonly List<double> Raw = [];
public DateTime WindowStart = DateTime.Now;
private bool rawSorted;
public void Add(double ms)
{
TotalMs += ms;
Count++;
if (ms > MaxMs) MaxMs = ms;
if (Raw.Count < maxRawSamples) Raw.Add(ms);
for (int i = 0; i < bucketBoundsMs.Length; i++)
if (ms <= bucketBoundsMs[i]) { Buckets[i]++; break; }
}
public double Percentile(double fraction)
{
if (Count == 0) return 0;
double value = Raw.Count == Count ? ExactPercentile(fraction) : BucketPercentile(fraction);
return value > MaxMs ? MaxMs : value;
}
private double ExactPercentile(double fraction)
{
if (!rawSorted)
{
Raw.Sort();
rawSorted = true;
}
int index = (int)Math.Ceiling(Raw.Count * fraction) - 1;
if (index < 0) index = 0;
if (index >= Raw.Count) index = Raw.Count - 1;
return Raw[index];
}
private double BucketPercentile(double fraction)
{
int target = (int)Math.Ceiling(Count * fraction);
if (target < 1) target = 1;
int seen = 0;
double lower = 0;
for (int i = 0; i < bucketBoundsMs.Length; i++)
{
double upper = bucketBoundsMs[i] == double.MaxValue || bucketBoundsMs[i] > MaxMs ? MaxMs : bucketBoundsMs[i];
if (seen + Buckets[i] >= target)
{
if (Buckets[i] <= 0) return upper;
return lower + (upper - lower) * ((target - seen) / (double)Buckets[i]);
}
seen += Buckets[i];
lower = upper;
}
return MaxMs;
}
public void Reset()
{
TotalMs = 0;
MaxMs = 0;
Count = 0;
Array.Clear(Buckets);
Raw.Clear();
rawSorted = false;
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();
@ -42,6 +42,7 @@ namespace src.player
private static void OnMapStart(string mapName)
{
PlayerManager.SetServerActive(true);
Instance.GameRules = null;
Event.OnMapChange();
BotManager.Initialize();
@ -52,6 +53,7 @@ namespace src.player
PerfLog.Info("===== MAP END (clean map change) =====");
Debug.WriteToDebug("===== MAP END (clean map change) =====");
BotManager.Stop();
PlayerManager.SetServerActive(false);
}
public static void InitializeGameRules()
@ -108,7 +110,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 +150,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

@ -550,10 +550,6 @@ namespace src.player
var playerTarget = Utilities.GetPlayerFromIndex((int)playerIndex);
if (playerTarget == null || !playerTarget.IsValid) return;
if (randomSkill.Display)
SkillUtils.PrintToChat(playerTarget, $"{ChatColors.DarkRed}{playerTarget.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {playerTarget.GetSkillDescription(randomSkill.Skill)}",
border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == playerTarget.Team && p != playerTarget) ? "tb" : "t");
if (SkillsInfo.GetValue<bool>(randomSkill.Skill, "disableOnFreezeTime") && SkillUtils.IsFreezeTime())
Instance?.AddTimer(Config.LoadedConfig.SkillTimeBeforeStart, () =>
{
@ -575,9 +571,18 @@ namespace src.player
Debug.WriteToDebug($"Player {skillPlayer.PlayerName} has got the skill \"{SkillNames.Get(randomSkill.Skill)}\".", DebugCategory.Skill);
UpdateSkillHudExpired(skillPlayer, randomSkill.Skill);
if (randomSkill.Display)
Instance?.AddTimer(.6f, () =>
{
var descTarget = Utilities.GetPlayerFromIndex((int)playerIndex);
if (descTarget == null || !descTarget.IsValid) return;
SkillUtils.PrintToChat(descTarget, $"{ChatColors.DarkRed}{descTarget.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {descTarget.GetSkillDescription(randomSkill.Skill)}", border: "tb");
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
if (Config.LoadedConfig.TeamMateSkillChatInfo)
{
Instance?.AddTimer(.6f, () =>
Instance?.AddTimer(.2f, () =>
{
if (player == null || !player.IsValid) return;
@ -597,7 +602,8 @@ namespace src.player
foreach (string text in teammateSkills.Split("\n"))
if (!string.IsNullOrEmpty(text))
SkillUtils.PrintToChat(player, text, title: player.GetTranslationWithoutIlliterate("teammate_skills"), border: "");
SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslationWithoutIlliterate("teammate_skills"), border: "b");
if (!randomSkill.Display)
SkillUtils.PrintToChat(player, string.Empty, title: player.GetTranslationWithoutIlliterate("teammate_skills"), border: "b");
}
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
@ -700,8 +706,7 @@ namespace src.player
skillPlayer.SpecialSkill = Skills.None;
if (randomSkill.Display && Config.LoadedConfig.YourSkillChatInfo)
SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(randomSkill.Skill)}",
border: !Utilities.GetPlayers().Any(p => p != null && p.IsValid && p.Team == player.Team && p != player) ? "tb" : "t");
SkillUtils.PrintToChat(player, $"{ChatColors.DarkRed}{player.GetSkillName(randomSkill.Skill)}{ChatColors.Lime}: {player.GetSkillDescription(randomSkill.Skill)}", border: "tb");
if (randomSkill.Skill == Skills.Illiterate)
Illiterate.Enable();

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.Core.Attributes;
using CounterStrikeSharp.API.Modules.Utils;
@ -170,7 +170,7 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) return;
var emptyProp = EntityManager.CreateTrackedDynamicProp(player.Index);
var emptyProp = EntityManager.CreateTrackedDynamicProp(player.Index, trackAs: "empty_prop");
if (emptyProp == null || !emptyProp.IsValid) return;
var playerPawn = player.PlayerPawn?.Value;
@ -188,7 +188,6 @@ namespace src.player.skills
Utilities.SetStateChanged(emptyProp, "CBaseEntity", "m_CBodyComponent");
EntityManager.RegisterExisting(emptyProp, player.Index, "empty_prop");
}
public static void PlayerHurt(EventPlayerHurt @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;
@ -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;
@ -16,9 +16,6 @@ namespace src.player.skills
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
try { defaultAutoKick = ConVar.Find("mp_autokick")?.GetPrimitiveValue<bool>() ?? true; }
catch { defaultAutoKick = true; }
}
public static void NewRound()
@ -26,22 +23,36 @@ namespace src.player.skills
if (!autoKickOverridden) return;
autoKickOverridden = false;
Server.ExecuteCommand($"mp_autokick {(defaultAutoKick ? 1 : 0)}");
if (defaultAutoKick) Server.ExecuteCommand("mp_autokick 1");
}
public static void OnTakeDamage(DynamicHook h)
private static bool TrySuppressAutoKick()
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (autoKickOverridden) return true;
if (!SkillsInfo.GetValue<bool>(skillName, "manageAutoKick")) return false;
if (param == null || param.Entity == null || param2 == null || param2.Attacker == null || param2.Attacker.Value == null)
bool live;
try { live = ConVar.Find("mp_autokick")?.GetPrimitiveValue<bool>() ?? false; }
catch { return false; }
if (!live) return false;
defaultAutoKick = true;
autoKickOverridden = true;
Server.ExecuteCommand("mp_autokick 0");
return true;
}
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
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,14 +78,10 @@ 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)
{
autoKickOverridden = true;
Server.ExecuteCommand("mp_autokick 0");
}
TrySuppressAutoKick();
SkillUtils.AddHealth(
victimPawn,
@ -83,9 +90,10 @@ namespace src.player.skills
);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#ff0000", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = true, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float healthDamageMultiplier = .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 = "#ff0000", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = true, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float healthDamageMultiplier = .3f, bool manageAutoKick = true) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float HealthDamageMultiplier { get; set; } = healthDamageMultiplier;
public bool ManageAutoKick { get; set; } = manageAutoKick;
}
}
}

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

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Utils;
@ -114,7 +114,7 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) return;
var emptyProp = EntityManager.CreateTrackedDynamicProp(player.Index);
var emptyProp = EntityManager.CreateTrackedDynamicProp(player.Index, trackAs: "empty_prop");
if (emptyProp == null || !emptyProp.IsValid) return;
var playerPawn = player.PlayerPawn?.Value;
@ -132,7 +132,6 @@ namespace src.player.skills
Utilities.SetStateChanged(emptyProp, "CBaseEntity", "m_CBodyComponent");
EntityManager.RegisterExisting(emptyProp, player.Index, "empty_prop");
}
public static void DisableSkill(CCSPlayerController player)

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

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Utils;
@ -143,7 +143,7 @@ namespace src.player.skills
{
if (player == null || !player.IsValid) return;
var emptyProp = EntityManager.CreateTrackedDynamicProp(player.Index);
var emptyProp = EntityManager.CreateTrackedDynamicProp(player.Index, trackAs: "empty_prop");
if (emptyProp == null || !emptyProp.IsValid) return;
var playerPawn = player.PlayerPawn?.Value;
@ -161,7 +161,6 @@ namespace src.player.skills
Utilities.SetStateChanged(emptyProp, "CBaseEntity", "m_CBodyComponent");
EntityManager.RegisterExisting(emptyProp, player.Index, "empty_prop");
}
private static void UpdateNinja(CCSPlayerController? player)

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",
@ -260,13 +273,13 @@ namespace src.utils
ChangeLanguageCommand = new NormalCommand("lang, language, changelang, change_lang, jezyk, język", ""),
ReloadCommand = new NormalCommand("reload, refresh", "@jRandomSkills/admin"),
NextCommand = new NormalCommand("next_skill", "@jRandomSkills/admin"),
CheckEntityCommand = new NormalCommand("ent, entity, checkentity, check_entity, sprawdzencje, checkent, check_ent, 检查实体, 检查实体", "@jRandomSkills/owner"),
CheckEntityCommand = new NormalCommand("ent, entity, checkentity, check_entity, sprawdzencje, checkent, check_ent, 检查实体", "@jRandomSkills/owner"),
};
VotingCommands = new VotingCommands
{
StartGameCommand = new StartGameCommand(true, "start, go, começar, iniciar, 开始, 启动", "@jRandomSkills/admin", "mp_freezetime 15; mp_forcecamera 0; mp_overtime_enable 1; sv_cheats 0", "mp_freezetime 0; mp_forcecamera 0; mp_overtime_enable 1; sv_cheats 1", 15, 60, 15, 500, 2),
ChangeMapCommand = new VotingCommand(true, "map, mapa, changemap, zmienmape, zmienmape, mudarMapa, trocarMapa, 更换地图, 更改地图", "@jRandomSkills/admin", 25, 90, 15, 500, 2),
ChangeMapCommand = new VotingCommand(true, "map, mapa, changemap, zmienmape, mudarMapa, trocarMapa, 更换地图, 更改地图", "@jRandomSkills/admin", 25, 90, 15, 500, 2),
SwapCommand = new VotingCommand(true, "swap, zmiana, trocar, 交换, 切换", "@jRandomSkills/admin", 15, 90, 15, 20, 2),
ShuffleCommand = new VotingCommand(true, "shuffle, embaralhar, 随机排序, 洗牌", "@jRandomSkills/admin", 15, 90, 15, 20, 2),
PauseCommand = new VotingCommand(true, "pause, unpause, pausar, despausar, 暂停, 恢复", "@jRandomSkills/admin", 15, 60, 15, 2, 2),
@ -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

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
@ -42,6 +42,8 @@ namespace src.utils
{
if (entityIndex == 0) return;
bool alreadyTracked = trackedEntities.TryGetValue(entityIndex, out var previous);
trackedEntities[entityIndex] = new EntityData
{
EntityIndex = entityIndex,
@ -50,8 +52,12 @@ namespace src.utils
CreatedAt = DateTime.UtcNow
};
if (Config.DebugEnabled(DebugCategory.Entity))
if (!Config.DebugEnabled(DebugCategory.Entity)) return;
if (!alreadyTracked)
Debug.WriteToDebug($"[Entity] + {entityType} #{entityIndex} owner={DescribeOwner(playerIndex)} tracked={trackedEntities.Count}", DebugCategory.Entity);
else if (previous.EntityType != entityType)
Debug.WriteToDebug($"[Entity] ~ {previous.EntityType} -> {entityType} #{entityIndex} owner={DescribeOwner(playerIndex)} tracked={trackedEntities.Count}", DebugCategory.Entity);
}
private static string DescribeOwner(uint playerIndex)
@ -91,6 +97,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)
{
@ -115,7 +136,7 @@ namespace src.utils
}
}
public static CDynamicProp? CreateTrackedDynamicProp(uint playerIndex, string designerName = "prop_dynamic")
public static CDynamicProp? CreateTrackedDynamicProp(uint playerIndex, string designerName = "prop_dynamic", string? trackAs = null)
{
try
{
@ -123,7 +144,7 @@ namespace src.utils
var prop = Utilities.CreateEntityByName<CDynamicProp>(designerName);
if (prop == null || !prop.IsValid) return null;
RegisterEntity(prop.Index, playerIndex, designerName);
RegisterEntity(prop.Index, playerIndex, trackAs ?? designerName);
return prop;
}
catch (Exception ex)

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,41 @@ namespace src.utils
private static int cachedBombTick = int.MinValue;
private static CC4? cachedBomb;
private static int cachedIdleTick = int.MinValue;
private static bool cachedIdle = true;
private static bool serverActive;
public static void SetServerActive(bool active) => serverActive = active;
public static bool IsServerIdle()
{
if (!serverActive) return true;
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,14 +59,40 @@ 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()
{
if (!serverActive) return [];
EnsureTickCache();
return cachedControllers;
}
public static CC4? GetTickBomb()
{
if (!serverActive) return null;
int tick = Server.TickCount;
if (tick != cachedBombTick)
{
@ -69,12 +130,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 +142,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

@ -1,4 +1,4 @@
{
{
"ConfigName": "Default",
"GameMode": 3,
"YourSkillChatInfo": true,
@ -126,6 +126,7 @@
"WSADMenuItemLineColor": "white",
"WSADMenuItemHoverLineColor": "orange",
"WSADMenuItemLineSize": "sm",
"WSADMenuVisibleItems": 3,
"WSADMenuControllsLineSize": "sm",
"WSADMenuControllsLineColor1": "cyan",
"WSADMenuControllsLineColor2": "white",
@ -195,7 +196,7 @@
"Permissions": "@jRandomSkills/admin"
},
"CheckEntityCommand": {
"Alias": "ent, entity, checkentity, check_entity, sprawdzencje, checkent, check_ent, 检查实体, 检查实体",
"Alias": "ent, entity, checkentity, check_entity, sprawdzencje, checkent, check_ent, 检查实体",
"Permissions": "@jRandomSkills/owner"
}
},
@ -219,7 +220,7 @@
"TimeToNextVoting": 15.0,
"TimeToNextSameVoting": 500.0,
"MinimumPlayersToStartVoting": 2,
"Alias": "map, mapa, changemap, zmienmape, zmienmape, mudarMapa, trocarMapa, 更换地图, 更改地图",
"Alias": "map, mapa, changemap, zmienmape, mudarMapa, trocarMapa, 更换地图, 更改地图",
"Permissions": "@jRandomSkills/admin"
},
"SwapCommand": {

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,
@ -1697,6 +1702,7 @@
},
{
"HealthDamageMultiplier": 0.3,
"ManageAutoKick": true,
"NeedsTeammates": true,
"DisableOnFreezeTime": false,
"DisableOnPistolRound": false,
@ -1798,6 +1804,7 @@
"Rarity": "Epic"
},
{
"Cooldown": 5.0,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"DisableOnPistolRound": false,
@ -1832,6 +1839,7 @@
"ChanceFrom": 0.15,
"ChanceTo": 0.3,
"DmgReductionForTeamates": 0.5,
"FriendlyFire": true,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"DisableOnPistolRound": false,
@ -1851,6 +1859,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 +2017,7 @@
"ExplosionRadius": 500.0,
"ExplosionDamage": 999,
"DmgReductionForTeamates": 0.5,
"FriendlyFire": true,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"DisableOnPistolRound": false,
@ -2103,6 +2113,7 @@
"Rarity": "Common"
},
{
"FriendlyFire": true,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"DisableOnPistolRound": false,
@ -2243,6 +2254,7 @@
"DmgReductionForTeamates": 0.5,
"Cooldown": 10.0,
"Force": 1000.0,
"FriendlyFire": true,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"DisableOnPistolRound": false,
@ -2276,6 +2288,7 @@
{
"MaxSpeedVelocity": 2.0,
"MaxDamageVelocity": 2.0,
"FriendlyFire": true,
"NeedsTeammates": false,
"DisableOnFreezeTime": false,
"DisableOnPistolRound": false,
@ -2356,6 +2369,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}",