v1.2.3.b9

- #### Fixes
    - **Pyro** - Fire damage killed the holder at low health. The regeneration now runs before the damage is applied instead of after.
    - **Iana** - The clone did not absorb a lethal hit, same cause as Pyro.
    - **Dracula** - Lifesteal used the raw damage, so an AWP headshot healed for 400. It is now capped to the health the victim actually had, works on the killing blow, and can no longer leave the attacker alive at 0 HP.
    - **RobinHood** - Stolen money used the raw damage and a hardcoded 16000 cap. It is now capped to the victim's real health, works on the killing blow, and respects `mp_maxmoney`. The victim loses exactly what the attacker gains.
    - **Voodoo** - Reflected damage used the raw damage, now capped to the victim's real health.
    - **Config** - The `Rifle`, `Pistol` and `Grenade` weapon pools grew every time the config was loaded, so removed weapons came back on the next start.
    - **Use button** - Skills no longer trigger when a CT presses `Use` while looking at a planted C4, so the skill key no longer competes with the defuse key.

- #### Other
    - Built against CounterStrikeSharp 1.0.373.
This commit is contained in:
ByDexter 2026-08-27 01:23:50 +03:00
parent d5fefbe178
commit a62ec611ca
17 changed files with 150 additions and 62 deletions

View file

@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.371" />
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.373" />
<PackageReference Include="MaxMind.Db" Version="5.1.0">
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />

View file

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

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Memory;
@ -89,6 +89,7 @@ namespace src.player
lock (setLock)
{
CountDamageResolution(info);
SkillUtils.TrackHealthBeforeHit(entity, info);
object[] args = [entity, info];
DispatchOnTakeDamage(entity, info, args);

View file

@ -798,6 +798,8 @@ namespace src.player
return;
}
if (designer == "planted_c4" && player.Team == CsTeam.CounterTerrorist) return;
if (designer.Contains("button") || designer.Contains("weapon") || designer.Contains("blocker")) return;
}
}

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Admin;
@ -182,6 +182,7 @@ namespace src.player
Instance.RemoveListener<CheckTransmit>(CheckTransmit);
Fortnite.skillInThisRound = false;
SkillUtils.ClearHealthBeforeHit();
EntityManager.SuppressKills = true;
try

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
@ -9,7 +9,6 @@ namespace src.player.skills
public class Dracula : ISkill
{
private const Skills skillName = Skills.Dracula;
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
@ -20,11 +19,11 @@ namespace src.player.skills
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
if (!Instance.IsPlayerValid(attacker) || victim == null || !victim.IsValid || attacker == victim) return;
var playerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (playerInfo?.Skill != skillName) return;
if (playerInfo?.Skill == skillName && victim!.PawnIsAlive)
HealAttacker(attacker!, @event.DmgHealth);
HealAttacker(attacker!, SkillUtils.CapToVictimHealth(victim, @event.DmgHealth));
}
public static void DisableSkill(CCSPlayerController player)
@ -46,22 +45,30 @@ namespace src.player.skills
private static void HealAttacker(CCSPlayerController attacker, float damage)
{
var attackerPawn = attacker.PlayerPawn?.Value;
if (attackerPawn == null || !attackerPawn.IsValid) return;
if (attackerPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE || attackerPawn.Health <= 0) return;
int extraHealth = (int)(damage * SkillsInfo.GetValue<float>(skillName, "healthRegainScale"));
if (extraHealth <= 0) return;
attackerPawn.Health += extraHealth;
Utilities.SetStateChanged(attackerPawn, "CBaseEntity", "m_iHealth");
uint attackerIndex = attacker.Index;
if (attackerPawn.MaxHealth < attackerPawn.Health)
Server.NextFrame(() =>
{
attackerPawn.MaxHealth = attackerPawn.Health;
Utilities.SetStateChanged(attackerPawn, "CBaseEntity", "m_iMaxHealth");
}
var target = Utilities.GetPlayerFromIndex((int)attackerIndex);
if (target == null || !target.IsValid) return;
var attackerPawn = target.PlayerPawn?.Value;
if (attackerPawn == null || !attackerPawn.IsValid) return;
if (attackerPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE || attackerPawn.Health <= 0) return;
attackerPawn.Health += extraHealth;
Utilities.SetStateChanged(attackerPawn, "CBaseEntity", "m_iHealth");
if (attackerPawn.MaxHealth < attackerPawn.Health)
{
attackerPawn.MaxHealth = attackerPawn.Health;
Utilities.SetStateChanged(attackerPawn, "CBaseEntity", "m_iMaxHealth");
}
});
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#FA050D", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = false, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float healthRegainScale = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)

View file

@ -302,13 +302,43 @@ namespace src.player.skills
return shooter != null && shooter.IsValid ? shooter : null;
}
private static void AbsorbWithClone(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
if (damageInfo.Damage <= 0) return;
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (!victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller?.Value;
if (victimController == null || !victimController.IsValid) return;
var victim = PlayerManager.GetPlayerEvent(victimController.As<CCSPlayerController>());
if (victim == null || !victim.IsValid) return;
if (PlayerManager.GetPlayerByIndex(victim.Index)?.Skill != skillName) return;
if (!playersInfo.TryGetValue(victim.Index, out var playerSkill)) return;
bool hasClone = playerSkill.CloneProp != null;
if (!hasClone && playerSkill.LastHit + 4 <= Server.TickCount) return;
damageInfo.Damage = 0;
if (!hasClone) return;
KillClone(playerSkill);
playerSkill.LastHit = Server.TickCount;
}
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
if (damagedEntity == null || damagedEntity.Entity == null || damageInfo == null)
return;
if (string.IsNullOrEmpty(damagedEntity.Entity.Name)) return;
if (!damagedEntity.Entity.Name.StartsWith("IanaClone_")) return;
if (string.IsNullOrEmpty(damagedEntity.Entity.Name) || !damagedEntity.Entity.Name.StartsWith("IanaClone_"))
{
AbsorbWithClone(damagedEntity, damageInfo);
return;
}
var nameParams = damagedEntity.Entity.Name.Split('_')[2];
_ = uint.TryParse(nameParams, out uint userIndex);
@ -357,26 +387,6 @@ namespace src.player.skills
}
}
public static void PlayerHurt(EventPlayerHurt @event)
{
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
if (victim == null || !victim.IsValid) return;
if (PlayerManager.GetPlayerByIndex(victim.Index)?.Skill != skillName) return;
if (playersInfo.TryGetValue(victim.Index, out var playerSkill))
{
if (playerSkill.CloneProp != null)
{
KillClone(playerSkill);
playerSkill.LastHit = Server.TickCount;
}
if (playerSkill.LastHit + 4 > Server.TickCount)
SkillUtils.RestoreHealth(victim);
}
}
private static void BlockWeapon(CCSPlayerController player, bool block)
{
if (player == null || !player.IsValid) return;

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
@ -17,19 +17,46 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
public static void PlayerHurt(EventPlayerHurt @event)
private const string infernoDamage = "inferno";
private static bool IsInfernoDamage(CTakeDamageInfo damageInfo)
{
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
int damage = @event.DmgHealth;
string weapon = @event.Weapon;
var ability = damageInfo.Ability?.Value;
if (ability != null && ability.IsValid && ability.DesignerName == infernoDamage) return true;
if (weapon != "inferno" || !Instance.IsPlayerValid(victim)) return;
var victimInfo = PlayerManager.GetPlayerByIndex(victim!.Index);
var inflictor = damageInfo.Inflictor?.Value;
return inflictor != null && inflictor.IsValid && inflictor.DesignerName == infernoDamage;
}
if (victimInfo == null || victimInfo.Skill != skillName) return;
public static void OnTakeDamage(CBaseEntity damagedEntity, CTakeDamageInfo damageInfo)
{
if (damagedEntity == null || !damagedEntity.IsValid || damageInfo == null) return;
if (damageInfo.Damage <= 0 || !IsInfernoDamage(damageInfo)) return;
var pawn = victim!.PlayerPawn.Value;
SkillUtils.AddHealth(pawn, (int)(damage * SkillsInfo.GetValue<float>(skillName, "regenerationMultiplier")));
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (!victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller?.Value;
if (victimController == null || !victimController.IsValid) return;
var victim = PlayerManager.GetPlayerEvent(victimController.As<CCSPlayerController>());
if (victim == null || !victim.IsValid) return;
if (PlayerManager.GetPlayerByIndex(victim.Index)?.Skill != skillName) return;
float damage = damageInfo.Damage;
float net = damage * (SkillsInfo.GetValue<float>(skillName, "regenerationMultiplier") - 1f);
if (net < 0)
{
damageInfo.Damage = -net;
return;
}
damageInfo.Damage = 0;
int heal = (int)MathF.Round(net);
if (heal > 0) SkillUtils.AddHealth(victimPawn, heal);
}
public static void GrenadeThrown(EventGrenadeThrown @event)

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using static src.jRandomSkills;
@ -26,7 +26,6 @@ namespace src.player.skills
{
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
int damage = @event.DmgHealth;
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);

View file

@ -1,5 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using static src.jRandomSkills;
@ -15,16 +16,18 @@ namespace src.player.skills
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
private static int GetMaxMoney() => ConVar.Find("mp_maxmoney")?.GetPrimitiveValue<int>() ?? 16000;
public static void PlayerHurt(EventPlayerHurt @event)
{
var victim = PlayerManager.GetPlayerEvent(@event.Userid);
var attacker = PlayerManager.GetPlayerEvent(@event.Attacker);
var damage = @event.DmgHealth;
if (!Instance.IsPlayerValid(attacker) || !Instance.IsPlayerValid(victim) || attacker == victim) return;
if (!Instance.IsPlayerValid(attacker) || victim == null || !victim.IsValid || attacker == victim) return;
var attackerInfo = PlayerManager.GetPlayerByIndex(attacker!.Index);
if (attackerInfo?.Skill != skillName) return;
int damage = SkillUtils.CapToVictimHealth(victim, @event.DmgHealth);
int moneyToSteal = damage * SkillsInfo.GetValue<int>(skillName, "moneyMultiplier");
StealMoney(victim!, attacker!, moneyToSteal);
}
@ -35,11 +38,17 @@ namespace src.player.skills
var attackerMoneyServices = attacker?.InGameMoneyServices;
if (victimMoneyServices == null || attackerMoneyServices == null) return;
var moneyToAdd = victimMoneyServices.Account < money ? victimMoneyServices.Account : money;
victimMoneyServices.Account = Math.Max(victimMoneyServices.Account - money, 0);
int maxMoney = GetMaxMoney();
int headroom = Math.Max(maxMoney - attackerMoneyServices.Account, 0);
int transfer = Math.Min(money, victimMoneyServices.Account);
transfer = Math.Min(transfer, headroom);
if (transfer <= 0) return;
victimMoneyServices.Account -= transfer;
Utilities.SetStateChanged(victim!, "CCSPlayerController", "m_pInGameMoneyServices");
attackerMoneyServices.Account = Math.Min(attackerMoneyServices.Account + moneyToAdd, 16000);
attackerMoneyServices.Account += transfer;
Utilities.SetStateChanged(attacker!, "CCSPlayerController", "m_pInGameMoneyServices");
}

View file

@ -1,4 +1,4 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils;
@ -138,7 +138,7 @@ namespace src.player.skills
if (victimPawn == null || !victimPawn.IsValid || victimPawn.Health <= 0) return;
if (victimPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
int reflected = (int)MathF.Round(@event.DmgHealth * SkillsInfo.GetValue<float>(skillName, "reflectPercent"));
int reflected = (int)MathF.Round(SkillUtils.CapToVictimHealth(owner, @event.DmgHealth) * SkillsInfo.GetValue<float>(skillName, "reflectPercent"));
if (reflected < 1) return;
SkillUtils.TakeHealth(victimPawn, reflected, owner, KillfeedIcons.Fist);

View file

@ -290,8 +290,11 @@ namespace src.utils
public class WeaponPools
{
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)]
public required List<string> Rifle { get; set; }
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)]
public required List<string> Pistol { get; set; }
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)]
public required List<string> Grenade { get; set; }
}

