v1.2.4.b1

This commit is contained in:
ByDexter 2026-09-01 15:37:13 +03:00
parent 7ad753e3b5
commit dcf4020981
39 changed files with 434 additions and 204 deletions

View file

@ -31,7 +31,7 @@ namespace src
public override string ModuleName => "[CS2] [ jRandomSkills ]"; public override string ModuleName => "[CS2] [ jRandomSkills ]";
public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)"; public override string ModuleAuthor => "D3X (Original), Juzlus (Modifier), ByDexterTR (Contributor)";
public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus."; public override string ModuleDescription => "Plugin adds random skills every round for CS2 by D3X. Modified by Juzlus.";
public override string ModuleVersion => "1.2.3.b9"; public override string ModuleVersion => "1.2.4.b1";
public override void Load(bool hotReload) public override void Load(bool hotReload)
{ {

View file

@ -82,7 +82,7 @@
"catapult": "Mancınık", "catapult": "Mancınık",
"catapult_desc": "Vurduğun rakibi uzaya (yukarı doğru) fırlatma şansın var", "catapult_desc": "Vurduğun rakibi uzaya (yukarı doğru) fırlatma şansın var",
"catapult_desc2": "Fırlatma şansın: %{0}", "catapult_desc2": "Vurduğun rakibi havaya fırlatma şansın: %{0}",
"chameleon": "Bukalemun", "chameleon": "Bukalemun",
"chameleon_desc": "Öldürdüğün ilk oyuncunun yeteneği sana geçer", "chameleon_desc": "Öldürdüğün ilk oyuncunun yeteneği sana geçer",
@ -452,7 +452,7 @@
"push": "İtici", "push": "İtici",
"push_desc": "Düşmana vurduğunda onu geri itme şansın olur", "push_desc": "Düşmana vurduğunda onu geri itme şansın olur",
"push_desc2": "Geri itme şansın: %{0}", "push_desc2": "Vurduğun rakibi geri itme şansın: %{0}",
"pyro": "Ateşbaz", "pyro": "Ateşbaz",
"pyro_desc": "Molotof canını yeniler", "pyro_desc": "Molotof canını yeniler",
@ -511,7 +511,7 @@
"shade": "Gölge", "shade": "Gölge",
"shade_desc": "Vurduğun düşmanın arkasına ışınlanma şansın olur", "shade_desc": "Vurduğun düşmanın arkasına ışınlanma şansın olur",
"shade_desc2": "Arkasına ışınlanma şansın: %{0}", "shade_desc2": "Vurduğun rakibin arkasına ışınlanma şansın: %{0}",
"shade_nospace": "Uygun alan yok", "shade_nospace": "Uygun alan yok",
"shortbomb": "Kısa Fünye", "shortbomb": "Kısa Fünye",

View file

@ -294,10 +294,14 @@ namespace src.player
} }
} }
private static bool IsEventAlive(GameEvent? @event) => @event != null && @event.Handle != nint.Zero;
private static HookResult WeaponEquip(EventItemEquip @event, GameEventInfo info) private static HookResult WeaponEquip(EventItemEquip @event, GameEventInfo info)
{ {
lock (setLock) lock (setLock)
{ {
if (!IsEventAlive(@event)) return HookResult.Continue;
DispatchToActiveSkills("WeaponEquip", @event); DispatchToActiveSkills("WeaponEquip", @event);
return HookResult.Continue; return HookResult.Continue;
} }
@ -307,6 +311,8 @@ namespace src.player
{ {
lock (setLock) lock (setLock)
{ {
if (!IsEventAlive(@event)) return HookResult.Continue;
DispatchToActiveSkills("WeaponPickup", @event); DispatchToActiveSkills("WeaponPickup", @event);
return HookResult.Continue; return HookResult.Continue;
} }
@ -584,7 +590,7 @@ namespace src.player
string welcomeMsg = player.GetTranslationWithoutIlliterate("welcome_message", "welcome"); string welcomeMsg = player.GetTranslationWithoutIlliterate("welcome_message", "welcome");
foreach (string line in welcomeMsg.Split("\n")) foreach (string line in welcomeMsg.Split("\n"))
player.PrintToChat($" {ChatColors.Green}" + line.Replace("{PLAYER}", $" {ChatColors.Red}\u202A{player.PlayerName}\u202C{ChatColors.Green}", StringComparison.OrdinalIgnoreCase) player.PrintToChat($" {ChatColors.Green}" + line.Replace("{PLAYER}", $" {ChatColors.Red}\u202A{player.PlayerName}\u202C{ChatColors.Green}", StringComparison.OrdinalIgnoreCase)
.Replace("{SERVER_NAME}", $" {ChatColors.Red}{ConVar.Find("hostname")?.StringValue ?? "Default Server"}{ChatColors.Green}", StringComparison.OrdinalIgnoreCase) .Replace("{SERVER_NAME}", $" {ChatColors.Red}{SkillUtils.CvarString("hostname", "Default Server")}{ChatColors.Green}", StringComparison.OrdinalIgnoreCase)
.Replace("{VERSION}", $" {ChatColors.Red}v{Instance.ModuleVersion}{ChatColors.Green}", StringComparison.OrdinalIgnoreCase) .Replace("{VERSION}", $" {ChatColors.Red}v{Instance.ModuleVersion}{ChatColors.Green}", StringComparison.OrdinalIgnoreCase)
.Replace("{SKILLS_COUNT}", $" {ChatColors.Red}{SkillData.Skills.Count - 1}{ChatColors.Green}", StringComparison.OrdinalIgnoreCase) .Replace("{SKILLS_COUNT}", $" {ChatColors.Red}{SkillData.Skills.Count - 1}{ChatColors.Green}", StringComparison.OrdinalIgnoreCase)
.Replace("{AUTHOR1}", $" {ChatColors.Red}Jakub Bartosik (D3X){ChatColors.Green} ({ChatColors.Red}https://github.com/jakubbartosik/dRandomSkills{ChatColors.Green})", StringComparison.OrdinalIgnoreCase) .Replace("{AUTHOR1}", $" {ChatColors.Red}Jakub Bartosik (D3X){ChatColors.Green} ({ChatColors.Red}https://github.com/jakubbartosik/dRandomSkills{ChatColors.Green})", StringComparison.OrdinalIgnoreCase)

View file

@ -2,7 +2,6 @@
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes; using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Admin; using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Events; using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
@ -20,7 +19,17 @@ namespace src.player
{ {
public static partial class Event public static partial class Event
{ {
private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List<jSkill_SkillInfo> candidates, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode) private static bool IsVip(CCSPlayerController? player)
{
if (player == null || !player.IsValid || player.IsBot) return false;
string flag = Config.LoadedConfig.VIPFlag;
if (string.IsNullOrWhiteSpace(flag)) return false;
return AdminManager.PlayerHasPermissions(player, flag);
}
private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List<jSkill_SkillInfo> candidates, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode, bool vip)
{ {
if (candidates == null || candidates.Count == 0) return noneSkill; if (candidates == null || candidates.Count == 0) return noneSkill;
@ -31,7 +40,7 @@ namespace src.player
for (int attempt = 0; attempt < attempts; attempt++) for (int attempt = 0; attempt < attempts; attempt++)
{ {
var (roll, rolled) = RarityManager.RollRarity(); var (roll, rolled) = RarityManager.RollRarity(vip);
string rolledName = rolled.ToString(); string rolledName = rolled.ToString();
filtered.Clear(); filtered.Clear();
@ -92,7 +101,7 @@ namespace src.player
} }
Instance.RemoveListener<CheckTransmit>(CheckTransmit); Instance.RemoveListener<CheckTransmit>(CheckTransmit);
int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue<Int32>() ?? 0; int freezetime = SkillUtils.CvarValue("mp_freezetime", 0);
freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0)); freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0));
setSkillTimer?.Kill(); setSkillTimer?.Kill();
@ -212,7 +221,7 @@ namespace src.player
PlayerManager.Clear(); PlayerManager.Clear();
ConVar.Find("sv_legacy_jump")?.SetValue("1"); SkillUtils.Cvar("sv_legacy_jump")?.SetValue("1");
} }
} }
@ -348,7 +357,7 @@ namespace src.player
if (skillList.Count == 0) skills.Clear(); if (skillList.Count == 0) skills.Clear();
} }
var randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); var randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode, IsVip(player));
if (gameMode == Config.GameModes.NoRepeat) if (gameMode == Config.GameModes.NoRepeat)
{ {
@ -686,7 +695,7 @@ namespace src.player
else assignmentCounts[sp.Skill] = 1; else assignmentCounts[sp.Skill] = 1;
} }
randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode); randomSkill = skillList.Count == 0 ? noneSkill : ChooseSkillByRarityAndMax(skillList, assignmentCounts, gameMode, IsVip(player));
} }
else if (gameMode == Config.GameModes.TeamSkills) else if (gameMode == Config.GameModes.TeamSkills)
randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill;

