v1.2.4.b1
This commit is contained in:
parent
7ad753e3b5
commit
dcf4020981
39 changed files with 434 additions and 204 deletions
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<jSkill_SkillInfo> candidates, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode)
|
||||
private static bool IsVip(CCSPlayerController? player)
|
||||
{
|
||||
if (player == null || !player.IsValid || player.IsBot) return false;
|
||||
|
||||
string flag = Config.LoadedConfig.VIPFlag;
|
||||
if (string.IsNullOrWhiteSpace(flag)) return false;
|
||||
|
||||
return AdminManager.PlayerHasPermissions(player, flag);
|
||||
}
|
||||
|
||||
private static jSkill_SkillInfo ChooseSkillByRarityAndMax(List<jSkill_SkillInfo> candidates, Dictionary<Skills, int> assignmentCounts, Config.GameModes gameMode, bool vip)
|
||||
{
|
||||
if (candidates == null || candidates.Count == 0) return noneSkill;
|
||||
|
||||
|
|
@ -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>(CheckTransmit);
|
||||
int freezetime = ConVar.Find("mp_freezetime")?.GetPrimitiveValue<Int32>() ?? 0;
|
||||
int freezetime = SkillUtils.CvarValue("mp_freezetime", 0);
|
||||
freezeTimeEnd = DateTime.Now.AddSeconds(freezetime + (Instance?.GameRules?.TeamIntroPeriod == true ? 7 : 0));
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +357,7 @@ namespace src.player
|
|||
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)
|
||||
{
|
||||
|
|
@ -686,7 +695,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;
|
||||
|
|
|
|||
|
|
@ -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<int>() ?? 2;
|
||||
int flashbangLimit = SkillUtils.CvarValue("ammo_grenade_limit_flashbang", 2);
|
||||
int grenadeLimit = SkillsInfo.GetValue<int>(skillName, "grenadeLimit");
|
||||
|
||||
if (grenadeLimit > flashbangLimit)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<int>() ?? 16000;
|
||||
private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000);
|
||||
|
||||
private static bool GiveMoney(CCSPlayerController player, int amount)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ namespace src.player.skills
|
|||
float currentTime = Server.CurrentTime;
|
||||
float extraTime = SkillsInfo.GetValue<float>(skillName, "bombArmedTime");
|
||||
|
||||
foreach (var player in PlayerManager.GetTickPlayers().Where(p => p.Team == CsTeam.Terrorist))
|
||||
foreach (var player in PlayerManager.GetTickPlayers().Where(p => p != null && p.IsValid && p.Team == CsTeam.Terrorist))
|
||||
{
|
||||
if (player == null || !player.IsValid) continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<int, (byte Team, uint Owner)> nades = [];
|
||||
private static readonly ConcurrentDictionary<uint, int> lastTickByPlayer = [];
|
||||
private static readonly ConcurrentDictionary<int, ConcurrentQueue<(byte Team, uint Owner)>> 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<float>(skillName, "damage");
|
||||
heProjectile.DmgRadius = SkillsInfo.GetValue<float>(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);
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ namespace src.player.skills
|
|||
{
|
||||
private const Skills skillName = Skills.FireRain;
|
||||
private static readonly ConcurrentDictionary<uint, byte> decoys = [];
|
||||
private static int rainTick = -1;
|
||||
private static CCSPlayerPawn? rainThrower;
|
||||
private static byte rainTeam = (byte)CsTeam.None;
|
||||
private static readonly ConcurrentDictionary<int, ConcurrentQueue<RainBatch>> rainBatches = [];
|
||||
private static readonly ConcurrentDictionary<uint, (uint ThrowerRaw, byte Team)> 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<RainBatch>())
|
||||
.Enqueue(new RainBatch { Thrower = thrower, Team = team, Remaining = count });
|
||||
}
|
||||
|
||||
private static void ConsumeOne(ConcurrentQueue<RainBatch> queue, RainBatch batch)
|
||||
{
|
||||
if (--batch.Remaining > 0) return;
|
||||
queue.TryDequeue(out _);
|
||||
}
|
||||
|
||||
public static void OnEntitySpawned(CEntityInstance entity)
|
||||
{
|
||||
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<CMolotovProjectile>();
|
||||
if (molotov == null || !molotov.IsValid) return;
|
||||
if (molotov == null || !molotov.IsValid) { ConsumeOne(queue, batch); return; }
|
||||
|
||||
molotov.TeamNum = rainTeam;
|
||||
ConsumeOne(queue, batch);
|
||||
|
||||
molotov.TeamNum = batch.Team;
|
||||
molotov.Thrower.Raw = thrower.EntityHandle.Raw;
|
||||
molotov.OwnerEntity.Raw = thrower.EntityHandle.Raw;
|
||||
rainMolotovs[molotov.Index] = (thrower.EntityHandle.Raw, rainTeam);
|
||||
rainMolotovs[molotov.Index] = (thrower.EntityHandle.Raw, batch.Team);
|
||||
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<bool>(skillName, "manageAutoKick")) return false;
|
||||
|
||||
bool live;
|
||||
try { live = ConVar.Find("mp_autokick")?.GetPrimitiveValue<bool>() ?? false; }
|
||||
try { live = SkillUtils.CvarValue("mp_autokick", false); }
|
||||
catch { return false; }
|
||||
|
||||
if (!live) return false;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<int>() ?? 2;
|
||||
int flashbangLimit = SkillUtils.CvarValue("ammo_grenade_limit_flashbang", 2);
|
||||
int grenadeLimit = SkillsInfo.GetValue<int>(skillName, "grenadeLimit");
|
||||
|
||||
if (grenadeLimit > flashbangLimit)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<string>(skillName, "color"));
|
||||
defaultNoSpread = ConVar.Find("weapon_accuracy_nospread")?.GetPrimitiveValue<bool>() ?? false;
|
||||
defaultNoSpread = SkillUtils.CvarValue("weapon_accuracy_nospread", false);
|
||||
}
|
||||
|
||||
public static void NewRound()
|
||||
|
|
|
|||
|
|
@ -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<uint, int> 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<CCSWeaponBase>();
|
||||
if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier;
|
||||
|
||||
var vdata = weapon.GetVData<CCSWeaponBaseVData>();
|
||||
if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier;
|
||||
|
||||
return vdata.HeadshotMultiplier;
|
||||
}
|
||||
|
||||
public static void EnableSkill(CCSPlayerController player)
|
||||
{
|
||||
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
||||
|
|
|
|||
|
|
@ -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<string>(skillName, "color"));
|
||||
defaultC4Timer = ConVar.Find("mp_c4timer")?.GetPrimitiveValue<int>() ?? 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<uint, int> 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<CCSWeaponBase>();
|
||||
if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier;
|
||||
|
||||
var vdata = weapon.GetVData<CCSWeaponBaseVData>();
|
||||
if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier;
|
||||
|
||||
return vdata.HeadshotMultiplier;
|
||||
}
|
||||
|
||||
private static void DropAllBotWeapons(CCSPlayerController player)
|
||||
{
|
||||
if (player == null || !player.IsValid || !player.IsBot) return;
|
||||
|
|
|
|||
|
|
@ -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<uint, int> accountBeforeBonus = [];
|
||||
|
||||
public static void LoadSkill()
|
||||
{
|
||||
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000;
|
||||
private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000);
|
||||
|
||||
public static void EnableSkill(CCSPlayerController player)
|
||||
{
|
||||
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
||||
if (playerInfo == null) return;
|
||||
int moneyBonus = Instance.Random.Next(SkillsInfo.GetValue<int>(skillName, "minMoney"), SkillsInfo.GetValue<int>(skillName, "maxMoney"));
|
||||
if (player == null || !player.IsValid) return;
|
||||
|
||||
var moneyServices = player.InGameMoneyServices;
|
||||
if (moneyServices == null) return;
|
||||
|
||||
int moneyBonus = Instance.Random.Next(SkillsInfo.GetValue<int>(skillName, "minMoney"), SkillsInfo.GetValue<int>(skillName, "maxMoney"));
|
||||
moneyBonus = Math.Min(moneyBonus, GetMaxMoney() - moneyServices.Account);
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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<string>(skillName, "color"));
|
||||
}
|
||||
|
||||
private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000;
|
||||
private static int GetMaxMoney() => SkillUtils.CvarValue("mp_maxmoney", 16000);
|
||||
|
||||
public static void PlayerHurt(EventPlayerHurt @event)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<nint, int> 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<CCSWeaponBase>();
|
||||
if (weapon == null || !weapon.IsValid) return DefaultHeadshotMultiplier;
|
||||
|
||||
var vdata = weapon.GetVData<CCSWeaponBaseVData>();
|
||||
if (vdata == null || vdata.HeadshotMultiplier <= 0) return DefaultHeadshotMultiplier;
|
||||
|
||||
return vdata.HeadshotMultiplier;
|
||||
}
|
||||
|
||||
public static bool TryConsumeRevive(CCSPlayerController? victim, CCSPlayerPawn? victimPawn)
|
||||
{
|
||||
if (victim == null || !victim.IsValid) return false;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<string>(skillName, "color"));
|
||||
defaultC4Timer = ConVar.Find("mp_c4timer")?.GetPrimitiveValue<int>() ?? 40;
|
||||
defaultC4Timer = SkillUtils.CvarValue("mp_c4timer", 40);
|
||||
}
|
||||
|
||||
public static void EnableSkill(CCSPlayerController player)
|
||||
|
|
|
|||
|
|
@ -26,23 +26,30 @@ namespace src.player.skills
|
|||
}
|
||||
|
||||
private static bool roundEnded;
|
||||
private static readonly ConcurrentDictionary<uint, byte> owedKnife = [];
|
||||
|
||||
public static void NewRound()
|
||||
{
|
||||
roundEnded = false;
|
||||
KillAllKnives();
|
||||
owedKnife.Clear();
|
||||
}
|
||||
|
||||
public static void RoundEnd()
|
||||
{
|
||||
roundEnded = true;
|
||||
KillAllKnives();
|
||||
KillAllKnives(rememberOwed: true);
|
||||
}
|
||||
|
||||
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 +162,24 @@ namespace src.player.skills
|
|||
|
||||
public static void DisableSkill(CCSPlayerController player)
|
||||
{
|
||||
if (player == null || !player.IsValid) return;
|
||||
|
||||
bool wasDropped = owedKnife.TryRemove(player.Index, out _);
|
||||
|
||||
if (knivesInfo.TryRemove(player.Index, out KnifeInfo? knifeInfo) && knifeInfo != null)
|
||||
{
|
||||
bool wasDropped = knifeInfo.IsDropped;
|
||||
wasDropped |= knifeInfo.IsDropped;
|
||||
DisableKnifeSkill(knifeInfo);
|
||||
|
||||
if (wasDropped && player != null && player.IsValid && player.PawnIsAlive)
|
||||
player.GiveNamedItem("weapon_knife");
|
||||
}
|
||||
|
||||
if (wasDropped && player.PawnIsAlive)
|
||||
player.GiveNamedItem("weapon_knife");
|
||||
}
|
||||
|
||||
public static void PlayerDisconnect(uint playerIndex)
|
||||
{
|
||||
owedKnife.TryRemove(playerIndex, out _);
|
||||
|
||||
if (knivesInfo.TryRemove(playerIndex, out KnifeInfo? knifeInfo) && knifeInfo != null)
|
||||
DisableKnifeSkill(knifeInfo);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace src.player.skills
|
|||
private const Skills skillName = Skills.TrueArmor;
|
||||
|
||||
private static readonly ConcurrentDictionary<uint, byte> holders = [];
|
||||
private static readonly ConcurrentDictionary<uint, int> pending = [];
|
||||
private static readonly ConcurrentDictionary<uint, PendingArmor> pending = [];
|
||||
|
||||
public static void LoadSkill()
|
||||
{
|
||||
|
|
@ -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<CCSPlayer_ItemServices>()?.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,22 @@ 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<CCSPlayer_ItemServices>();
|
||||
if (itemServices == null || itemServices.HasHelmet) return;
|
||||
|
||||
itemServices.HasHelmet = true;
|
||||
Utilities.SetStateChanged(victim, "CCSPlayerController", "m_bPawnHasHelmet");
|
||||
}
|
||||
|
||||
private readonly record struct PendingArmor(int Armor, bool HasHelmet);
|
||||
|
||||
private static bool TryResolveVictim(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo, out CCSPlayerController? victim, out CCSPlayerPawn? pawn, out CTakeDamageInfo? info)
|
||||
{
|
||||
victim = null;
|
||||
|
|
|
|||
|
|
@ -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<Rarity, float> ToRarityTable(Dictionary<string, float>? source)
|
||||
{
|
||||
var table = new Dictionary<Rarity, float>();
|
||||
if (source == null) return table;
|
||||
|
||||
foreach (var kv in source)
|
||||
if (Enum.TryParse<Rarity>(kv.Key, true, out var rarity))
|
||||
table[rarity] = kv.Value;
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private static bool HasMissingKeys(string json)
|
||||
{
|
||||
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<string, float> SkillsChance { get; set; }
|
||||
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)]
|
||||
public Dictionary<string, float> 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<string, float>
|
||||
{
|
||||
["Common"] = 0.70f,
|
||||
["Uncommon"] = 0.14f,
|
||||
["Rare"] = 0.10f,
|
||||
["Epic"] = 0.05f,
|
||||
["Legendary"] = 0.01f,
|
||||
};
|
||||
|
||||
VIPSkillsChance = new Dictionary<string, float>
|
||||
{
|
||||
["Common"] = 0.55f,
|
||||
["Uncommon"] = 0.23f,
|
||||
["Rare"] = 0.14f,
|
||||
["Epic"] = 0.07f,
|
||||
["Legendary"] = 0.01f,
|
||||
};
|
||||
CurseSkillPerPlayer = null;
|
||||
ShowDecoyRing = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<uint, DateTime> recentlyDestroyed = new();
|
||||
private readonly record struct DyingEntity(DateTime Expires, uint HandleRaw);
|
||||
private static readonly ConcurrentDictionary<uint, DyingEntity> recentlyDestroyed = new();
|
||||
|
||||
public static List<uint> GetRecentlyDestroyedSnapshot()
|
||||
{
|
||||
|
|
@ -274,8 +275,20 @@ namespace src.utils
|
|||
var result = new List<uint>();
|
||||
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<CBaseEntity>((int)kvp.Key);
|
||||
if (entity == null || !entity.IsValid || entity.EntityHandle.Raw != kvp.Value.HandleRaw)
|
||||
{
|
||||
recentlyDestroyed.TryRemove(kvp.Key, out _);
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(kvp.Key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -297,7 +310,7 @@ namespace src.utils
|
|||
var entity = Utilities.GetEntityFromIndex<CBaseEntity>((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);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ namespace src.utils
|
|||
{ Rarity.Legendary, 1f }
|
||||
};
|
||||
|
||||
private static Dictionary<Rarity, float> vipRarityPercentages = new()
|
||||
{
|
||||
{ Rarity.Common, 55f },
|
||||
{ Rarity.Uncommon, 23f },
|
||||
{ Rarity.Rare, 14f },
|
||||
{ Rarity.Epic, 7f },
|
||||
{ Rarity.Legendary, 1f }
|
||||
};
|
||||
|
||||
public static IReadOnlyDictionary<Rarity, float> RarityPercentages
|
||||
{
|
||||
get
|
||||
|
|
@ -34,26 +43,39 @@ namespace src.utils
|
|||
|
||||
public static void SetRarityPercentages(IDictionary<Rarity, float> percentages)
|
||||
{
|
||||
if (percentages == null || percentages.Count == 0) return;
|
||||
var table = Normalize(percentages);
|
||||
if (table == null) return;
|
||||
|
||||
lock (rarityLock)
|
||||
{
|
||||
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<Rarity, float>();
|
||||
public static void SetVipRarityPercentages(IDictionary<Rarity, float> 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<Rarity, float>? Normalize(IDictionary<Rarity, float> 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<Rarity, float>();
|
||||
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<Rarity>())
|
||||
{
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,32 @@ namespace src.utils
|
|||
{
|
||||
public static class SkillUtils
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, ConVar> cvarCache = [];
|
||||
|
||||
public static ConVar? Cvar(string name)
|
||||
{
|
||||
if (cvarCache.TryGetValue(name, out var cached)) return cached;
|
||||
|
||||
var cvar = ConVar.Find(name);
|
||||
if (cvar != null) cvarCache[name] = cvar;
|
||||
return cvar;
|
||||
}
|
||||
|
||||
public static T CvarValue<T>(string name, T fallback) where T : unmanaged
|
||||
{
|
||||
var cvar = Cvar(name);
|
||||
if (cvar == null) return fallback;
|
||||
|
||||
try { return cvar.GetPrimitiveValue<T>(); }
|
||||
catch { return fallback; }
|
||||
}
|
||||
|
||||
public static string CvarString(string name, string fallback)
|
||||
{
|
||||
var cvar = Cvar(name);
|
||||
return cvar == null ? fallback : cvar.StringValue;
|
||||
}
|
||||
|
||||
private static Lazy<T?> LazySig<T>(string name, Func<string, T> factory) where T : class =>
|
||||
new(() =>
|
||||
{
|
||||
|
|
@ -82,9 +108,9 @@ namespace src.utils
|
|||
|
||||
try
|
||||
{
|
||||
if (ConVar.Find("mp_halftime")?.GetPrimitiveValue<bool>() != true) return false;
|
||||
if (!CvarValue("mp_halftime", false)) return false;
|
||||
|
||||
int maxRounds = ConVar.Find("mp_maxrounds")?.GetPrimitiveValue<int>() ?? 0;
|
||||
int maxRounds = CvarValue("mp_maxrounds", 0);
|
||||
return maxRounds > 0 && maxRounds / 2 == gameRules.TotalRoundsPlayed;
|
||||
}
|
||||
catch
|
||||
|
|
@ -310,6 +336,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<CCSPlayer_ItemServices>()?.HasHelmet ?? false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
private static CCSWeaponBaseVData? GetWeaponVData(CTakeDamageInfo info)
|
||||
{
|
||||
var ability = info.Ability?.Value;
|
||||
if (ability == null || !ability.IsValid) return null;
|
||||
|
||||
var weapon = ability.As<CCSWeaponBase>();
|
||||
if (weapon == null || !weapon.IsValid) return null;
|
||||
|
||||
return weapon.GetVData<CCSWeaponBaseVData>();
|
||||
}
|
||||
|
||||
public static void CreateHEGrenadeProjectile(Vector pos, QAngle angle, Vector vel, int teamNum)
|
||||
{
|
||||
HEGrenadeProjectile_CreateFunc.Value?.Invoke(pos.Handle, angle.Handle, vel.Handle, vel.Handle, IntPtr.Zero, 44, teamNum);
|
||||
|
|
@ -341,8 +423,8 @@ namespace src.utils
|
|||
if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return false; // non-player inflictor
|
||||
if (attackerPawn.TeamNum != victimPawn.TeamNum) return false; // enemy -> real damage
|
||||
|
||||
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
|
||||
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? 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 +433,8 @@ namespace src.utils
|
|||
if (!IsSameTeamHit(info, victimPawn)) return false;
|
||||
if (!SkillsInfo.GetValue<bool>(skill, "friendlyFire")) return true;
|
||||
|
||||
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
|
||||
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
|
||||
bool ff = CvarValue("mp_friendlyfire", false);
|
||||
bool tae = CvarValue("mp_teammates_are_enemies", false);
|
||||
return !ff && !tae;
|
||||
}
|
||||
|
||||
|
|
@ -1199,17 +1281,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<int>() ?? 3500
|
||||
: ConVar.Find("cash_team_terrorist_win_bomb")?.GetPrimitiveValue<int>() ?? 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<int>() ?? 16000;
|
||||
int maxMoney = CvarValue("mp_maxmoney", 16000);
|
||||
int personalBonus = bonusPlayer == null
|
||||
? 0
|
||||
: winnerTeam == CsTeam.CounterTerrorist
|
||||
? ConVar.Find("cash_player_defused_bomb")?.GetPrimitiveValue<int>() ?? 300
|
||||
: ConVar.Find("cash_player_bomb_planted")?.GetPrimitiveValue<int>() ?? 300;
|
||||
? CvarValue("cash_player_defused_bomb", 300)
|
||||
: CvarValue("cash_player_bomb_planted", 300);
|
||||
|
||||
foreach (var player in PlayerManager.GetTickPlayers())
|
||||
{
|
||||
|
|
@ -1232,10 +1314,10 @@ namespace src.utils
|
|||
{
|
||||
if (jRandomSkills.Instance == null || jRandomSkills.Instance.GameRules == null) return;
|
||||
int totalRoundsPlayed = ctScore + tScore;
|
||||
int maxRounds = ConVar.Find("mp_maxrounds")?.GetPrimitiveValue<int>() ?? 24;
|
||||
int maxRounds = CvarValue("mp_maxrounds", 24);
|
||||
int halfRounds = maxRounds / 2;
|
||||
int overtimeMaxRounds = ConVar.Find("mp_overtime_maxrounds")?.GetPrimitiveValue<int>() ?? 6;
|
||||
int overtimeLimit = ConVar.Find("mp_overtime_limit")?.GetPrimitiveValue<int>() ?? 1;
|
||||
int overtimeMaxRounds = CvarValue("mp_overtime_maxrounds", 6);
|
||||
int overtimeLimit = CvarValue("mp_overtime_limit", 1);
|
||||
|
||||
var gameRulesProxy = jRandomSkills.Instance.GameRules;
|
||||
gameRulesProxy.TotalRoundsPlayed = totalRoundsPlayed;
|
||||
|
|
|
|||
|
|
@ -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<JArray>(json);
|
||||
bool needsRewrite = root == null;
|
||||
bool populateFailed = false;
|
||||
var present = new HashSet<string>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue