v1.2.4.b1

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

View file

@ -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)

View file

@ -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;

View file

@ -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)

View file

@ -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);
}

View file

@ -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)
{

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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(() =>
{

View file

@ -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;

View file

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

View file

@ -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)

View file

@ -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);

View file

@ -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()

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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)

View file

@ -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)
{

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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)

View file

@ -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);
}

View file

@ -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;