Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 126 additions & 1 deletion Game.Server/BaseCombatCharacter.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<char> weaponName) => throw new NotImplementedException();
public virtual void Weapon_Equip(BaseCombatWeapon weapon) {
for (int i = 0; i < MAX_WEAPONS; i++) {
Expand Down Expand Up @@ -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;
}
69 changes: 68 additions & 1 deletion Game.Server/BaseCombatWeapon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions Game.Server/BaseEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 19 additions & 1 deletion Game.Server/PlayerPickup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Game.Server;

using Source.Common.Mathematics;
using Source.Common.Physics;

using System.Numerics;

Expand Down Expand Up @@ -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();
Expand Down
86 changes: 86 additions & 0 deletions Game.Shared/GarrysMod/WeaponCrowbar.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -49,6 +54,87 @@ public static readonly
];

public override ReadOnlySpan<ActTable> 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;
Expand Down
Loading
Loading