diff --git a/Game.Server/BaseCombatCharacter.cs b/Game.Server/BaseCombatCharacter.cs index 3e55da7e8..a206b0571 100644 --- a/Game.Server/BaseCombatCharacter.cs +++ b/Game.Server/BaseCombatCharacter.cs @@ -1,10 +1,11 @@ -using Game.Shared; +using Game.Shared; using Source; using Source.Common; using Source.Common.Commands; using Source.Common.Engine; using Source.Common.Formats.BSP; +using Source.Common.Mathematics; using System.Numerics; using System.Runtime.CompilerServices; @@ -284,6 +285,66 @@ public virtual void Weapon_FrameUpdate() { ActiveWeapon.Get()!.Operator_FrameUpdate(this); } + public virtual void Weapon_HandleAnimEvent(ref AnimEvent animEvent) { + if (ActiveWeapon.Get() != null) + ActiveWeapon.Get()!.Operator_HandleAnimEvent(ref animEvent, this); + } + + public static readonly ConVar ai_show_hull_attacks = new("ai_show_hull_attacks", "0"); + + public virtual BaseEntity? CheckTraceHullAttack(float dist, in Vector3 mins, in Vector3 maxs, float damage, DamageType dmgType, float forceScale = 1.0f, bool damageAnyNPC = false) { + MathLib.AngleVectors(GetAbsAngles(), out Vector3 forward); + Vector3 start = GetAbsOrigin(); + + float verticalOffset = WorldAlignSize().Z * 0.5f; + + if (verticalOffset < maxs.Z) + verticalOffset = maxs.Z + 1.0f; + + start.Z += verticalOffset; + Vector3 end = start + (forward * dist); + return CheckTraceHullAttack(start, end, mins, maxs, damage, dmgType, forceScale, damageAnyNPC); + } + + public virtual BaseEntity? CheckTraceHullAttack(in Vector3 start, in Vector3 end, in Vector3 mins, in Vector3 maxs, float damage, DamageType dmgType, float forceScale = 1.0f, bool damageAnyNPC = false) { + if (ai_show_hull_attacks.GetBool()) { + float length = (end - start).Length(); + Vector3 direction = end - start; + MathLib.VectorNormalize(ref direction); + Vector3 hullMaxs = maxs; + hullMaxs.X = length + hullMaxs.X; + DebugOverlay.BoxDirection(start, mins, hullMaxs, direction, 100, 255, 255, 20, 1.0f); + DebugOverlay.BoxDirection(start, mins, maxs, direction, 255, 0, 0, 20, 1.0f); + } + + TakeDamageInfo dmgInfo = new(this, this, damage, dmgType); + + TraceFilterMelee traceFilter = new(this, Source.CollisionGroup.Projectile, dmgInfo, forceScale, damageAnyNPC); + + Ray ray = default; + ray.Init(start, end, mins, maxs); + + enginetrace.TraceRay(in ray, Mask.ShotHull, ref traceFilter, out Trace tr); + + BaseEntity? entity = traceFilter.Hit; + + if (entity == null) { + Vector3 topCenter = GetAbsOrigin(); + CollisionProp().WorldSpaceAABB(out _, out Vector3 aabbMaxs); + topCenter.Z = aabbMaxs.Z + 1.0f; + + ray.Init(topCenter, end, mins, maxs); + enginetrace.TraceRay(in ray, Mask.ShotHull, ref traceFilter, out tr); + + entity = traceFilter.Hit; + } + + if (entity != null && !entity.CanBeHitByMeleeAttack(this)) + entity = null; + + return entity; + } + public BaseCombatWeapon? Weapon_Create(ReadOnlySpan weaponName) => throw new NotImplementedException(); public virtual void Weapon_Equip(BaseCombatWeapon weapon) { for (int i = 0; i < MAX_WEAPONS; i++) { @@ -462,3 +523,67 @@ public Vector3 GetAttackSpread(BaseCombatWeapon? weapon, BaseEntity? target = nu return VECTOR_CONE_15DEGREES; } } + +public struct TraceFilterMelee(IHandleEntity? passentity, CollisionGroup collisionGroup, TakeDamageInfo dmgInfo, float forceScale, bool damageAnyNPC) : ITraceFilter +{ + public IHandleEntity? PassEnt = passentity; + public CollisionGroup CollisionGroup = collisionGroup; + public TakeDamageInfo DmgInfo = dmgInfo; + public BaseEntity? Hit; + public float ForceScale = forceScale; + public bool DamageAnyNPC = damageAnyNPC; + + public bool ShouldHitEntity(IHandleEntity handleEntity, Contents contentsMask) { + if (!StandardFilterRules(handleEntity, contentsMask)) + return false; + + if (!PassServerEntityFilter(handleEntity, PassEnt)) + return false; + + BaseEntity? entity = EntityFromEntityHandle(handleEntity); + + if (entity != null) { + if (!entity.ShouldCollide(CollisionGroup, contentsMask)) + return false; + + if (!g_pGameRules.ShouldCollide(CollisionGroup, entity.GetCollisionGroup())) + return false; + + if (entity.m_takedamage == (byte)Damage.No) + return false; + + Vector3 attackDir = entity.WorldSpaceCenter() - DmgInfo.GetAttacker()!.WorldSpaceCenter(); + MathLib.VectorNormalize(ref attackDir); + + TakeDamageInfo info = DmgInfo; + CalculateMeleeDamageForce(ref info, attackDir, info.GetAttacker()!.WorldSpaceCenter(), ForceScale); + + BaseCombatCharacter? bcc = ToBaseCombatCharacter(info.GetAttacker()); + BaseCombatCharacter? victimBCC = ToBaseCombatCharacter(entity); + + if (bcc != null && victimBCC != null) { + if (DamageAnyNPC || bcc.IRelationType(entity) == Disposition.HT) { + if (info.GetDamage() != 0) + entity.TakeDamage(info); + + SoundEnt.InsertSound(SoundInstanceType.Combat, info.GetDamagePosition(), 200, 0.2f, info.GetAttacker()); + + Hit = entity; + return true; + } + } + else { + Hit = entity; + + Pickup.ForcePlayerToDropThisObject(entity); + + if (info.GetDamage() != 0) + entity.TakeDamage(info); + } + } + + return false; + } + + public readonly TraceType GetTraceType() => TraceType.EntitiesOnly; +} diff --git a/Game.Server/BaseCombatWeapon.cs b/Game.Server/BaseCombatWeapon.cs index 839b73594..6de975dc5 100644 --- a/Game.Server/BaseCombatWeapon.cs +++ b/Game.Server/BaseCombatWeapon.cs @@ -34,7 +34,74 @@ public static void W_Precache(){ public virtual Capability CapabilitiesGet() => 0; - public virtual void Operator_FrameUpdate(BaseCombatCharacter op) => throw new NotImplementedException(); + public virtual void Operator_FrameUpdate(BaseCombatCharacter op) { + StudioFrameAdvance(); + + if (IsSequenceFinished()) { + if (SequenceLoops) { + int sequence = SelectWeightedSequence(GetActivity()); + if (sequence != StudioHdr.ACTIVITY_NOT_AVAILABLE) + ResetSequence(sequence); + } + } + + DispatchAnimEvents(op); + + BasePlayer? owner = ToBasePlayer(GetOwner()); + + if (owner == null) + return; + + BaseViewModel? vm = owner.GetViewModel(nViewModelIndex); + + if (vm != null) { + vm.StudioFrameAdvance(); + vm.DispatchAnimEvents(this); + } + } + + public virtual void Operator_HandleAnimEvent(ref AnimEvent animEvent, BaseCombatCharacter op) { + if ((animEvent.Type & AnimEventType.NewEventSystem) != 0 && (animEvent.Type & AnimEventType.Server) != 0) { + if (animEvent.Event == (int)Animevent.AE_NPC_WEAPON_FIRE) { + bool secondary = int.TryParse(animEvent.Options, out int value) && value != 0; + Operator_ForceNPCFire(op, secondary); + return; + } + else if (animEvent.Event == (int)Animevent.AE_WPN_PLAYWPNSOUND) { + int snd = WeaponParse.GetWeaponSoundFromString(animEvent.Options); + if (snd != -1) + WeaponSound((WeaponSound)snd); + } + } + + DevWarning(2, $"Unhandled animation event {animEvent.Event} from {op.GetClassname()} --> {GetClassname()}\n"); + } + + public override void HandleAnimEvent(ref AnimEvent animEvent) { + BasePlayer? owner = ToBasePlayer(GetOwner()); + + if (owner != null) + Operator_HandleAnimEvent(ref animEvent, owner); + } + + public virtual void Operator_ForceNPCFire(BaseCombatCharacter op, bool secondary) { } + + public virtual SCOND_t WeaponRangeAttack1Condition(float dot, float dist) { + if (UsesPrimaryAmmo() && !HasPrimaryAmmo()) + return SCOND_t.COND_NO_PRIMARY_AMMO; + else if (dist < MinRange1) + return SCOND_t.COND_TOO_CLOSE_TO_ATTACK; + else if (dist > MaxRange1) + return SCOND_t.COND_TOO_FAR_TO_ATTACK; + else if (dot < 0.5) + return SCOND_t.COND_NOT_FACING_ATTACK; + + return SCOND_t.COND_CAN_RANGE_ATTACK1; + } + + public virtual SCOND_t WeaponRangeAttack2Condition(float dot, float dist) => SCOND_t.COND_NONE; + public virtual SCOND_t WeaponMeleeAttack1Condition(float dot, float dist) => SCOND_t.COND_NONE; + public virtual SCOND_t WeaponMeleeAttack2Condition(float dot, float dist) => SCOND_t.COND_NONE; public BaseEntity? Respawn() { BaseEntity? newWeapon = Create(GetClassname(), g_pGameRules.VecWeaponRespawnSpot(this), GetLocalAngles(), GetOwnerEntity()); diff --git a/Game.Server/BaseEntity.cs b/Game.Server/BaseEntity.cs index c79c055a2..ca7a3f19a 100644 --- a/Game.Server/BaseEntity.cs +++ b/Game.Server/BaseEntity.cs @@ -3183,6 +3183,8 @@ public virtual void Teleport(Vector3? newPosition, QAngle? newAngles, Vector3? n } public bool IsWorld() => EntIndex() == 0; + public virtual bool CanBeHitByMeleeAttack(BaseEntity? attacker) => true; + public virtual bool FVisible(BaseEntity entity, Mask traceMask = Mask.BlockLOS) { if ((entity.GetFlags() & EntityFlags.NoTarget) != 0) return false; diff --git a/Game.Server/PlayerPickup.cs b/Game.Server/PlayerPickup.cs index 8da92aff6..d9beae73e 100644 --- a/Game.Server/PlayerPickup.cs +++ b/Game.Server/PlayerPickup.cs @@ -1,6 +1,7 @@ using Game.Server; using Source.Common.Mathematics; +using Source.Common.Physics; using System.Numerics; @@ -34,7 +35,24 @@ public enum PhysGunForce public static class Pickup { public static void PlayerPickupObject(BasePlayer player, BaseEntity? obj) => throw new NotImplementedException(); - public static void ForcePlayerToDropThisObject(BaseEntity? target) => throw new NotImplementedException(); + public static void ForcePlayerToDropThisObject(BaseEntity? target) { + if (target == null) + return; + + IPhysicsObject? physics = target.VPhysicsGetObject(); + + if (physics == null) + return; + + if ((physics.GetGameFlags() & PhysicsFlags.PlayerHeld) != 0) { + for (int i = 1; i <= gpGlobals.MaxClients; i++) { + BasePlayer? player = Util.PlayerByIndex(i); + if (player == null) + return; + player.ForceDropOfCarriedPhysObjects(target); + } + } + } public static void OnPhysGunDrop(BaseEntity? obj, BasePlayer player, PhysGunDrop reason) => throw new NotImplementedException(); public static void OnPhysGunPickup(BaseEntity? obj, BasePlayer player, PhysGunPickup reason = PhysGunPickup.PickedUpByCannon) => throw new NotImplementedException(); diff --git a/Game.Shared/GarrysMod/WeaponCrowbar.cs b/Game.Shared/GarrysMod/WeaponCrowbar.cs index 7b797caab..9ef0b2566 100644 --- a/Game.Shared/GarrysMod/WeaponCrowbar.cs +++ b/Game.Shared/GarrysMod/WeaponCrowbar.cs @@ -1,6 +1,11 @@ #if (CLIENT_DLL || GAME_DLL) && GMOD_DLL +#if GAME_DLL +using Game.Server; +#endif using Source; using Source.Common; +using Source.Common.Commands; +using Source.Common.Formats.BSP; using Source.Common.Mathematics; using System.Numerics; @@ -49,6 +54,87 @@ public static readonly ]; public override ReadOnlySpan ActivityList() => acttable; + + public static readonly ConVar sk_npc_dmg_crowbar = new("sk_npc_dmg_crowbar", "0"); + public static readonly ConVar sk_crowbar_lead_time = new("sk_crowbar_lead_time", "0.9"); + + public override SCOND_t WeaponMeleeAttack1Condition(float dot, float dist) { + AI_BaseNPC npc = GetOwner()!.MyNPCPointer()!; + BaseEntity? enemy = npc.GetEnemy(); + if (enemy == null) + return SCOND_t.COND_NONE; + + Vector3 velocity = enemy.GetSmoothedVelocity(); + + float dt = sk_crowbar_lead_time.GetFloat(); + dt += random.RandomFloat(-0.3f, 0.2f); + if (dt < 0.0f) + dt = 0.0f; + + MathLib.VectorMA(enemy.WorldSpaceCenter(), dt, velocity, out Vector3 extrapolatedPos); + + Vector3 delta = extrapolatedPos - npc.WorldSpaceCenter(); + + if (MathF.Abs(delta.Z) > 70) + return SCOND_t.COND_TOO_FAR_TO_ATTACK; + + Vector3 forward = npc.BodyDirection2D(); + delta.Z = 0.0f; + Vector2 delta2D = delta.AsVector2D(); + float extrapolatedDist = delta2D.Length(); + if (extrapolatedDist != 0) + delta2D /= extrapolatedDist; + if ((dist > 64) && (extrapolatedDist > 64)) + return SCOND_t.COND_TOO_FAR_TO_ATTACK; + + float extrapolatedDot = MathLib.DotProduct2D(delta2D, forward.AsVector2D()); + if ((dot < 0.7) && (extrapolatedDot < 0.7)) + return SCOND_t.COND_NOT_FACING_ATTACK; + + return SCOND_t.COND_CAN_MELEE_ATTACK1; + } + + void HandleAnimEventMeleeHit(ref AnimEvent animEvent, BaseCombatCharacter op) { + MathLib.AngleVectors(GetAbsAngles(), out Vector3 direction); + + BaseEntity? enemy = op.MyNPCPointer()?.GetEnemy(); + if (enemy != null) { + Vector3 delta = enemy.WorldSpaceCenter() - op.Weapon_ShootPosition(); + MathLib.VectorNormalize(ref delta); + + Vector2 delta2D = delta.AsVector2D(); + float length2D = delta2D.Length(); + if (length2D != 0) + delta2D /= length2D; + if (MathLib.DotProduct2D(delta2D, direction.AsVector2D()) > 0.8f) + direction = delta; + } + + MathLib.VectorMA(op.Weapon_ShootPosition(), 50, direction, out Vector3 end); + BaseEntity? hurt = op.CheckTraceHullAttack(op.Weapon_ShootPosition(), end, + new(-16, -16, -16), new(36, 36, 36), sk_npc_dmg_crowbar.GetFloat(), DamageType.Club, 0.75f); + + if (hurt != null) { + WeaponSound(Shared.WeaponSound.MeleeHit); + + Util.TraceLine(op.Weapon_ShootPosition(), hurt.GetAbsOrigin(), Mask.ShotHull, op, Source.CollisionGroup.None, out Trace traceHit); + ImpactEffect(ref traceHit); + } + else + WeaponSound(Shared.WeaponSound.MeleeMiss); + } + + public override void Operator_HandleAnimEvent(ref AnimEvent animEvent, BaseCombatCharacter op) { + switch (animEvent.Event) { + case EVENT_WEAPON_MELEE_HIT: + HandleAnimEventMeleeHit(ref animEvent, op); + break; + + default: + base.Operator_HandleAnimEvent(ref animEvent, op); + break; + } + } #endif public override float GetDamageForActivity(Activity hitActivity) => 25.0f; diff --git a/Game.Shared/GarrysMod/WeaponStunStick.cs b/Game.Shared/GarrysMod/WeaponStunStick.cs index ce4e00e13..33d436ac1 100644 --- a/Game.Shared/GarrysMod/WeaponStunStick.cs +++ b/Game.Shared/GarrysMod/WeaponStunStick.cs @@ -1,12 +1,19 @@ #if (CLIENT_DLL || GAME_DLL) && GMOD_DLL +#if GAME_DLL +using Game.Server; +#endif +using Source; using Source.Common; +using Source.Common.Mathematics; + +using System.Numerics; namespace Game.Shared.GarrysMod; using FIELD = Source.FIELD; [LinkEntityToClass("weapon_stunstick")] [PrecacheWeaponRegister("weapon_stunstick")] [NetworkName("CWeaponStunStick")] -public class WeaponStunStick : BaseHL2MPBludgeonWeapon +public partial class WeaponStunStick : BaseHL2MPBludgeonWeapon { public static readonly #if CLIENT_DLL @@ -28,7 +35,210 @@ public static readonly public static readonly new ServerClass ServerClass = new ServerClass(DT_WeaponStunStick); #endif [NetworkName("m_bActive")] +#if CLIENT_DLL public bool Active; - public override float GetFireRate() => 0.2f; +#else + [NetworkVar] public partial bool Active { get; set; } +#endif + + public const float STUNSTICK_RANGE = 75.0f; + public const float STUNSTICK_REFIRE = 0.8f; + public const string STUNSTICK_BEAM_MATERIAL = "sprites/lgtning.vmt"; + +#if !CLIENT_DLL + static readonly ActTable[] acttable = [ + new() { BaseAct = Activity.ACT_RANGE_ATTACK1, WeaponAct = Activity.ACT_RANGE_ATTACK_SLAM, Required = true }, + new() { BaseAct = Activity.ACT_HL2MP_IDLE, WeaponAct = Activity.ACT_HL2MP_IDLE_MELEE, Required = false }, + new() { BaseAct = Activity.ACT_HL2MP_RUN, WeaponAct = Activity.ACT_HL2MP_RUN_MELEE, Required = false }, + new() { BaseAct = Activity.ACT_HL2MP_IDLE_CROUCH, WeaponAct = Activity.ACT_HL2MP_IDLE_CROUCH_MELEE, Required = false }, + new() { BaseAct = Activity.ACT_HL2MP_WALK_CROUCH, WeaponAct = Activity.ACT_HL2MP_WALK_CROUCH_MELEE, Required = false }, + new() { BaseAct = Activity.ACT_HL2MP_GESTURE_RANGE_ATTACK, WeaponAct = Activity.ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, Required = false }, + new() { BaseAct = Activity.ACT_HL2MP_GESTURE_RELOAD, WeaponAct = Activity.ACT_HL2MP_GESTURE_RELOAD_MELEE, Required = false }, + new() { BaseAct = Activity.ACT_HL2MP_JUMP, WeaponAct = Activity.ACT_HL2MP_JUMP_MELEE, Required = false }, + ]; + + public override ReadOnlySpan ActivityList() => acttable; +#endif + + public WeaponStunStick() { + Active = false; + } + + public override void Spawn() { + Precache(); + + base.Spawn(); + AddSolidFlags(SolidFlags.NotStandable); + } + + public override void Precache() { + base.Precache(); + + PrecacheScriptSound("Weapon_StunStick.Activate"); + PrecacheScriptSound("Weapon_StunStick.Deactivate"); + + PrecacheModel(STUNSTICK_BEAM_MATERIAL); + PrecacheModel("sprites/light_glow02_add.vmt"); + PrecacheModel("effects/blueflare1.vmt"); + PrecacheModel("sprites/light_glow02_add_noz.vmt"); + } + + public override float GetRange() => STUNSTICK_RANGE; + public override float GetFireRate() => STUNSTICK_REFIRE; + + public override float GetDamageForActivity(Activity hitActivity) => 40.0f; + + public override bool PlayFleshyHittySoundOnHit() => true; + + public override void SecondaryAttack() { } + +#if !CLIENT_DLL + public override SCOND_t WeaponMeleeAttack1Condition(float dot, float dist) { + AI_BaseNPC npc = GetOwner()!.MyNPCPointer()!; + BaseEntity? enemy = npc.GetEnemy(); + if (enemy == null) + return SCOND_t.COND_NONE; + + enemy.GetVelocity(out Vector3 velocity, out _); + + float dt = WeaponCrowbar.sk_crowbar_lead_time.GetFloat(); + dt += random.RandomFloat(-0.3f, 0.2f); + if (dt < 0.0f) + dt = 0.0f; + + MathLib.VectorMA(enemy.WorldSpaceCenter(), dt, velocity, out Vector3 extrapolatedPos); + + Vector3 delta = extrapolatedPos - npc.WorldSpaceCenter(); + + if (MathF.Abs(delta.Z) > 70) + return SCOND_t.COND_TOO_FAR_TO_ATTACK; + + Vector3 forward = npc.BodyDirection2D(); + delta.Z = 0.0f; + float extrapolatedDot = MathLib.DotProduct2D(delta.AsVector2D(), forward.AsVector2D()); + if ((dot < 0.7) && (extrapolatedDot < 0.7)) + return SCOND_t.COND_NOT_FACING_ATTACK; + + float extrapolatedDist = MathLib.Vector2DLength(delta.AsVector2D()); + + if (enemy.IsPlayer()) { + Vector3 projectEnemy = enemy.GetAbsOrigin() + (enemy.GetAbsVelocity() * 0.35f); + Vector3 projectMe = GetAbsOrigin(); + + if (MathLib.Vector2DLength((projectMe - projectEnemy).AsVector2D()) <= 48.0f) + return SCOND_t.COND_CAN_MELEE_ATTACK1; + } + + float targetDist = 48.0f; + if ((dist > targetDist) && (extrapolatedDist > targetDist)) + return SCOND_t.COND_TOO_FAR_TO_ATTACK; + + return SCOND_t.COND_CAN_MELEE_ATTACK1; + } + + public override void Operator_HandleAnimEvent(ref AnimEvent animEvent, BaseCombatCharacter op) { + switch (animEvent.Event) { + case EVENT_WEAPON_MELEE_HIT: { + MathLib.AngleVectors(GetAbsAngles(), out Vector3 direction); + + BaseEntity? enemy = op.MyNPCPointer()?.GetEnemy(); + if (enemy != null) { + Vector3 delta = enemy.WorldSpaceCenter() - op.Weapon_ShootPosition(); + MathLib.VectorNormalize(ref delta); + + Vector2 delta2D = delta.AsVector2D(); + float length2D = delta2D.Length(); + if (length2D != 0) + delta2D /= length2D; + if (MathLib.DotProduct2D(delta2D, direction.AsVector2D()) > 0.8f) + direction = delta; + } + + MathLib.VectorMA(op.Weapon_ShootPosition(), 32, direction, out Vector3 end); + BaseEntity? hurt = op.CheckTraceHullAttack(op.Weapon_ShootPosition(), end, + new(-16, -16, -40), new(16, 16, 16), GetDamageForActivity(GetActivity()), DamageType.Club, 0.5f, false); + + if (hurt != null) { + WeaponSound(Shared.WeaponSound.MeleeHit); + + BasePlayer? player = ToBasePlayer(hurt); + + if (player != null && (player.GetFlags() & EntityFlags.GodMode) == 0) { + float yawKick = random.RandomFloat(-48, -24); + + player.ViewPunch(new QAngle(-16, yawKick, 2)); + + Vector3 dir = hurt.GetAbsOrigin() - GetAbsOrigin(); + + if (player.GetGroundEntity() == op) { + dir = direction; + dir.Z = 0; + } + + MathLib.VectorNormalize(ref dir); + + dir *= 500.0f; + + if ((player.GetFlags() & EntityFlags.OnGround) == 0) + dir.Z = 0.0f; + + hurt.ApplyAbsVelocityImpulse(dir); + + Util.ScreenFade(player, new(128, 0, 0, 128), 0.5f, 0.1f, FadeFlags.In); + + player.ForceDropOfCarriedPhysObjects(null); + } + } + else + WeaponSound(Shared.WeaponSound.MeleeMiss); + } + break; + default: + base.Operator_HandleAnimEvent(ref animEvent, op); + break; + } + } +#endif + + public void SetStunState(bool state) { + Active = state; + + if (Active) { +#if !CLIENT_DLL + GetAttachment(1, out Vector3 attachment, out QAngle _); + g_pEffects.Sparks(attachment); +#endif + + EmitSound("Weapon_StunStick.Activate"); + } + else + EmitSound("Weapon_StunStick.Deactivate"); + } + + public bool GetStunState() => Active; + + public override bool Deploy() { + SetStunState(true); + + return base.Deploy(); + } + + public override bool Holster(BaseCombatWeapon? switchingTo = null) { + if (base.Holster(switchingTo) == false) + return false; + + SetStunState(false); + SetWeaponVisible(false); + + return true; + } + + public override void Drop(in Vector3 velocity) { + SetStunState(false); + +#if !CLIENT_DLL + Util.Remove(this); +#endif + } } #endif diff --git a/Game.Shared/NPCEvent.cs b/Game.Shared/NPCEvent.cs index a8c0c4816..0b0fe414c 100644 --- a/Game.Shared/NPCEvent.cs +++ b/Game.Shared/NPCEvent.cs @@ -19,6 +19,34 @@ public struct AnimEvent public static class NPCEventGlobals { + public const int EVENT_SPECIFIC = 0; + public const int EVENT_SCRIPTED = 1000; + public const int EVENT_SHARED = 2000; + public const int EVENT_WEAPON = 3000; public const int EVENT_CLIENT = 5000; + + public const int EVENT_WEAPON_MELEE_HIT = 3001; + public const int EVENT_WEAPON_SMG1 = 3002; + public const int EVENT_WEAPON_MELEE_SWISH = 3003; + public const int EVENT_WEAPON_SHOTGUN_FIRE = 3004; + public const int EVENT_WEAPON_THROW = 3005; + public const int EVENT_WEAPON_AR1 = 3006; + public const int EVENT_WEAPON_AR2 = 3007; + public const int EVENT_WEAPON_HMG1 = 3008; + public const int EVENT_WEAPON_SMG2 = 3009; + public const int EVENT_WEAPON_MISSILE_FIRE = 3010; + public const int EVENT_WEAPON_SNIPER_RIFLE_FIRE = 3011; + public const int EVENT_WEAPON_AR2_GRENADE = 3012; + public const int EVENT_WEAPON_THROW2 = 3013; + public const int EVENT_WEAPON_PISTOL_FIRE = 3014; + public const int EVENT_WEAPON_RELOAD = 3015; + public const int EVENT_WEAPON_THROW3 = 3016; + public const int EVENT_WEAPON_RELOAD_SOUND = 3017; + public const int EVENT_WEAPON_RELOAD_FILL_CLIP = 3018; + public const int EVENT_WEAPON_SMG1_BURST1 = 3101; + public const int EVENT_WEAPON_SMG1_BURSTN = 3102; + public const int EVENT_WEAPON_AR2_ALTFIRE = 3103; + public const int EVENT_WEAPON_SEQUENCE_FINISHED = 3900; + public const int EVENT_WEAPON_LAST = 3999; } #endif diff --git a/Game.Shared/WeaponParse.cs b/Game.Shared/WeaponParse.cs index eb618977c..ff7633816 100644 --- a/Game.Shared/WeaponParse.cs +++ b/Game.Shared/WeaponParse.cs @@ -45,7 +45,11 @@ public static class WeaponParse static readonly Dictionary WeaponInfoDatabase = []; public static int GetWeaponSoundFromString(ReadOnlySpan str) { - throw new NotImplementedException(); + for (WeaponSound i = WeaponSound.Empty; i < WeaponSound.Num; i++) { + if (stricmp(str, FileWeaponInfo.WeaponSoundCategories[(int)i]) == 0) + return (int)i; + } + return -1; } internal static void PrecacheFileWeaponInfoDatabase(IFileSystem filesystem) {