diff --git a/jRandomSkills - SRC Files/src/jRandomSkills.cs b/jRandomSkills - SRC Files/src/jRandomSkills.cs index 26bdc3d..976d1ae 100644 --- a/jRandomSkills - SRC Files/src/jRandomSkills.cs +++ b/jRandomSkills - SRC Files/src/jRandomSkills.cs @@ -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.b9"; + public override string ModuleVersion => "1.2.4.b1"; public override void Load(bool hotReload) { diff --git a/jRandomSkills - SRC Files/src/lang/tr.json b/jRandomSkills - SRC Files/src/lang/tr.json index 617a3dd..05d408f 100644 --- a/jRandomSkills - SRC Files/src/lang/tr.json +++ b/jRandomSkills - SRC Files/src/lang/tr.json @@ -82,7 +82,7 @@ "catapult": "Mancınık", "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_desc": "Öldürdüğün ilk oyuncunun yeteneği sana geçer", @@ -452,7 +452,7 @@ "push": "İtici", "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_desc": "Molotof canını yeniler", @@ -511,7 +511,7 @@ "shade": "Gölge", "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", "shortbomb": "Kısa Fünye", diff --git a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs index 8f5507d..8467a6c 100644 --- a/jRandomSkills - SRC Files/src/player/PlayerEvents.cs +++ b/jRandomSkills - SRC Files/src/player/PlayerEvents.cs @@ -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) { lock (setLock) { + if (!IsEventAlive(@event)) return HookResult.Continue; + DispatchToActiveSkills("WeaponEquip", @event); return HookResult.Continue; } @@ -307,6 +311,8 @@ namespace src.player { lock (setLock) { + if (!IsEventAlive(@event)) return HookResult.Continue; + DispatchToActiveSkills("WeaponPickup", @event); return HookResult.Continue; } @@ -584,7 +590,7 @@ namespace src.player string welcomeMsg = player.GetTranslationWithoutIlliterate("welcome_message", "welcome"); foreach (string line in welcomeMsg.Split("\n")) 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("{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) @@ -776,6 +782,7 @@ namespace src.player if (pawn.AbsOrigin == null || pawn.AbsRotation == null) return; if (pawn.IsDefusing) return; + if (IsAimingAtPlantedBomb(player, pawn)) return; Vector eyePos = new(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + pawn.ViewOffset.Z); Vector endPos = eyePos + SkillUtils.GetForwardVector(pawn.EyeAngles) * 80; @@ -809,6 +816,35 @@ namespace src.player } } + private const float BombBlockRange = 100f; + private const float BombBlockCos = 0.7071f; + + private static bool IsAimingAtPlantedBomb(CCSPlayerController player, CCSPlayerPawn pawn) + { + if (player.Team != CsTeam.CounterTerrorist) return false; + if (pawn.AbsOrigin == null) return false; + + foreach (var bomb in Utilities.FindAllEntitiesByDesignerName("planted_c4")) + { + if (bomb == null || !bomb.IsValid || bomb.AbsOrigin == null) continue; + if (!bomb.BombTicking || bomb.BombDefused) continue; + + float dx = bomb.AbsOrigin.X - pawn.AbsOrigin.X; + float dy = bomb.AbsOrigin.Y - pawn.AbsOrigin.Y; + float dz = bomb.AbsOrigin.Z - (pawn.AbsOrigin.Z + pawn.ViewOffset.Z); + + float distance = MathF.Sqrt(dx * dx + dy * dy + dz * dz); + if (distance > BombBlockRange || distance <= 0.01f) continue; + + var forward = SkillUtils.GetForwardVector(pawn.EyeAngles); + float dot = (forward.X * dx + forward.Y * dy + forward.Z * dz) / distance; + + if (dot >= BombBlockCos) return true; + } + + return false; + } + private static HookResult BulletImpact(EventBulletImpact @event, GameEventInfo info) { lock (setLock) diff --git a/jRandomSkills - SRC Files/src/player/RoundEvents.cs b/jRandomSkills - SRC Files/src/player/RoundEvents.cs index 9908d4f..a51a3e9 100644 --- a/jRandomSkills - SRC Files/src/player/RoundEvents.cs +++ b/jRandomSkills - SRC Files/src/player/RoundEvents.cs @@ -2,7 +2,6 @@ using CounterStrikeSharp.API.Core; using CounterStrikeSharp.API.Core.Attributes; using CounterStrikeSharp.API.Modules.Admin; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Events; using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; @@ -20,7 +19,17 @@ namespace src.player { public static partial class Event { - private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List candidates, Dictionary 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 candidates, Dictionary assignmentCounts, Config.GameModes gameMode, bool vip) { if (candidates == null || candidates.Count == 0) return noneSkill; @@ -31,7 +40,7 @@ namespace src.player for (int attempt = 0; attempt < attempts; attempt++) { - var (roll, rolled) = RarityManager.RollRarity(); + var (roll, rolled) = RarityManager.RollRarity(vip); string rolledName = rolled.ToString(); filtered.Clear(); @@ -92,7 +101,7 @@ namespace src.player } Instance.RemoveListener(CheckTransmit); - int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue() ?? 0; + int freezetime = SkillUtils.CvarValue("mp_freezetime", 0); freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0)); setSkillTimer?.Kill(); @@ -212,7 +221,7 @@ namespace src.player PlayerManager.Clear(); - ConVar.Find("sv_legacy_jump")?.SetValue("1"); + SkillUtils.Cvar("sv_legacy_jump")?.SetValue("1"); } } @@ -283,6 +292,7 @@ namespace src.player public required HashSet CtOnly { get; init; } public required HashSet TOnly { get; init; } public required HashSet PistolRoundBanned { get; init; } + public required HashSet MinPlayerBanned { get; init; } public required int TerroristCount { get; init; } public required int CounterTerroristCount { get; init; } } @@ -307,6 +317,7 @@ namespace src.player PistolRoundBanned = SkillUtils.IsPistolRound() ? ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.DisableOnPistolRound).Select(s => s.Name)) : [], + MinPlayerBanned = ToSkillSet(SkillsInfo.LoadedConfig.Where(s => s.MinPlayer > 0 && validPlayers.Count < s.MinPlayer).Select(s => s.Name)), TerroristCount = validPlayers.Count(p => p.Team == CsTeam.Terrorist), CounterTerroristCount = validPlayers.Count(p => p.Team == CsTeam.CounterTerrorist), }; @@ -342,13 +353,16 @@ namespace src.player if (ctx.PistolRoundBanned.Count != 0) skillList.RemoveAll(s => ctx.PistolRoundBanned.Contains(s.Skill)); + if (ctx.MinPlayerBanned.Count != 0) + skillList.RemoveAll(s => ctx.MinPlayerBanned.Contains(s.Skill)); + if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out HashSet? skills)) { skillList.RemoveAll(s => skills.Contains(s.Skill)); 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) { @@ -374,6 +388,7 @@ namespace src.player if (def == null) return false; if (def.DisableOnPistolRound && SkillUtils.IsPistolRound()) return false; if (def.NeedsTeammates && validPlayers.Count(p => p.Team == player.Team) == 1) return false; + if (def.MinPlayer > 0 && validPlayers.Count < def.MinPlayer) return false; if (def.MaxPerServer >= 0 && assignmentCounts.TryGetValue(pick.Skill, out var c) && c >= def.MaxPerServer) return false; return true; @@ -672,6 +687,10 @@ namespace src.player if (SkillUtils.IsPistolRound()) skillList.RemoveAll(s => SkillsInfo.GetValue(s.Skill, "disableOnPistolRound")); + SkillsInfo.DefaultSkillInfo[] skillsMinPlayer = [.. SkillsInfo.LoadedConfig.Where(s => s.MinPlayer > 0 && validPlayers.Count < s.MinPlayer)]; + if (skillsMinPlayer.Length != 0) + skillList.RemoveAll(s => skillsMinPlayer.Any(s2 => s2.Name == s.Skill.ToString())); + if (gameMode == Config.GameModes.NoRepeat && playersSkills.TryGetValue(player.Index, out HashSet? skills)) { skillList.RemoveAll(s => skills.Contains(s.Skill)); @@ -686,7 +705,7 @@ namespace src.player 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) randomSkill = player.Team == CsTeam.Terrorist ? tSkill : ctSkill; diff --git a/jRandomSkills - SRC Files/src/player/skills/AntyFlash.cs b/jRandomSkills - SRC Files/src/player/skills/AntyFlash.cs index 0e30862..a637f5a 100644 --- a/jRandomSkills - SRC Files/src/player/skills/AntyFlash.cs +++ b/jRandomSkills - SRC Files/src/player/skills/AntyFlash.cs @@ -1,5 +1,4 @@ using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Utils; using src.utils; @@ -86,7 +85,7 @@ namespace src.player.skills { if (player == null || !player.IsValid) return; - int flashbangLimit = ConVar.Find("ammo_grenade_limit_flashbang")?.GetPrimitiveValue() ?? 2; + int flashbangLimit = SkillUtils.CvarValue("ammo_grenade_limit_flashbang", 2); int grenadeLimit = SkillsInfo.GetValue(skillName, "grenadeLimit"); if (grenadeLimit > flashbangLimit) diff --git a/jRandomSkills - SRC Files/src/player/skills/BlastShot.cs b/jRandomSkills - SRC Files/src/player/skills/BlastShot.cs index 3671de9..9fcb878 100644 --- a/jRandomSkills - SRC Files/src/player/skills/BlastShot.cs +++ b/jRandomSkills - SRC Files/src/player/skills/BlastShot.cs @@ -179,7 +179,7 @@ namespace src.player.skills 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); } diff --git a/jRandomSkills - SRC Files/src/player/skills/Bounty.cs b/jRandomSkills - SRC Files/src/player/skills/Bounty.cs index 48b55d3..aa10a2f 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Bounty.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Bounty.cs @@ -1,6 +1,5 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Utils; using src.utils; using System.Collections.Concurrent; @@ -152,7 +151,7 @@ namespace src.player.skills killerEvent.PrintToChat($" {ChatColors.Green}{killerEvent.GetTranslation("bounty_claimed_info", victim.PlayerName, reward)}"); } - private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue() ?? 16000; + private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000); private static bool GiveMoney(CCSPlayerController player, int amount) { diff --git a/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs b/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs index a4362c8..b8b0ba5 100644 --- a/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs +++ b/jRandomSkills - SRC Files/src/player/skills/C4Camouflage.cs @@ -183,8 +183,8 @@ namespace src.player.skills Vector newPos = new(playerPawn.AbsOrigin.X, playerPawn.AbsOrigin.Y, playerPawn.AbsOrigin.Z + 30); QAngle newAngle = new(playerPawn.AbsRotation.X, playerPawn.AbsRotation.Y, playerPawn.AbsRotation.Z); - emptyProp.Teleport(newPos, newAngle); emptyProp.DispatchSpawn(); + emptyProp.Teleport(newPos, newAngle); Utilities.SetStateChanged(emptyProp, "CBaseEntity", "m_CBodyComponent"); diff --git a/jRandomSkills - SRC Files/src/player/skills/Catapult.cs b/jRandomSkills - SRC Files/src/player/skills/Catapult.cs index 585be94..1d49c1f 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Catapult.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Catapult.cs @@ -21,6 +21,7 @@ namespace src.player.skills var victim = PlayerManager.GetPlayerEvent(@event.Userid); if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return; + if (!SkillUtils.FiresBullets(@event.Weapon)) return; var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); if (Heavyweight.Resists(victim)) return; @@ -29,8 +30,9 @@ namespace src.player.skills if (Instance.Random.NextDouble() <= attackerInfo.SkillChance) { var victimPawn = victim.PlayerPawn?.Value; - if (victimPawn != null) - victimPawn.AbsVelocity.Z = 300f; + if (victimPawn == null || !victimPawn.IsValid || victimPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) return; + + victimPawn.AbsVelocity.Z = 300f; } } diff --git a/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs b/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs index 425e7b1..36a1383 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ChillOut.cs @@ -84,7 +84,7 @@ namespace src.player.skills float currentTime = Server.CurrentTime; float extraTime = SkillsInfo.GetValue(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; diff --git a/jRandomSkills - SRC Files/src/player/skills/DeathBomb.cs b/jRandomSkills - SRC Files/src/player/skills/DeathBomb.cs index 4facc37..14b06c7 100644 --- a/jRandomSkills - SRC Files/src/player/skills/DeathBomb.cs +++ b/jRandomSkills - SRC Files/src/player/skills/DeathBomb.cs @@ -128,7 +128,7 @@ namespace src.player.skills 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); } diff --git a/jRandomSkills - SRC Files/src/player/skills/ExplodingBarrel.cs b/jRandomSkills - SRC Files/src/player/skills/ExplodingBarrel.cs index 04ea71d..c25881e 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ExplodingBarrel.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ExplodingBarrel.cs @@ -240,7 +240,7 @@ namespace src.player.skills 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); } diff --git a/jRandomSkills - SRC Files/src/player/skills/ExplosiveShot.cs b/jRandomSkills - SRC Files/src/player/skills/ExplosiveShot.cs index 70ff794..7bdedc6 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ExplosiveShot.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ExplosiveShot.cs @@ -15,13 +15,13 @@ namespace src.player.skills private const Skills skillName = Skills.ExplosiveShot; private static readonly QAngle angle = new(5, 10, -4); - private static int lastTick = 0; - private static byte pendingTeam = (byte)CsTeam.None; - private static readonly ConcurrentDictionary nades = []; + private static readonly ConcurrentDictionary lastTickByPlayer = []; + private static readonly ConcurrentDictionary> nades = []; public static void NewRound() { nades.Clear(); + lastTickByPlayer.Clear(); } public static void LoadSkill() @@ -43,9 +43,8 @@ namespace src.player.skills private static void SpawnExplosion(Vector vector, CCSPlayerController player) { - lastTick = Server.TickCount; - pendingTeam = player.TeamNum; - nades.AddOrUpdate(Server.TickCount, (player.TeamNum, player.Index), (_, _) => (player.TeamNum, player.Index)); + lastTickByPlayer[player.Index] = Server.TickCount; + nades.GetOrAdd(Server.TickCount, static _ => new ConcurrentQueue<(byte, uint)>()).Enqueue((player.TeamNum, player.Index)); 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))) 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.TeamNum = pendingTeam; + heProjectile.TeamNum = source.Team; heProjectile.Damage = SkillsInfo.GetValue(skillName, "damage"); heProjectile.DmgRadius = SkillsInfo.GetValue(skillName, "damageRadius"); 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); 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); } @@ -120,11 +121,12 @@ namespace src.player.skills public static void BulletImpact(EventBulletImpact @event) { - if (lastTick == Server.TickCount) return; - var player = PlayerManager.GetPlayerEvent(@event.Userid); 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 playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); diff --git a/jRandomSkills - SRC Files/src/player/skills/FireRain.cs b/jRandomSkills - SRC Files/src/player/skills/FireRain.cs index 0f38d47..9b9271e 100644 --- a/jRandomSkills - SRC Files/src/player/skills/FireRain.cs +++ b/jRandomSkills - SRC Files/src/player/skills/FireRain.cs @@ -11,9 +11,7 @@ namespace src.player.skills { private const Skills skillName = Skills.FireRain; private static readonly ConcurrentDictionary decoys = []; - private static int rainTick = -1; - private static CCSPlayerPawn? rainThrower; - private static byte rainTeam = (byte)CsTeam.None; + private static readonly ConcurrentDictionary> rainBatches = []; private static readonly ConcurrentDictionary rainMolotovs = []; public static void LoadSkill() @@ -25,6 +23,7 @@ namespace src.player.skills public static void NewRound() { KillAllDecoys(); + rainBatches.Clear(); rainMolotovs.Clear(); DecoyRing.ClearAll(skillName); } @@ -54,10 +53,6 @@ namespace src.player.skills const float spawnHeight = 1500.0f; const float approachDistance = 600.0f; - rainTick = Server.TickCount; - rainThrower = pawn; - rainTeam = player.TeamNum; - float startAngle = Random.Shared.NextSingle() * MathF.Tau; Vector? skyCenter = null; @@ -88,10 +83,12 @@ namespace src.player.skills if (!foundPosition || skyCenter == null) { + QueueRain(pawn, player.TeamNum, grenadeGroundCount); CreateMolotovSplash(targetPos, grenadeGroundCount, player.TeamNum); return; } + QueueRain(pawn, player.TeamNum, grenadeCount); 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()) + .Enqueue(new RainBatch { Thrower = thrower, Team = team, Remaining = count }); + } + + private static void ConsumeOne(ConcurrentQueue queue, RainBatch batch) + { + if (--batch.Remaining > 0) return; + queue.TryDequeue(out _); + } + public static void OnEntitySpawned(CEntityInstance entity) { var name = entity.DesignerName; 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; - if (thrower == null || !thrower.IsValid) return; + var thrower = batch.Thrower; + if (thrower == null || !thrower.IsValid) { ConsumeOne(queue, batch); return; } var molotov = entity.As(); - 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.OwnerEntity.Raw = thrower.EntityHandle.Raw; - rainMolotovs[molotov.Index] = (thrower.EntityHandle.Raw, rainTeam); + rainMolotovs[molotov.Index] = (thrower.EntityHandle.Raw, batch.Team); Server.NextWorldUpdate(() => { diff --git a/jRandomSkills - SRC Files/src/player/skills/FriendlyFire.cs b/jRandomSkills - SRC Files/src/player/skills/FriendlyFire.cs index 5f47e04..dc1ea2c 100644 --- a/jRandomSkills - SRC Files/src/player/skills/FriendlyFire.cs +++ b/jRandomSkills - SRC Files/src/player/skills/FriendlyFire.cs @@ -1,6 +1,5 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions; using CounterStrikeSharp.API.Modules.Utils; using src.utils; @@ -32,7 +31,7 @@ namespace src.player.skills if (!SkillsInfo.GetValue(skillName, "manageAutoKick")) return false; bool live; - try { live = ConVar.Find("mp_autokick")?.GetPrimitiveValue() ?? false; } + try { live = SkillUtils.CvarValue("mp_autokick", false); } catch { return false; } if (!live) return false; diff --git a/jRandomSkills - SRC Files/src/player/skills/Gambler.cs b/jRandomSkills - SRC Files/src/player/skills/Gambler.cs index 5b9a604..60216d2 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Gambler.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Gambler.cs @@ -138,6 +138,11 @@ namespace src.player.skills else skillList.RemoveAll(s => Event.terroristSkills.Any(s2 => s2.Name == s.Skill.ToString())); + int playerCount = PlayerManager.GetTickPlayers().Count(p => p != null && p.IsValid && !p.IsHLTV && (p.Team == CsTeam.Terrorist || p.Team == CsTeam.CounterTerrorist)); + SkillsInfo.DefaultSkillInfo[] skillsMinPlayer = SkillsInfo.LoadedConfig.Where(s => s.MinPlayer > 0 && playerCount < s.MinPlayer).ToArray(); + if (skillsMinPlayer.Length != 0) + skillList.RemoveAll(s => skillsMinPlayer.Any(s2 => s2.Name == s.Skill.ToString())); + return skillList.Count == 0 ? [Event.noneSkill] : skillList; } diff --git a/jRandomSkills - SRC Files/src/player/skills/Ghost.cs b/jRandomSkills - SRC Files/src/player/skills/Ghost.cs index edf6de6..2923a48 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Ghost.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Ghost.cs @@ -127,8 +127,8 @@ namespace src.player.skills Vector newPos = new(playerPawn.AbsOrigin.X, playerPawn.AbsOrigin.Y, playerPawn.AbsOrigin.Z + 30); QAngle newAngle = new(playerPawn.AbsRotation.X, playerPawn.AbsRotation.Y, playerPawn.AbsRotation.Z); - emptyProp.Teleport(newPos, newAngle); emptyProp.DispatchSpawn(); + emptyProp.Teleport(newPos, newAngle); Utilities.SetStateChanged(emptyProp, "CBaseEntity", "m_CBodyComponent"); diff --git a/jRandomSkills - SRC Files/src/player/skills/GravityDecoy.cs b/jRandomSkills - SRC Files/src/player/skills/GravityDecoy.cs index 6a583b8..24ccc18 100644 --- a/jRandomSkills - SRC Files/src/player/skills/GravityDecoy.cs +++ b/jRandomSkills - SRC Files/src/player/skills/GravityDecoy.cs @@ -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) { if (player == null || !player.IsValid) return; diff --git a/jRandomSkills - SRC Files/src/player/skills/KillerFlash.cs b/jRandomSkills - SRC Files/src/player/skills/KillerFlash.cs index 926b3b2..da45424 100644 --- a/jRandomSkills - SRC Files/src/player/skills/KillerFlash.cs +++ b/jRandomSkills - SRC Files/src/player/skills/KillerFlash.cs @@ -1,5 +1,4 @@ using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Entities.Constants; using CounterStrikeSharp.API.Modules.Utils; using jRandomSkills.src.utils; @@ -79,7 +78,7 @@ namespace src.player.skills { if (player == null || !player.IsValid) return; - int flashbangLimit = ConVar.Find("ammo_grenade_limit_flashbang")?.GetPrimitiveValue() ?? 2; + int flashbangLimit = SkillUtils.CvarValue("ammo_grenade_limit_flashbang", 2); int grenadeLimit = SkillsInfo.GetValue(skillName, "grenadeLimit"); if (grenadeLimit > flashbangLimit) diff --git a/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs b/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs index 3f1499b..8d50b72 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Magnifier.cs @@ -54,7 +54,7 @@ namespace src.player.skills if (playerInfo == null || playerInfo.Skill != skillName) 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()))); SkillUtils.UpdateMenu(player, menuItems); diff --git a/jRandomSkills - SRC Files/src/player/skills/Ninja.cs b/jRandomSkills - SRC Files/src/player/skills/Ninja.cs index dee0544..8c238b0 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Ninja.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Ninja.cs @@ -156,8 +156,8 @@ namespace src.player.skills Vector newPos = new(playerPawn.AbsOrigin.X, playerPawn.AbsOrigin.Y, playerPawn.AbsOrigin.Z + 30); QAngle newAngle = new(playerPawn.AbsRotation.X, playerPawn.AbsRotation.Y, playerPawn.AbsRotation.Z); - emptyProp.Teleport(newPos, newAngle); emptyProp.DispatchSpawn(); + emptyProp.Teleport(newPos, newAngle); Utilities.SetStateChanged(emptyProp, "CBaseEntity", "m_CBodyComponent"); diff --git a/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs b/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs index 90ddde7..14e1907 100644 --- a/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs +++ b/jRandomSkills - SRC Files/src/player/skills/NoRecoil.cs @@ -1,6 +1,5 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Utils; using src.utils; using System.Collections.Concurrent; @@ -20,7 +19,7 @@ namespace src.player.skills public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); - defaultNoSpread = ConVar.Find("weapon_accuracy_nospread")?.GetPrimitiveValue() ?? false; + defaultNoSpread = SkillUtils.CvarValue("weapon_accuracy_nospread", false); } public static void NewRound() diff --git a/jRandomSkills - SRC Files/src/player/skills/Phoenix.cs b/jRandomSkills - SRC Files/src/player/skills/Phoenix.cs index b31581b..d8353f0 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Phoenix.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Phoenix.cs @@ -11,7 +11,6 @@ namespace src.player.skills public class Phoenix : ISkill { private const Skills skillName = Skills.Phoenix; - private const float DefaultHeadshotMultiplier = 4f; private static readonly ConcurrentDictionary phoenixTicks = new(); public static void LoadSkill() @@ -53,11 +52,7 @@ namespace src.player.skills if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return; - float effectiveDamage = damageInfo.Damage; - if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD) - effectiveDamage *= GetHeadshotMultiplier(damageInfo); - - if (effectiveDamage < victimPawn.Health) return; + if (!SkillUtils.IsPredictedLethal(damageInfo, victimPawn)) return; if (TryConsumeRevive(victim, victimPawn)) damageInfo.Damage = 0; @@ -99,20 +94,6 @@ namespace src.player.skills return true; } - private static float GetHeadshotMultiplier(CTakeDamageInfo info) - { - var ability = info.Ability?.Value; - if (ability == null || !ability.IsValid) return DefaultHeadshotMultiplier; - - var weapon = ability.As(); - if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier; - - var vdata = weapon.GetVData(); - if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier; - - return vdata.HeadshotMultiplier; - } - public static void EnableSkill(CCSPlayerController player) { var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); diff --git a/jRandomSkills - SRC Files/src/player/skills/Planter.cs b/jRandomSkills - SRC Files/src/player/skills/Planter.cs index ee1acb3..6fe7b94 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Planter.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Planter.cs @@ -1,6 +1,5 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Memory; using CounterStrikeSharp.API.Modules.Utils; using src.utils; @@ -20,7 +19,7 @@ namespace src.player.skills public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); - defaultC4Timer = ConVar.Find("mp_c4timer")?.GetPrimitiveValue() ?? 40; + defaultC4Timer = SkillUtils.CvarValue("mp_c4timer", 40); } public static void EnableSkill(CCSPlayerController player) @@ -96,7 +95,7 @@ namespace src.player.skills float currentTime = Server.CurrentTime; 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; diff --git a/jRandomSkills - SRC Files/src/player/skills/Push.cs b/jRandomSkills - SRC Files/src/player/skills/Push.cs index d5e9115..3464dc1 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Push.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Push.cs @@ -23,6 +23,8 @@ namespace src.player.skills if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return; + if (!SkillUtils.FiresBullets(@event.Weapon)) return; + if (Heavyweight.Resists(victim)) return; var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); diff --git a/jRandomSkills - SRC Files/src/player/skills/ReZombie.cs b/jRandomSkills - SRC Files/src/player/skills/ReZombie.cs index 4a4d93a..d5451e2 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ReZombie.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ReZombie.cs @@ -11,7 +11,6 @@ namespace src.player.skills public class ReZombie : ISkill { private const Skills skillName = Skills.ReZombie; - private const float DefaultHeadshotMultiplier = 4f; private static readonly ConcurrentDictionary zombies = []; 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. if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return; - 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 (!SkillUtils.IsPredictedLethal(damageInfo, victimPawn)) return; // survivable, let it through if (TryBecomeZombie(victim, victimPawn)) damageInfo.Damage = 0; @@ -138,20 +133,6 @@ namespace src.player.skills holdTime: 600); } - private static float GetHeadshotMultiplier(CTakeDamageInfo info) - { - var ability = info.Ability?.Value; - if (ability == null || !ability.IsValid) return DefaultHeadshotMultiplier; - - var weapon = ability.As(); - if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier; - - var vdata = weapon.GetVData(); - if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier; - - return vdata.HeadshotMultiplier; - } - private static void DropAllBotWeapons(CCSPlayerController player) { if (player == null || !player.IsValid || !player.IsBot) return; diff --git a/jRandomSkills - SRC Files/src/player/skills/RichBoy.cs b/jRandomSkills - SRC Files/src/player/skills/RichBoy.cs index cdce359..42bf074 100644 --- a/jRandomSkills - SRC Files/src/player/skills/RichBoy.cs +++ b/jRandomSkills - SRC Files/src/player/skills/RichBoy.cs @@ -1,8 +1,8 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Utils; using src.utils; +using System.Collections.Concurrent; using static src.jRandomSkills; namespace src.player.skills @@ -11,48 +11,49 @@ namespace src.player.skills { private const Skills skillName = Skills.RichBoy; + private static readonly ConcurrentDictionary accountBeforeBonus = []; + public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); } - private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue() ?? 16000; + private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000); public static void EnableSkill(CCSPlayerController player) { - var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - if (playerInfo == null) return; - int moneyBonus = Instance.Random.Next(SkillsInfo.GetValue(skillName, "minMoney"), SkillsInfo.GetValue(skillName, "maxMoney")); + if (player == null || !player.IsValid) return; var moneyServices = player.InGameMoneyServices; if (moneyServices == null) return; + int moneyBonus = Instance.Random.Next(SkillsInfo.GetValue(skillName, "minMoney"), SkillsInfo.GetValue(skillName, "maxMoney")); moneyBonus = Math.Min(moneyBonus, GetMaxMoney() - moneyServices.Account); + if (moneyBonus <= 0) return; - playerInfo.SkillChance = moneyBonus; - AddMoney(player, moneyBonus); + accountBeforeBonus[player.Index] = moneyServices.Account; + + moneyServices.Account += moneyBonus; + Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices"); } public static void DisableSkill(CCSPlayerController player) { - var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index); - if (playerInfo == null) return; + if (player == null || !player.IsValid) return; + if (!accountBeforeBonus.TryRemove(player.Index, out int accountBefore)) return; var moneyServices = player.InGameMoneyServices; if (moneyServices == null) return; - int money = Math.Abs((int)playerInfo.SkillChance! - moneyServices.CashSpentThisRound); - AddMoney(player, -money, 3000); + if (moneyServices.Account <= accountBefore) return; + + 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; - var moneyServices = player.InGameMoneyServices; - if (moneyServices == null) return; - - moneyServices.Account = Math.Clamp(moneyServices.Account + money, minimum, GetMaxMoney()); - Utilities.SetStateChanged(player, "CCSPlayerController", "m_pInGameMoneyServices"); + accountBeforeBonus.TryRemove(playerIndex, out _); } 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) diff --git a/jRandomSkills - SRC Files/src/player/skills/RobinHood.cs b/jRandomSkills - SRC Files/src/player/skills/RobinHood.cs index b5f1b4a..7f92aff 100644 --- a/jRandomSkills - SRC Files/src/player/skills/RobinHood.cs +++ b/jRandomSkills - SRC Files/src/player/skills/RobinHood.cs @@ -1,6 +1,5 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Utils; using src.utils; using static src.jRandomSkills; @@ -16,7 +15,7 @@ namespace src.player.skills SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); } - private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue() ?? 16000; + private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000); public static void PlayerHurt(EventPlayerHurt @event) { diff --git a/jRandomSkills - SRC Files/src/player/skills/Rubber.cs b/jRandomSkills - SRC Files/src/player/skills/Rubber.cs index 1d6b817..d1d0f12 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Rubber.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Rubber.cs @@ -28,6 +28,7 @@ namespace src.player.skills var victim = PlayerManager.GetPlayerEvent(@event.Userid); if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return; + if (!SkillUtils.FiresBullets(@event.Weapon)) return; var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); if (Heavyweight.Resists(victim)) return; diff --git a/jRandomSkills - SRC Files/src/player/skills/SecondLife.cs b/jRandomSkills - SRC Files/src/player/skills/SecondLife.cs index b32357b..1aac6bc 100644 --- a/jRandomSkills - SRC Files/src/player/skills/SecondLife.cs +++ b/jRandomSkills - SRC Files/src/player/skills/SecondLife.cs @@ -11,7 +11,6 @@ namespace src.player.skills public class SecondLife : ISkill { private const Skills skillName = Skills.SecondLife; - private const float DefaultHeadshotMultiplier = 4f; private static readonly ConcurrentDictionary usedThisRound = []; private static readonly object setLock = new(); @@ -45,11 +44,7 @@ namespace src.player.skills if (SkillUtils.IsFriendlyFireBlocked(damageInfo, victimPawn)) return; - float effectiveDamage = damageInfo.Damage; - if (SkillUtils.GetHitGroup(damageInfo) == HitGroup_t.HITGROUP_HEAD) - effectiveDamage *= GetHeadshotMultiplier(damageInfo); - - if (effectiveDamage < victimPawn.Health) return; + if (!SkillUtils.IsPredictedLethal(damageInfo, victimPawn)) return; if (usedThisRound.TryGetValue(victim.Handle, out int savedTick)) { @@ -62,20 +57,6 @@ namespace src.player.skills 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(); - if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier; - - var vdata = weapon.GetVData(); - if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier; - - return vdata.HeadshotMultiplier; - } - public static bool TryConsumeRevive(CCSPlayerController? victim, CCSPlayerPawn? victimPawn) { if (victim == null || !victim.IsValid) return false; diff --git a/jRandomSkills - SRC Files/src/player/skills/Shade.cs b/jRandomSkills - SRC Files/src/player/skills/Shade.cs index 9ca3a91..3b1d5f0 100644 --- a/jRandomSkills - SRC Files/src/player/skills/Shade.cs +++ b/jRandomSkills - SRC Files/src/player/skills/Shade.cs @@ -32,6 +32,7 @@ namespace src.player.skills if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim)) return; if (attacker!.Index == victim!.Index) return; + if (!SkillUtils.FiresBullets(@event.Weapon)) return; var victimInfo = PlayerManager.GetPlayerByIndex(victim!.Index); var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index); diff --git a/jRandomSkills - SRC Files/src/player/skills/ShortBomb.cs b/jRandomSkills - SRC Files/src/player/skills/ShortBomb.cs index d315095..d8d223d 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ShortBomb.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ShortBomb.cs @@ -1,6 +1,5 @@ using CounterStrikeSharp.API; using CounterStrikeSharp.API.Core; -using CounterStrikeSharp.API.Modules.Cvars; using CounterStrikeSharp.API.Modules.Utils; using src.utils; using static src.jRandomSkills; @@ -17,7 +16,7 @@ namespace src.player.skills public static void LoadSkill() { SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue(skillName, "color")); - defaultC4Timer = ConVar.Find("mp_c4timer")?.GetPrimitiveValue() ?? 40; + defaultC4Timer = SkillUtils.CvarValue("mp_c4timer", 40); } public static void EnableSkill(CCSPlayerController player) diff --git a/jRandomSkills - SRC Files/src/player/skills/SoundMaker.cs b/jRandomSkills - SRC Files/src/player/skills/SoundMaker.cs index 5ba9dc0..e143579 100644 --- a/jRandomSkills - SRC Files/src/player/skills/SoundMaker.cs +++ b/jRandomSkills - SRC Files/src/player/skills/SoundMaker.cs @@ -75,25 +75,27 @@ namespace src.player.skills } } - if (emitter == null) - { - foreach (var p in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid)) - um.Recipients.Remove(p); - return; - } + List allowed = []; - foreach (var recipient in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid)) - { - bool hasSkill = SkillPlayerInfo.ContainsKey(recipient.Index); + if (emitter != null) + foreach (var recipient in um.Recipients) + { + if (recipient == null || !recipient.IsValid) continue; + if (recipient.Team == emitter.Team) continue; - var bot = PlayerManager.GetPlayerEvent(recipient); - bool botSkill = bot != null && SkillPlayerInfo.ContainsKey(bot.Index); + bool hasSkill = SkillPlayerInfo.ContainsKey(recipient.Index); - bool isTeammate = recipient.Team == emitter.Team; + var bot = PlayerManager.GetPlayerEvent(recipient); + bool botSkill = bot != null && bot.IsValid && SkillPlayerInfo.ContainsKey(bot.Index); - if ((!hasSkill && !botSkill) || isTeammate) - um.Recipients.Remove(recipient); - } + if (!hasSkill && !botSkill) continue; + + allowed.Add(recipient); + } + + um.Recipients.Clear(); + foreach (var recipient in allowed) + um.Recipients.Add(recipient); } public static void OnTick() diff --git a/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs b/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs index d32f35f..6ee7163 100644 --- a/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs +++ b/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs @@ -26,6 +26,7 @@ namespace src.player.skills } private static bool roundEnded; + private static readonly ConcurrentDictionary owedKnife = []; public static void NewRound() { @@ -36,13 +37,27 @@ namespace src.player.skills public static void RoundEnd() { roundEnded = true; - KillAllKnives(); + KillAllKnives(rememberOwed: true); + + foreach (var player in PlayerManager.GetTickPlayers()) + { + if (player == null || !player.IsValid || !player.PawnIsAlive) continue; + if (PlayerManager.GetPlayerByIndex(player.Index)?.Skill != skillName) continue; + if (CheckHasKnife(player)) continue; + + owedKnife[player.Index] = 0; + } } - private static void KillAllKnives() + private static void KillAllKnives(bool rememberOwed = false) { foreach (var knifeInfo in knivesInfo.Values.ToArray()) + { + if (rememberOwed && knifeInfo.IsDropped) + owedKnife[knifeInfo.PlayerIndex] = 0; + DisableKnifeSkill(knifeInfo); + } knivesInfo.Clear(); } @@ -155,18 +170,69 @@ namespace src.player.skills public static void DisableSkill(CCSPlayerController player) { + if (player == null || !player.IsValid) return; + + bool wasDropped = owedKnife.ContainsKey(player.Index); + if (knivesInfo.TryRemove(player.Index, out KnifeInfo? knifeInfo) && knifeInfo != null) { - bool wasDropped = knifeInfo.IsDropped; + wasDropped |= knifeInfo.IsDropped; DisableKnifeSkill(knifeInfo); - - if (wasDropped && player != null && player.IsValid && player.PawnIsAlive) - player.GiveNamedItem("weapon_knife"); } + + if (!wasDropped) return; + + if (!player.PawnIsAlive) + { + owedKnife[player.Index] = 0; + ScheduleRepay(player.Index, 0); + return; + } + + if (!CheckHasKnife(player)) + player.GiveNamedItem("weapon_knife"); + + owedKnife.TryRemove(player.Index, out _); + } + + private const int RepayAttempts = 10; + + private static void ScheduleRepay(uint playerIndex, int attempt) + { + if (attempt >= RepayAttempts) + { + owedKnife.TryRemove(playerIndex, out _); + return; + } + + Instance.AddTimer(1f, () => + { + if (!owedKnife.ContainsKey(playerIndex)) return; + + var player = Utilities.GetPlayerFromIndex((int)playerIndex); + if (player == null || !player.IsValid) + { + owedKnife.TryRemove(playerIndex, out _); + return; + } + + if (!player.PawnIsAlive) + { + ScheduleRepay(playerIndex, attempt + 1); + return; + } + + if (!CheckHasKnife(player)) + player.GiveNamedItem("weapon_knife"); + + owedKnife.TryRemove(playerIndex, out _); + }, TimerFlags.STOP_ON_MAPCHANGE); } public static void PlayerDisconnect(uint playerIndex) { + owedKnife.TryRemove(playerIndex, out _); + if (knivesInfo.TryRemove(playerIndex, out KnifeInfo? knifeInfo) && knifeInfo != null) DisableKnifeSkill(knifeInfo); } diff --git a/jRandomSkills - SRC Files/src/player/skills/TrueArmor.cs b/jRandomSkills - SRC Files/src/player/skills/TrueArmor.cs index 39a1582..97c0556 100644 --- a/jRandomSkills - SRC Files/src/player/skills/TrueArmor.cs +++ b/jRandomSkills - SRC Files/src/player/skills/TrueArmor.cs @@ -12,7 +12,7 @@ namespace src.player.skills private const Skills skillName = Skills.TrueArmor; private static readonly ConcurrentDictionary holders = []; - private static readonly ConcurrentDictionary pending = []; + private static readonly ConcurrentDictionary pending = []; public static void LoadSkill() { @@ -62,13 +62,16 @@ namespace src.player.skills info.Damage = damage - absorbed; - pending[victim.Index] = (int)MathF.Round(armor - absorbed); + int restoredArmor = (int)MathF.Round(armor - absorbed); + bool hadHelmet = victimPawn.ItemServices?.As()?.HasHelmet ?? false; + + pending[victim.Index] = new PendingArmor(restoredArmor, hadHelmet); victimPawn.ArmorValue = 0; Utilities.SetStateChanged(victimPawn, "CCSPlayerPawn", "m_ArmorValue"); 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) @@ -76,12 +79,27 @@ namespace src.player.skills if (pending.IsEmpty) 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"); + + if (!restored.HasHelmet) return; + + var itemServices = victimPawn.ItemServices?.As(); + if (itemServices != null && !itemServices.HasHelmet) + itemServices.HasHelmet = true; + + var owner = victimPawn.Controller.Value?.As(); + if (owner != null && owner.IsValid && !owner.PawnHasHelmet) + { + owner.PawnHasHelmet = true; + Utilities.SetStateChanged(owner, "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) { victim = null; diff --git a/jRandomSkills - SRC Files/src/player/skills/WeaponsSwap.cs b/jRandomSkills - SRC Files/src/player/skills/WeaponsSwap.cs index 529f051..91bb427 100644 --- a/jRandomSkills - SRC Files/src/player/skills/WeaponsSwap.cs +++ b/jRandomSkills - SRC Files/src/player/skills/WeaponsSwap.cs @@ -114,7 +114,8 @@ namespace src.player.skills (WeaponInfo[]? playerWeapon, bool playerC4) = GetWeapons(player); (WeaponInfo[]? enemyWeapon, bool enemyC4) = GetWeapons(enemy); - if (playerWeapon == null || !playerWeapon.Any(w => weapons.Contains(w.Name))) + if (playerWeapon == null || !playerWeapon.Any(w => weapons.Contains(w.Name)) + || enemyWeapon == null || !enemyWeapon.Any(w => weapons.Contains(w.Name))) { skillInfo.FindedEnemy = true; skillInfo.HaveWeapon = false; @@ -268,7 +269,12 @@ namespace src.player.skills private static CCSPlayerController? GetRandomEnemy(CCSPlayerController player) { - CCSPlayerController[] enemies = [.. PlayerManager.GetTickPlayers().FindAll(e => e.Team != player.Team && e.PlayerPawn?.Value?.Health > 0)]; + CCSPlayerController[] enemies = [.. PlayerManager.GetTickPlayers().FindAll(e => + e != null && e.IsValid + && e.Team != player.Team && e.Team != CsTeam.Spectator && e.Team != CsTeam.None + && e.LifeState == (byte)LifeState_t.LIFE_ALIVE + && e.PlayerPawn?.Value != null && e.PlayerPawn.Value.IsValid + && e.PlayerPawn.Value.Health > 0)]; if (enemies.Length == 0) return null; return enemies[Instance.Random.Next(enemies.Length)]; } diff --git a/jRandomSkills - SRC Files/src/utils/Config.cs b/jRandomSkills - SRC Files/src/utils/Config.cs index 8766989..a39b120 100644 --- a/jRandomSkills - SRC Files/src/utils/Config.cs +++ b/jRandomSkills - SRC Files/src/utils/Config.cs @@ -24,9 +24,11 @@ namespace src.utils { var newConfig = new SettingsModel(); + Instance.Logger.LogInformation("config path: {Path} (exists={Exists})", configPath, 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); debugFlags = DebugCategories.Parse(newConfig.DebugMode); return config = newConfig; @@ -43,19 +45,44 @@ namespace src.utils if (HasMissingKeys(json) || IsLegacyDebugMode(json)) 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) newConfig.SkillDescriptionDuration = 9999; debugFlags = DebugCategories.Parse(newConfig.DebugMode); + ApplyRarityTables(newConfig); return config = newConfig; } } + private static void ApplyRarityTables(SettingsModel model) + { + RarityManager.SetRarityPercentages(ToRarityTable(model.SkillsChance)); + RarityManager.SetVipRarityPercentages(ToRarityTable(model.VIPSkillsChance)); + } + + private static Dictionary ToRarityTable(Dictionary? source) + { + var table = new Dictionary(); + if (source == null) return table; + + foreach (var kv in source) + if (Enum.TryParse(kv.Key, true, out var rarity)) + table[rarity] = kv.Value; + + return table; + } + private static bool HasMissingKeys(string json) { try @@ -111,12 +138,11 @@ namespace src.utils string tempPath = $"{configPath}.temp"; File.WriteAllText(tempPath, json); - File.Copy(tempPath, configPath, overwrite: true); - File.Delete(tempPath); + File.Move(tempPath, configPath, overwrite: true); } - 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 string DisableHUDOnDeathPermission { get; set; } public bool DisableSkillsOnRoundEnd { get; set; } + public string VIPFlag { get; set; } + [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)] + public Dictionary SkillsChance { get; set; } + [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)] + public Dictionary VIPSkillsChance { get; set; } public int? CurseSkillPerPlayer { get; set; } public bool ShowDecoyRing { get; set; } public WeaponPools Weapons { get; set; } @@ -179,6 +210,25 @@ namespace src.utils HideHudForOtherPlugins = true; DisableHUDOnDeathPermission = "@jRandomSkills/death"; DisableSkillsOnRoundEnd = false; + VIPFlag = "@css/vip"; + + SkillsChance = new Dictionary + { + ["Common"] = 0.70f, + ["Uncommon"] = 0.14f, + ["Rare"] = 0.10f, + ["Epic"] = 0.05f, + ["Legendary"] = 0.01f, + }; + + VIPSkillsChance = new Dictionary + { + ["Common"] = 0.55f, + ["Uncommon"] = 0.23f, + ["Rare"] = 0.14f, + ["Epic"] = 0.07f, + ["Legendary"] = 0.01f, + }; CurseSkillPerPlayer = null; ShowDecoyRing = true; diff --git a/jRandomSkills - SRC Files/src/utils/EntityManager.cs b/jRandomSkills - SRC Files/src/utils/EntityManager.cs index 83a2068..ec57874 100644 --- a/jRandomSkills - SRC Files/src/utils/EntityManager.cs +++ b/jRandomSkills - SRC Files/src/utils/EntityManager.cs @@ -264,7 +264,8 @@ namespace src.utils } // Dying entities stay out of transmit until the engine processes the kill (Event.CheckTransmit). - private static readonly ConcurrentDictionary recentlyDestroyed = new(); + private readonly record struct DyingEntity(DateTime Expires, uint HandleRaw); + private static readonly ConcurrentDictionary recentlyDestroyed = new(); public static List GetRecentlyDestroyedSnapshot() { @@ -274,8 +275,20 @@ namespace src.utils var result = new List(); foreach (var kvp in recentlyDestroyed) { - if (now > kvp.Value) recentlyDestroyed.TryRemove(kvp.Key, out _); - else result.Add(kvp.Key); + if (now > kvp.Value.Expires) + { + recentlyDestroyed.TryRemove(kvp.Key, out _); + continue; + } + + var entity = Utilities.GetEntityFromIndex((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; } @@ -297,7 +310,7 @@ namespace src.utils var entity = Utilities.GetEntityFromIndex((int)entityIndex); 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. entity.AcceptInput("ClearParent"); entity.AddEntityIOEvent("Kill", entity, delay: delay); diff --git a/jRandomSkills - SRC Files/src/utils/RarityManager.cs b/jRandomSkills - SRC Files/src/utils/RarityManager.cs index 002a8f2..8580906 100644 --- a/jRandomSkills - SRC Files/src/utils/RarityManager.cs +++ b/jRandomSkills - SRC Files/src/utils/RarityManager.cs @@ -23,6 +23,15 @@ namespace src.utils { Rarity.Legendary, 1f } }; + private static Dictionary vipRarityPercentages = new() + { + { Rarity.Common, 55f }, + { Rarity.Uncommon, 23f }, + { Rarity.Rare, 14f }, + { Rarity.Epic, 7f }, + { Rarity.Legendary, 1f } + }; + public static IReadOnlyDictionary RarityPercentages { get @@ -34,26 +43,39 @@ namespace src.utils public static void SetRarityPercentages(IDictionary percentages) { - if (percentages == null || percentages.Count == 0) return; + var table = Normalize(percentages); + if (table == null) return; lock (rarityLock) - { - double sum = percentages.Values.Sum(v => (double)v); - if (sum <= 0) - return; + rarityPercentages = table; + } - if (Math.Abs(sum - 100.0) > 0.0001) - { - var normalized = new Dictionary(); + public static void SetVipRarityPercentages(IDictionary percentages) + { + var table = Normalize(percentages); + if (table == null) return; - foreach (var kv in percentages) - normalized[kv.Key] = (float)((kv.Value / sum) * 100.0); + lock (rarityLock) + vipRarityPercentages = table; + } - rarityPercentages = normalized; - } - else - rarityPercentages = percentages.ToDictionary(k => k.Key, v => v.Value); - } + // Accepts either percentages (70, 14, ...) or fractions (0.7, 0.14, ...); both are + // rescaled so the table sums to 100. + private static Dictionary? Normalize(IDictionary percentages) + { + 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(); + foreach (var kv in percentages) + normalized[kv.Key] = (float)((kv.Value / sum) * 100.0); + + return normalized; } public static float GetRarityPercentage(Rarity rarity) @@ -62,16 +84,18 @@ namespace src.utils 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 accum = 0.0; lock (rarityLock) { + var table = vip ? vipRarityPercentages : rarityPercentages; + foreach (var r in Enum.GetValues(typeof(Rarity)).Cast()) { - float chance = rarityPercentages.TryGetValue(r, out var val) ? val : 0f; + float chance = table.TryGetValue(r, out var val) ? val : 0f; accum += chance; if (roll <= accum) return (roll, r); diff --git a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs index 9599df3..bff7c21 100644 --- a/jRandomSkills - SRC Files/src/utils/SkillUtils.cs +++ b/jRandomSkills - SRC Files/src/utils/SkillUtils.cs @@ -21,6 +21,32 @@ namespace src.utils { public static class SkillUtils { + private static readonly ConcurrentDictionary 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(string name, T fallback) where T : unmanaged + { + var cvar = Cvar(name); + if (cvar == null) return fallback; + + try { return cvar.GetPrimitiveValue(); } + catch { return fallback; } + } + + public static string CvarString(string name, string fallback) + { + var cvar = Cvar(name); + return cvar == null ? fallback : cvar.StringValue; + } + private static Lazy LazySig(string name, Func factory) where T : class => new(() => { @@ -82,9 +108,9 @@ namespace src.utils try { - if (ConVar.Find("mp_halftime")?.GetPrimitiveValue() != true) return false; + if (!CvarValue("mp_halftime", false)) return false; - int maxRounds = ConVar.Find("mp_maxrounds")?.GetPrimitiveValue() ?? 0; + int maxRounds = CvarValue("mp_maxrounds", 0); return maxRounds > 0 && maxRounds / 2 == gameRules.TotalRoundsPlayed; } catch @@ -122,10 +148,15 @@ namespace src.utils if (ammo == 1) return; + uint weaponIndex = weapon.Value.Index; + jRandomSkills.Instance.AddTimer(.1f, () => { - if (weapon == null || !weapon.IsValid || weapon.Value == null || !weapon.Value.IsValid) return; - weapon.Value.Clip1 = 1; + var tracked = Utilities.GetEntityFromIndex((int)weaponIndex); + if (tracked == null || !tracked.IsValid || tracked.DesignerName != itemString) return; + + tracked.Clip1 = 1; + Utilities.SetStateChanged(tracked, "CBasePlayerWeapon", "m_iClip1"); }, TimerFlags.STOP_ON_MAPCHANGE); } @@ -310,6 +341,62 @@ namespace src.utils 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()?.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(); + if (weapon == null || !weapon.IsValid) return null; + + return weapon.GetVData(); + } + 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); @@ -341,8 +428,8 @@ namespace src.utils if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return false; // non-player inflictor if (attackerPawn.TeamNum != victimPawn.TeamNum) return false; // enemy -> real damage - bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue() ?? false; - bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue() ?? false; + bool ff = CvarValue("mp_friendlyfire", false); + bool tae = CvarValue("mp_teammates_are_enemies", false); return !ff && !tae; // same team + FF off -> engine will zero this damage } @@ -351,8 +438,8 @@ namespace src.utils if (!IsSameTeamHit(info, victimPawn)) return false; if (!SkillsInfo.GetValue(skill, "friendlyFire")) return true; - bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue() ?? false; - bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue() ?? false; + bool ff = CvarValue("mp_friendlyfire", false); + bool tae = CvarValue("mp_teammates_are_enemies", false); return !ff && !tae; } @@ -715,6 +802,7 @@ namespace src.utils var pawn = controller.PlayerPawn.Value; if (pawn == null || !pawn.IsValid) continue; + if (pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE || pawn.Health <= 0) continue; hidden.Add(new HiddenPawn(controller.Index, controller.Team, pawn, bombOwnerIndex == controller.Index, ResolveCarriedIndexes(pawn))); } @@ -1199,17 +1287,17 @@ namespace src.utils private static void AwardRoundEndMoney(CsTeam winnerTeam, CCSPlayerController? bonusPlayer = null) { int winnerReward = winnerTeam == CsTeam.CounterTerrorist - ? ConVar.Find("cash_team_win_by_defusing_bomb")?.GetPrimitiveValue() ?? 3500 - : ConVar.Find("cash_team_terrorist_win_bomb")?.GetPrimitiveValue() ?? 3500; + ? CvarValue("cash_team_win_by_defusing_bomb", 3500) + : CvarValue("cash_team_terrorist_win_bomb", 3500); if (winnerReward <= 0) return; - int maxMoney = ConVar.Find("mp_maxmoney")?.GetPrimitiveValue() ?? 16000; + int maxMoney = CvarValue("mp_maxmoney", 16000); int personalBonus = bonusPlayer == null ? 0 : winnerTeam == CsTeam.CounterTerrorist - ? ConVar.Find("cash_player_defused_bomb")?.GetPrimitiveValue() ?? 300 - : ConVar.Find("cash_player_bomb_planted")?.GetPrimitiveValue() ?? 300; + ? CvarValue("cash_player_defused_bomb", 300) + : CvarValue("cash_player_bomb_planted", 300); foreach (var player in PlayerManager.GetTickPlayers()) { @@ -1232,10 +1320,10 @@ namespace src.utils { if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return; int totalRoundsPlayed = ctScore + tScore; - int maxRounds = ConVar.Find("mp_maxrounds")?.GetPrimitiveValue() ?? 24; + int maxRounds = CvarValue("mp_maxrounds", 24); int halfRounds = maxRounds / 2; - int overtimeMaxRounds = ConVar.Find("mp_overtime_maxrounds")?.GetPrimitiveValue() ?? 6; - int overtimeLimit = ConVar.Find("mp_overtime_limit")?.GetPrimitiveValue() ?? 1; + int overtimeMaxRounds = CvarValue("mp_overtime_maxrounds", 6); + int overtimeLimit = CvarValue("mp_overtime_limit", 1); var gameRulesProxy = jRandomSkills.Instance.GameRules; gameRulesProxy.TotalRoundsPlayed = totalRoundsPlayed; diff --git a/jRandomSkills - SRC Files/src/utils/SkillsInfo.cs b/jRandomSkills - SRC Files/src/utils/SkillsInfo.cs index 3045dd1..323ce37 100644 --- a/jRandomSkills - SRC Files/src/utils/SkillsInfo.cs +++ b/jRandomSkills - SRC Files/src/utils/SkillsInfo.cs @@ -29,9 +29,12 @@ namespace src.utils { 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)) { - 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); return config = newConfig; } @@ -45,6 +48,7 @@ namespace src.utils var root = JsonConvert.DeserializeObject(json); bool needsRewrite = root == null; + bool populateFailed = false; var present = new HashSet(StringComparer.Ordinal); if (root != null) @@ -61,21 +65,40 @@ namespace src.utils if (skillObj is JObject current && HasMissingKeys(current, instance)) 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))) 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); 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; @@ -104,12 +127,11 @@ namespace src.utils string tempPath = $"{configPath}.temp"; File.WriteAllText(tempPath, json); - File.Copy(tempPath, configPath, overwrite: true); - File.Delete(tempPath); + File.Move(tempPath, configPath, overwrite: true); } - 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); } } } @@ -191,7 +213,7 @@ namespace src.utils } } - public class DefaultSkillInfo(Skills skill, bool active = true, string color = "#ffffff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, bool disableOnPistolRound = false) + public class DefaultSkillInfo(Skills skill, bool active = true, string color = "#ffffff", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, bool disableOnPistolRound = false, int minPlayer = 0) { public bool NeedsTeammates { get; set; } = needsTeammates; public bool DisableOnFreezeTime { get; set; } = disableOnFreezeTime; @@ -204,6 +226,7 @@ namespace src.utils public float? DescriptionHudDuration { get; set; } = descriptionHudDuration; public string RequiredPermission { get; set; } = requiredPermission; public int MaxPerServer { get; set; } = maxPerServer; + public int MinPlayer { get; set; } = minPlayer; public string Rarity { get; set; } = rarity.ToString(); } diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll index d0aee34..04041cc 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/WASDMenuAPI.dll differ diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json index 9221eb3..edb4b46 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/config.json @@ -21,6 +21,21 @@ "TraceRayBeam": false, "DisableHUDOnDeathPermission": "@jRandomSkills/death", "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, "ShowDecoyRing": true, "Weapons": { diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json index 0e701e8..a41acff 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/configs/skillsInfo.json @@ -11,6 +11,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -25,6 +26,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -40,6 +42,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -55,6 +58,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -72,6 +76,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -86,6 +91,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Epic" }, { @@ -101,6 +107,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -116,6 +123,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Uncommon" }, { @@ -137,6 +145,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -158,6 +167,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -174,6 +184,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -191,6 +202,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -206,6 +218,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -220,6 +233,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -236,6 +250,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -253,6 +268,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -269,6 +285,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -285,6 +302,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -301,6 +319,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -317,6 +336,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -334,6 +354,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -348,6 +369,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -363,6 +385,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -377,6 +400,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -392,6 +416,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -409,6 +434,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -424,6 +450,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -438,6 +465,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -454,6 +482,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -469,6 +498,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -497,6 +527,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -513,6 +544,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -532,6 +564,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -549,6 +582,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Uncommon" }, { @@ -563,6 +597,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -578,6 +613,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -596,6 +632,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -612,6 +649,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -627,6 +665,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -644,6 +683,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -660,6 +700,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -674,6 +715,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -688,6 +730,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -704,6 +747,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -722,6 +766,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -739,6 +784,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -756,6 +802,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -770,6 +817,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -784,6 +832,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -802,6 +851,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -817,6 +867,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -836,6 +887,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -852,6 +904,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -869,6 +922,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -883,6 +937,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -898,6 +953,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -912,6 +968,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -926,6 +983,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -940,6 +998,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -957,6 +1016,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -974,6 +1034,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -993,6 +1054,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -1009,6 +1071,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -1023,6 +1086,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1040,6 +1104,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1058,6 +1123,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1073,6 +1139,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1088,6 +1155,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1105,6 +1173,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1121,6 +1190,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Uncommon" }, { @@ -1139,6 +1209,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1153,6 +1224,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1170,6 +1242,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -1186,6 +1259,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1203,6 +1277,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Epic" }, { @@ -1218,6 +1293,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1233,6 +1309,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1247,6 +1324,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1262,6 +1340,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1279,6 +1358,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1293,6 +1373,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1308,6 +1389,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1322,6 +1404,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -1336,6 +1419,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1350,6 +1434,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1370,6 +1455,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1384,6 +1470,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1401,6 +1488,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1418,6 +1506,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1436,6 +1525,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1453,6 +1543,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1469,6 +1560,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1483,6 +1575,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Uncommon" }, { @@ -1502,6 +1595,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1522,6 +1616,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Legendary" }, { @@ -1536,6 +1631,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1553,6 +1649,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1577,6 +1674,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -1593,6 +1691,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1607,6 +1706,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1621,6 +1721,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1636,6 +1737,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1652,6 +1754,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1666,6 +1769,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Epic" }, { @@ -1681,6 +1785,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1698,6 +1803,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1714,6 +1820,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1729,6 +1836,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1747,6 +1855,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 5, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1770,6 +1879,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Legendary" }, { @@ -1786,6 +1896,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1801,6 +1912,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Epic" }, { @@ -1816,6 +1928,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1831,6 +1944,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1851,6 +1965,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1871,6 +1986,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1886,6 +2002,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1902,6 +2019,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1918,6 +2036,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Uncommon" }, { @@ -1934,6 +2053,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1948,6 +2068,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1963,6 +2084,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1977,6 +2099,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -1993,6 +2116,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2010,6 +2134,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2029,6 +2154,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2043,6 +2169,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2057,6 +2184,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2076,6 +2204,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2094,6 +2223,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -2110,6 +2240,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2125,6 +2256,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2140,6 +2272,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2154,6 +2287,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2168,6 +2302,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 2, + "MinPlayer": 0, "Rarity": "Rare" }, { @@ -2184,6 +2319,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2200,6 +2336,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2214,6 +2351,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Uncommon" }, { @@ -2231,6 +2369,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2246,6 +2385,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2266,6 +2406,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2283,6 +2424,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2300,6 +2442,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2316,6 +2459,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2334,6 +2478,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2348,6 +2493,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2364,6 +2510,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2381,6 +2528,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2397,6 +2545,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2411,6 +2560,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": 1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2425,6 +2575,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2441,6 +2592,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2457,6 +2609,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2473,6 +2626,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" }, { @@ -2487,6 +2641,7 @@ "DescriptionHudDuration": null, "RequiredPermission": "", "MaxPerServer": -1, + "MinPlayer": 0, "Rarity": "Common" } ] diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll index 1753ecb..860a603 100644 Binary files a/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll and b/jRandomSkills - Server Files/plugins/jRandomSkills/jRandomSkills.dll differ diff --git a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json index 617a3dd..05d408f 100644 --- a/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json +++ b/jRandomSkills - Server Files/plugins/jRandomSkills/languages/tr.json @@ -82,7 +82,7 @@ "catapult": "Mancınık", "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_desc": "Öldürdüğün ilk oyuncunun yeteneği sana geçer", @@ -452,7 +452,7 @@ "push": "İtici", "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_desc": "Molotof canını yeniler", @@ -511,7 +511,7 @@ "shade": "Gölge", "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", "shortbomb": "Kısa Fünye",