View file

@ -7,6 +7,7 @@ using RayTraceAPI;
using System.Drawing;
using System.Numerics;
using TraceOptions = RayTraceAPI.TraceOptions;
using TraceResult = RayTraceAPI.TraceResult;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace src.utils

View file

@ -619,6 +619,34 @@ namespace src.utils
.Cast<CCSPlayerController>()];
}
private static readonly ConcurrentDictionary<uint, int> healthBeforeHit = [];
public static void TrackHealthBeforeHit(CBaseEntity? damagedEntity, CTakeDamageInfo? damageInfo)
{
if (damagedEntity == null || damageInfo == null || damageInfo.Damage <= 0) return;
CCSPlayerPawn victimPawn = new(damagedEntity.Handle);
if (!victimPawn.IsValid || victimPawn.DesignerName != "player") return;
var victimController = victimPawn.Controller?.Value;
if (victimController == null || !victimController.IsValid) return;
var victim = PlayerManager.GetPlayerEvent(victimController.As<CCSPlayerController>());
if (victim == null || !victim.IsValid) return;
healthBeforeHit[victim.Index] = victimPawn.Health;
}
public static int CapToVictimHealth(CCSPlayerController? victim, int dmgHealth)
{
if (victim == null || !victim.IsValid) return dmgHealth;
if (!healthBeforeHit.TryGetValue(victim.Index, out int healthBefore) || healthBefore <= 0) return dmgHealth;
return dmgHealth > healthBefore ? healthBefore : dmgHealth;
}
public static void ClearHealthBeforeHit() => healthBeforeHit.Clear();
public static bool TakeHealth(CCSPlayerPawn? pawn, int damage, CCSPlayerController? damageAttacker = null, KillfeedIcons? killfeedIcon = null)
{
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE)