diff --git a/Game.Server/BaseAnimating.cs b/Game.Server/BaseAnimating.cs index fec3320bd..cfa6cc87c 100644 --- a/Game.Server/BaseAnimating.cs +++ b/Game.Server/BaseAnimating.cs @@ -31,7 +31,7 @@ public partial class InfoLightingRelative : BaseEntity } [NetworkName("CBaseAnimating")] -public class BaseAnimating : BaseEntity +public partial class BaseAnimating : BaseEntity { public const int ANIMATION_SKIN_BITS = 10; public const int ANIMATION_BODY_BITS = 32; @@ -107,17 +107,17 @@ public class BaseAnimating : BaseEntity public InlineArrayMaxStudioBoneCtrls EncodedController; public InlineArrayMaxStudioBoneCtrls OldEncodedController; [NetworkName("m_nSequence")] - public int Sequence; + [NetworkVar] public partial int Sequence { get; set; } [NetworkName("m_flPlaybackRate")] - public TimeUnit_t PlaybackRate; + [NetworkVar] public partial TimeUnit_t PlaybackRate { get; set; } [NetworkName("m_bClientSideAnimation")] public bool ClientSideAnimation; [NetworkName("m_bClientSideFrameReset")] public bool ClientSideFrameReset; [NetworkName("m_nNewSequenceParity")] - public int NewSequenceParity; + [NetworkVar] public partial int NewSequenceParity { get; set; } [NetworkName("m_nResetEventsParity")] - public int ResetEventsParity; + [NetworkVar] public partial int ResetEventsParity { get; set; } [NetworkName("m_nMuzzleFlashParity")] public int MuzzleFlashParity; [NetworkName("m_hLightingOrigin")] @@ -135,7 +135,7 @@ public class BaseAnimating : BaseEntity [NetworkName("m_flFadeScale")] public float FadeScale; [NetworkName("m_flCycle")] - public TimeUnit_t Cycle; + [NetworkVar] public partial TimeUnit_t Cycle { get; set; } [NetworkName("m_OverrideViewTarget")] public Vector3 OverrideViewTarget; @@ -329,6 +329,10 @@ public Activity GetSequenceActivity(int sequence) { public void SetPlaybackRate(TimeUnit_t rate) => PlaybackRate = rate; public bool IsSequenceFinished() => SequenceFinished; + public bool IsDissolving() => (GetFlags() & EntityFlags.Dissolving) != 0; + + public virtual void SetLightingOriginRelative(BaseEntity? lightingOriginRelative) => LightingOriginRelative.Set(lightingOriginRelative); + public BaseEntity? GetLightingOriginRelative() => LightingOriginRelative.Get(); public bool IsModelScaleFractional() => ModelScale < 1.0f; public bool IsModelScaled() => ModelScale > 1.0f + FLT_EPSILON || ModelScale < 1.0f - FLT_EPSILON; diff --git a/Game.Server/BaseCombatCharacter.cs b/Game.Server/BaseCombatCharacter.cs index d8ad3f81c..3e55da7e8 100644 --- a/Game.Server/BaseCombatCharacter.cs +++ b/Game.Server/BaseCombatCharacter.cs @@ -115,6 +115,8 @@ public virtual bool FInViewCone(in Vector3 spot) { return false; } + public override bool IsBaseCombatCharacter() => true; + public virtual BaseEntity? GetVehicleEntity() => null; public virtual bool IsInAVehicle() => false; @@ -283,7 +285,159 @@ public virtual void Weapon_FrameUpdate() { } public BaseCombatWeapon? Weapon_Create(ReadOnlySpan weaponName) => throw new NotImplementedException(); - public virtual void Weapon_Equip(BaseCombatWeapon weapon) => throw new NotImplementedException(); + public virtual void Weapon_Equip(BaseCombatWeapon weapon) { + for (int i = 0; i < MAX_WEAPONS; i++) { + if (MyWeapons[i].Get() == null) { + MyWeapons[i].Set(weapon); + break; + } + } + + weapon.ChangeTeam(GetTeamNumber()); + + if (weapon.GetMaxClip1() == -1) + GiveAmmo(weapon.GetDefaultClip1(), weapon.PrimaryAmmoType); + else if (weapon.GetDefaultClip1() > weapon.GetMaxClip1()) { + weapon.iClip1 = weapon.GetMaxClip1(); + GiveAmmo(weapon.GetDefaultClip1() - weapon.GetMaxClip1(), weapon.PrimaryAmmoType); + } + + if (weapon.GetMaxClip2() == -1) + GiveAmmo(weapon.GetDefaultClip2(), weapon.SecondaryAmmoType); + else if (weapon.GetDefaultClip2() > weapon.GetMaxClip2()) { + weapon.iClip2 = weapon.GetMaxClip2(); + GiveAmmo(weapon.GetDefaultClip2() - weapon.GetMaxClip2(), weapon.SecondaryAmmoType); + } + + weapon.Equip(this); + + if (IsPlayer() == false) { + if (ActiveWeapon.Get() != null) { + ActiveWeapon.Get()!.Holster(); + ActiveWeapon.Get()!.AddEffects(EntityEffects.NoDraw); + } + SetActiveWeapon(weapon); + ActiveWeapon.Get()!.RemoveEffects(EntityEffects.NoDraw); + } + + if (IsPlayer() == false) { + if (HasSpawnFlags(AI_BaseNPCGlobals.SF_NPC_LONG_RANGE)) { + ActiveWeapon.Get()!.MaxRange1 = 999999999; + ActiveWeapon.Get()!.MaxRange2 = 999999999; + } + } + + WeaponProficiency proficiency; + proficiency = CalcWeaponProficiency(weapon); + + if (BaseCombatWeapon.weapon_showproficiency.GetBool()) + Msg($"{GetClassname()} equipped with {weapon.GetClassname()}, proficiency is {GetWeaponProficiencyName(proficiency)}\n"); + + SetCurrentWeaponProficiency(proficiency); + + weapon.SetLightingOriginRelative(GetLightingOriginRelative()); + } + + public bool Weapon_EquipAmmoOnly(BaseCombatWeapon weapon) { + for (int i = 0; i < MAX_WEAPONS; i++) { + if (MyWeapons[i].Get() != null && FClassnameIs(MyWeapons[i].Get(), weapon.GetClassname())) { + int primaryGiven = (weapon.UsesClipsForAmmo1()) ? weapon.iClip1 : weapon.GetPrimaryAmmoCount(); + int secondaryGiven = (weapon.UsesClipsForAmmo2()) ? weapon.iClip2 : weapon.GetSecondaryAmmoCount(); + + int takenPrimary = GiveAmmo(primaryGiven, weapon.PrimaryAmmoType); + int takenSecondary = GiveAmmo(secondaryGiven, weapon.SecondaryAmmoType); + + if (weapon.UsesClipsForAmmo1()) + weapon.iClip1 -= takenPrimary; + else + weapon.SetPrimaryAmmoCount(weapon.GetPrimaryAmmoCount() - takenPrimary); + + if (weapon.UsesClipsForAmmo2()) + weapon.iClip2 -= takenSecondary; + else + weapon.SetSecondaryAmmoCount(weapon.GetSecondaryAmmoCount() - takenSecondary); + + if (takenPrimary > 0 || takenSecondary > 0) + return true; + + return false; + } + } + + return false; + } + + public virtual bool Weapon_CanUse(BaseCombatWeapon weapon) { + ReadOnlySpan table = weapon.ActivityList(); + + if (table.Length < 1) + return false; + + for (int i = 0; i < table.Length; i++) { + if (table[i].Required) { + Activity translatedActivity = NPC_TranslateActivity(table[i].WeaponAct); + + if (SelectWeightedSequence(translatedActivity) == StudioHdr.ACTIVITY_NOT_AVAILABLE) + return false; + } + } + + return true; + } + + public virtual int GiveAmmo(int count, int ammoIndex, bool suppressSound = false) { + if (count <= 0) + return 0; + + if (!g_pGameRules.CanHaveAmmo(this, ammoIndex)) + return 0; + + if (ammoIndex < 0 || ammoIndex >= MAX_AMMO_SLOTS) + return 0; + + int max = GetAmmoDef().MaxCarry(ammoIndex); + int add = Math.Min(count, max - Ammo[ammoIndex]); + if (add < 1) + return 0; + + if (!suppressSound) + EmitSound("BaseCombatCharacter.AmmoPickup"); + + Ammo.Set(ammoIndex, Ammo[ammoIndex] + add); + + return add; + } + + public int GiveAmmo(int count, ReadOnlySpan name, bool suppressSound = false) { + int ammoType = GetAmmoDef().Index(name); + if (ammoType == -1) { + Msg($"ERROR: Attempting to give unknown ammo type ({name})\n"); + return 0; + } + return GiveAmmo(count, ammoType, suppressSound); + } + + public void SetActiveWeapon(BaseCombatWeapon? newWeapon) { + BaseCombatWeapon? oldWeapon = ActiveWeapon.Get(); + if (newWeapon != oldWeapon) { + ActiveWeapon.Set(newWeapon); + OnChangeActiveWeapon(oldWeapon, newWeapon); + } + } + + public virtual void OnChangeActiveWeapon(BaseCombatWeapon? oldWeapon, BaseCombatWeapon? newWeapon) { } + + public bool PreventWeaponPickup; + public bool IsAllowedToPickupWeapons() => !PreventWeaponPickup; + + public void SetCurrentWeaponProficiency(WeaponProficiency proficiency) => CurrentWeaponProficiency = proficiency; + public virtual WeaponProficiency CalcWeaponProficiency(BaseCombatWeapon? weapon) => WeaponProficiency.Average; + + public override void SetLightingOriginRelative(BaseEntity? lightingOrigin) { + base.SetLightingOriginRelative(lightingOrigin); + if (GetActiveWeapon() != null) + GetActiveWeapon()!.SetLightingOriginRelative(lightingOrigin); + } public int WeaponCount() => MAX_WEAPONS; public BaseCombatWeapon? GetWeapon(int i) => MyWeapons[i].Get(); diff --git a/Game.Server/BaseCombatWeapon.cs b/Game.Server/BaseCombatWeapon.cs index 0f678f4c8..839b73594 100644 --- a/Game.Server/BaseCombatWeapon.cs +++ b/Game.Server/BaseCombatWeapon.cs @@ -1,6 +1,30 @@ -namespace Game.Server; +using Game.Shared; + +using Source; +using Source.Common; +using Source.Common.Commands; +using Source.Common.Formats.BSP; +using Source.Common.Physics; + +namespace Game.Server; + +using DEFINE = Source.DEFINE; + public partial class BaseCombatWeapon : BaseAnimating { + public static readonly ConVar weapon_showproficiency = new("weapon_showproficiency", "0"); + + public OutputEvent OnPlayerPickup = new(); + public OutputEvent OnNPCPickup = new(); + public OutputEvent OnCacheInteraction = new(); + + public static readonly new DataMap DataDesc = new(typeof(BaseCombatWeapon), BaseEntity.DataDesc, [ + DEFINE.OUTPUT(nameof(OnPlayerPickup), "OnPlayerPickup", eventFuncs), + DEFINE.OUTPUT(nameof(OnNPCPickup), "OnNPCPickup", eventFuncs), + DEFINE.OUTPUT(nameof(OnCacheInteraction), "OnCacheInteraction", eventFuncs), + ]); + public override DataMap? GetDataDescMap() => DataDesc; + public override bool IsWeapon() => true; public override GarrysMod.LuaClass Lua_GetLuaClass() => GarrysMod.LuaEntity.LC_Weapon; @@ -11,4 +35,96 @@ public static void W_Precache(){ public virtual Capability CapabilitiesGet() => 0; public virtual void Operator_FrameUpdate(BaseCombatCharacter op) => throw new NotImplementedException(); + + public BaseEntity? Respawn() { + BaseEntity? newWeapon = Create(GetClassname(), g_pGameRules.VecWeaponRespawnSpot(this), GetLocalAngles(), GetOwnerEntity()); + + if (newWeapon != null) { + newWeapon.AddEffects(EntityEffects.NoDraw); + newWeapon.SetTouch(null); + newWeapon.SetThink(((BaseCombatWeapon)newWeapon).AttemptToMaterialize); + + Util.DropToFloor(this, Mask.Solid); + + newWeapon.SetNextThink(gpGlobals.CurTime + g_pGameRules.FlWeaponRespawnTime(this)); + } + else + Warning($"Respawn failed to create {GetClassname()}!\n"); + + return newWeapon; + } + + public void FallInit() { + SetModel(GetWorldModel()); + VPhysicsDestroyObject(); + + if (VPhysicsInitNormal(SolidType.BBox, GetSolidFlags() | SolidFlags.Trigger, false) == null) { + SetMoveType(Source.MoveType.FlyGravity); + SetSolid(SolidType.BBox); + AddSolidFlags(SolidFlags.Trigger); + } + + SetPickupTouch(); + + SetThink(FallThink); + + SetNextThink(gpGlobals.CurTime + 0.1f); + } + + public void FallThink() { + SetNextThink(gpGlobals.CurTime + 0.1f); + + bool shouldMaterialize = false; + IPhysicsObject? physics = VPhysicsGetObject(); + if (physics != null) + shouldMaterialize = physics.IsAsleep(); + else + shouldMaterialize = (GetFlags() & EntityFlags.OnGround) != 0; + + if (shouldMaterialize) { + if (GetOwnerEntity() != null) + EmitSound("BaseCombatWeapon.WeaponDrop"); + + Materialize(); + } + } + + public void Materialize() { + if (IsEffectActive(EntityEffects.NoDraw)) { + EmitSound("AlyxEmp.Charge"); + + RemoveEffects(EntityEffects.NoDraw); + DoMuzzleFlash(); + } + + if (HasSpawnFlags(BasePlayer.SF_NORESPAWN) == false) { + VPhysicsInitNormal(SolidType.BBox, GetSolidFlags() | SolidFlags.Trigger, false); + SetMoveType(Source.MoveType.VPhysics); + } + + SetPickupTouch(); + + SetThink(null); + } + + public void AttemptToMaterialize() { + TimeUnit_t time = g_pGameRules.FlWeaponTryRespawn(this); + + if (time == 0) { + Materialize(); + return; + } + + SetNextThink(gpGlobals.CurTime + time); + } + + public void CheckRespawn() { + switch (g_pGameRules.WeaponShouldRespawn(this)) { + case GameRulesRespawnReturnCode.WeaponRespawnYes: + Respawn(); + break; + case GameRulesRespawnReturnCode.WeaponRespawnNo: + return; + } + } } diff --git a/Game.Server/BaseEntity.cs b/Game.Server/BaseEntity.cs index a07c97b48..c79c055a2 100644 --- a/Game.Server/BaseEntity.cs +++ b/Game.Server/BaseEntity.cs @@ -3183,7 +3183,42 @@ public virtual void Teleport(Vector3? newPosition, QAngle? newAngles, Vector3? n } public bool IsWorld() => EntIndex() == 0; - public virtual bool FVisible(BaseEntity entity) => throw new NotImplementedException(); + public virtual bool FVisible(BaseEntity entity, Mask traceMask = Mask.BlockLOS) { + if ((entity.GetFlags() & EntityFlags.NoTarget) != 0) + return false; + + Vector3 vecLookerOrigin = EyePosition(); + Vector3 vecTargetOrigin = entity.EyePosition(); + + Trace tr; + if (AI_BaseNPCGlobals.ai_LOS_mode.GetBool()) + Util.TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, this, Source.CollisionGroup.None, out tr); + else { + if (traceMask == Mask.BlockLOS) + traceMask = Mask.BlockLOSAndNPCs; + + if (IsPlayer()) + traceMask &= ~(Mask)Contents.BlockLOS; + + TraceFilterLOS traceFilter = new(this, Source.CollisionGroup.None, entity); + Util.TraceLine(vecLookerOrigin, vecTargetOrigin, traceMask, ref traceFilter, out tr); + } + + if (tr.Fraction != 1.0 || tr.StartSolid) { + if (tr.Ent == entity) + return true; + + if (entity.IsPlayer()) { + BasePlayer player = (BasePlayer)entity; + if (tr.Ent == player.GetVehicleEntity()) + return true; + } + + return false; + } + + return true; + } public virtual void GetVectors(out Vector3 forward, out Vector3 right, out Vector3 up) { ref readonly Matrix3x4 entityToWorld = ref EntityToWorldTransform(); diff --git a/Game.Server/HL2/HL2_Player.cs b/Game.Server/HL2/HL2_Player.cs index a24248bbe..31dc2f31c 100644 --- a/Game.Server/HL2/HL2_Player.cs +++ b/Game.Server/HL2/HL2_Player.cs @@ -352,18 +352,39 @@ bool ShouldKeepLockedAutoaimTarget(EHANDLE lockedTarget) { throw new NotImplementedException(); } - int GiveAmmo(int count, int ammoIndex, bool suppressSound) { - throw new NotImplementedException(); + public override int GiveAmmo(int count, int ammoIndex, bool suppressSound = false) { + if (ammoIndex < 0) + return 0; + + bool checkAutoSwitch = false; + if (!HasAnyAmmoOfType(ammoIndex)) + checkAutoSwitch = true; + + int add = base.GiveAmmo(count, ammoIndex, suppressSound); + + if (checkAutoSwitch) { + BaseCombatWeapon? weapon = g_pGameRules.GetNextBestWeapon(this, GetActiveWeapon()); + + if (weapon != null && weapon.PrimaryAmmoType == ammoIndex) + SwitchToNextBestWeapon(GetActiveWeapon()!); + } + + return add; } - bool Weapon_CanUse(BaseCombatWeapon weapon) { - throw new NotImplementedException(); + public override bool Weapon_CanUse(BaseCombatWeapon weapon) { + return base.Weapon_CanUse(weapon); } - public override void Weapon_Equip(BaseCombatWeapon weapon) { } + public override void Weapon_Equip(BaseCombatWeapon weapon) { + if (GetActiveWeapon() == null) + HL2Local.WeaponLowered = false; - bool BumpWeapon(BaseCombatWeapon weapon) { - throw new NotImplementedException(); + base.Weapon_Equip(weapon); + } + + public override bool BumpWeapon(BaseCombatWeapon weapon) { + return base.BumpWeapon(weapon); } bool ClientCommand(in TokenizedCommand args) { diff --git a/Game.Server/HL2MP/HL2MP_Player.cs b/Game.Server/HL2MP/HL2MP_Player.cs index e456a1250..88a2b8a0f 100644 --- a/Game.Server/HL2MP/HL2MP_Player.cs +++ b/Game.Server/HL2MP/HL2MP_Player.cs @@ -18,14 +18,14 @@ namespace Game.Server.HL2MP; public partial class HL2MP_Player : HL2_Player { public static readonly SendTable DT_HL2MPLocalPlayerExclusive = new(nameof(DT_HL2MPLocalPlayerExclusive), [ - SendPropVector(NetworkVarFields.Origin, 0, PropFlags.NoScale|PropFlags.ChangesOften, 0.0f, Constants.HIGH_DEFAULT), + SendPropVector(BaseEntity.NetworkVarFields.Origin, 0, PropFlags.NoScale|PropFlags.ChangesOften, 0.0f, Constants.HIGH_DEFAULT), SendPropFloat(FIELD.OF_VECTORELEM(nameof(AngEyeAngles), 0), 11, PropFlags.ChangesOften | PropFlags.RoundDown, 0, 360f ), SendPropAngle(FIELD.OF_VECTORELEM(nameof(AngEyeAngles), 1), 11, PropFlags.ChangesOften | PropFlags.RoundDown, 0, 360f ), ]); public static readonly SendTable DT_HL2MPNonLocalPlayerExclusive = new(nameof(DT_HL2MPNonLocalPlayerExclusive), [ - SendPropVector(NetworkVarFields.Origin, 0, PropFlags.CoordMPLowPrecision|PropFlags.ChangesOften, 0.0f, Constants.HIGH_DEFAULT), + SendPropVector(BaseEntity.NetworkVarFields.Origin, 0, PropFlags.CoordMPLowPrecision|PropFlags.ChangesOften, 0.0f, Constants.HIGH_DEFAULT), SendPropFloat(FIELD.OF_VECTORELEM(nameof(AngEyeAngles), 0), 11, PropFlags.ChangesOften | PropFlags.RoundDown, 0, 360f), SendPropAngle(FIELD.OF_VECTORELEM(nameof(AngEyeAngles), 1), 11, PropFlags.ChangesOften | PropFlags.RoundDown, 0, 360f), @@ -488,8 +488,38 @@ public override void SetAnimation(PlayerAnim playerAnim) { SetCycle(0); } - bool BumpWeapon(BaseCombatWeapon weapon) { - throw new NotImplementedException(); + public override bool BumpWeapon(BaseCombatWeapon weapon) { + BaseCombatCharacter? owner = weapon.GetOwner(); + + if (!IsAllowedToPickupWeapons()) + return false; + + if (owner != null || !Weapon_CanUse(weapon) || !g_pGameRules.CanHavePlayerItem(this, weapon)) { + if (gEvilImpulse101) + Util.Remove(weapon); + return false; + } + + if (!weapon.FVisible(this, Source.Common.Formats.BSP.Mask.Solid) && (GetFlags() & EntityFlags.NoTarget) == 0) + return false; + + bool ownsWeaponAlready = Weapon_OwnsThisType(weapon.GetClassname(), weapon.GetSubType()) != null; + + if (ownsWeaponAlready == true) { + if (Weapon_EquipAmmoOnly(weapon)) { + weapon.CheckRespawn(); + + Util.Remove(weapon); + return true; + } + else + return false; + } + + weapon.CheckRespawn(); + Weapon_Equip(weapon); + + return true; } public override void ChangeTeam(int team, bool autoTeam = false, bool silent = false, bool autoBalance = false) { @@ -535,7 +565,7 @@ bool ShouldRunRateLimitedCommand(in TokenizedCommand args) { throw new NotImplementedException(); } - void CreateViewModel(int index = 0) { + public override void CreateViewModel(int index = 0) { Assert(index >= 0 && index < MAX_VIEWMODELS); if (GetViewModel(index) != null) @@ -544,11 +574,11 @@ void CreateViewModel(int index = 0) { PredictedViewModel? vm = (PredictedViewModel?)CreateEntityByName("predicted_viewmodel"); if (vm != null) { vm.SetAbsOrigin(GetAbsOrigin()); - // vm.SetOwner(this); - // vm.SetIndex(index); + vm.SetOwner(this); + vm.SetIndex(index); Util.DispatchSpawn(vm); vm.FollowEntity(this, false); - // VieweModel.Set(index, vm); + ViewModel[index].Set(vm); } } diff --git a/Game.Server/Player.cs b/Game.Server/Player.cs index ad200b0e1..7f99f0491 100644 --- a/Game.Server/Player.cs +++ b/Game.Server/Player.cs @@ -517,7 +517,7 @@ public void SetBodyPitch(float pitch) { [NetworkName("m_hViewEntity")] public EHANDLE ViewEntity = new(); [NetworkName("m_hViewModel")] - InlineArrayNewMaxViewmodels> ViewModel = new(); + protected InlineArrayNewMaxViewmodels> ViewModel = new(); readonly List> SimulatedByThisPlayer = []; public IServerVehicle? GetVehicle() => Vehicle.Get()?.GetServerVehicle(); @@ -728,6 +728,103 @@ public TimeUnit_t ConsumeMovementTimeForUserCmdProcessing(TimeUnit_t timeNeeded) public AnonymousSafeFieldPointer CurrentCommand; public int CurrentCommandNumber() => CurrentCommand.Get().CommandNumber; + public static bool gEvilImpulse101; + + public virtual bool BumpWeapon(BaseCombatWeapon weapon) { + BaseCombatCharacter? owner = weapon.GetOwner(); + + if (!IsAllowedToPickupWeapons()) + return false; + + if (owner != null || !Weapon_CanUse(weapon) || !g_pGameRules.CanHavePlayerItem(this, weapon)) { + if (gEvilImpulse101) + Util.Remove(weapon); + return false; + } + + if (hl2_episodic.GetBool()) { + if (!Util.ItemCanBeTouchedByPlayer(weapon, this) && !gEvilImpulse101) + return false; + } + else { + if (weapon.FVisible(this, Mask.Solid) == false && (GetFlags() & EntityFlags.NoTarget) == 0) + return false; + } + + if (Weapon_OwnsThisType(weapon.GetClassname(), weapon.GetSubType()) != null) { + if (Weapon_EquipAmmoOnly(weapon)) { + if (weapon.HasPrimaryAmmo()) + return false; + + Util.Remove(weapon); + return true; + } + else + return false; + } + else { + weapon.CheckRespawn(); + + weapon.AddSolidFlags(SolidFlags.NotSolid); + weapon.AddEffects(EntityEffects.NoDraw); + + Weapon_Equip(weapon); + if (IsInAVehicle()) + weapon.Holster(); + else { +#if HL2_DLL + if (!Game.Shared.GarrysMod.WeaponPhysCannon.PlayerHasMegaPhysCannon()) { + if (weapon.UsesClipsForAmmo1()) + weapon.iClip1 = weapon.GetMaxClip1(); + + Weapon_Switch(weapon); + } +#endif + } + return true; + } + } + + public override bool Weapon_CanUse(BaseCombatWeapon weapon) => true; + + public override void Weapon_Equip(BaseCombatWeapon weapon) { + base.Weapon_Equip(weapon); + + bool shouldSwitch = g_pGameRules.FShouldSwitchWeapon(this, weapon); + + if (shouldSwitch) + Weapon_Switch(weapon); + } + + public bool HasAnyAmmoOfType(int ammoIndex) { + if (ammoIndex < 0) + return false; + + if (GetAmmoCount(ammoIndex) != 0) + return true; + + BaseCombatWeapon? weapon; + + for (int i = 0; i < MAX_WEAPONS; i++) { + weapon = GetWeapon(i); + + if (weapon == null) + continue; + + if (weapon.UsesClipsForAmmo1() && weapon.PrimaryAmmoType == ammoIndex) { + if (weapon.HasPrimaryAmmo()) + return true; + } + + if (weapon.UsesClipsForAmmo2() && weapon.SecondaryAmmoType == ammoIndex) { + if (weapon.HasSecondaryAmmo()) + return true; + } + } + + return false; + } + public BaseEntity? GiveNamedItem(ReadOnlySpan name, int subType = 0) { if (Weapon_OwnsThisType(name, subType) != null) return null; @@ -919,6 +1016,8 @@ public override void Spawn() { InitHUD = true; + CreateViewModel(); + // more todo // GameRules.PlayerSpawn(this); @@ -930,6 +1029,23 @@ public override void Spawn() { } + public virtual void CreateViewModel(int index = 0) { + Assert(index >= 0 && index < MAX_VIEWMODELS); + + if (GetViewModel(index) != null) + return; + + BaseViewModel? vm = (BaseViewModel?)CreateEntityByName("viewmodel"); + if (vm != null) { + vm.SetAbsOrigin(GetAbsOrigin()); + vm.SetOwner(this); + vm.SetIndex(index); + Util.DispatchSpawn(vm); + vm.FollowEntity(this); + ViewModel[index].Set(vm); + } + } + public TimeUnit_t GetDeathTime() => DeathTime; public virtual void SetAnimation(PlayerAnim playerAnim) { } // todo diff --git a/Game.Server/Util.cs b/Game.Server/Util.cs index da3bb29d7..e506c3919 100644 --- a/Game.Server/Util.cs +++ b/Game.Server/Util.cs @@ -247,6 +247,31 @@ public static int DropToFloor(BaseEntity entity, Mask mask) { return 1; } + public static bool ItemCanBeTouchedByPlayer(BaseEntity? item, BasePlayer? player) { + if (item == null || player == null) + return false; + + if (player.IsInAVehicle()) + return true; + + Vector3 vecStartPos; + Source.Common.Physics.IPhysicsObject? physObj = item.VPhysicsGetObject(); + if (physObj != null) + physObj.GetPosition(out vecStartPos, out _); + else + vecStartPos = item.CollisionProp().WorldSpaceCenter(); + + Vector3 vecEndPos = player.EyePosition(); + + TraceFilterSkipTwoEntities filter = new(player, item, Source.CollisionGroup.PlayerMovement); + TraceLine(vecStartPos, vecEndPos, Mask.Solid, ref filter, out Trace tr); + + if (tr.Fraction < 1.0f) + return false; + + return true; + } + public static void EmitAmbientSound(int entindex, in Vector3 vecOrigin, ReadOnlySpan samp, float vol, Source.Common.Audio.SoundLevel soundlevel, int fFlags, int pitch, TimeUnit_t soundtime = 0.0f) => EmitAmbientSound(entindex, vecOrigin, samp, vol, soundlevel, fFlags, pitch, soundtime, out _); public static void EmitAmbientSound(int entindex, in Vector3 vecOrigin, ReadOnlySpan samp, float vol, Source.Common.Audio.SoundLevel soundlevel, int fFlags, int pitch, TimeUnit_t soundtime, out TimeUnit_t duration) { diff --git a/Game.Shared/BaseCombatWeaponShared.cs b/Game.Shared/BaseCombatWeaponShared.cs index 33d167ce3..3472f6831 100644 --- a/Game.Shared/BaseCombatWeaponShared.cs +++ b/Game.Shared/BaseCombatWeaponShared.cs @@ -1174,8 +1174,34 @@ public virtual void WeaponSound(WeaponSound soundType, TimeUnit_t soundTime = 0. public override void Precache() { PrimaryAmmoType = SecondaryAmmoType = -1; if (WeaponParse.ReadWeaponDataFromFileForSlot(filesystem, GetClassname(), out WeaponFileInfoHandle)) { - + if (GetWpnData().Ammo1[0] != '\0') { + PrimaryAmmoType = GetAmmoDef().Index(GetWpnData().Ammo1); + if (PrimaryAmmoType == -1) + Msg($"ERROR: Weapon ({GetClassname()}) using undefined primary ammo type ({GetWpnData().Ammo1.AsSpan().SliceNullTerminatedString()})\n"); + } + if (GetWpnData().Ammo2[0] != '\0') { + SecondaryAmmoType = GetAmmoDef().Index(GetWpnData().Ammo2); + if (SecondaryAmmoType == -1) + Msg($"ERROR: Weapon ({GetClassname()}) using undefined secondary ammo type ({GetWpnData().Ammo2.AsSpan().SliceNullTerminatedString()})\n"); + } +#if CLIENT_DLL + gWR.LoadWeaponSprites(GetWeaponFileInfoHandle()); +#endif + iViewModelIndex = 0; + WorldModelIndex = 0; + if (!GetViewModel().IsEmpty) + iViewModelIndex = PrecacheModel(GetViewModel()); + if (!GetWorldModel().IsEmpty) + WorldModelIndex = PrecacheModel(GetWorldModel()); + + for (int i = 0; i < (int)Shared.WeaponSound.Num; ++i) { + ReadOnlySpan shootsound = GetShootSound(i); + if (!shootsound.IsEmpty) + PrecacheScriptSound(shootsound); + } } + else + Warning($"Error reading weapon data file for: {GetClassname()}\n"); } TimeUnit_t NextEmptySoundTime; @@ -1201,6 +1227,70 @@ public void SetOwner(BaseCombatCharacter? owner) { + public const int SF_WEAPON_START_CONSTRAINED = 1 << 0; + public const int SF_WEAPON_NO_PLAYER_PICKUP = 1 << 1; + public const int SF_WEAPON_NO_PHYSCANNON_PUNT = 1 << 2; + + public virtual void OnPickedUp(BaseCombatCharacter newOwner) { +#if !CLIENT_DLL + RemoveEffects(EntityEffects.ItemBlink); + + if (newOwner.IsPlayer()) { + OnPlayerPickup.FireOutput(newOwner, this); + + RecipientFilter filter = new(); + for (int i = 1; i <= gpGlobals.MaxClients; ++i) { + BasePlayer? player = Util.PlayerByIndex(i); + if (player != null && !player.IsAlive() && player.GetObserverMode() == Shared.ObserverMode.InEye) + filter.AddRecipient(player); + } + if (filter.GetRecipientCount() != 0) + EmitSound(filter, newOwner.EntIndex(), "Player.PickupWeapon", in Unsafe.NullRef(), 0, out _); + + SetName((string?)null); + } + else + OnNPCPickup.FireOutput(newOwner, this); + + Removable = false; +#endif + } + + public void GiveTo(BaseEntity other) => DefaultTouch(other); + + public void DefaultTouch(BaseEntity? other) { +#if !CLIENT_DLL + if (IsDissolving()) + return; + + BasePlayer? player = ToBasePlayer(other); + if (player == null) + return; + + if (Util.ItemCanBeTouchedByPlayer(this, player)) + OnCacheInteraction.FireOutput(other, this); + + if (HasSpawnFlags(SF_WEAPON_NO_PLAYER_PICKUP)) + return; + + if (player.BumpWeapon(this)) + OnPickedUp(player); +#endif + } + + public void SetPickupTouch() { +#if !CLIENT_DLL + SetTouch(DefaultTouch); + + if (gpGlobals.MaxClients > 1) { + if (HasSpawnFlags(BasePlayer.SF_NORESPAWN)) { + SetThink(SUB_Remove); + SetNextThink(gpGlobals.CurTime + 30.0f); + } + } +#endif + } + public virtual void Equip(BaseCombatCharacter owner) { SetAbsVelocity(vec3_origin); RemoveSolidFlags(SolidFlags.Trigger); @@ -1264,17 +1354,17 @@ public override void Spawn() { SetModel(GetWorldModel()); #if !CLIENT_DLL // todo - // FallInit(); - // SetCollisionGroup(COLLISION_GROUP_WEAPON); - // m_takedamage = DAMAGE_EVENTS_ONLY; + FallInit(); + SetCollisionGroup(Source.CollisionGroup.Weapon); + m_takedamage = (byte)Damage.EventsOnly; - // SetBlocksLOS(false); + SetBlocksLOS(false); // Default to non-removeable, because we don't want the // game_weapon_manager entity to remove weapons that have // been hand-placed by level designers. We only want to remove // weapons that have been dropped by NPC's. - // SetRemoveable(false); + Removable = false; #endif // Bloat the box for player pickup diff --git a/Game.Shared/BaseViewModel.cs b/Game.Shared/BaseViewModel.cs index 1becaa2f3..1189fb866 100644 --- a/Game.Shared/BaseViewModel.cs +++ b/Game.Shared/BaseViewModel.cs @@ -11,6 +11,7 @@ namespace Game.Server; using Source; using Source.Common; +using Source.Common.Engine; using Source.Common.Mathematics; using System; @@ -123,12 +124,29 @@ private static void RecvProxy_SequenceNum(ref readonly RecvProxyData data, objec [NetworkName("m_hWeapon")] public Handle Weapon = new(); [NetworkName("m_nAnimationParity")] +#if CLIENT_DLL public int AnimationParity; +#else + [NetworkVar] public partial int AnimationParity { get; set; } +#endif public BaseCombatWeapon? GetOwningWeapon() => Weapon.Get(); public int ViewModelIndex() => _ViewModelIndex; + public override void Spawn() { + Precache(); + CollisionProp().SetCollisionBounds(new(-8, -4, -2), new(8, 4, 2)); + SetSolid(SolidType.None); + } + + public void SetOwner(BaseEntity? entity) => Owner.Set(entity); + + public void SetIndex(int index) { + _ViewModelIndex = index; + Assert(_ViewModelIndex < (1 << VIEWMODEL_INDEX_BITS)); + } + public const int VIEWMODEL_ANIMATION_PARITY_BITS = 3; public virtual void SendViewModelMatchingSequence(int sequence) { @@ -257,8 +275,44 @@ public void SetWeaponModel(ReadOnlySpan modelname, BaseCombatWeapon? weapo #if CLIENT_DLL SetModel(modelname); #else + string? str = modelname.IsEmpty ? null : new string(modelname); + if (str != VMName) { + VMName = str; + SetModel(VMName); + } #endif } + +#if !CLIENT_DLL + string? VMName; + + public override EdictFlags UpdateTransmitState() { + if (IsEffectActive(EntityEffects.NoDraw)) + return SetTransmitState(EdictFlags.DontSend); + + return SetTransmitState(EdictFlags.FullCheck); + } + + public override EdictFlags ShouldTransmit(CheckTransmitInfo info) { + BasePlayer? owner = ToBasePlayer(Owner.Get()); + + if (owner != null && owner.Edict() == info.ClientEnt) + return EdictFlags.Always; + + BaseEntity? recipientEntity = Instance(info.ClientEnt); + + if (recipientEntity!.IsPlayer()) { + BasePlayer player = (BasePlayer)recipientEntity; + if (player.IsHLTV()) + return EdictFlags.PVSCheck; + + if ((player.GetObserverMode() == Shared.ObserverMode.InEye) && (player.GetObserverTarget() == owner)) + return EdictFlags.Always; + } + + return EdictFlags.DontSend; + } +#endif } #endif diff --git a/Game.Shared/GarrysMod/BaseHL2MPBludgeonWeapon.cs b/Game.Shared/GarrysMod/BaseHL2MPBludgeonWeapon.cs index 5a0188104..58265efb1 100644 --- a/Game.Shared/GarrysMod/BaseHL2MPBludgeonWeapon.cs +++ b/Game.Shared/GarrysMod/BaseHL2MPBludgeonWeapon.cs @@ -1,5 +1,10 @@ -#if (CLIENT_DLL || GAME_DLL) && GMOD_DLL +#if (CLIENT_DLL || GAME_DLL) && GMOD_DLL +using Source; using Source.Common; +using Source.Common.Formats.BSP; +using Source.Common.Mathematics; + +using System.Numerics; namespace Game.Shared.GarrysMod; using FIELD = Source.FIELD; [NetworkName("CBaseHL2MPBludgeonWeapon")] @@ -24,9 +29,218 @@ public static readonly public static readonly new ServerClass ServerClass = new ServerClass(DT_BaseHL2MPBludgeonWeapon); #endif + const int BLUDGEON_HULL_DIM = 16; + + static readonly Vector3 g_bludgeonMins = new(-BLUDGEON_HULL_DIM, -BLUDGEON_HULL_DIM, -BLUDGEON_HULL_DIM); + static readonly Vector3 g_bludgeonMaxs = new(BLUDGEON_HULL_DIM, BLUDGEON_HULL_DIM, BLUDGEON_HULL_DIM); + + public BaseHL2MPBludgeonWeapon() { + FiresUnderwater = true; + } + public override void Spawn() { - + MinRange1 = 0; + MinRange2 = 0; + MaxRange1 = 64; + MaxRange2 = 64; base.Spawn(); } + + public override void ItemPostFrame() { + BasePlayer? owner = ToBasePlayer(GetOwner()); + + if (owner == null) + return; + + if ((owner.Buttons & InButtons.Attack) != 0 && NextPrimaryAttack <= gpGlobals.CurTime) + PrimaryAttack(); + else if ((owner.Buttons & InButtons.Attack2) != 0 && NextSecondaryAttack <= gpGlobals.CurTime) + SecondaryAttack(); + else { + WeaponIdle(); + return; + } + } + + public override void PrimaryAttack() { +#if !CLIENT_DLL + HL2MP_Player player = ToHL2MPPlayer(GetPlayerOwner())!; + lagcompensation.StartLagCompensation(player); +#endif + Swing(false); +#if !CLIENT_DLL + lagcompensation.FinishLagCompensation(player); +#endif + } + + public override void SecondaryAttack() { + Swing(true); + } + + public override Activity GetPrimaryAttackActivity() => Activity.ACT_VM_HITCENTER; + public override Activity GetSecondaryAttackActivity() => Activity.ACT_VM_HITCENTER2; + + public override float GetFireRate() => 0.2f; + public virtual float GetRange() => 32.0f; + public virtual float GetDamageForActivity(Activity hitActivity) => 1.0f; + + public virtual bool PlayFleshyHittySoundOnHit() => false; + + void Hit(ref Trace traceHit, Activity hitActivity) { + BasePlayer? player = ToBasePlayer(GetOwner()); + + BaseEntity? hitEntity = traceHit.Ent; + + if (hitEntity != null) { + player!.EyeVectors(out Vector3 hitDirection); + MathLib.VectorNormalize(ref hitDirection); + +#if !CLIENT_DLL + TakeDamageInfo info = new(GetOwner(), GetOwner(), GetDamageForActivity(hitActivity), DamageType.Club); + + if (player != null && hitEntity.IsNPC()) + info.AdjustPlayerDamageInflictedForSkillLevel(); + + CalculateMeleeDamageForce(ref info, hitDirection, traceHit.EndPos, 1.0f); + + hitEntity.DispatchTraceAttack(info, hitDirection, ref traceHit); + ApplyMultiDamage(); + + TraceAttackToTriggers(info, traceHit.StartPos, traceHit.EndPos, hitDirection); +#endif + + if (PlayFleshyHittySoundOnHit()) + WeaponSound(Shared.WeaponSound.MeleeHit); + } + + ImpactEffect(ref traceHit); + } + + Activity ChooseIntersectionPointAndActivity(ref Trace hitTrace, in Vector3 mins, in Vector3 maxs, BasePlayer owner) { + int i, j, k; + float distance; + ReadOnlySpan minmaxs = [mins, maxs]; + Vector3 vecHullEnd = hitTrace.EndPos; + Vector3 vecEnd = default; + + distance = 1e6f; + Vector3 vecSrc = hitTrace.StartPos; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc) * 2); + Util.TraceLine(vecSrc, vecHullEnd, Mask.ShotHull, owner, Source.CollisionGroup.None,out Trace tmpTrace); + if (tmpTrace.Fraction == 1.0) { + for (i = 0; i < 2; i++) { + for (j = 0; j < 2; j++) { + for (k = 0; k < 2; k++) { + vecEnd.X = vecHullEnd.X + minmaxs[i].X; + vecEnd.Y = vecHullEnd.Y + minmaxs[j].Y; + vecEnd.Z = vecHullEnd.Z + minmaxs[k].Z; + + Util.TraceLine(vecSrc, vecEnd, Mask.ShotHull, owner, Source.CollisionGroup.None,out tmpTrace); + if (tmpTrace.Fraction < 1.0) { + float thisDistance = (tmpTrace.EndPos - vecSrc).Length(); + if (thisDistance < distance) { + hitTrace = tmpTrace; + distance = thisDistance; + } + } + } + } + } + } + else + hitTrace = tmpTrace; + + return Activity.ACT_VM_HITCENTER; + } + + bool ImpactWater(in Vector3 start, in Vector3 end) { + if ((Util.PointContents(start) & (Contents.Water | Contents.Slime)) != 0) + return false; + + if ((Util.PointContents(end) & (Contents.Water | Contents.Slime)) == 0) + return false; + + Util.TraceLine(start, end, (Mask)(Contents.Water | Contents.Slime), GetOwner(), Source.CollisionGroup.None,out Trace waterTrace); + + if (waterTrace.Fraction < 1.0f) { +#if !CLIENT_DLL + EffectData data = new(); + + data.Flags = 0; + data.Origin = waterTrace.EndPos; + data.Normal = waterTrace.Plane.Normal; + data.Scale = 8.0f; + + if ((waterTrace.Contents & Contents.Slime) != 0) + data.Flags |= (int)WaterSplashFlags.InSlime; + + DispatchEffect("watersplash", data); +#endif + } + + return true; + } + + protected virtual void ImpactEffect(ref Trace traceHit) { + if (ImpactWater(traceHit.StartPos, traceHit.EndPos)) + return; + } + + void Swing(bool isSecondary) { + BasePlayer? owner = ToBasePlayer(GetOwner()); + if (owner == null) + return; + + Vector3 swingStart = owner.Weapon_ShootPosition(); + owner.EyeVectors(out Vector3 forward); + + Vector3 swingEnd = swingStart + forward * GetRange(); + Util.TraceLine(swingStart, swingEnd, Mask.ShotHull, owner, Source.CollisionGroup.None,out Trace traceHit); + Activity hitActivity = Activity.ACT_VM_HITCENTER; + +#if !CLIENT_DLL + TakeDamageInfo triggerInfo = new(GetOwner(), GetOwner(), GetDamageForActivity(hitActivity), DamageType.Club); + TraceAttackToTriggers(triggerInfo, traceHit.StartPos, traceHit.EndPos, vec3_origin); +#endif + + if (traceHit.Fraction == 1.0) { + float bludgeonHullRadius = 1.732f * BLUDGEON_HULL_DIM; + + swingEnd -= forward * bludgeonHullRadius; + + Util.TraceHull(swingStart, swingEnd, g_bludgeonMins, g_bludgeonMaxs, Mask.ShotHull, owner, Source.CollisionGroup.None,out traceHit); + if (traceHit.Fraction < 1.0 && traceHit.Ent != null) { + Vector3 vecToTarget = traceHit.Ent.GetAbsOrigin() - swingStart; + MathLib.VectorNormalize(ref vecToTarget); + + float dot = Vector3.Dot(vecToTarget, forward); + + if (dot < 0.70721f) + traceHit.Fraction = 1.0f; + else + hitActivity = ChooseIntersectionPointAndActivity(ref traceHit, g_bludgeonMins, g_bludgeonMaxs, owner); + } + } + + WeaponSound(Shared.WeaponSound.Single); + + if (traceHit.Fraction == 1.0f) { + hitActivity = isSecondary ? Activity.ACT_VM_MISSCENTER2 : Activity.ACT_VM_MISSCENTER; + + Vector3 testEnd = swingStart + forward * GetRange(); + + ImpactWater(swingStart, testEnd); + } + else + Hit(ref traceHit, hitActivity); + + SendWeaponAnim(hitActivity); + + owner.SetAnimation(PlayerAnim.Attack1); + + NextPrimaryAttack = gpGlobals.CurTime + GetFireRate(); + NextSecondaryAttack = gpGlobals.CurTime + SequenceDuration(); + } } #endif diff --git a/Game.Shared/GarrysMod/WeaponCrowbar.cs b/Game.Shared/GarrysMod/WeaponCrowbar.cs index ff9951a69..7b797caab 100644 --- a/Game.Shared/GarrysMod/WeaponCrowbar.cs +++ b/Game.Shared/GarrysMod/WeaponCrowbar.cs @@ -1,5 +1,9 @@ -#if (CLIENT_DLL || GAME_DLL) && GMOD_DLL +#if (CLIENT_DLL || GAME_DLL) && GMOD_DLL +using Source; using Source.Common; +using Source.Common.Mathematics; + +using System.Numerics; namespace Game.Shared.GarrysMod; using FIELD = Source.FIELD; @@ -28,5 +32,51 @@ public static readonly #else public static readonly new ServerClass ServerClass = new ServerClass(DT_WeaponCrowbar); #endif + + public const float CROWBAR_RANGE = 75.0f; + public const float CROWBAR_REFIRE = 0.4f; + +#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 override float GetDamageForActivity(Activity hitActivity) => 25.0f; + + public override void AddViewKick() { + BasePlayer? player = ToBasePlayer(GetOwner()); + + if (player == null) + return; + + QAngle punchAng = default; + + punchAng.X = SharedRandomFloat("crowbarpax", 1, 2); + punchAng.Y = SharedRandomFloat("crowbarpay", -2, -1); + punchAng.Z = 0.0f; + + player.ViewPunch(punchAng); + } + + public override void SecondaryAttack() { } + + public override void Drop(in Vector3 velocity) { +#if !CLIENT_DLL + Util.Remove(this); +#endif + } + + public override float GetRange() => CROWBAR_RANGE; + public override float GetFireRate() => CROWBAR_REFIRE; } #endif diff --git a/Game.Shared/GarrysMod/WeaponPhysCannon.cs b/Game.Shared/GarrysMod/WeaponPhysCannon.cs index 35835fa5c..5e11dc00a 100644 --- a/Game.Shared/GarrysMod/WeaponPhysCannon.cs +++ b/Game.Shared/GarrysMod/WeaponPhysCannon.cs @@ -26,6 +26,8 @@ namespace Game.Shared.GarrysMod; [NetworkName("CWeaponPhysCannon")] public class WeaponPhysCannon : BaseHL2MPCombatWeapon { + public static bool PlayerHasMegaPhysCannon() => false; + public enum EffectState_t { diff --git a/Game.Shared/MultiplayRules.cs b/Game.Shared/MultiplayRules.cs index 7e1bfb7e5..cd407b80d 100644 --- a/Game.Shared/MultiplayRules.cs +++ b/Game.Shared/MultiplayRules.cs @@ -211,7 +211,22 @@ public override void FrameUpdatePostEntityThink() { public override bool IsDeathmatch() => true; public override bool IsCoOp() => false; public override bool FShouldSwitchWeapon(BasePlayer player, BaseCombatWeapon? weapon) { - throw new NotImplementedException(); + if (!player.Weapon_CanSwitchTo(weapon!)) + return false; + + if (player.GetActiveWeapon() == null) + return true; + + if (!weapon!.AllowsAutoSwitchTo()) + return false; + + if (!player.GetActiveWeapon()!.AllowsAutoSwitchFrom()) + return false; + + if (weapon.GetWeight() > player.GetActiveWeapon()!.GetWeight()) + return true; + + return false; } public override BaseCombatWeapon? GetNextBestWeapon(BaseCombatCharacter player, BaseCombatWeapon? currentWeapon) { return base.GetNextBestWeapon(player, currentWeapon); diff --git a/Game.Shared/UtilShared.cs b/Game.Shared/UtilShared.cs index a424891f2..bc943e5db 100644 --- a/Game.Shared/UtilShared.cs +++ b/Game.Shared/UtilShared.cs @@ -498,6 +498,35 @@ public bool ShouldHitEntity(IHandleEntity handleEntity, Contents contentsMask) { } } +public struct TraceFilterSkipTwoEntities(IHandleEntity? passentity, IHandleEntity? passentity2, CollisionGroup collisionGroup) : ITraceFilter +{ + public TraceFilterSimple Simple = new(passentity, collisionGroup); + public IHandleEntity? PassEnt2 = passentity2; + + public bool ShouldHitEntity(IHandleEntity handleEntity, Contents contentsMask) { + if (!PassServerEntityFilter(handleEntity, PassEnt2)) + return false; + + return Simple.ShouldHitEntity(handleEntity, contentsMask); + } +} + +#if !CLIENT_DLL +public struct TraceFilterLOS(IHandleEntity? handleEntity, CollisionGroup collisionGroup, IHandleEntity? handleEntity2 = null) : ITraceFilter +{ + public TraceFilterSkipTwoEntities SkipTwo = new(handleEntity, handleEntity2, collisionGroup); + + public bool ShouldHitEntity(IHandleEntity handleEntity, Contents contentsMask) { + BaseEntity? entity = EntityFromEntityHandle(handleEntity); + + if (!entity!.BlocksLOS()) + return false; + + return SkipTwo.Simple.ShouldHitEntity(handleEntity, contentsMask); + } +} +#endif + public struct TraceFilterNoNPCsOrPlayer(IHandleEntity? passentity, CollisionGroup collisionGroup) : ITraceFilter { TraceFilterSimple Inner = new(passentity, collisionGroup); diff --git a/Source.Engine/EngineTrace.cs b/Source.Engine/EngineTrace.cs index 1d01dc96d..ec3adbfbc 100644 --- a/Source.Engine/EngineTrace.cs +++ b/Source.Engine/EngineTrace.cs @@ -31,6 +31,23 @@ public IterationRetval EnumElement(IHandleEntity? handleEntity) { } } +public ref struct EnumerationFilter : IPartitionEnumerator where IEE : IEntityEnumerator, allows ref struct +{ + public IEE Enumerator; + + public EnumerationFilter(IEE enumerator) => Enumerator = enumerator; + + public IterationRetval EnumElement(IHandleEntity? handleEntity) { + if (StaticPropMgr().IsStaticProp(handleEntity!)) + return IterationRetval.Continue; + + if (!Enumerator.EnumEntity(handleEntity)) + return IterationRetval.Stop; + + return IterationRetval.Continue; + } +} + public abstract class EngineTrace : IEngineTrace { public abstract ICollideable? GetWorldCollideable(); @@ -326,11 +343,19 @@ protected virtual void IntersectRayWithOBB(in Ray ray, in Vector3 origin, in QAn public void EnumerateEntities(in Ray ray, bool triggers, scoped ref IEE enumerator) where IEE : IEntityEnumerator, allows ref struct { - throw new NotImplementedException(); + int mask = !triggers ? SpatialPartitionMask() : SpatialPartitionTriggerMask(); + + if (mask != 0) { + EnumerationFilter filter = new(enumerator); + SpatialPartition().EnumerateElementsAlongRay(mask, ray, false, ref filter); + enumerator = filter.Enumerator; + } } public void EnumerateEntities(in Vector3 absMins, in Vector3 absMaxs, scoped ref IEE enumerator) where IEE : IEntityEnumerator, allows ref struct { - throw new NotImplementedException(); + EnumerationFilter filter = new(enumerator); + SpatialPartition().EnumerateElementsInBox(SpatialPartitionMask(), absMins, absMaxs, false, ref filter); + enumerator = filter.Enumerator; } public void GetBrushesInAABB(in Vector3 mins, in Vector3 maxs, List output, Contents contentsMask = (Contents)(-1)) {