v1.2.3.b4

- Curses are now tracked at all times and cleared when a player leaves, so a curse can no longer stick to whoever joins into that player slot afterwards
- Wallhack and Nightmare no longer shows up for the whole server during the round start skill reveal
- One skill throwing during round cleanup no longer cancels the rest of the cleanup or leaves entity kills suppressed for the rest of the map
- Fixed the TakeAmmo crash on map change while restoring weapon reserve ammo
- Friendly fire reduction now applies to BlastShot, DeathBomb, ExplodingBarrel, ExplosiveShot and ToxicSmoke
- FireRain molotovs now land around the decoy instead of on nearby rooftops, and the fire keeps its owner and team so friendly fire rules apply to it
- Teammates can no longer shoot the chickens spawned by HealingChicken
- Fortnite wall now spawns with its model and health set up front, so it takes the configured amount of damage instead of breaking on the first bullet
- JetKick, CarefulBullets and WildThrow now clean up after their target disconnects
This commit is contained in:
ByDexter 2026-08-03 01:25:04 +03:00
parent 20f1ba212a
commit db7108517a
43 changed files with 449 additions and 208 deletions

View file

@ -206,7 +206,7 @@ namespace src.player
Server.PrintToConsole($"[jRandomSkills] CheckTransmit dying-filter failed: {ex.Message}");
}
DispatchToActiveSkills("CheckTransmit", infoList);
DispatchCheckTransmit([infoList]);
}
PerfLog.Sample("CheckTransmit", perfStart);
}

View file

@ -136,6 +136,39 @@ namespace src.player
}
}
private static void ReleaseCursesTargeting(uint victimIndex)
{
foreach (uint curserIndex in SkillUtils.GetCursersOf(victimIndex))
{
if (curserIndex == victimIndex) continue;
var curser = Utilities.GetPlayerFromIndex((int)curserIndex);
if (curser == null || !curser.IsValid) continue;
if (PlayerManager.GetPlayerByIndex(curserIndex)?.Skill is not Skills curserSkill || curserSkill == Skills.None)
continue;
try
{
Instance.SkillAction(curserSkill.ToString(), "DisableSkill", [curser]);
}
catch (Exception ex)
{
Server.PrintToConsole($"[jRandomSkills] {curserSkill}.DisableSkill failed while releasing curse on leaving player: {(ex.InnerException ?? ex).Message}");
}
}
}
private static void DispatchCheckTransmit(object[] args)
{
var seen = new HashSet<Skills>();
foreach (var p in Instance.SkillPlayer)
{
if (!seen.Add(p.Skill)) continue;
InvokeSkill(p.Skill, "CheckTransmit", args);
}
}
private static void DispatchOnTakeDamage(DynamicHook h, bool post = false)
{
object[] args = [h];
@ -517,6 +550,9 @@ namespace src.player
Instance.SkillAction(skillPlayer.Skill.ToString(), "DisableSkill", [player]);
uint leavingIndex = player.Index;
ReleaseCursesTargeting(leavingIndex);
foreach (var skill in SkillData.Skills)
Instance.SkillAction(skill.Skill.ToString(), "PlayerDisconnect", [leavingIndex]);

View file

@ -152,12 +152,24 @@ namespace src.player
// nobody drew now would otherwise never clear state left over from an earlier round.
// Skills that never ran cannot hold state, so they stay out of the sweep.
foreach (var skillName in SkillsUsedThisMap.Keys)
Instance.SkillAction(skillName, "NewRound");
SafeNewRound(skillName);
ActiveSkillsThisRound.Clear();
tickFailuresLogged.Clear();
}
}
private static void SafeNewRound(string skillName)
{
try
{
Instance.SkillAction(skillName, "NewRound");
}
catch (Exception ex)
{
Server.PrintToConsole($"[jRandomSkills] {skillName}.NewRound failed, cleanup continues: {(ex.InnerException ?? ex).Message}");
}
}
public static void OnMapChange()
{
lock (setLock)
@ -168,10 +180,16 @@ namespace src.player
Fortnite.skillInThisRound = false;
EntityManager.SuppressKills = true;
EntityManager.DestroyAllTracked();
foreach (var skill in SkillData.Skills)
Instance.SkillAction(skill.Skill.ToString(), "NewRound");
EntityManager.SuppressKills = false;
try
{
EntityManager.DestroyAllTracked();
foreach (var skill in SkillData.Skills)
SafeNewRound(skill.Skill.ToString());
}
finally
{
EntityManager.SuppressKills = false;
}
ActiveSkillsThisRound.Clear();
SkillsUsedThisMap.Clear();

View file

@ -135,7 +135,7 @@ namespace src.player.skills
heProjectile.DmgRadius = SkillsInfo.GetValue<float>(skillName, "explosionRadius");
if (nades.TryRemove(lastTick, out var source))
heProjectile.Globalname = $"deathbomb_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
heProjectile.Globalname = $"blastshot_team_{source.Team}_{source.Owner}_{heProjectile.Index}";
});
}
@ -150,7 +150,7 @@ namespace src.player.skills
if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("deathbomb_team_")) return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("blastshot_team_")) return;
var parts = nade.Globalname.Split('_');
if (parts.Length < 4) return;
@ -169,33 +169,12 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam)
{
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
if (IsFriendlyFireOff())
{
int teamDamage = (int)param2.Damage;
param2.Damage = 0;
if (teamDamage > 0)
SkillUtils.TakeHealth(victimPawn, teamDamage, owner, KillfeedIcons.Explosion);
return;
}
}
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
}
private static bool IsFriendlyFireOff()
{
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
public class PlayerSkillInfo

View file

@ -234,6 +234,29 @@ namespace src.player.skills
SkillUtils.CloseMenu(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
lock (setLock)
{
Forget(playerIndex);
if (playersToTarget.TryRemove(playerIndex, out uint targetIndex))
Forget(targetIndex);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
}
private static void Forget(uint index)
{
targetPlayers.TryRemove(index, out _);
lastShot.TryRemove(index, out _);
hitPlayer.TryRemove(index, out _);
targetToPlayer.TryRemove(index, out _);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#db6c35", 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 damageAfterMiss = 5) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int DamageAfterMiss { get; set; } = damageAfterMiss;

View file

@ -13,12 +13,12 @@ namespace src.player.skills
private static readonly HashSet<string> disabledWeapons =
[
"weapon_ak47", "weapon_m4a4", "weapon_m4a1", "weapon_m4a1_silencer",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg553",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg556",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7",
"weapon_ump45", "weapon_p90", "weapon_mp5sd", "weapon_ssg08",
"weapon_awp", "weapon_scar20", "weapon_g3sg1", "weapon_nova",
"weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249",
"weapon_negev", "weapon_sg556"
"weapon_negev"
];
private const string chickenModelPath = "models/chicken/chicken.vmdl";
private static readonly ConcurrentDictionary<uint, uint> chickens = [];
@ -127,7 +127,7 @@ namespace src.player.skills
var chickenModel = EntityManager.CreateTrackedDynamicProp(player.Index);
if (chickenModel == null)
return;
chickenModel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(chickenModel.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
chickenModel.SetModel(chickenModelPath);
chickenModel.Render = Color.FromArgb(255, 255, 255, 255);
@ -138,8 +138,9 @@ namespace src.player.skills
chickenModel.CBodyComponent.SceneNode.GetSkeletonInstance().Scale = 1;
Utilities.SetStateChanged(chickenModel, "CBaseEntity", "m_CBodyComponent");
Server.NextFrame(() => {
Server.NextFrame(() =>
{
if (chickenModel == null || !chickenModel.IsValid) return;
chickenModel.AcceptInput("SetScale", chickenModel, chickenModel, "1");
});

View file

@ -123,33 +123,12 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam)
{
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
if (IsFriendlyFireOff())
{
int teamDamage = (int)param2.Damage;
param2.Damage = 0;
if (teamDamage > 0)
SkillUtils.TakeHealth(victimPawn, teamDamage, owner, KillfeedIcons.Explosion);
return;
}
}
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
}
private static bool IsFriendlyFireOff()
{
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
private static bool IsDeadPlayerValid(CCSPlayerController? player)

View file

@ -229,33 +229,12 @@ namespace src.player.skills
if (owner != null && !owner.IsValid) owner = null;
if (victimPawn.TeamNum == nadeTeam)
{
float reduction = SkillsInfo.GetValue<float>(skillName, "dmgReductionForTeamates");
param2.Damage *= 1f - Math.Clamp(reduction, 0f, 1f);
if (IsFriendlyFireOff())
{
int teamDamage = (int)param2.Damage;
param2.Damage = 0;
if (teamDamage > 0)
SkillUtils.TakeHealth(victimPawn, teamDamage, owner, KillfeedIcons.Explosion);
return;
}
}
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
}
private static bool IsFriendlyFireOff()
{
bool ff = ConVar.Find("mp_friendlyfire")?.GetPrimitiveValue<bool>() ?? false;
bool tae = ConVar.Find("mp_teammates_are_enemies")?.GetPrimitiveValue<bool>() ?? false;
return !ff && !tae;
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
public class PlayerSkillInfo

View file

@ -1,7 +1,10 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils;
using src.utils;
using System.Collections.Concurrent;
using static src.jRandomSkills;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
@ -13,6 +16,13 @@ namespace src.player.skills
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 = [];
public static void NewRound()
{
nades.Clear();
}
public static void LoadSkill()
{
@ -31,10 +41,12 @@ namespace src.player.skills
border: !PlayerManager.GetTickPlayers().Any(p => p.Team == player.Team && p != player) ? "tb" : "t");
}
private static void SpawnExplosion(Vector vector)
private static void SpawnExplosion(Vector vector, CCSPlayerController player)
{
lastTick = Server.TickCount;
SkillUtils.CreateHEGrenadeProjectile(vector, angle, new Vector(0, 0, 0), 0);
pendingTeam = player.TeamNum;
nades.AddOrUpdate(Server.TickCount, (player.TeamNum, player.Index), (_, _) => (player.TeamNum, player.Index));
SkillUtils.CreateHEGrenadeProjectile(vector, angle, new Vector(0, 0, 0), player.TeamNum);
}
public static void OnEntitySpawned(CEntityInstance entity)
@ -44,6 +56,8 @@ namespace src.player.skills
var heProjectile = entity.As<CBaseCSGrenadeProjectile>();
if (heProjectile == null || !heProjectile.IsValid || heProjectile.AbsRotation == null) return;
int spawnTick = Server.TickCount;
Server.NextFrame(() =>
{
if (heProjectile == null || !heProjectile.IsValid) return;
@ -51,13 +65,52 @@ namespace src.player.skills
return;
heProjectile.TicksAtZeroVelocity = 100;
heProjectile.TeamNum = (byte)CsTeam.None;
heProjectile.TeamNum = pendingTeam;
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}";
});
}
public static void OnTakeDamage(DynamicHook h)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param.Entity == null || param2 == null) return;
var nade = param2.Attacker?.Value;
if (nade == null || !nade.IsValid) return;
if (nade.DesignerName != "hegrenade_projectile") return;
if (string.IsNullOrEmpty(nade.Globalname) || !nade.Globalname.StartsWith("explosiveshot_team_")) return;
var parts = nade.Globalname.Split('_');
if (parts.Length < 4) return;
if (!int.TryParse(parts[2], out int nadeTeam)) return;
if (!uint.TryParse(parts[3], out uint ownerIndex)) return;
CCSPlayerPawn victimPawn = new(param.Handle);
if (victimPawn.DesignerName != "player") return;
if (victimPawn.Controller?.Value == null) return;
var victim = victimPawn.Controller.Value.As<CCSPlayerController>();
if (victim == null || !victim.IsValid || victim.Index == ownerIndex) return;
if (victimPawn.TeamNum == nadeTeam)
param2.Damage *= SkillUtils.GetTeamDamageMultiplier(skillName);
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner != null && !owner.IsValid) owner = null;
if (owner != null && param2.Damage >= victimPawn.Health)
SkillUtils.RegisterKillCredit(victim.Index, owner.Index, KillfeedIcons.Explosion);
}
private static bool NearlyEquals(float a, float b, float epsilon = 0.001f) => Math.Abs(a - b) < epsilon;
public static void BulletImpact(EventBulletImpact @event)
@ -73,15 +126,16 @@ namespace src.player.skills
if (playerInfo == null || playerInfo.Skill != skillName) return;
if (Instance.Random.NextDouble() <= playerInfo.SkillChance)
SpawnExplosion(pos);
SpawnExplosion(pos, player);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9c0000", 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 damage = 25f, float damageRadius = 210f, float chanceFrom = .15f, float chanceTo = .3f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#9c0000", 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 damage = 25f, float damageRadius = 210f, float chanceFrom = .15f, float chanceTo = .3f, float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public float Damage { get; set; } = damage;
public float DamageRadius { get; set; } = damageRadius;
public float ChanceFrom { get; set; } = chanceFrom;
public float ChanceTo { get; set; } = chanceTo;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
}
}
}

View file

@ -12,6 +12,10 @@ namespace src.player.skills
private const Skills skillName = Skills.FireRain;
private static readonly QAngle angle = new(10, -4, 13);
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<uint, (uint ThrowerRaw, byte Team)> rainMolotovs = [];
public static void LoadSkill()
{
@ -21,6 +25,7 @@ namespace src.player.skills
public static void NewRound()
{
KillAllDecoys();
rainMolotovs.Clear();
}
private static void KillAllDecoys()
@ -43,58 +48,79 @@ namespace src.player.skills
return;
const int grenadeCount = 5;
const float spawnHeight = 800.0f;
const float approachDistance = 600.0f;
const float clusterRadius = 130.0f;
const float flightTime = 0.65f;
const float gravity = 800.0f;
const float maxSpawnHeight = 400.0f;
const float clusterRadius = 110.0f;
const float dropSpeed = 250.0f;
float approachAngle = Random.Shared.NextSingle() * MathF.Tau;
float dirX = MathF.Cos(approachAngle);
float dirY = MathF.Sin(approachAngle);
rainTick = Server.TickCount;
rainThrower = pawn;
rainTeam = player.TeamNum;
Vector skyCenter = new(
targetPos.X + dirX * approachDistance,
targetPos.Y + dirY * approachDistance,
targetPos.Z + spawnHeight
);
float dx = targetPos.X - skyCenter.X;
float dy = targetPos.Y - skyCenter.Y;
float dz = targetPos.Z - skyCenter.Z;
float vx = dx / flightTime;
float vy = dy / flightTime;
float vz = (dz + 0.5f * gravity * flightTime * flightTime) / flightTime;
Vector velocity = new(vx, vy, vz);
float yaw = MathF.Atan2(vy, vx) * (180.0f / MathF.PI);
float horizontalSpeed = MathF.Sqrt(vx * vx + vy * vy);
float pitch = -MathF.Atan2(vz, horizontalSpeed) * (180.0f / MathF.PI);
QAngle launchAngle = new(pitch, yaw, 0.0f);
Vector decoyGround = new(targetPos.X, targetPos.Y, targetPos.Z + 8f);
Vector dropVelocity = new(0, 0, -dropSpeed);
for (int i = 0; i < grenadeCount; i++)
{
float offsetAngle = Random.Shared.NextSingle() * MathF.Tau;
float offsetDist = MathF.Sqrt(Random.Shared.NextSingle()) * clusterRadius;
float offsetX = MathF.Cos(offsetAngle) * offsetDist;
float offsetY = MathF.Sin(offsetAngle) * offsetDist;
Vector spawnPos = new(
skyCenter.X + offsetX,
skyCenter.Y + offsetY,
skyCenter.Z
Vector ground = new(
decoyGround.X + MathF.Cos(offsetAngle) * offsetDist,
decoyGround.Y + MathF.Sin(offsetAngle) * offsetDist,
decoyGround.Z
);
SkillUtils.CreateMolotovProjectile(spawnPos, launchAngle, velocity, player.TeamNum);
var reach = RayTrace.TraceShape(player, decoyGround, ground, null, 0);
if (reach.HasValue && reach.Value.DidHit)
ground = decoyGround;
float height = maxSpawnHeight;
var ceiling = RayTrace.TraceShape(player, ground, new Vector(ground.X, ground.Y, ground.Z + maxSpawnHeight), null, 0);
if (ceiling.HasValue && ceiling.Value.DidHit)
height = Math.Clamp(maxSpawnHeight * ceiling.Value.Fraction - 24f, 24f, maxSpawnHeight);
Vector spawnPos = new(ground.X, ground.Y, ground.Z + height);
SkillUtils.CreateMolotovProjectile(spawnPos, angle, dropVelocity, player.TeamNum);
}
}
public static void OnEntitySpawned(CEntityInstance entity)
{
var name = entity.DesignerName;
if (name == "molotov_projectile")
{
if (Server.TickCount != rainTick) return;
var thrower = rainThrower;
if (thrower == null || !thrower.IsValid) return;
var molotov = entity.As<CMolotovProjectile>();
if (molotov == null || !molotov.IsValid) return;
molotov.TeamNum = rainTeam;
molotov.Thrower.Raw = thrower.EntityHandle.Raw;
molotov.OwnerEntity.Raw = thrower.EntityHandle.Raw;
rainMolotovs[molotov.Index] = (thrower.EntityHandle.Raw, rainTeam);
return;
}
if (name == "inferno")
{
var inferno = entity.As<CInferno>();
if (inferno == null || !inferno.IsValid) return;
var ownerHandle = inferno.OwnerEntity;
var source = ownerHandle?.Value;
if (ownerHandle == null || source == null || !source.IsValid) return;
if (!rainMolotovs.TryGetValue(source.Index, out var origin)) return;
inferno.TeamNum = origin.Team;
ownerHandle.Raw = origin.ThrowerRaw;
return;
}
if (name != "decoy_projectile")
return;

View file

@ -112,13 +112,31 @@ namespace src.player.skills
box.Entity!.Name = box.Globalname = $"FortniteWall_{Server.TickCount}";
box.Collision.SolidType = SolidType_t.SOLID_VPHYSICS;
box.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(box.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
box.DispatchSpawn();
box.Health = box.MaxHealth = 1_000_000;
var keys = new CEntityKeyValues();
try
{
keys.SetString("model", SkillsInfo.GetValue<string>(skillName, "propModel"));
keys.SetVector("origin", pos);
keys.SetBool("solid", true);
box.DispatchSpawn(keys);
}
finally
{
keys.Dispose();
}
if (!box.IsValid) return;
box.Teleport(pos, angle, null);
barricades.TryAdd(box.Index, SkillsInfo.GetValue<int>(skillName, "barricadeHealth"));
Server.NextFrame(() =>
{
if (box == null || !box.IsValid) return;
box.SetModel(SkillsInfo.GetValue<string>(skillName, "propModel"));
box.Teleport(pos, angle, null);
Utilities.SetStateChanged(box, "CBaseEntity", "m_CBodyComponent");
});
}

View file

@ -72,6 +72,8 @@ namespace src.player.skills
{
if (Server.TickCount % 32 != 0) return;
ReapplyScales();
foreach (var player in PlayerManager.GetTickPlayers())
{
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
@ -155,6 +157,29 @@ namespace src.player.skills
}
}
private static void ReapplyScales()
{
lock (setLock)
{
foreach (var kvp in enlargedEnemies)
{
float expected = PlayerManager.GetPlayerByIndex(kvp.Key)?.SkillChance ?? 0f;
if (expected <= 0f) continue;
var target = Utilities.GetPlayerFromIndex((int)kvp.Value);
if (target == null || !target.IsValid) continue;
var pawn = target.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) continue;
var skeleton = pawn.CBodyComponent?.SceneNode?.GetSkeletonInstance();
if (skeleton == null || Math.Abs(skeleton.Scale - expected) < 0.01f) continue;
SkillUtils.ChangePlayerScale(target, expected);
}
}
}
private static void ResetScale(uint targetIndex, bool notify = false)
{
var target = Utilities.GetPlayerFromIndex((int)targetIndex);

View file

@ -1,5 +1,6 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
using src.utils;
using System.Collections.Concurrent;
@ -19,6 +20,7 @@ namespace src.player.skills
}
private static readonly ConcurrentDictionary<uint, List<ChickenState>> activeChickens = new();
private static readonly ConcurrentDictionary<uint, uint> chickenOwners = new();
public static void LoadSkill()
{
@ -28,6 +30,29 @@ namespace src.player.skills
public static void NewRound()
{
activeChickens.Clear();
chickenOwners.Clear();
}
public static void OnTakeDamage(DynamicHook h)
{
CEntityInstance param = h.GetParam<CEntityInstance>(0);
CTakeDamageInfo param2 = h.GetParam<CTakeDamageInfo>(1);
if (param == null || param2 == null) return;
if (!chickenOwners.TryGetValue(param.Index, out uint ownerIndex)) return;
var owner = Utilities.GetPlayerFromIndex((int)ownerIndex);
if (owner == null || !owner.IsValid) return;
var attackerEnt = param2.Attacker?.Value;
if (attackerEnt == null || !attackerEnt.IsValid) return;
var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle);
if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return;
if (attackerPawn.TeamNum != owner.TeamNum) return;
param2.Damage = 0;
}
public static void EnableSkill(CCSPlayerController player)
@ -40,7 +65,10 @@ namespace src.player.skills
if (activeChickens.TryRemove(player.Index, out var chickens))
foreach (var state in chickens)
if (state.Chicken != null && state.Chicken.IsValid)
{
chickenOwners.TryRemove(state.Chicken.Index, out _);
state.Chicken.Remove();
}
}
private static void SpawnChicken(CCSPlayerController player)
@ -59,6 +87,7 @@ namespace src.player.skills
if (chicken == null || !chicken.IsValid) continue;
chicken.Render = Color.LightGreen;
chickenOwners[chicken.Index] = player.Index;
Vector offset = new(
(float)(100 * Math.Cos(2 * Math.PI * i / amount)),

View file

@ -33,6 +33,21 @@ namespace src.player.skills
}
}
public static void PlayerDisconnect(uint playerIndex)
{
lock (setLock)
{
targetedPlayers.TryRemove(playerIndex, out _);
if (playersToTarget.TryRemove(playerIndex, out uint targetIndex))
targetedPlayers.TryRemove(targetIndex, out _);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
}
public static void OnTick()
{
if (Server.TickCount % 32 != 0) return;
@ -75,7 +90,7 @@ namespace src.player.skills
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
if (enemy == null)
if (enemy == null || !enemy.IsValid || enemy.Team == player.Team)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;

View file

@ -25,7 +25,9 @@ namespace src.player.skills
lock (setLock)
{
RemoveVolume(playerIndex);
playersToTarget.TryRemove(playerIndex, out _);
if (playersToTarget.TryRemove(playerIndex, out uint leavingTarget))
RemoveVolume(leavingTarget);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
@ -104,7 +106,7 @@ namespace src.player.skills
}
var enemy = Utilities.GetPlayerFromIndex((int)enemyIndex);
if (enemy == null || !enemy.IsValid)
if (enemy == null || !enemy.IsValid || enemy.Team == player.Team)
{
playerEvent.PrintToChat($" {ChatColors.Red}" + playerEvent.GetTranslation("selectplayerskill_incorrect_enemy_index"));
return;

View file

@ -15,12 +15,12 @@ namespace src.player.skills
private static readonly string[] disabledWeapons =
[
"weapon_ak47", "weapon_m4a4", "weapon_m4a1", "weapon_m4a1_silencer",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg553",
"weapon_famas", "weapon_galilar", "weapon_aug", "weapon_sg556",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7",
"weapon_ump45", "weapon_p90", "weapon_mp5sd", "weapon_ssg08",
"weapon_awp", "weapon_scar20", "weapon_g3sg1", "weapon_nova",
"weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249",
"weapon_negev", "weapon_sg556"
"weapon_negev"
];
public static void LoadSkill()

View file

@ -23,7 +23,7 @@ namespace src.player.skills
{
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a1", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg553", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev"
};

View file

@ -20,7 +20,7 @@ namespace src.player.skills
[
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a1", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg553", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev"
];

View file

@ -185,10 +185,12 @@ namespace src.player.skills
camNode.Flags = (uint)(camNode.Flags & ~(1 << 2));
camera.SetModel(cameraViewModel);
camera.Render = Color.FromArgb(1, 255, 255, 255);
camera.Teleport(pos, angle);
camera.DispatchSpawn();
camera.Render = Color.FromArgb(1, 255, 255, 255);
Utilities.SetStateChanged(camera, "CBaseModelEntity", "m_clrRender");
CBaseEntity? target = pawn != null && pawn.IsValid ? pawn : null;
var entities = EntityManager.GetPlayerEntities(enemy.Index, "empty_prop");
@ -203,10 +205,11 @@ namespace src.player.skills
camera.AcceptInput("SetParent", target, target, "!activator");
});
if (cameras.TryGetValue(player.Index, out var cameraInfo))
cameras.AddOrUpdate(player.Index, (cameraInfo.Item1, camera.Index, enemy.Index), (k, v) => (cameraInfo.Item1, camera.Index, enemy.Index));
else
cameras.AddOrUpdate(player.Index, (pawn.CameraServices.ViewEntity.Raw, camera.Index, enemy.Index), (k, v) => (pawn.CameraServices.ViewEntity.Raw, camera.Index, enemy.Index));
uint originalView = cameras.TryGetValue(player.Index, out var cameraInfo)
? cameraInfo.Item1
: player.PlayerPawn?.Value?.CameraServices?.ViewEntity.Raw ?? 0;
cameras[player.Index] = (originalView, camera.Index, enemy.Index);
return camera.EntityHandle.Raw;
}

View file

@ -29,9 +29,16 @@ namespace src.player.skills
foreach (var kvp in OriginalWeaponMaxAmmo)
{
var vdata = VirtualFunctions.GetCSWeaponDataFromKey(-1, kvp.Value.Item2);
if (vdata != null)
try
{
var vdata = VirtualFunctions.GetCSWeaponDataFromKey(-1, kvp.Value.Item2);
if (vdata == null || vdata.Handle == nint.Zero) continue;
vdata.PrimaryReserveAmmoMax = kvp.Value.Item1;
}
catch (Exception ex)
{
Server.PrintToConsole($"[jRandomSkills] TakeAmmo could not restore ammo for {kvp.Value.Item2}: {ex.Message}");
}
}
OriginalWeaponMaxAmmo.Clear();
}

View file

@ -95,9 +95,17 @@ namespace src.player.skills
var pawn = eventPlayer.PlayerPawn.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) continue;
if (SkillUtils.GetDistance(smoke.Key, pawn.AbsOrigin) <= smokeRadius)
if (SkillUtils.TakeHealth(pawn, smokeDamage, thrower, KillfeedIcons.Smokegrenade))
player.EmitSound("Player.DamageBody.Onlooker");
if (SkillUtils.GetDistance(smoke.Key, pawn.AbsOrigin) > smokeRadius) continue;
int damage = smokeDamage;
if (thrower != null && eventPlayer.Index != thrower.Index && pawn.TeamNum == thrower.TeamNum)
{
damage = (int)MathF.Round(smokeDamage * SkillUtils.GetTeamDamageMultiplier(skillName));
if (damage <= 0) continue;
}
if (SkillUtils.TakeHealth(pawn, damage, thrower, KillfeedIcons.Smokegrenade))
player.EmitSound("Player.DamageBody.Onlooker");
}
}
}
@ -162,12 +170,13 @@ namespace src.player.skills
SkillUtils.UpdateGrenadeCount(player, CsItem.SmokeGrenade, 1);
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", 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 smokeDamage = 2, float smokeRadius = 180, int tickCooldown = 17, int grenadeLimit = 1) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#507529", 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 smokeDamage = 2, float smokeRadius = 180, int tickCooldown = 17, int grenadeLimit = 1, float dmgReductionForTeamates = 0.5f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public int SmokeDamage { get; set; } = smokeDamage;
public float SmokeRadius { get; set; } = smokeRadius;
public int TickCooldown { get; set; } = tickCooldown;
public int GrenadeLimit { get; set; } = grenadeLimit;
public float DmgReductionForTeamates { get; set; } = dmgReductionForTeamates;
}
}
}

View file

@ -17,7 +17,7 @@ namespace src.player.skills
"weapon_cz75a", "weapon_fiveseven", "weapon_p250", "weapon_tec9", "weapon_elite", "weapon_hkp2000",
"weapon_mp9", "weapon_mac10", "weapon_bizon", "weapon_mp7", "weapon_ump45", "weapon_p90", "weapon_sg556",
"weapon_mp5sd", "weapon_famas", "weapon_galilar", "weapon_m4a4", "weapon_m4a1_silencer", "weapon_ak47",
"weapon_aug", "weapon_sg553", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_aug", "weapon_sg556", "weapon_ssg08", "weapon_awp", "weapon_scar20", "weapon_g3sg1",
"weapon_nova", "weapon_xm1014", "weapon_mag7", "weapon_sawedoff", "weapon_m249", "weapon_negev" ];
public static void LoadSkill()

View file

@ -75,6 +75,21 @@ namespace src.player.skills
SkillUtils.CloseMenu(player);
}
public static void PlayerDisconnect(uint playerIndex)
{
lock (setLock)
{
infectedPlayers.TryRemove(playerIndex, out _);
if (playersToTarget.TryRemove(playerIndex, out uint targetIndex))
infectedPlayers.TryRemove(targetIndex, out _);
foreach (var kvp in playersToTarget)
if (kvp.Value == playerIndex)
playersToTarget.TryRemove(kvp.Key, out _);
}
}
public static void OnTick()
{
if (Server.TickCount % 32 != 0) return;