View file

@ -1,5 +1,4 @@
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
@ -86,7 +85,7 @@ namespace src.player.skills
{ {
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
int flashbangLimit = ConVar.Find("ammo_grenade_limit_flashbang")?.GetPrimitiveValue<int>() ?? 2; int flashbangLimit = SkillUtils.CvarValue("ammo_grenade_limit_flashbang", 2);
int grenadeLimit = SkillsInfo.GetValue<int>(skillName, "grenadeLimit"); int grenadeLimit = SkillsInfo.GetValue<int>(skillName, "grenadeLimit");
if (grenadeLimit > flashbangLimit) if (grenadeLimit > flashbangLimit)

View file

@ -179,7 +179,7 @@ namespace src.player.skills
damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName); damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
} }
if (owner != null && damageInfo.Damage >= victimPawn.Health) if (owner != null && SkillUtils.IsPredictedLethal(damageInfo, victimPawn))
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion); SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
} }

View file

@ -1,6 +1,5 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -152,7 +151,7 @@ namespace src.player.skills
killerEvent.PrintToChat($" {ChatColors.Green}{killerEvent.GetTranslation("bounty_claimed_info", victim.PlayerName, reward)}"); killerEvent.PrintToChat($" {ChatColors.Green}{killerEvent.GetTranslation("bounty_claimed_info", victim.PlayerName, reward)}");
} }
private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000; private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000);
private static bool GiveMoney(CCSPlayerController player, int amount) private static bool GiveMoney(CCSPlayerController player, int amount)
{ {

View file

@ -21,6 +21,7 @@ namespace src.player.skills
var victim = PlayerManager.GetPlayerEvent(@event.Userid); var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return; if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
if (!SkillUtils.FiresBullets(@event.Weapon)) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (Heavyweight.Resists(victim)) return; if (Heavyweight.Resists(victim)) return;
@ -29,8 +30,9 @@ namespace src.player.skills
if (Instance.Random.NextDouble() <= attackerInfo.SkillChance) if (Instance.Random.NextDouble() <= attackerInfo.SkillChance)
{ {
var victimPawn = victim.PlayerPawn?.Value; var victimPawn = victim.PlayerPawn?.Value;
if (victimPawn != null) if (victimPawn == null || !victimPawn.IsValid || victimPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
victimPawn.AbsVelocity.Z = 300f;
victimPawn.AbsVelocity.Z = 300f;
} }
} }

View file

@ -84,7 +84,7 @@ namespace src.player.skills
float currentTime = Server.CurrentTime; float currentTime = Server.CurrentTime;
float extraTime = SkillsInfo.GetValue<float>(skillName, "bombArmedTime"); float extraTime = SkillsInfo.GetValue<float>(skillName, "bombArmedTime");
foreach (var player in PlayerManager.GetTickPlayers().Where(p => p.Team == CsTeam.Terrorist)) foreach (var player in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid && p.Team == CsTeam.Terrorist))
{ {
if (player == null || !player.IsValid) continue; if (player == null || !player.IsValid) continue;

View file

@ -128,7 +128,7 @@ namespace src.player.skills
damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName); damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
} }
if (owner != null && damageInfo.Damage >= victimPawn.Health) if (owner != null && SkillUtils.IsPredictedLethal(damageInfo, victimPawn))
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion); SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
} }

View file

@ -240,7 +240,7 @@ namespace src.player.skills
damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName); damageInfo.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
} }
if (owner != null && damageInfo.Damage >= victimPawn.Health) if (owner != null && SkillUtils.IsPredictedLethal(damageInfo, victimPawn))
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion); SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
} }

View file

@ -15,13 +15,13 @@ namespace src.player.skills
private const Skills skillName = Skills.ExplosiveShot; private const Skills skillName = Skills.ExplosiveShot;
private static readonly QAngle angle = new(5, 10, -4); private static readonly QAngle angle = new(5, 10, -4);
private static int lastTick = 0; private static readonly ConcurrentDictionary<uint, int> lastTickByPlayer = [];
private static byte pendingTeam = (byte)CsTeam.None; private static readonly ConcurrentDictionary<int, ConcurrentQueue<(byte Team, uint Owner)>> nades = [];
private static readonly ConcurrentDictionary<int, (byte Team, uint Owner)> nades = [];
public static void NewRound() public static void NewRound()
{ {
nades.Clear(); nades.Clear();
lastTickByPlayer.Clear();
} }
public static void LoadSkill() public static void LoadSkill()
@ -43,9 +43,8 @@ namespace src.player.skills
private static void SpawnExplosion(Vector vector, CCSPlayerController player) private static void SpawnExplosion(Vector vector, CCSPlayerController player)
{ {
lastTick = Server.TickCount; lastTickByPlayer[player.Index] = Server.TickCount;
pendingTeam = player.TeamNum; nades.GetOrAdd(Server.TickCount, static _ => new ConcurrentQueue<(byte, uint)>()).Enqueue((player.TeamNum, player.Index));
nades.AddOrUpdate(Server.TickCount, (player.TeamNum, player.Index), (_, _) => (player.TeamNum, player.Index));
SkillUtils.CreateHEGrenadeProjectile(vector, angle, new Vector(0, 0, 0), player.TeamNum); SkillUtils.CreateHEGrenadeProjectile(vector, angle, new Vector(0, 0, 0), player.TeamNum);
} }
@ -64,14 +63,16 @@ namespace src.player.skills
if (!(NearlyEquals(angle.X, heProjectile.AbsRotation.X) && NearlyEquals(angle.Y, heProjectile.AbsRotation.Y) && NearlyEquals(angle.Z, heProjectile.AbsRotation.Z))) if (!(NearlyEquals(angle.X, heProjectile.AbsRotation.X) && NearlyEquals(angle.Y, heProjectile.AbsRotation.Y) && NearlyEquals(angle.Z, heProjectile.AbsRotation.Z)))
return; return;
if (!nades.TryGetValue(spawnTick, out var queue) || !queue.TryDequeue(out var source)) return;
if (queue.IsEmpty) nades.TryRemove(spawnTick, out _);
heProjectile.TicksAtZeroVelocity = 100; heProjectile.TicksAtZeroVelocity = 100;
heProjectile.TeamNum = pendingTeam; heProjectile.TeamNum = source.Team;
heProjectile.Damage = SkillsInfo.GetValue<float>(skillName, "damage"); heProjectile.Damage = SkillsInfo.GetValue<float>(skillName, "damage");
heProjectile.DmgRadius = SkillsInfo.GetValue<float>(skillName, "damageRadius"); heProjectile.DmgRadius = SkillsInfo.GetValue<float>(skillName, "damageRadius");
heProjectile.DetonateTime = 0; heProjectile.DetonateTime = 0;
if (nades.TryRemove(spawnTick, out var source)) heProjectile.Globalname = $"explosiveshot_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
heProjectile.Globalname = $"explosiveshot_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
}); });
} }
@ -112,7 +113,7 @@ namespace src.player.skills
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex); var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner != null && !owner.IsValid) owner = null; if (owner != null && !owner.IsValid) owner = null;
if (owner != null && damageInfo.Damage >= victimPawn.Health) if (owner != null && SkillUtils.IsPredictedLethal(damageInfo, victimPawn))
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion); SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
} }
@ -120,11 +121,12 @@ namespace src.player.skills
public static void BulletImpact(EventBulletImpact @event) public static void BulletImpact(EventBulletImpact @event)
{ {
if (lastTick == Server.TickCount) return;
var player = PlayerManager.GetPlayerEvent(@event.Userid); var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
// One explosion per player per tick: a shotgun reports every pellet as its own impact.
if (lastTickByPlayer.TryGetValue(player.Index, out int last) && last == Server.TickCount) return;
var pos = new Vector(@event.X, @event.Y, @event.Z); var pos = new Vector(@event.X, @event.Y, @event.Z);
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);

View file

@ -11,9 +11,7 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.FireRain; private const Skills skillName = Skills.FireRain;
private static readonly ConcurrentDictionary<uint, byte> decoys = []; private static readonly ConcurrentDictionary<uint, byte> decoys = [];
private static int rainTick = -1; private static readonly ConcurrentDictionary<int, ConcurrentQueue<RainBatch>> rainBatches = [];
private static CCSPlayerPawn? rainThrower;
private static byte rainTeam = (byte)CsTeam.None;
private static readonly ConcurrentDictionary<uint, (uint ThrowerRaw, byte Team)> rainMolotovs = []; private static readonly ConcurrentDictionary<uint, (uint ThrowerRaw, byte Team)> rainMolotovs = [];
public static void LoadSkill() public static void LoadSkill()
@ -25,6 +23,7 @@ namespace src.player.skills
public static void NewRound() public static void NewRound()
{ {
KillAllDecoys(); KillAllDecoys();
rainBatches.Clear();
rainMolotovs.Clear(); rainMolotovs.Clear();
DecoyRing.ClearAll(skillName); DecoyRing.ClearAll(skillName);
} }
@ -54,10 +53,6 @@ namespace src.player.skills
const float spawnHeight = 1500.0f; const float spawnHeight = 1500.0f;
const float approachDistance = 600.0f; const float approachDistance = 600.0f;
rainTick = Server.TickCount;
rainThrower = pawn;
rainTeam = player.TeamNum;
float startAngle = Random.Shared.NextSingle() * MathF.Tau; float startAngle = Random.Shared.NextSingle() * MathF.Tau;
Vector? skyCenter = null; Vector? skyCenter = null;
@ -88,10 +83,12 @@ namespace src.player.skills
if (!foundPosition || skyCenter == null) if (!foundPosition || skyCenter == null)
{ {
QueueRain(pawn, player.TeamNum, grenadeGroundCount);
CreateMolotovSplash(targetPos, grenadeGroundCount, player.TeamNum); CreateMolotovSplash(targetPos, grenadeGroundCount, player.TeamNum);
return; return;
} }
QueueRain(pawn, player.TeamNum, grenadeCount);
CreateMolotovRaid(targetPos, skyCenter, grenadeCount, player.TeamNum); CreateMolotovRaid(targetPos, skyCenter, grenadeCount, player.TeamNum);
} }
@ -202,24 +199,45 @@ namespace src.player.skills
} }
} }
private sealed class RainBatch
{
public required CCSPlayerPawn Thrower;
public required byte Team;
public int Remaining;
}
private static void QueueRain(CCSPlayerPawn thrower, byte team, int count)
{
rainBatches.GetOrAdd(Server.TickCount, static _ => new ConcurrentQueue<RainBatch>())
.Enqueue(new RainBatch { Thrower = thrower, Team = team, Remaining = count });
}
private static void ConsumeOne(ConcurrentQueue<RainBatch> queue, RainBatch batch)
{
if (--batch.Remaining > 0) return;
queue.TryDequeue(out _);
}
public static void OnEntitySpawned(CEntityInstance entity) public static void OnEntitySpawned(CEntityInstance entity)
{ {
var name = entity.DesignerName; var name = entity.DesignerName;
if (name == "molotov_projectile") if (name == "molotov_projectile")
{ {
if (Server.TickCount != rainTick) return; if (!rainBatches.TryGetValue(Server.TickCount, out var queue) || !queue.TryPeek(out var batch)) return;
var thrower = rainThrower; var thrower = batch.Thrower;
if (thrower == null || !thrower.IsValid) return; if (thrower == null || !thrower.IsValid) { ConsumeOne(queue, batch); return; }
var molotov = entity.As<CMolotovProjectile>(); var molotov = entity.As<CMolotovProjectile>();
if (molotov == null || !molotov.IsValid) return; if (molotov == null || !molotov.IsValid) { ConsumeOne(queue, batch); return; }
molotov.TeamNum = rainTeam; ConsumeOne(queue, batch);
molotov.TeamNum = batch.Team;
molotov.Thrower.Raw = thrower.EntityHandle.Raw; molotov.Thrower.Raw = thrower.EntityHandle.Raw;
molotov.OwnerEntity.Raw = thrower.EntityHandle.Raw; molotov.OwnerEntity.Raw = thrower.EntityHandle.Raw;
rainMolotovs[molotov.Index] = (thrower.EntityHandle.Raw, rainTeam); rainMolotovs[molotov.Index] = (thrower.EntityHandle.Raw, batch.Team);
Server.NextWorldUpdate(() => Server.NextWorldUpdate(() =>
{ {

View file

@ -1,6 +1,5 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
@ -32,7 +31,7 @@ namespace src.player.skills
if (!SkillsInfo.GetValue<bool>(skillName, "manageAutoKick")) return false; if (!SkillsInfo.GetValue<bool>(skillName, "manageAutoKick")) return false;
bool live; bool live;
try { live = ConVar.Find("mp_autokick")?.GetPrimitiveValue<bool>() ?? false; } try { live = SkillUtils.CvarValue("mp_autokick", false); }
catch { return false; } catch { return false; }
if (!live) return false; if (!live) return false;

View file

@ -150,6 +150,27 @@ namespace src.player.skills
} }
} }
public static void WeaponEquip(EventItemEquip @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
if (playersWithSkill.TryGetValue(player.Index, out int grenadesLeft) && grenadesLeft > 1)
SkillUtils.UpdateGrenadeCount(player, CsItem.DecoyGrenade, grenadesLeft);
}
public static void WeaponPickup(EventItemPickup @event)
{
var player = PlayerManager.GetPlayerEvent(@event.Userid);
if (player == null || !player.IsValid) return;
var weapon = @event.Item;
if (string.IsNullOrEmpty(weapon) || weapon != "decoy") return;
if (playersWithSkill.TryGetValue(player.Index, out int grenadesLeft) && grenadesLeft > 1)
SkillUtils.UpdateGrenadeCount(player, CsItem.DecoyGrenade, grenadesLeft);
}
public static void EnableSkill(CCSPlayerController player) public static void EnableSkill(CCSPlayerController player)
{ {
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;

View file

@ -1,5 +1,4 @@
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils; using jRandomSkills.src.utils;
@ -79,7 +78,7 @@ namespace src.player.skills
{ {
if (player == null || !player.IsValid) return; if (player == null || !player.IsValid) return;
int flashbangLimit = ConVar.Find("ammo_grenade_limit_flashbang")?.GetPrimitiveValue<int>() ?? 2; int flashbangLimit = SkillUtils.CvarValue("ammo_grenade_limit_flashbang", 2);
int grenadeLimit = SkillsInfo.GetValue<int>(skillName, "grenadeLimit"); int grenadeLimit = SkillsInfo.GetValue<int>(skillName, "grenadeLimit");
if (grenadeLimit > flashbangLimit) if (grenadeLimit > flashbangLimit)

View file

@ -54,7 +54,7 @@ namespace src.player.skills
if (playerInfo == null || playerInfo.Skill != skillName) continue; if (playerInfo == null || playerInfo.Skill != skillName) continue;
if (!SkillUtils.HasMenu(player)) continue; if (!SkillUtils.HasMenu(player)) continue;
var enemies = PlayerManager.GetTickPlayers().Where(p => p.PawnIsAlive && p.Team != player.Team && p.IsValid && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray(); var enemies = PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid && p.PawnIsAlive && p.Team != player.Team && p.Team != CsTeam.Spectator && p.Team != CsTeam.None).ToArray();
ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString()))); ConcurrentBag<(string, string)> menuItems = new(enemies.Select(e => (e.PlayerName, e.Index.ToString())));
SkillUtils.UpdateMenu(player, menuItems); SkillUtils.UpdateMenu(player, menuItems);

View file

@ -1,6 +1,5 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -20,7 +19,7 @@ namespace src.player.skills
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
defaultNoSpread = ConVar.Find("weapon_accuracy_nospread")?.GetPrimitiveValue<bool>() ?? false; defaultNoSpread = SkillUtils.CvarValue("weapon_accuracy_nospread", false);
} }
public static void NewRound() public static void NewRound()

View file

@ -11,7 +11,6 @@ namespace src.player.skills
public class Phoenix : ISkill public class Phoenix : ISkill
{ {
private const Skills skillName = Skills.Phoenix; private const Skills skillName = Skills.Phoenix;
private const float DefaultHeadshotMultiplier = 4f;
private static readonly ConcurrentDictionary<uint, int> phoenixTicks = new(); private static readonly ConcurrentDictionary<uint, int> phoenixTicks = new();
public static void LoadSkill() public static void LoadSkill()
@ -53,11 +52,7 @@ namespace src.player.skills
if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return; if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return;
float effectiveDamage = damageInfo.Damage; if (!SkillUtils.IsPredictedLethal(damageInfo, victimPawn)) return;
if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(damageInfo);
if (effectiveDamage < victimPawn.Health) return;
if (TryConsumeRevive(victim, victimPawn)) if (TryConsumeRevive(victim, victimPawn))
damageInfo.Damage = 0; damageInfo.Damage = 0;
@ -99,20 +94,6 @@ namespace src.player.skills
return true; return true;
} }
private static float GetHeadshotMultiplier(CTakeDamageInfo info)
{
var ability = info.Ability?.Value;
if (ability == null || !ability.IsValid) return DefaultHeadshotMultiplier;
var weapon = ability.As<CCSWeaponBase>();
if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier;
var vdata = weapon.GetVData<CCSWeaponBaseVData>();
if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier;
return vdata.HeadshotMultiplier;
}
public static void EnableSkill(CCSPlayerController player) public static void EnableSkill(CCSPlayerController player)
{ {
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);

View file

@ -1,6 +1,5 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
@ -20,7 +19,7 @@ namespace src.player.skills
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
defaultC4Timer = ConVar.Find("mp_c4timer")?.GetPrimitiveValue<int>() ?? 40; defaultC4Timer = SkillUtils.CvarValue("mp_c4timer", 40);
} }
public static void EnableSkill(CCSPlayerController player) public static void EnableSkill(CCSPlayerController player)
@ -96,7 +95,7 @@ namespace src.player.skills
float currentTime = Server.CurrentTime; float currentTime = Server.CurrentTime;
bool hudFrame = SkillUtils.IsHudFrame(); bool hudFrame = SkillUtils.IsHudFrame();
foreach (var player in PlayerManager.GetTickPlayers().Where(p => p.Team == CsTeam.Terrorist)) foreach (var player in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid && p.Team == CsTeam.Terrorist))
{ {
if (!Instance.IsPlayerValid(player)) continue; if (!Instance.IsPlayerValid(player)) continue;

View file

@ -23,6 +23,8 @@ namespace src.player.skills
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim)
return; return;
if (!SkillUtils.FiresBullets(@event.Weapon)) return;
if (Heavyweight.Resists(victim)) return; if (Heavyweight.Resists(victim)) return;
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);

View file

@ -11,7 +11,6 @@ namespace src.player.skills
public class ReZombie : ISkill public class ReZombie : ISkill
{ {
private const Skills skillName = Skills.ReZombie; private const Skills skillName = Skills.ReZombie;
private const float DefaultHeadshotMultiplier = 4f;
private static readonly ConcurrentDictionary<uint, int> zombies = []; private static readonly ConcurrentDictionary<uint, int> zombies = [];
private static readonly object setLock = new(); private static readonly object setLock = new();
@ -88,11 +87,7 @@ namespace src.player.skills
// Friendly-fire-off teammate hit deals 0 damage; don't zombify over a hit that never lands. // Friendly-fire-off teammate hit deals 0 damage; don't zombify over a hit that never lands.
if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return; if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return;
float effectiveDamage = damageInfo.Damage; if (!SkillUtils.IsPredictedLethal(damageInfo, victimPawn)) return; // survivable, let it through
if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD)
effectiveDamage *= GetHeadshotMultiplier(damageInfo);
if (effectiveDamage < victimPawn.Health) return; // survivable, let it through
if (TryBecomeZombie(victim, victimPawn)) if (TryBecomeZombie(victim, victimPawn))
damageInfo.Damage = 0; damageInfo.Damage = 0;
@ -138,20 +133,6 @@ namespace src.player.skills
holdTime: 600); holdTime: 600);
} }
private static float GetHeadshotMultiplier(CTakeDamageInfo info)
{
var ability = info.Ability?.Value;
if (ability == null || !ability.IsValid) return DefaultHeadshotMultiplier;
var weapon = ability.As<CCSWeaponBase>();
if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier;
var vdata = weapon.GetVData<CCSWeaponBaseVData>();
if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier;
return vdata.HeadshotMultiplier;
}
private static void DropAllBotWeapons(CCSPlayerController player) private static void DropAllBotWeapons(CCSPlayerController player)
{ {
if (player == null || !player.IsValid || !player.IsBot) return; if (player == null || !player.IsValid || !player.IsBot) return;

View file

@ -1,8 +1,8 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills; using static src.jRandomSkills;
namespace src.player.skills namespace src.player.skills
@ -11,48 +11,49 @@ namespace src.player.skills
{ {
private const Skills skillName = Skills.RichBoy; private const Skills skillName = Skills.RichBoy;
private static readonly ConcurrentDictionary<uint, int> accountBeforeBonus = [];
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
} }
private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000; private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000);
public static void EnableSkill(CCSPlayerController player) public static void EnableSkill(CCSPlayerController player)
{ {
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (player == null || !player.IsValid) return;
if (playerInfo == null) return;
int moneyBonus = Instance.Random.Next(SkillsInfo.GetValue<int>(skillName, "minMoney"), SkillsInfo.GetValue<int>(skillName, "maxMoney"));
var moneyServices = player.InGameMoneyServices; var moneyServices = player.InGameMoneyServices;
if (moneyServices == null) return; if (moneyServices == null) return;
int moneyBonus = Instance.Random.Next(SkillsInfo.GetValue<int>(skillName, "minMoney"), SkillsInfo.GetValue<int>(skillName, "maxMoney"));
moneyBonus = Math.Min(moneyBonus, GetMaxMoney() - moneyServices.Account); moneyBonus = Math.Min(moneyBonus, GetMaxMoney() - moneyServices.Account);
if (moneyBonus <= 0) return;
playerInfo.SkillChance = moneyBonus; accountBeforeBonus[player.Index] = moneyServices.Account;
AddMoney(player, moneyBonus);
moneyServices.Account += moneyBonus;
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
} }
public static void DisableSkill(CCSPlayerController player) public static void DisableSkill(CCSPlayerController player)
{ {
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); if (player == null || !player.IsValid) return;
if (playerInfo == null) return; if (!accountBeforeBonus.TryRemove(player.Index, out int accountBefore)) return;
var moneyServices = player.InGameMoneyServices; var moneyServices = player.InGameMoneyServices;
if (moneyServices == null) return; if (moneyServices == null) return;
int money = Math.Abs((int)playerInfo.SkillChance! - moneyServices.CashSpentThisRound); if (moneyServices.Account <= accountBefore) return;
AddMoney(player, -money, 3000);
moneyServices.Account = accountBefore;
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
} }
private static void AddMoney(CCSPlayerController player, int money, int minimum = 0) public static void PlayerDisconnect(uint playerIndex)
{ {
if (player == null || !player.IsValid) return; accountBeforeBonus.TryRemove(playerIndex, out _);
var moneyServices = player.InGameMoneyServices;
if (moneyServices == null) return;
moneyServices.Account = Math.Clamp(moneyServices.Account + money, minimum, GetMaxMoney());
Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices");
} }
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#D4AF37", 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 minMoney = 5000, int maxMoney = 15000) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity) public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#D4AF37", 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 minMoney = 5000, int maxMoney = 15000) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -1,6 +1,5 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
using static src.jRandomSkills; using static src.jRandomSkills;
@ -16,7 +15,7 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
} }
private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000; private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000);
public static void PlayerHurt(EventPlayerHurt @event) public static void PlayerHurt(EventPlayerHurt @event)
{ {

View file

@ -28,6 +28,7 @@ namespace src.player.skills
var victim = PlayerManager.GetPlayerEvent(@event.Userid); var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return; if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
if (!SkillUtils.FiresBullets(@event.Weapon)) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (Heavyweight.Resists(victim)) return; if (Heavyweight.Resists(victim)) return;

View file

@ -11,7 +11,6 @@ namespace src.player.skills
public class SecondLife : ISkill public class SecondLife : ISkill
{ {
private const Skills skillName = Skills.SecondLife; private const Skills skillName = Skills.SecondLife;
private const float DefaultHeadshotMultiplier = 4f;
private static readonly ConcurrentDictionary<nint, int> usedThisRound = []; private static readonly ConcurrentDictionary<nint, int> usedThisRound = [];
private static readonly object setLock = new(); private static readonly object setLock = new();
@ -45,11 +44,7 @@ namespace src.player.skills
if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return; if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return;
float effectiveDamage = damageInfo.Damage; if (!SkillUtils.IsPredictedLethal(damageInfo, victimPawn)) return;
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 (usedThisRound.TryGetValue(victim.Handle, out int savedTick))
{ {
@ -62,20 +57,6 @@ namespace src.player.skills
damageInfo.Damage = 0; damageInfo.Damage = 0;
} }
private static float GetHeadshotMultiplier(CTakeDamageInfo info)
{
var ability = info.Ability?.Value;
if (ability == null || !ability.IsValid) return DefaultHeadshotMultiplier;
var weapon = ability.As<CCSWeaponBase>();
if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier;
var vdata = weapon.GetVData<CCSWeaponBaseVData>();
if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier;
return vdata.HeadshotMultiplier;
}
public static bool TryConsumeRevive(CCSPlayerController? victim, CCSPlayerPawn? victimPawn) public static bool TryConsumeRevive(CCSPlayerController? victim, CCSPlayerPawn? victimPawn)
{ {
if (victim == null || !victim.IsValid) return false; if (victim == null || !victim.IsValid) return false;

View file

@ -32,6 +32,7 @@ namespace src.player.skills
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return; if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return;
if (attacker!.Index == victim!.Index) return; if (attacker!.Index == victim!.Index) return;
if (!SkillUtils.FiresBullets(@event.Weapon)) return;
var victimInfo = PlayerManager.GetPlayerByIndex(victim!.Index); var victimInfo = PlayerManager.GetPlayerByIndex(victim!.Index);
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);

View file

@ -1,6 +1,5 @@
using CounterStrikeSharp.API; using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils; using CounterStrikeSharp.API.Modules.Utils;
using src.utils; using src.utils;
using static src.jRandomSkills; using static src.jRandomSkills;
@ -17,7 +16,7 @@ namespace src.player.skills
public static void LoadSkill() public static void LoadSkill()
{ {
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color")); SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
defaultC4Timer = ConVar.Find("mp_c4timer")?.GetPrimitiveValue<int>() ?? 40; defaultC4Timer = SkillUtils.CvarValue("mp_c4timer", 40);
} }
public static void EnableSkill(CCSPlayerController player) public static void EnableSkill(CCSPlayerController player)

View file

@ -26,23 +26,30 @@ namespace src.player.skills
} }
private static bool roundEnded; private static bool roundEnded;
private static readonly ConcurrentDictionary<uint, byte> owedKnife = [];
public static void NewRound() public static void NewRound()
{ {
roundEnded = false; roundEnded = false;
KillAllKnives(); KillAllKnives();
owedKnife.Clear();
} }
public static void RoundEnd() public static void RoundEnd()
{ {
roundEnded = true; roundEnded = true;
KillAllKnives(); KillAllKnives(rememberOwed: true);
} }
private static void KillAllKnives() private static void KillAllKnives(bool rememberOwed = false)
{ {
foreach (var knifeInfo in knivesInfo.Values.ToArray()) foreach (var knifeInfo in knivesInfo.Values.ToArray())
{
if (rememberOwed && knifeInfo.IsDropped)
owedKnife[knifeInfo.PlayerIndex] = 0;
DisableKnifeSkill(knifeInfo); DisableKnifeSkill(knifeInfo);
}
knivesInfo.Clear(); knivesInfo.Clear();
} }
@ -155,18 +162,24 @@ namespace src.player.skills
public static void DisableSkill(CCSPlayerController player) public static void DisableSkill(CCSPlayerController player)
{ {
if (player == null || !player.IsValid) return;
bool wasDropped = owedKnife.TryRemove(player.Index, out _);
if (knivesInfo.TryRemove(player.Index, out KnifeInfo? knifeInfo) && knifeInfo != null) if (knivesInfo.TryRemove(player.Index, out KnifeInfo? knifeInfo) && knifeInfo != null)
{ {
bool wasDropped = knifeInfo.IsDropped; wasDropped |= knifeInfo.IsDropped;
DisableKnifeSkill(knifeInfo); DisableKnifeSkill(knifeInfo);
if (wasDropped && player != null && player.IsValid && player.PawnIsAlive)
player.GiveNamedItem("weapon_knife");
} }
if (wasDropped && player.PawnIsAlive)
player.GiveNamedItem("weapon_knife");
} }
public static void PlayerDisconnect(uint playerIndex) public static void PlayerDisconnect(uint playerIndex)
{ {
owedKnife.TryRemove(playerIndex, out _);
if (knivesInfo.TryRemove(playerIndex, out KnifeInfo? knifeInfo) && knifeInfo != null) if (knivesInfo.TryRemove(playerIndex, out KnifeInfo? knifeInfo) && knifeInfo != null)
DisableKnifeSkill(knifeInfo); DisableKnifeSkill(knifeInfo);
} }

View file

@ -12,7 +12,7 @@ namespace src.player.skills
private const Skills skillName = Skills.TrueArmor; private const Skills skillName = Skills.TrueArmor;
private static readonly ConcurrentDictionary<uint, byte> holders = []; private static readonly ConcurrentDictionary<uint, byte> holders = [];
private static readonly ConcurrentDictionary<uint, int> pending = []; private static readonly ConcurrentDictionary<uint, PendingArmor> pending = [];
public static void LoadSkill() public static void LoadSkill()
{ {
@ -62,13 +62,16 @@ namespace src.player.skills
info.Damage = damage - absorbed; info.Damage = damage - absorbed;
pending[victim.Index] = (int)MathF.Round(armor - absorbed); int restoredArmor = (int)MathF.Round(armor - absorbed);
bool hadHelmet = victimPawn.ItemServices?.As<CCSPlayer_ItemServices>()?.HasHelmet ?? false;
pending[victim.Index] = new PendingArmor(restoredArmor, hadHelmet);
victimPawn.ArmorValue = 0; victimPawn.ArmorValue = 0;
Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue"); Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue");
Debug.WriteToDebug($"[TrueArmor] {victim.PlayerName}: raw={damage:0.#} absorbed={absorbed:0.#} " + Debug.WriteToDebug($"[TrueArmor] {victim.PlayerName}: raw={damage:0.#} absorbed={absorbed:0.#} " +
$"passed={info.Damage:0.#} armor={armor}->{pending[victim.Index]} hp={victimPawn.Health}", DebugCategory.Damage); $"passed={info.Damage:0.#} armor={armor}->{restoredArmor} helmet={hadHelmet} hp={victimPawn.Health}", DebugCategory.Damage);
} }
public static void OnTakeDamagePost(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, CTakeDamageResult damageResult) public static void OnTakeDamagePost(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, CTakeDamageResult damageResult)
@ -76,12 +79,22 @@ namespace src.player.skills
if (pending.IsEmpty) return; if (pending.IsEmpty) return;
if (!TryResolveVictim(damagedEntity, damageInfo, out var victim, out var victimPawn, out _)) return; if (!TryResolveVictim(damagedEntity, damageInfo, out var victim, out var victimPawn, out _)) return;
if (!pending.TryRemove(victim!.Index, out int restored)) return; if (!pending.TryRemove(victim!.Index, out var restored)) return;
victimPawn!.ArmorValue = restored; victimPawn!.ArmorValue = restored.Armor;
Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue"); Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue");
if (!restored.HasHelmet) return;
var itemServices = victimPawn.ItemServices?.As<CCSPlayer_ItemServices>();
if (itemServices == null || itemServices.HasHelmet) return;
itemServices.HasHelmet = true;
Utilities.SetStateChanged(victim, "CCSPlayerController", "m_bPawnHasHelmet");
} }
private readonly record struct PendingArmor(int Armor, bool HasHelmet);
private static bool TryResolveVictim(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, out CCSPlayerController? victim, out CCSPlayerPawn? pawn, out CTakeDamageInfo? info) private static bool TryResolveVictim(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, out CCSPlayerController? victim, out CCSPlayerPawn? pawn, out CTakeDamageInfo? info)
{ {
victim = null; victim = null;

View file

@ -24,9 +24,11 @@ namespace src.utils
{ {
var newConfig = new SettingsModel(); var newConfig = new SettingsModel();
Instance.Logger.LogInformation("config path: {Path} (exists={Exists})", configPath, File.Exists(configPath));
if (!File.Exists(configPath)) if (!File.Exists(configPath))
{ {
Instance.Logger.LogInformation("Config file does not exist. Create a new config file..."); Instance.Logger.LogError("config.json NOT FOUND at the path above; a fresh file with defaults is being written.");
SaveConfig(newConfig); SaveConfig(newConfig);
debugFlags = DebugCategories.Parse(newConfig.DebugMode); debugFlags = DebugCategories.Parse(newConfig.DebugMode);
return config = newConfig; return config = newConfig;
@ -43,19 +45,44 @@ namespace src.utils
if (HasMissingKeys(json) || IsLegacyDebugMode(json)) if (HasMissingKeys(json) || IsLegacyDebugMode(json))
SaveConfig(newConfig); SaveConfig(newConfig);
} }
catch catch (Exception ex)
{ {
Instance.Logger.LogError("Error when loading the config file."); Instance.Logger.LogError("Error when loading the config file: {Message}", ex.Message);
if (config != null)
{
Instance.Logger.LogError("config.json was not applied; the previously loaded settings are kept.");
return config;
}
} }
if (newConfig.DisplayAlwaysDescription) if (newConfig.DisplayAlwaysDescription)
newConfig.SkillDescriptionDuration = 9999; newConfig.SkillDescriptionDuration = 9999;
debugFlags = DebugCategories.Parse(newConfig.DebugMode); debugFlags = DebugCategories.Parse(newConfig.DebugMode);
ApplyRarityTables(newConfig);
return config = newConfig; return config = newConfig;
} }
} }
private static void ApplyRarityTables(SettingsModel model)
{
RarityManager.SetRarityPercentages(ToRarityTable(model.SkillsChance));
RarityManager.SetVipRarityPercentages(ToRarityTable(model.VIPSkillsChance));
}
private static Dictionary<Rarity, float> ToRarityTable(Dictionary<string, float>? source)
{
var table = new Dictionary<Rarity, float>();
if (source == null) return table;
foreach (var kv in source)
if (Enum.TryParse<Rarity>(kv.Key, true, out var rarity))
table[rarity] = kv.Value;
return table;
}
private static bool HasMissingKeys(string json) private static bool HasMissingKeys(string json)
{ {
try try
@ -111,12 +138,11 @@ namespace src.utils
string tempPath = $"{configPath}.temp"; string tempPath = $"{configPath}.temp";
File.WriteAllText(tempPath, json); File.WriteAllText(tempPath, json);
File.Copy(tempPath, configPath, overwrite: true); File.Move(tempPath, configPath, overwrite: true);
File.Delete(tempPath);
} }
catch catch (Exception ex)
{ {
Instance.Logger.LogError("Error when saving the config file."); Instance.Logger.LogError("Error when saving the config file: {Message}", ex.Message);
} }
} }
} }
@ -146,6 +172,11 @@ namespace src.utils
public bool TraceRayBeam { get; set; } public bool TraceRayBeam { get; set; }
public string DisableHUDOnDeathPermission { get; set; } public string DisableHUDOnDeathPermission { get; set; }
public bool DisableSkillsOnRoundEnd { get; set; } public bool DisableSkillsOnRoundEnd { get; set; }
public string VIPFlag { get; set; }
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<string, float> SkillsChance { get; set; }
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)]
public Dictionary<string, float> VIPSkillsChance { get; set; }
public int? CurseSkillPerPlayer { get; set; } public int? CurseSkillPerPlayer { get; set; }
public bool ShowDecoyRing { get; set; } public bool ShowDecoyRing { get; set; }
public WeaponPools Weapons { get; set; } public WeaponPools Weapons { get; set; }
@ -179,6 +210,25 @@ namespace src.utils
HideHudForOtherPlugins = true; HideHudForOtherPlugins = true;
DisableHUDOnDeathPermission = "@jRandomSkills/death"; DisableHUDOnDeathPermission = "@jRandomSkills/death";
DisableSkillsOnRoundEnd = false; DisableSkillsOnRoundEnd = false;
VIPFlag = "@css/vip";
SkillsChance = new Dictionary<string, float>
{
["Common"] = 0.70f,
["Uncommon"] = 0.14f,
["Rare"] = 0.10f,
["Epic"] = 0.05f,
["Legendary"] = 0.01f,
};
VIPSkillsChance = new Dictionary<string, float>
{
["Common"] = 0.55f,
["Uncommon"] = 0.23f,
["Rare"] = 0.14f,
["Epic"] = 0.07f,
["Legendary"] = 0.01f,
};
CurseSkillPerPlayer = null; CurseSkillPerPlayer = null;
ShowDecoyRing = true; ShowDecoyRing = true;

View file

@ -264,7 +264,8 @@ namespace src.utils
} }
// Dying entities stay out of transmit until the engine processes the kill (Event.CheckTransmit). // Dying entities stay out of transmit until the engine processes the kill (Event.CheckTransmit).
private static readonly ConcurrentDictionary<uint, DateTime> recentlyDestroyed = new(); private readonly record struct DyingEntity(DateTime Expires, uint HandleRaw);
private static readonly ConcurrentDictionary<uint, DyingEntity> recentlyDestroyed = new();
public static List<uint> GetRecentlyDestroyedSnapshot() public static List<uint> GetRecentlyDestroyedSnapshot()
{ {
@ -274,8 +275,20 @@ namespace src.utils
var result = new List<uint>(); var result = new List<uint>();
foreach (var kvp in recentlyDestroyed) foreach (var kvp in recentlyDestroyed)
{ {
if (now > kvp.Value) recentlyDestroyed.TryRemove(kvp.Key, out _); if (now > kvp.Value.Expires)
else result.Add(kvp.Key); {
recentlyDestroyed.TryRemove(kvp.Key, out _);
continue;
}
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)kvp.Key);
if (entity == null || !entity.IsValid || entity.EntityHandle.Raw != kvp.Value.HandleRaw)
{
recentlyDestroyed.TryRemove(kvp.Key, out _);
continue;
}
result.Add(kvp.Key);
} }
return result; return result;
} }
@ -297,7 +310,7 @@ namespace src.utils
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)entityIndex); var entity = Utilities.GetEntityFromIndex<CBaseEntity>((int)entityIndex);
if (entity != null && entity.IsValid) if (entity != null && entity.IsValid)
{ {
recentlyDestroyed[entityIndex] = DateTime.UtcNow.AddSeconds(delay + 2.0); recentlyDestroyed[entityIndex] = new DyingEntity(DateTime.UtcNow.AddSeconds(delay + 0.5), entity.EntityHandle.Raw);
// Detach first so no follower is left on a freed parent. // Detach first so no follower is left on a freed parent.
entity.AcceptInput("ClearParent"); entity.AcceptInput("ClearParent");
entity.AddEntityIOEvent("Kill", entity, delay: delay); entity.AddEntityIOEvent("Kill", entity, delay: delay);

View file

@ -23,6 +23,15 @@ namespace src.utils
{ Rarity.Legendary, 1f } { Rarity.Legendary, 1f }
}; };
private static Dictionary<Rarity, float> vipRarityPercentages = new()
{
{ Rarity.Common, 55f },
{ Rarity.Uncommon, 23f },
{ Rarity.Rare, 14f },
{ Rarity.Epic, 7f },
{ Rarity.Legendary, 1f }
};
public static IReadOnlyDictionary<Rarity, float> RarityPercentages public static IReadOnlyDictionary<Rarity, float> RarityPercentages
{ {
get get
@ -34,26 +43,39 @@ namespace src.utils
public static void SetRarityPercentages(IDictionary<Rarity, float> percentages) public static void SetRarityPercentages(IDictionary<Rarity, float> percentages)
{ {
if (percentages == null || percentages.Count == 0) return; var table = Normalize(percentages);
if (table == null) return;
lock (rarityLock) lock (rarityLock)
{ rarityPercentages = table;
double sum = percentages.Values.Sum(v => (double)v); }
if (sum <= 0)
return;
if (Math.Abs(sum - 100.0) > 0.0001) public static void SetVipRarityPercentages(IDictionary<Rarity, float> percentages)
{ {
var normalized = new Dictionary<Rarity, float>(); var table = Normalize(percentages);
if (table == null) return;
foreach (var kv in percentages) lock (rarityLock)
normalized[kv.Key] = (float)((kv.Value / sum) * 100.0); vipRarityPercentages = table;
}
rarityPercentages = normalized; // Accepts either percentages (70, 14, ...) or fractions (0.7, 0.14, ...); both are
} // rescaled so the table sums to 100.
else private static Dictionary<Rarity, float>? Normalize(IDictionary<Rarity, float> percentages)
rarityPercentages = percentages.ToDictionary(k => k.Key, v => v.Value); {
} if (percentages == null || percentages.Count == 0) return null;
double sum = percentages.Values.Sum(v => (double)v);
if (sum <= 0) return null;
if (Math.Abs(sum - 100.0) <= 0.0001)
return percentages.ToDictionary(k => k.Key, v => v.Value);
var normalized = new Dictionary<Rarity, float>();
foreach (var kv in percentages)
normalized[kv.Key] = (float)((kv.Value / sum) * 100.0);
return normalized;
} }
public static float GetRarityPercentage(Rarity rarity) public static float GetRarityPercentage(Rarity rarity)
@ -62,16 +84,18 @@ namespace src.utils
return rarityPercentages.TryGetValue(rarity, out var v) ? v : 0f; return rarityPercentages.TryGetValue(rarity, out var v) ? v : 0f;
} }
public static (double, Rarity) RollRarity() public static (double, Rarity) RollRarity(bool vip = false)
{ {
double roll = Random.Shared.NextDouble() * 100.0; double roll = Random.Shared.NextDouble() * 100.0;
double accum = 0.0; double accum = 0.0;
lock (rarityLock) lock (rarityLock)
{ {
var table = vip ? vipRarityPercentages : rarityPercentages;
foreach (var r in Enum.GetValues(typeof(Rarity)).Cast<Rarity>()) foreach (var r in Enum.GetValues(typeof(Rarity)).Cast<Rarity>())
{ {
float chance = rarityPercentages.TryGetValue(r, out var val) ? val : 0f; float chance = table.TryGetValue(r, out var val) ? val : 0f;
accum += chance; accum += chance;
if (roll <= accum) if (roll <= accum)
return (roll, r); return (roll, r);

View file

@ -21,6 +21,32 @@ namespace src.utils
{ {
public static class SkillUtils public static class SkillUtils
{ {
private static readonly ConcurrentDictionary<string, ConVar> cvarCache = [];
public static ConVar? Cvar(string name)
{
if (cvarCache.TryGetValue(name, out var cached)) return cached;
var cvar = ConVar.Find(name);
if (cvar != null) cvarCache[name] = cvar;
return cvar;
}
public static T CvarValue<T>(string name, T fallback) where T : unmanaged
{
var cvar = Cvar(name);
if (cvar == null) return fallback;
try { return cvar.GetPrimitiveValue<T>(); }
catch { return fallback; }
}
public static string CvarString(string name, string fallback)
{
var cvar = Cvar(name);
return cvar == null ? fallback : cvar.StringValue;
}
private static Lazy<T?> LazySig<T>(string name, Func<string, T> factory) where T : class => private static Lazy<T?> LazySig<T>(string name, Func<string, T> factory) where T : class =>
new(() => new(() =>
{ {
@ -82,9 +108,9 @@ namespace src.utils
try try
{ {
if (ConVar.Find("mp_halftime")?.GetPrimitiveValue<bool>() != true) return false; if (!CvarValue("mp_halftime", false)) return false;
int maxRounds = ConVar.Find("mp_maxrounds")?.GetPrimitiveValue<int>() ?? 0; int maxRounds = CvarValue("mp_maxrounds", 0);
return maxRounds > 0 && maxRounds / 2 == gameRules.TotalRoundsPlayed; return maxRounds > 0 && maxRounds / 2 == gameRules.TotalRoundsPlayed;
} }
catch catch
@ -310,6 +336,62 @@ namespace src.utils
return (HitGroup_t)Marshal.ReadInt32(hitGroupData, 56); return (HitGroup_t)Marshal.ReadInt32(hitGroupData, 56);
} }
private const float DefaultHeadshotMultiplier = 4f;
private const float StomachMultiplier = 1.25f;
private const float LegMultiplier = 0.75f;
public static float GetAppliedDamageScale(CTakeDamageInfo? info, CCSPlayerPawn? victimPawn)
{
if (info == null || victimPawn == null || !victimPawn.IsValid) return 1f;
var hitGroup = GetHitGroup(info);
var vdata = GetWeaponVData(info);
float scale = hitGroup switch
{
HitGroup_t.HITGROUP_HEAD => vdata != null && vdata.HeadshotMultiplier > 0 ? vdata.HeadshotMultiplier : DefaultHeadshotMultiplier,
HitGroup_t.HITGROUP_STOMACH => StomachMultiplier,
HitGroup_t.HITGROUP_LEFTLEG or HitGroup_t.HITGROUP_RIGHTLEG => LegMultiplier,
_ => 1f,
};
if (vdata == null || vdata.ArmorRatio <= 0 || vdata.ArmorRatio >= 1f) return scale;
if (victimPawn.ArmorValue <= 0) return scale;
if (!ArmorCovers(hitGroup, victimPawn)) return scale;
return scale * vdata.ArmorRatio;
}
public static float PredictAppliedDamage(CTakeDamageInfo? info, CCSPlayerPawn? victimPawn)
{
if (info == null) return 0f;
return info.Damage * GetAppliedDamageScale(info, victimPawn);
}
public static bool IsPredictedLethal(CTakeDamageInfo? info, CCSPlayerPawn? victimPawn)
{
if (victimPawn == null || !victimPawn.IsValid) return false;
return PredictAppliedDamage(info, victimPawn) >= victimPawn.Health;
}
private static bool ArmorCovers(HitGroup_t hitGroup, CCSPlayerPawn victimPawn) => hitGroup switch
{
HitGroup_t.HITGROUP_LEFTLEG or HitGroup_t.HITGROUP_RIGHTLEG => false,
HitGroup_t.HITGROUP_HEAD => victimPawn.ItemServices?.As<CCSPlayer_ItemServices>()?.HasHelmet ?? false,
_ => true,
};
private static CCSWeaponBaseVData? GetWeaponVData(CTakeDamageInfo info)
{
var ability = info.Ability?.Value;
if (ability == null || !ability.IsValid) return null;
var weapon = ability.As<CCSWeaponBase>();
if (weapon == null || !weapon.IsValid) return null;
return weapon.GetVData<CCSWeaponBaseVData>();
}
public static void CreateHEGrenadeProjectile(Vector pos, QAngle angle, Vector vel, int teamNum) public static void CreateHEGrenadeProjectile(Vector pos, QAngle angle, Vector vel, int teamNum)
{ {
HEGrenadeProjectile_CreateFunc.Value?.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 44, teamNum); HEGrenadeProjectile_CreateFunc.Value?.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 44, teamNum);
@ -341,8 +423,8 @@ namespace src.utils
if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return false; // non-player inflictor if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return false; // non-player inflictor
if (attackerPawn.TeamNum != victimPawn.TeamNum) return false; // enemy -> real damage if (attackerPawn.TeamNum != victimPawn.TeamNum) return false; // enemy -> real damage
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false; bool ff = CvarValue("mp_friendlyfire", false);
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false; bool tae = CvarValue("mp_teammates_are_enemies", false);
return !ff && !tae; // same team + FF off -> engine will zero this damage return !ff && !tae; // same team + FF off -> engine will zero this damage
} }
@ -351,8 +433,8 @@ namespace src.utils
if (!IsSameTeamHit(info, victimPawn)) return false; if (!IsSameTeamHit(info, victimPawn)) return false;
if (!SkillsInfo.GetValue<bool>(skill, "friendlyFire")) return true; if (!SkillsInfo.GetValue<bool>(skill, "friendlyFire")) return true;
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false; bool ff = CvarValue("mp_friendlyfire", false);
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false; bool tae = CvarValue("mp_teammates_are_enemies", false);
return !ff && !tae; return !ff && !tae;
} }
@ -1199,17 +1281,17 @@ namespace src.utils
private static void AwardRoundEndMoney(CsTeam winnerTeam, CCSPlayerController? bonusPlayer = null) private static void AwardRoundEndMoney(CsTeam winnerTeam, CCSPlayerController? bonusPlayer = null)
{ {
int winnerReward = winnerTeam == CsTeam.CounterTerrorist int winnerReward = winnerTeam == CsTeam.CounterTerrorist
? ConVar.Find("cash_team_win_by_defusing_bomb")?.GetPrimitiveValue<int>() ?? 3500 ? CvarValue("cash_team_win_by_defusing_bomb", 3500)
: ConVar.Find("cash_team_terrorist_win_bomb")?.GetPrimitiveValue<int>() ?? 3500; : CvarValue("cash_team_terrorist_win_bomb", 3500);
if (winnerReward <= 0) return; if (winnerReward <= 0) return;
int maxMoney = ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000; int maxMoney = CvarValue("mp_maxmoney", 16000);
int personalBonus = bonusPlayer == null int personalBonus = bonusPlayer == null
? 0 ? 0
: winnerTeam == CsTeam.CounterTerrorist : winnerTeam == CsTeam.CounterTerrorist
? ConVar.Find("cash_player_defused_bomb")?.GetPrimitiveValue<int>() ?? 300 ? CvarValue("cash_player_defused_bomb", 300)
: ConVar.Find("cash_player_bomb_planted")?.GetPrimitiveValue<int>() ?? 300; : CvarValue("cash_player_bomb_planted", 300);
foreach (var player in PlayerManager.GetTickPlayers()) foreach (var player in PlayerManager.GetTickPlayers())
{ {
@ -1232,10 +1314,10 @@ namespace src.utils
{ {
if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return; if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return;
int totalRoundsPlayed = ctScore + tScore; int totalRoundsPlayed = ctScore + tScore;
int maxRounds = ConVar.Find("mp_maxrounds")?.GetPrimitiveValue<int>() ?? 24; int maxRounds = CvarValue("mp_maxrounds", 24);
int halfRounds = maxRounds / 2; int halfRounds = maxRounds / 2;
int overtimeMaxRounds = ConVar.Find("mp_overtime_maxrounds")?.GetPrimitiveValue<int>() ?? 6; int overtimeMaxRounds = CvarValue("mp_overtime_maxrounds", 6);
int overtimeLimit = ConVar.Find("mp_overtime_limit")?.GetPrimitiveValue<int>() ?? 1; int overtimeLimit = CvarValue("mp_overtime_limit", 1);
var gameRulesProxy = jRandomSkills.Instance.GameRules; var gameRulesProxy = jRandomSkills.Instance.GameRules;
gameRulesProxy.TotalRoundsPlayed = totalRoundsPlayed; gameRulesProxy.TotalRoundsPlayed = totalRoundsPlayed;

View file

@ -29,9 +29,12 @@ namespace src.utils
{ {
var newConfig = new SkillsInfoModel(); var newConfig = new SkillsInfoModel();
Instance.Logger.LogInformation("skillsInfo path: {Path} (exists={Exists}, defaults built={Built})",
configPath, File.Exists(configPath), newConfig.Count);
if (!File.Exists(configPath)) if (!File.Exists(configPath))
{ {
Instance.Logger.LogInformation("Config file does not exist. Create a new skills info file..."); Instance.Logger.LogError("skillsInfo.json NOT FOUND at the path above; a fresh file with defaults is being written.");
SaveConfig(newConfig); SaveConfig(newConfig);
return config = newConfig; return config = newConfig;
} }
@ -45,6 +48,7 @@ namespace src.utils
var root = JsonConvert.DeserializeObject<JArray>(json); var root = JsonConvert.DeserializeObject<JArray>(json);
bool needsRewrite = root == null; bool needsRewrite = root == null;
bool populateFailed = false;
var present = new HashSet<string>(StringComparer.Ordinal); var present = new HashSet<string>(StringComparer.Ordinal);
if (root != null) if (root != null)
@ -61,21 +65,40 @@ namespace src.utils
if (skillObj is JObject current && HasMissingKeys(current, instance)) if (skillObj is JObject current && HasMissingKeys(current, instance))
needsRewrite = true; needsRewrite = true;
JsonConvert.PopulateObject(skillObj.ToString(), instance); try
{
JsonConvert.PopulateObject(skillObj.ToString(), instance);
}
catch (Exception ex)
{
populateFailed = true;
Instance.Logger.LogError("skillsInfo.json: \"{Name}\" could not be read ({Message}); its defaults are kept.", name, ex.Message);
}
} }
if (newConfig.Any(s => !string.IsNullOrEmpty(s.Name) && !present.Contains(s.Name))) if (newConfig.Any(s => !string.IsNullOrEmpty(s.Name) && !present.Contains(s.Name)))
needsRewrite = true; needsRewrite = true;
if (needsRewrite) Instance.Logger.LogInformation("skillsInfo.json read: {Read} entries, {Matched} matched a known skill, rewrite={Rewrite}",
root?.Count ?? 0, present.Count, needsRewrite);
if (populateFailed)
Instance.Logger.LogError("skillsInfo.json was not rewritten because some entries failed to load; fix them before reloading.");
else if (needsRewrite)
{ {
SaveConfig(newConfig); SaveConfig(newConfig);
Instance.Logger.LogInformation("skillsInfo.json was missing keys; rewritten with the defaults filled in."); Instance.Logger.LogInformation("skillsInfo.json was missing keys; rewritten with the defaults filled in.");
} }
} }
catch catch (Exception ex)
{ {
Instance.Logger.LogError("Error when loading the skills info file."); Instance.Logger.LogError("Error when loading the skills info file: {Message}", ex.Message);
if (config != null)
{
Instance.Logger.LogError("skillsInfo.json was not applied; the previously loaded skill settings are kept.");
return config;
}
} }
return config = newConfig; return config = newConfig;
@ -104,12 +127,11 @@ namespace src.utils
string tempPath = $"{configPath}.temp"; string tempPath = $"{configPath}.temp";
File.WriteAllText(tempPath, json); File.WriteAllText(tempPath, json);
File.Copy(tempPath, configPath, overwrite: true); File.Move(tempPath, configPath, overwrite: true);
File.Delete(tempPath);
} }
catch catch (Exception ex)
{ {
Instance.Logger.LogError("Error when saving the skills info file."); Instance.Logger.LogError("Error when saving the skills info file: {Message}", ex.Message);
} }
} }
} }

View file

@ -21,6 +21,21 @@
"TraceRayBeam": false, "TraceRayBeam": false,
"DisableHUDOnDeathPermission": "@jRandomSkills/death", "DisableHUDOnDeathPermission": "@jRandomSkills/death",
"DisableSkillsOnRoundEnd": false, "DisableSkillsOnRoundEnd": false,
"VIPFlag": "@css/vip",
"SkillsChance": {
"Common": 0.7,
"Uncommon": 0.14,
"Rare": 0.1,
"Epic": 0.05,
"Legendary": 0.01
},
"VIPSkillsChance": {
"Common": 0.55,
"Uncommon": 0.23,
"Rare": 0.14,
"Epic": 0.07,
"Legendary": 0.01
},
"CurseSkillPerPlayer": null, "CurseSkillPerPlayer": null,
"ShowDecoyRing": true, "ShowDecoyRing": true,
"Weapons": { "Weapons": {

View file

@ -82,7 +82,7 @@
"catapult": "Mancınık", "catapult": "Mancınık",
"catapult_desc": "Vurduğun rakibi uzaya (yukarı doğru) fırlatma şansın var", "catapult_desc": "Vurduğun rakibi uzaya (yukarı doğru) fırlatma şansın var",
"catapult_desc2": "Fırlatma şansın: %{0}", "catapult_desc2": "Vurduğun rakibi havaya fırlatma şansın: %{0}",
"chameleon": "Bukalemun", "chameleon": "Bukalemun",
"chameleon_desc": "Öldürdüğün ilk oyuncunun yeteneği sana geçer", "chameleon_desc": "Öldürdüğün ilk oyuncunun yeteneği sana geçer",
@ -452,7 +452,7 @@
"push": "İtici", "push": "İtici",
"push_desc": "Düşmana vurduğunda onu geri itme şansın olur", "push_desc": "Düşmana vurduğunda onu geri itme şansın olur",
"push_desc2": "Geri itme şansın: %{0}", "push_desc2": "Vurduğun rakibi geri itme şansın: %{0}",
"pyro": "Ateşbaz", "pyro": "Ateşbaz",
"pyro_desc": "Molotof canını yeniler", "pyro_desc": "Molotof canını yeniler",
@ -511,7 +511,7 @@
"shade": "Gölge", "shade": "Gölge",
"shade_desc": "Vurduğun düşmanın arkasına ışınlanma şansın olur", "shade_desc": "Vurduğun düşmanın arkasına ışınlanma şansın olur",
"shade_desc2": "Arkasına ışınlanma şansın: %{0}", "shade_desc2": "Vurduğun rakibin arkasına ışınlanma şansın: %{0}",
"shade_nospace": "Uygun alan yok", "shade_nospace": "Uygun alan yok",
"shortbomb": "Kısa Fünye", "shortbomb": "Kısa Fünye",