New skills (9) - Bounty: put a cash price on an enemy's head, the killer collects it. - EmpGrenade: grenade victims lose radar and crosshair for a while. - GravityDecoy: the decoy alters gravity for everyone in its radius. - Heavyweight: immune to push and slow effects from other skills. - Nemesis: the marked player takes extra damage from everyone. - Punisher: banks part of the damage you take and releases it on your next hit. - Rewind: drop a marker and teleport back to it shortly after. - TrueArmor: armor absorbs part of incoming damage instead of health. - Voodoo: the marked enemy shares the damage you take. Shared systems - DecoyRing: reusable ground ring so decoy skills show their real area of effect. Adopted by FireRain, FrozenDecoy, GravityDecoy and MagneticDecoy. - WeaponPool: single cached weapon classification (rifle/pistol/grenade) with a normalizing lookup, backed by the new config Weapons section. Replaces the duplicated hardcoded arrays in Assassin, Chicken, Ghost, NoNades, PrimaryBan, RandomWeapon, SniperElite and WeaponsSwap. [because of compatibility with WeaponRestrict] - HUD suppression registry in SkillUtils: crosshair and radar hiding is now refcounted per owner, so overlapping skills (Glitch, Jammer, EmpGrenade) cannot restore each other's state early. - SkillUtils.ResolveHiddenPawns: shared invisible-pawn resolution for C4Camouflage, Ghost and Ninja. - Config gained a Weapons section with automatic migration for existing installs, plus a per-skill DisableOnPistolRound switch honoured by both the round draw and css_setskill. Sound - Volume rebalance - Add SkillUtils.EmitSoundToPlayer with a private RecipientFilter. A null filter had been broadcasting every skill sound to all clients. Migrated 16 call sites: Poison, DemonEye, ToxicSmoke, CarefulBullets, Thorns, HotBomb, LastGasp, LongKnife, Nightmare, Jester, Tripwire, Medic, HealingSmoke, Rewind, Watchmaker. - New effects: LongKnife knife-hit feedback (Weapon_Knife.Hit.Heavy/Light), Grapple hook impact, Fortnite barricade placement, Ricochet bounce, Nightmare cursed tone, Replicator/Illusionist spawn, PsychicDefusing defuse cue, TakeAmmo pickup, Tripwire and Jammer trigger tones. - 29 skills gained a configurable SoundVolume, LongKnife also HitSoundVolume. - Grapple: emit the hook impact at the hook entity instead of at the owner so the enemy team can locate it. - PawelJumper: the engine already plays the first jump, only extra jumps emit. Models and visuals - Grapple: real hook model with configurable hookScale and hookEmbed so the hook sits in the surface it hit. - Pilot: glowing exhaust trail parented to the pawn, active only while airborne. Performance - SkillsInfo.GetValue now resolves through a ConcurrentDictionary cache instead of reflecting on every call; invalidated on config reload. - HUD render cache keyed on player, config, description flag and the three source lines; identical frames skip the rebuild. - Glaz.CheckTransmit: hoist smoke index resolution out of the per-viewer loop and build the pawn-owner map lazily. - playersSkills switched from ConcurrentBag to HashSet<Skills>, turning the NoRepeat filter from an O(n) scan into a set lookup. Fixes - NoRepeat could hand a player two or three skills in one round because the history bag was never deduplicated; the HashSet rewrite closes it. - SniperElite: restore the saved weapon after the halftime team swap instead of dropping it on the pistol round. - Illiterate: the holder alert now reaches chat and is exempt from its own scrambler via PrintToChat(ignoreIlliterate). - EntityManager.DestroyBeam: zero out width and render colour before the kill so beams cannot linger visibly, with a next-frame Remove fallback. - Ricochet: drop all 18 diagnostic Trace calls and the Trace helper; the skill measured stable over 48h and the tracing dominated the debug log. Debug - Tag round start, round end and freeze-time-end lines with [WARMUP] so warmup load is separable from live rounds.
478 lines
No EOL
20 KiB
C#
478 lines
No EOL
20 KiB
C#
using CounterStrikeSharp.API;
|
|
using CounterStrikeSharp.API.Core;
|
|
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
|
using CounterStrikeSharp.API.Modules.Timers;
|
|
using CounterStrikeSharp.API.Modules.Utils;
|
|
using jRandomSkills.src.utils;
|
|
using src.utils;
|
|
using System.Collections.Concurrent;
|
|
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
|
|
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
|
|
|
|
namespace src.player.skills
|
|
{
|
|
public class Iana : ISkill
|
|
{
|
|
private const Skills skillName = Skills.Iana;
|
|
private static readonly ConcurrentDictionary<uint, PlayerSkill> playersInfo = [];
|
|
private static readonly ConcurrentDictionary<uint, byte> consumedClones = [];
|
|
private static readonly object setLock = new();
|
|
|
|
public static void LoadSkill()
|
|
{
|
|
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
|
|
}
|
|
|
|
public static void NewRound()
|
|
{
|
|
foreach (var playerSkill in playersInfo.Values)
|
|
KillClone(playerSkill);
|
|
|
|
lock (setLock)
|
|
{
|
|
playersInfo.Clear();
|
|
consumedClones.Clear();
|
|
}
|
|
}
|
|
|
|
public static void WeaponPickup(EventItemPickup @event)
|
|
{
|
|
var player = PlayerManager.GetPlayerEvent(@event.Userid);
|
|
if (player == null) return;
|
|
|
|
if (playersInfo.TryGetValue(player.Index, out var playerSkill))
|
|
if (playerSkill.CloneProp != null)
|
|
BlockWeapon(player, true);
|
|
}
|
|
|
|
public static void WeaponEquip(EventItemEquip @event)
|
|
{
|
|
var player = PlayerManager.GetPlayerEvent(@event.Userid);
|
|
if (player == null) return;
|
|
|
|
if (playersInfo.TryGetValue(player.Index, out var playerSkill))
|
|
if (playerSkill.CloneProp != null)
|
|
BlockWeapon(player, true);
|
|
}
|
|
|
|
public static void UseSkill(CCSPlayerController player)
|
|
{
|
|
var playerPawn = player.PlayerPawn.Value;
|
|
if (playerPawn?.CBodyComponent == null) return;
|
|
|
|
if (playersInfo.TryGetValue(player.Index, out var playerSkill))
|
|
if (playerSkill.CloneProp == null && playerSkill.NextUse <= Server.TickCount)
|
|
{
|
|
UpdateWeapons(player, playerSkill);
|
|
CreateClone(playerSkill);
|
|
SkillUtils.SetPlayerCollisions(player, false);
|
|
}
|
|
else if (playerSkill.CloneProp != null)
|
|
KillClone(playerSkill);
|
|
}
|
|
|
|
public static void EnableSkill(CCSPlayerController player)
|
|
{
|
|
if (player == null || !player.IsValid) return;
|
|
playersInfo.TryAdd(player.Index, new PlayerSkill
|
|
{
|
|
PlayerIndex = player.Index,
|
|
CloneProp = null,
|
|
NextUse = 0,
|
|
UseTime = 0,
|
|
});
|
|
}
|
|
|
|
public static void DisableSkill(CCSPlayerController player)
|
|
{
|
|
if (player == null) return;
|
|
if (playersInfo.TryGetValue(player.Index, out var playerSkill))
|
|
KillClone(playerSkill);
|
|
playersInfo.TryRemove(player.Index, out _);
|
|
EntityManager.DestroyPlayerEntities(player.Index);
|
|
SkillUtils.ResetPrintHTML(player);
|
|
}
|
|
|
|
private static void UpdateWeapons(CCSPlayerController player, PlayerSkill playerSkill)
|
|
{
|
|
if (player == null || !player.IsValid) return;
|
|
|
|
var playerPawn = player.PlayerPawn.Value;
|
|
if (playerPawn == null || !playerPawn.IsValid || playerPawn.WeaponServices == null) return;
|
|
|
|
playerSkill.Weapons.Clear();
|
|
|
|
foreach (var weapon in playerPawn.WeaponServices.MyWeapons)
|
|
if (weapon != null && weapon.IsValid && weapon.Value != null && weapon.Value.IsValid)
|
|
playerSkill.Weapons.Add(weapon.Value.AttributeManager.Item.ItemID);
|
|
}
|
|
|
|
private static void KillClone(PlayerSkill playerSkill)
|
|
{
|
|
var player = Utilities.GetPlayerFromIndex((int)playerSkill.PlayerIndex);
|
|
if (player == null || !player.IsValid) return;
|
|
|
|
player.EmitSound("SolidMetal.BulletImpact", volume: SkillsInfo.GetValue<float>(skillName, "soundVolume"));
|
|
|
|
CDynamicProp? cloneProp = null;
|
|
if (playerSkill.CloneProp != null)
|
|
cloneProp = Utilities.GetEntityFromIndex<CDynamicProp>((int)playerSkill.CloneProp);
|
|
|
|
if (cloneProp != null && cloneProp.IsValid && cloneProp.AbsOrigin != null && cloneProp.AbsRotation != null)
|
|
{
|
|
var playerPawn = player.PlayerPawn.Value;
|
|
if (playerPawn != null && playerPawn.IsValid)
|
|
{
|
|
Vector pos = new(cloneProp.AbsOrigin.X, cloneProp.AbsOrigin.Y, cloneProp.AbsOrigin.Z);
|
|
Server.NextFrame(() =>
|
|
{
|
|
if (playerPawn == null || !playerPawn.IsValid) return;
|
|
playerPawn.Teleport(pos);
|
|
});
|
|
}
|
|
EntityManager.DestroyEntity(cloneProp.Index);
|
|
playerSkill.CloneProp = null;
|
|
}
|
|
|
|
var eventTarget = PlayerManager.GetPlayerFromEvent(player);
|
|
if (eventTarget != null && eventTarget.IsValid)
|
|
SkillUtils.ApplyScreenColor(eventTarget, 0, 0, 0, 0, 10, 0, 2);
|
|
|
|
SkillUtils.SetPlayerCollisions(player, true);
|
|
|
|
BlockWeapon(player, false);
|
|
playerSkill.NextUse = Server.TickCount + SkillsInfo.GetValue<float>(skillName, "Cooldown") * 64;
|
|
playerSkill.UseTime = 0;
|
|
}
|
|
|
|
public static void OnTick()
|
|
{
|
|
if (!SkillUtils.IsHudFrame()) return;
|
|
foreach (var playerSkill in playersInfo.Values)
|
|
UpdateHUD(playerSkill);
|
|
}
|
|
|
|
private static CDynamicProp? CreateClone(PlayerSkill playerSkill)
|
|
{
|
|
var player = Utilities.GetPlayerFromIndex((int)playerSkill.PlayerIndex);
|
|
if (player == null || !player.IsValid) return null;
|
|
|
|
var playerPawn = player.PlayerPawn.Value;
|
|
if (playerPawn == null || !playerPawn.IsValid || playerPawn.AbsOrigin == null || playerPawn.AbsRotation == null) return null;
|
|
|
|
if (!((PlayerFlags)playerPawn.Flags).HasFlag(PlayerFlags.FL_ONGROUND))
|
|
{
|
|
playerSkill.InfoTime = Server.TickCount;
|
|
return null;
|
|
}
|
|
|
|
QAngle angle = new(0, playerPawn.EyeAngles.Y, 0);
|
|
Vector pos = playerPawn.AbsOrigin + SkillUtils.GetForwardVector(angle) * 50;
|
|
pos.Z += 2;
|
|
|
|
if (!CheckPosition(player, pos))
|
|
{
|
|
pos.Z += 10;
|
|
if (!CheckPosition(player, pos))
|
|
{
|
|
playerSkill.InfoTime = Server.TickCount;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
var clone = EntityManager.CreateTrackedDynamicProp(player.Index);
|
|
if (clone == null || !clone.IsValid) return null;
|
|
|
|
clone.Collision.SolidType = SolidType_t.SOLID_VPHYSICS;
|
|
clone.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(clone.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
|
|
clone.Entity!.Name = clone.Globalname = $"IanaClone_{Server.TickCount}_{player.Index}";
|
|
clone.DispatchSpawn();
|
|
|
|
Server.NextFrame(() =>
|
|
{
|
|
if (player == null || !player.IsValid) return;
|
|
if (playerPawn == null || !playerPawn.IsValid || playerPawn.AbsOrigin == null || playerPawn.AbsRotation == null) return;
|
|
|
|
if (clone == null || !clone.IsValid) return;
|
|
|
|
clone.SetModel(playerPawn!.CBodyComponent!.SceneNode!.GetSkeletonInstance().ModelState.ModelName);
|
|
clone.Teleport(playerPawn.AbsOrigin, new QAngle(0, playerPawn.V_angle.Y, 0));
|
|
|
|
playerPawn.Teleport(pos);
|
|
|
|
var eventTarget = PlayerManager.GetPlayerFromEvent(player);
|
|
if (eventTarget != null && eventTarget.IsValid)
|
|
SkillUtils.ApplyScreenColor(eventTarget, r: 255, g: 255, b: 0, a: 20, duration: 100, holdTime: 1020);
|
|
|
|
BlockWeapon(player, true);
|
|
|
|
playerSkill.UseTime = Server.TickCount;
|
|
playerSkill.NextUse = Server.TickCount + 64000;
|
|
playerSkill.CloneProp = clone.Index;
|
|
|
|
consumedClones.TryRemove(clone.Index, out _);
|
|
|
|
jRandomSkills.Instance.AddTimer(SkillsInfo.GetValue<float>(skillName, "Duration"), () =>
|
|
{
|
|
if (playerSkill.CloneProp != null)
|
|
KillClone(playerSkill);
|
|
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
|
|
|
|
uint playerIndex = player.Index;
|
|
|
|
Timer? cloneTimer = null;
|
|
cloneTimer = jRandomSkills.Instance.AddTimer(2f, () =>
|
|
{
|
|
var target = Utilities.GetPlayerFromIndex((int)playerIndex);
|
|
if (target == null || !target.IsValid || target.LifeState != (byte)LifeState_t.LIFE_ALIVE)
|
|
{
|
|
cloneTimer?.Kill();
|
|
return;
|
|
}
|
|
|
|
if (!playersInfo.TryGetValue(target.Index, out var playerSkill) || playerSkill.CloneProp == null)
|
|
{
|
|
cloneTimer?.Kill();
|
|
return;
|
|
}
|
|
|
|
var eventTarget = PlayerManager.GetPlayerFromEvent(player);
|
|
if (eventTarget == null || !eventTarget.IsValid)
|
|
{
|
|
cloneTimer?.Kill();
|
|
return;
|
|
}
|
|
|
|
SkillUtils.ApplyScreenColor(eventTarget, r: 255, g: 255, b: 0, a: 20, duration: 100, holdTime: 1020);
|
|
}, TimerFlags.STOP_ON_MAPCHANGE | TimerFlags.REPEAT);
|
|
});
|
|
|
|
return clone;
|
|
}
|
|
|
|
private static bool CheckPosition(CCSPlayerController player, Vector endPos)
|
|
{
|
|
var playerPawn = player.PlayerPawn.Value;
|
|
|
|
Vector start = new(playerPawn!.AbsOrigin!.X, playerPawn!.AbsOrigin!.Y, endPos.Z);
|
|
var result = RayTrace.TraceHullShape(start, endPos, player);
|
|
|
|
if (!result.HasValue)
|
|
return false;
|
|
|
|
return !result.Value.DidHit;
|
|
}
|
|
|
|
private static float CalculateDamage(CCSPlayerController player, string weaponName, float damage, bool isHeadshot)
|
|
{
|
|
float calculatedDamage = damage;
|
|
|
|
var weapon = Utilities.FindAllEntitiesByDesignerName<CCSWeaponBase>(weaponName).FirstOrDefault();
|
|
if (weapon == null) return calculatedDamage;
|
|
|
|
var vdata = weapon.GetVData<CCSWeaponBaseVData>();
|
|
if (vdata == null) return calculatedDamage;
|
|
|
|
float penetration = vdata.ArmorRatio * .5f;
|
|
|
|
var eventPlayer = PlayerManager.GetPlayerFromEvent(player);
|
|
if (eventPlayer == null || !eventPlayer.IsValid)
|
|
return calculatedDamage;
|
|
|
|
if (isHeadshot)
|
|
{
|
|
calculatedDamage *= vdata.HeadshotMultiplier;
|
|
if (eventPlayer.PawnHasHelmet)
|
|
calculatedDamage *= penetration;
|
|
}
|
|
else
|
|
if (eventPlayer.PawnArmor > 0)
|
|
calculatedDamage *= penetration;
|
|
|
|
return calculatedDamage;
|
|
}
|
|
|
|
private static CCSPlayerController? GetShooter(CTakeDamageInfo info)
|
|
{
|
|
var attackerEnt = info.Attacker?.Value;
|
|
if (attackerEnt == null || !attackerEnt.IsValid) return null;
|
|
|
|
var attackerPawn = new CCSPlayerPawn(attackerEnt.Handle);
|
|
if (!attackerPawn.IsValid || attackerPawn.DesignerName != "player") return null;
|
|
if (attackerPawn.Controller?.Value == null) return null;
|
|
|
|
var shooter = PlayerManager.GetPlayerEvent(attackerPawn.Controller.Value.As<CCSPlayerController>());
|
|
return shooter != null && shooter.IsValid ? shooter : null;
|
|
}
|
|
|
|
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;
|
|
|
|
if (string.IsNullOrEmpty(param.Entity.Name)) return;
|
|
if (!param.Entity.Name.StartsWith("IanaClone_")) return;
|
|
|
|
var nameParams = param.Entity.Name.Split('_')[2];
|
|
_ = uint.TryParse(nameParams, out uint userIndex);
|
|
if (userIndex == 0) return;
|
|
|
|
var player = Utilities.GetPlayerFromIndex((int)userIndex);
|
|
if (player == null || !player.IsValid) return;
|
|
|
|
float dealDamage = param2.Damage;
|
|
|
|
if (playersInfo.TryGetValue(player.Index, out var playerSkill))
|
|
{
|
|
CDynamicProp? cloneProp = null;
|
|
if (playerSkill.CloneProp != null)
|
|
cloneProp = Utilities.GetEntityFromIndex<CDynamicProp>((int)playerSkill.CloneProp);
|
|
|
|
if (cloneProp != null && cloneProp.IsValid && cloneProp.AbsOrigin != null)
|
|
{
|
|
Vector pos = param2.DamagePosition;
|
|
Vector posCl = cloneProp.AbsOrigin;
|
|
|
|
bool isHead = false;
|
|
float viewOffset = 63.27f;
|
|
float diff = 2f;
|
|
|
|
if (pos.Z >= posCl.Z + viewOffset - diff)
|
|
isHead = true;
|
|
|
|
float damage = param2.Damage;
|
|
string? weapon = param2.Ability?.Value?.DesignerName;
|
|
if (weapon != null)
|
|
dealDamage = CalculateDamage(player, weapon, damage, isHead);
|
|
}
|
|
|
|
if (playerSkill.CloneProp != null && !consumedClones.TryAdd(playerSkill.CloneProp.Value, 0))
|
|
return;
|
|
|
|
KillClone(playerSkill);
|
|
|
|
var playerPawn = player.PlayerPawn.Value;
|
|
if (playerPawn != null)
|
|
{
|
|
KillfeedIcons? killfeed = KillfeedIconsExtensions.FromWeaponName(param2.Ability?.Value?.DesignerName);
|
|
SkillUtils.TakeHealth(playerPawn, (int)dealDamage, GetShooter(param2), killfeed ?? KillfeedIcons.Leg);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
var pawn = player.PlayerPawn.Value;
|
|
if (pawn == null || !pawn.IsValid || pawn.WeaponServices == null) return;
|
|
|
|
foreach (var weapon in pawn.WeaponServices.MyWeapons)
|
|
if (weapon != null && weapon.IsValid && weapon.Value != null && weapon.Value.IsValid)
|
|
{
|
|
weapon.Value.NextPrimaryAttackTick = block ? int.MaxValue : Server.TickCount;
|
|
weapon.Value.NextSecondaryAttackTick = block ? int.MaxValue : Server.TickCount;
|
|
|
|
Utilities.SetStateChanged(weapon.Value, "CBasePlayerWeapon", "m_nNextPrimaryAttackTick");
|
|
Utilities.SetStateChanged(weapon.Value, "CBasePlayerWeapon", "m_nNextSecondaryAttackTick");
|
|
}
|
|
}
|
|
|
|
private static void UpdateHUD(PlayerSkill playerSkill)
|
|
{
|
|
if (playerSkill == null) return;
|
|
|
|
var player = Utilities.GetPlayerFromIndex((int)playerSkill.PlayerIndex);
|
|
if (player == null || !player.IsValid) return;
|
|
|
|
float cooldown = 0;
|
|
float time1 = (playerSkill.NextUse - Server.TickCount) / 64;
|
|
cooldown = (int)Math.Ceiling(Math.Max(time1, 0));
|
|
|
|
float duration = 0;
|
|
float time2 = SkillsInfo.GetValue<float>(skillName, "Duration") - (Server.TickCount - playerSkill.UseTime) / 64;
|
|
duration = (int)Math.Ceiling(Math.Max(time2, 0));
|
|
|
|
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
|
if (playerInfo == null) return;
|
|
|
|
if (playerSkill.InfoTime + (64 * 2) > Server.TickCount)
|
|
playerInfo.PrintHTML = $"{player.GetTranslation("shade_nospace")}";
|
|
else if (cooldown == 0 && duration == 0)
|
|
playerInfo.PrintHTML = null;
|
|
else if (duration > 0)
|
|
playerInfo.PrintHTML = $"{player.GetTranslation("hud_info", $"<font color='#00FF00'>{duration}</font>")}";
|
|
else if (cooldown > 0)
|
|
playerInfo.PrintHTML = $"{player.GetTranslation("hud_info", $"<font color='#FF0000'>{cooldown}</font>")}";
|
|
}
|
|
|
|
public static bool OnWeaponCanAcquire(DynamicHook hook, CCSPlayerController player, CEconItemView econItem, CCSWeaponBaseVData vdata)
|
|
{
|
|
string weaponName = vdata.Name;
|
|
if (string.IsNullOrEmpty(weaponName)) return false;
|
|
|
|
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
|
|
if (playerInfo?.Skill != skillName) return false;
|
|
|
|
if (!playersInfo.TryGetValue(player.Index, out var playerSkill)) return false;
|
|
if (playerSkill.CloneProp == null || playerSkill.Weapons.Contains(econItem.ItemID)) return false;
|
|
|
|
hook.SetReturn(AcquireResult.InvalidItem);
|
|
return true;
|
|
}
|
|
|
|
public static bool WeaponDrop(DynamicHook hook, CCSPlayerController player)
|
|
{
|
|
if (player == null || !player.IsValid) return false;
|
|
|
|
if (playersInfo.TryGetValue(player.Index, out var playerSkill))
|
|
if (playerSkill.CloneProp != null)
|
|
{
|
|
hook.SetReturn(1);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public class PlayerSkill
|
|
{
|
|
public required uint PlayerIndex { get; set; }
|
|
public uint? CloneProp { get; set; }
|
|
public float NextUse { get; set; }
|
|
public float UseTime { get; set; }
|
|
public int InfoTime { get; set; }
|
|
public int LastHit { get; set; }
|
|
public List<ulong> Weapons { get; set; } = [];
|
|
}
|
|
|
|
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#d0d930", CsTeam onlyTeam = CsTeam.None, bool disableOnFreezeTime = true, bool needsTeammates = false, string requiredPermission = "", float? hudDuration = null, float? descriptionHudDuration = null, int maxPerServer = -1, Rarity rarity = Rarity.Common, float cooldown = 30, float duration = 10, float soundVolume = 1f) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
|
|
{
|
|
public float SoundVolume { get; set; } = soundVolume;
|
|
public float Cooldown { get; set; } = cooldown;
|
|
public float Duration { get; set; } = duration;
|
|
}
|
|
}
|
|
} |