jRandomSkills/jRandomSkills - SRC Files/src/player/skills/ThrowingKnife.cs
2026-09-04 01:16:34 +03:00

497 lines
No EOL
20 KiB
C#

using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.UserMessages;
using CounterStrikeSharp.API.Modules.Utils;
using jRandomSkills.src.utils;
using src.utils;
using System.Collections.Concurrent;
using System.Drawing;
using static src.jRandomSkills;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace src.player.skills
{
public class ThrowingKnife : ISkill
{
private const Skills skillName = Skills.ThrowingKnife;
private readonly static ConcurrentDictionary<uint, KnifeInfo> knivesInfo = [];
public static void LoadSkill()
{
SkillUtils.RegisterSkill(skillName, SkillsInfo.GetValue<string>(skillName, "color"));
}
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(rememberOwed: true);
}
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();
}
public static void PlayerMakeSound(UserMessage um)
{
var soundevent = um.ReadUInt("soundevent_hash");
var userIndex = um.ReadUInt("source_entity_index");
if (soundevent != 3208928088 || userIndex != 0) return;
byte[] packedParams = um.ReadBytes("packed_params");
Vector? soundPos = null;
if (packedParams != null && packedParams.Length >= 19)
{
try
{
float x = BitConverter.ToSingle(packedParams, 7);
float y = BitConverter.ToSingle(packedParams, 11);
float z = BitConverter.ToSingle(packedParams, 15);
soundPos = new Vector(x, y, z);
}
catch { }
}
if (soundPos == null) return;
(var knife, var knifeInfo) = GetClosetKnife(soundPos);
if (knife == null || !knife.IsValid || knife.AbsRotation == null)
return;
if (knifeInfo == null || knifeInfo.InitialLook == null || knife.Collision == null)
return;
knifeInfo.IsDropped = false;
QAngle rotation = new(knifeInfo.InitialLook?.X, knifeInfo.InitialLook?.Y, knife.AbsRotation.Z);
Vector offset = SkillUtils.GetForwardVector(new QAngle(rotation.X - 90, rotation.Y, rotation.Z)) * 2;
knife.Teleport(
new Vector(soundPos.X - offset.X, soundPos.Y - offset.Y, soundPos.Z - offset.Z),
rotation,
Vector.Zero
);
knife.Collision.CollisionAttribute.InteractsWith = knifeInfo.OriginalCollisionMask;
Utilities.SetStateChanged(knife, "CCollisionProperty", "m_collisionAttribute");
var world = Utilities.GetEntityFromIndex<CBaseEntity>(0);
if (world != null && world.IsValid)
knife.AcceptInput("SetParent", world, world, "!activator");
if (knifeInfo.Timer != null)
knifeInfo.Timer?.Kill();
uint knifeIndex = knife.Index;
knifeInfo.Timer = Instance.AddTimer(3f, () =>
{
var droppedKnife = Utilities.GetEntityFromIndex<CBaseEntity>((int)knifeIndex);
if (droppedKnife == null || !droppedKnife.IsValid) return;
var player = Utilities.GetPlayerFromIndex((int)knifeInfo.PlayerIndex);
if (player != null && player.IsValid && CheckHasKnife(player)) return;
droppedKnife.AcceptInput("ClearParent");
}, TimerFlags.STOP_ON_MAPCHANGE);
}
public static bool OnWeaponCanAcquire(DynamicHook hook, CCSPlayerController player, CEconItemView econItem, CCSWeaponBaseVData vdata)
{
// vdata can be non-null yet point at freed/invalid schema memory; reading
// .Name then throws NullReferenceException inside get_Name(). Guard the handle.
if (vdata == null || vdata.Handle == IntPtr.Zero) return false;
string weaponName = vdata.Name;
if (string.IsNullOrEmpty(weaponName)) return false;
if (!weaponName.Contains("knife") && !weaponName.Contains("bayonet"))
return false;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return false;
if (!knivesInfo.TryGetValue(player.Index, out KnifeInfo? knifeInfo) || knifeInfo == null)
return false;
if (knifeInfo.KnifeId == econItem.ItemID)
{
knifeInfo.IsDropped = false;
if (knifeInfo.Timer != null)
{
knifeInfo.Timer?.Kill();
knifeInfo.Timer = null;
}
return false;
}
if (!knifeInfo.IsDropped) return false;
hook.SetReturn(AcquireResult.InvalidItem);
return true;
}
public static void EnableSkill(CCSPlayerController _)
{
Event.EnableTransmit();
SkillUtils.ForceFullUpdateToAll();
}
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)
{
wasDropped |= knifeInfo.IsDropped;
DisableKnifeSkill(knifeInfo);
}
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);
}
public static void DisableKnifeSkill(KnifeInfo knifeInfo)
{
if (knifeInfo.Timer != null)
{
knifeInfo.Timer?.Kill();
knifeInfo.Timer = null;
}
var trigger = knifeInfo.TriggerIndex;
var glow = knifeInfo.GlowIndex;
var relay = knifeInfo.RelayIndex;
knifeInfo.GlowIndex = null;
knifeInfo.TriggerIndex = null;
knifeInfo.RelayIndex = null;
SkillUtils.SafeKillEntity<CTriggerMultiple>(trigger);
SkillUtils.SafeKillEntity<CBaseEntity>(glow);
SkillUtils.SafeKillEntity<CBaseEntity>(relay);
if (knifeInfo.IsDropped)
{
knifeInfo.IsDropped = false;
EntityManager.DestroyEntity(knifeInfo.KnifeIndex, 0.25f);
}
}
public static void UseSkill(CCSPlayerController player)
{
if (player == null || !player.IsValid) return;
if (roundEnded) return;
var playerInfo = PlayerManager.GetPlayerByIndex(player!.Index);
if (playerInfo?.Skill != skillName) return;
DropKnife(player);
}
private static void DropKnife(CCSPlayerController player)
{
var playerEvent = PlayerManager.GetPlayerFromEvent(player);
if (playerEvent == null || !player.IsValid) return;
playerEvent.ExecuteClientCommand("slot3");
Instance.AddTickTimer(8, () =>
{
if (player == null || !player.IsValid || roundEnded) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.WeaponServices == null) return;
var weapon = pawn.WeaponServices.ActiveWeapon.Value;
if (weapon == null || !weapon.IsValid || (!weapon.DesignerName.Contains("knife") && !weapon.DesignerName.Contains("bayonet"))) return;
player.DropActiveWeapon();
Server.NextFrame(() =>
{
if (player == null || !player.IsValid || roundEnded) return;
if (weapon == null || !weapon.IsValid) return;
ThrowKnife(player, weapon);
});
});
}
private static void ThrowKnife(CCSPlayerController player, CBasePlayerWeapon knife)
{
if (player == null || !player.IsValid) return;
if (knife == null || !knife.IsValid || knife.AbsOrigin == null) return;
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.AbsOrigin == null) return;
float force = 2000f;
Vector pos = new(pawn.AbsOrigin.X, pawn.AbsOrigin.Y, pawn.AbsOrigin.Z + pawn.ViewOffset.Z);
QAngle angle = new(0, 0, 0);
Vector vel = SkillUtils.GetForwardVector(pawn.EyeAngles) * force;
knife.Teleport(pos, angle, vel);
if (knife.Collision == null)
return;
ulong originalMask = knife.Collision.CollisionAttribute.InteractsWith;
knife.Collision.CollisionAttribute.InteractsWith = pawn.Collision.CollisionAttribute.InteractsWith;
if (!knivesInfo.ContainsKey(player.Index))
{
uint? triggerIndex = CreateTrigger(player, knife);
(uint? relayIndex, uint? glowIndex) = CreateGlow(player, knife);
knivesInfo.TryAdd(player.Index, new KnifeInfo
{
PlayerIndex = player.Index,
KnifeIndex = knife.Index,
KnifeId = knife.AttributeManager.Item.ItemID,
TriggerIndex = triggerIndex,
GlowIndex = glowIndex,
RelayIndex = relayIndex,
OriginalCollisionMask = originalMask
});
}
if (knivesInfo.TryGetValue(player.Index, out KnifeInfo? knifeInfo))
{
knifeInfo.IsDropped = true;
knifeInfo.InitialLook = new QAngle(pawn.V_angle.X + 90, pawn.V_angle.Y, pawn.V_angle.Z);
}
}
private static uint? CreateTrigger(CCSPlayerController player, CBasePlayerWeapon knife)
{
if (player == null || !player.IsValid) return null;
if (knife == null || !knife.IsValid || knife.AbsOrigin == null) return null;
var trigger = SkillUtils.CreateTrigger($"trowingknife", 10, knife.AbsOrigin);
if (trigger == null || !trigger.IsValid) return null;
trigger.AcceptInput("SetParent", knife, knife, "!activator");
return trigger.Index;
}
private static (uint?, uint?) CreateGlow(CCSPlayerController player, CBaseEntity knife)
{
if (player == null || !player.IsValid) return (null, null);
if (knife == null || !knife.IsValid || knife.AbsOrigin == null) return (null, null);
var modelRelay = EntityManager.CreateTrackedPhysicsProp(player.Index);
if (modelRelay == null) return (null, null);
modelRelay.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(modelRelay.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
modelRelay.SetModel(knife!.CBodyComponent!.SceneNode!.GetSkeletonInstance().ModelState.ModelName);
modelRelay.Spawnflags = 256u;
modelRelay.Render = Color.FromArgb(1, 255, 255, 255);
modelRelay.DispatchSpawn();
modelRelay.Teleport(knife.AbsOrigin, knife.AbsRotation, knife.AbsVelocity);
modelRelay.AcceptInput("SetParent", knife, modelRelay, "!activator");
var modelGlow = EntityManager.CreateTrackedPhysicsProp(player.Index);
if (modelGlow == null) return (null, null);
modelGlow.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags = (uint)(modelGlow.CBodyComponent!.SceneNode!.Owner!.Entity!.Flags & ~(1 << 2));
modelGlow.SetModel(knife.CBodyComponent!.SceneNode!.GetSkeletonInstance().ModelState.ModelName);
modelGlow.DispatchSpawn();
modelGlow.Glow.GlowColorOverride = Color.GreenYellow;
modelGlow.Glow.GlowRange = 5000;
modelGlow.Glow.GlowTeam = -1;
modelGlow.Glow.GlowType = 3;
modelGlow.Glow.GlowRangeMin = 10;
modelGlow.Teleport(knife.AbsOrigin, knife.AbsRotation, knife.AbsVelocity);
modelGlow.AcceptInput("SetParent", modelRelay, modelGlow, "!activator");
return (modelRelay.Index, modelGlow.Index);
}
public static void OnTriggerEnter(CBaseTrigger trigger, CBaseEntity entity)
{
if (entity == null || !entity.IsValid || trigger == null || !trigger.IsValid) return;
string triggerName = trigger.Globalname;
if (entity.DesignerName != "player" || string.IsNullOrEmpty(triggerName) || !triggerName.StartsWith("trowingknife")) return;
CCSPlayerPawn victimPawn = entity.As<CCSPlayerPawn>();
if (victimPawn == null || !victimPawn.IsValid || victimPawn.LifeState != (byte)LifeState_t.LIFE_ALIVE) return;
KnifeInfo? knifeInfo = knivesInfo.Values.FirstOrDefault(ki => ki.TriggerIndex == trigger.Index);
if (knifeInfo != null)
{
var thrower = Utilities.GetPlayerFromIndex((int)knifeInfo.PlayerIndex);
if (thrower == null || !thrower.IsValid) return;
var throwerPawn = thrower.Pawn?.Value;
if (throwerPawn == null || !throwerPawn.IsValid)
return;
if (victimPawn.Index == throwerPawn.Index)
return;
var victimController = victimPawn.Controller.Value?.As<CCSPlayerController>();
bool friendlyFire = SkillsInfo.GetValue<bool>(skillName, "friendlyFire");
if (!friendlyFire)
{
if (thrower.TeamNum == victimPawn.TeamNum) return;
if (victimController != null && victimController.IsValid && thrower.Team == victimController.Team) return;
}
if (CheckHasKnife(thrower!)) return;
SkillUtils.TakeHealth(victimPawn, SkillsInfo.GetValue<int>(skillName, "damage"), thrower, KillfeedIcons.Knife);
}
}
private static bool CheckHasKnife(CCSPlayerController player)
{
var pawn = player.PlayerPawn?.Value;
if (pawn == null || !pawn.IsValid || pawn.WeaponServices == null) return false;
return pawn.WeaponServices.MyWeapons.Any(w =>
w != null && w.IsValid && w.Value != null && w.Value.IsValid &&
(w.Value.DesignerName.Contains("knife") || w.Value.DesignerName.Contains("bayonet")));
}
private static (CBaseEntity?, KnifeInfo?) GetClosetKnife(Vector pos)
{
double minDistance = double.MaxValue;
KnifeInfo? minKnifeInfo = null;
CBaseEntity? closet = null;
foreach (var knifeInfo in knivesInfo.Values.ToArray())
{
if (!knifeInfo.IsDropped) continue;
var knifeEntity = Utilities.GetEntityFromIndex<CBaseEntity>((int)knifeInfo.KnifeIndex);
if (knifeEntity == null || !knifeEntity.IsValid || knifeEntity.AbsOrigin == null) continue;
double distance = SkillUtils.GetDistance(knifeEntity.AbsOrigin, pos);
if (distance < minDistance)
{
minDistance = distance;
closet = knifeEntity;
minKnifeInfo = knifeInfo;
}
}
return (closet, minKnifeInfo);
}
private static readonly List<(uint OwnerIndex, uint GlowEntityIndex)> transmitGlows = [];
public static void CheckTransmit([CastFrom(typeof(nint))] CCheckTransmitInfoList infoList)
{
if (knivesInfo.IsEmpty) return;
transmitGlows.Clear();
foreach (var (knifeOwnerIndex, knifeInfo) in knivesInfo)
{
if (knifeInfo.GlowIndex == null) continue;
var glowEntity = Utilities.GetEntityFromIndex<CBaseEntity>((int)knifeInfo.GlowIndex);
if (glowEntity == null || !glowEntity.IsValid)
{
knifeInfo.GlowIndex = null;
continue;
}
transmitGlows.Add((knifeOwnerIndex, glowEntity.Index));
}
if (transmitGlows.Count == 0) return;
foreach (var (info, player) in infoList)
{
if (player == null || !player.IsValid) continue;
uint viewerIndex = PlayerManager.GetPlayerEvent(player)?.Index ?? player.Index;
uint? observedPlayerIndex = null;
var playerPawn = player.Pawn?.Value;
if (playerPawn != null && playerPawn.IsValid)
{
var obs = playerPawn.ObserverServices?.ObserverTarget?.Value;
if (obs != null && obs.IsValid)
{
var observed = PlayerManager.GetTickPlayers().FirstOrDefault(p =>
p != null && p.IsValid &&
p.Pawn?.Value?.Handle == obs.Handle);
if (observed != null)
observedPlayerIndex = observed.Index;
}
}
foreach (var (knifeOwnerIndex, glowEntityIndex) in transmitGlows)
{
if (knifeOwnerIndex == viewerIndex) continue;
if (observedPlayerIndex.HasValue && observedPlayerIndex.Value == knifeOwnerIndex) continue;
if (info.TransmitEntities.Contains(glowEntityIndex))
info.TransmitEntities.Remove(glowEntityIndex);
}
}
}
public class KnifeInfo()
{
public required uint PlayerIndex { get; set; }
public required uint KnifeIndex { get; set; }
public required ulong KnifeId { get; set; }
public required uint? TriggerIndex { get; set; }
public required uint? GlowIndex { get; set; }
public required uint? RelayIndex { get; set; }
public ulong OriginalCollisionMask { get; set; }
public bool IsDropped { get; set; } = false;
public QAngle? InitialLook { get; set; } = null;
public Timer? Timer { get; set; } = null;
}
public class SkillConfig(Skills skill = skillName, bool active = true, string color = "#8f108f", 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, bool friendlyFire = false, int damage = 9999) : SkillsInfo.DefaultSkillInfo(skill, active, color, onlyTeam, disableOnFreezeTime, needsTeammates, requiredPermission, hudDuration, descriptionHudDuration, maxPerServer, rarity)
{
public bool FriendlyFire { get; set; } = friendlyFire;
public int Damage { get; set; } = damage;
}
}
}