From 66745985d4e728672b20a4470bf8dd5de97fde58 Mon Sep 17 00:00:00 2001 From: Marco Cawthorne Date: Fri, 2 Jun 2023 19:27:16 -0700 Subject: [PATCH] Initial implementation of NSSquadMonster, squad management for NSTalkMonster based classes --- src/shared/NSSquadMonster.h | 60 +++++++++ src/shared/NSSquadMonster.qc | 216 ++++++++++++++++++++++++++++++ src/shared/NSTalkMonster.h | 2 +- src/shared/NSTalkMonster.qc | 15 ++- src/shared/NSWeapon.h | 119 ----------------- src/shared/NSWeapon.qc | 246 ----------------------------------- src/shared/defs.h | 2 +- src/shared/include.src | 2 +- 8 files changed, 288 insertions(+), 374 deletions(-) create mode 100644 src/shared/NSSquadMonster.h create mode 100644 src/shared/NSSquadMonster.qc delete mode 100644 src/shared/NSWeapon.h delete mode 100644 src/shared/NSWeapon.qc diff --git a/src/shared/NSSquadMonster.h b/src/shared/NSSquadMonster.h new file mode 100644 index 00000000..f8fd1604 --- /dev/null +++ b/src/shared/NSSquadMonster.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 Vera Visions LLC. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#define NSSQUADMONSTER_MAXMEMBERS 5 + +/** NSSquadMonster based NPCs are able to communicate strategies together. */ +class +NSSquadMonster:NSMonster +{ +public: + void NSSquadMonster(void); + +#ifdef SERVER + /** Overridable: Called when this NPC became squad leader. */ + virtual void HasBecomeSquadLeader(void); + /** Overridable: Called when this NPC joined a squad. */ + virtual void HasJoinedSquad(void); + + /** Returns true/false if they're in a squad. */ + nonvirtual bool InSquad(void); + /** Returns whether or not they're the squad leader. */ + nonvirtual bool IsSquadLeader(void); + /** Returns the leader of their squad. Invalid if none. */ + nonvirtual NSSquadMonster GetSquadLeader(void); + + /** Will find and attach to a Squad in the specified radius. */ + nonvirtual void FindSquadNearMe(float); + + /** Will add the specified NPC to this entity's current squad. */ + nonvirtual void AddToSquad(NSSquadMonster); + /** Will remove the specified NPC from this entity's current squad. Can be called on self. */ + nonvirtual void RemoveFromSquad(NSSquadMonster); + /** Returns the nearest available member of its squad. */ + nonvirtual NSSquadMonster GetNearestSquadMember(void); + /** Returns the farthest available member of its squad. */ + nonvirtual NSSquadMonster GetFarthestSquadMember(void); +#endif + +#ifdef SERVER +private: + bool m_inSquad; + NSSquadMonster m_eSquadLeader; + + /* stored only in the squad leader's memory */ + NSSquadMonster m_eSquadMembers[NSSQUADMONSTER_MAXMEMBERS]; +#endif +}; \ No newline at end of file diff --git a/src/shared/NSSquadMonster.qc b/src/shared/NSSquadMonster.qc new file mode 100644 index 00000000..06ba610f --- /dev/null +++ b/src/shared/NSSquadMonster.qc @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2023 Vera Visions LLC. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +void +NSSquadMonster::NSSquadMonster(void) +{ +#ifdef SERVER + m_inSquad = false; + m_eSquadLeader = __NULL__; + + for (int i = 0; i < NSSQUADMONSTER_MAXMEMBERS; i++) { + m_eSquadMembers[i] = __NULL__; + } +#endif +} + +#ifdef SERVER +void +NSSquadMonster::HasBecomeSquadLeader(void) +{ + /* implemented by sub-class */ +} + +void +NSSquadMonster::HasJoinedSquad(void) +{ + /* implemented by sub-class */ +} + +bool +NSSquadMonster::InSquad(void) +{ + return m_inSquad; +} + +bool +NSSquadMonster::IsSquadLeader(void) +{ + if (m_eSquadLeader == this) + return true; + + return false; +} + +NSSquadMonster +NSSquadMonster::GetSquadLeader(void) +{ + return m_eSquadLeader; +} + +void +NSSquadMonster::FindSquadNearMe(float searchRadius) +{ + entity searchResult = findradius(GetOrigin(), searchRadius); + + while (searchResult) { + /* found someone just like us */ + if (searchResult != this) + if (searchResult.classname == classname) { + NSSquadMonster squadMember = (NSSquadMonster)searchResult; + + /* we found someone, they may not be in a squad (that's ok!) + as they will then create one. */ + squadMember.AddToSquad(this); + return; + } + + /* advance the chain. */ + searchResult = searchResult.chain; + } +} + +void +NSSquadMonster::AddToSquad(NSSquadMonster addMember) +{ + NSSquadMonster startMember = __NULL__; + + if (!addMember) + return; + + /* no start? this monster just became a squad leader */ + if (InSquad() == false) { + m_eSquadLeader = this; + startMember = this; + m_inSquad = true; + print(sprintf("%s (%d) became squad leader\n", classname, num_for_edict(this))); + HasBecomeSquadLeader(); + } + + startMember = GetSquadLeader(); + + /* fit the member into the nearest slot */ + for (int i = 0; i < NSSQUADMONSTER_MAXMEMBERS; i++) { + if (startMember.m_eSquadMembers[i] == __NULL__) { + startMember.m_eSquadMembers[i] = addMember; + addMember.m_eSquadLeader = startMember; + addMember.m_inSquad = true; + addMember.HasJoinedSquad(); + print(sprintf("%s (%d) added to squad, member %i\n", classname, num_for_edict(this), i)); + return; + } + } +} + +void +NSSquadMonster::RemoveFromSquad(NSSquadMonster toRemove) +{ + NSSquadMonster startMember = __NULL__; + + /* don't bother if not in squad. */ + if (InSquad() == false) + return; + if (!toRemove) + return; + + startMember = GetSquadLeader(); + + /* fit the member into the nearest slot */ + for (int i = 0; i < NSSQUADMONSTER_MAXMEMBERS; i++) { + if (startMember.m_eSquadMembers[i] == toRemove) { + startMember.m_eSquadMembers[i] = __NULL__; + toRemove.m_eSquadLeader = __NULL__; + toRemove.m_inSquad = false; + return; + } + } +} + +NSSquadMonster +NSSquadMonster::GetNearestSquadMember(void) +{ + NSSquadMonster member = __NULL__; + NSSquadMonster nearestMember = __NULL__; + float dist = 0.0f; + float nearestDist = 99999.0f; + NSSquadMonster startMember; + + if (InSquad() == false) + return __NULL__; + + /* only leaders have the member list */ + startMember = GetSquadLeader(); + + /* iterate through members... */ + for (int i = 0; i < NSSQUADMONSTER_MAXMEMBERS; i++) { + member = startMember.m_eSquadMembers[i]; + + /* don't recognize self, ever */ + if (member == __NULL__ || member == this) + continue; + + /* check the distance from us to a valid member */ + member = startMember.m_eSquadMembers[i]; + dist = vlen(member.GetOrigin() - GetOrigin()); + + /* found one */ + if (dist < nearestDist) { + nearestDist = dist; + nearestMember = member; + } + } + + return nearestMember; +} + +NSSquadMonster +NSSquadMonster::GetFarthestSquadMember(void) +{ + NSSquadMonster member = __NULL__; + NSSquadMonster farthestMember = __NULL__; + float dist = 0.0f; + float farthestDist = 0.0f; + NSSquadMonster startMember; + + if (InSquad() == false) + return __NULL__; + + /* only leaders have the member list */ + startMember = GetSquadLeader(); + + /* iterate through members... */ + for (int i = 0; i < NSSQUADMONSTER_MAXMEMBERS; i++) { + member = startMember.m_eSquadMembers[i]; + + /* don't recognize self, ever */ + if (member == __NULL__ || member == this) + continue; + + /* check the distance from us to a valid member */ + member = startMember.m_eSquadMembers[i]; + dist = vlen(member.GetOrigin() - GetOrigin()); + + /* found one */ + if (dist > farthestDist) { + farthestDist = dist; + farthestMember = member; + } + } + + return farthestMember; +} + +#endif \ No newline at end of file diff --git a/src/shared/NSTalkMonster.h b/src/shared/NSTalkMonster.h index 0d42eabe..821648ec 100644 --- a/src/shared/NSTalkMonster.h +++ b/src/shared/NSTalkMonster.h @@ -34,7 +34,7 @@ information and can speak more complicated dialogue. They also can communicate with other NSTalkMonster based entities. */ -class NSTalkMonster:NSMonster +class NSTalkMonster:NSSquadMonster { public: void NSTalkMonster(void); diff --git a/src/shared/NSTalkMonster.qc b/src/shared/NSTalkMonster.qc index 452bb292..f785c1d2 100644 --- a/src/shared/NSTalkMonster.qc +++ b/src/shared/NSTalkMonster.qc @@ -490,9 +490,10 @@ NSTalkMonster::FollowPlayer(void) { float flPlayerDist; input_angles = vectoangles(m_eFollowingChain.origin - origin); - input_angles[0] = 0; - input_angles[1] = Math_FixDelta(input_angles[1]); - input_angles[2] = 0; + input_angles[0] = 0; + input_angles[1] = Math_FixDelta(input_angles[1]); + input_angles[2] = 0; + _LerpTurnToYaw(input_angles[1]); /* for best results, we want to ignore the Z plane this avoids the problem of a follower spinning @@ -521,7 +522,8 @@ NSTalkMonster::FollowPlayer(void) } } - input_movevalues[0] = m_flFollowSpeed; + if (DistanceFromYaw(vecParent) > 0.9f) + input_movevalues[0] = m_flFollowSpeed; other = world; traceline(origin, m_eFollowingChain.origin, MOVE_OTHERONLY, this); @@ -532,6 +534,7 @@ NSTalkMonster::FollowPlayer(void) input_angles[0] = 0; input_angles[1] = Math_FixDelta(input_angles[1]); input_angles[2] = 0; + _LerpTurnToYaw(input_angles[1]); } else { m_vecLastUserPos = m_eFollowingChain.origin; } @@ -617,7 +620,7 @@ NSTalkMonster::RunAI(void) void NSTalkMonster::Respawn(void) { - NSMonster::Respawn(); + super::Respawn(); m_eFollowing = world; m_eFollowingChain = world; } @@ -658,7 +661,7 @@ NSTalkMonster::SpawnKey(string strKey, string strValue) m_talkFollow = strcat("!", strValue); break; default: - NSMonster::SpawnKey(strKey, strValue); + super::SpawnKey(strKey, strValue); break; } } diff --git a/src/shared/NSWeapon.h b/src/shared/NSWeapon.h deleted file mode 100644 index c625c70f..00000000 --- a/src/shared/NSWeapon.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef MAX_WEAPONS -#define MAX_WEAPONS 32 -#endif - -/** This class represents inventory items and weapons that you can directly interact with. - - Trouble that's standing in the way of this taking off: - - Level changes currently only support client entities from setting up - changelevel parameters. There is parm_string that we *could* use to - store weapon entity information in, but this will grow massively. - - For the time being, we need to use the legacy system if we want to support - singleplayer. -*/ -class -NSWeapon:NSRenderableEntity -{ -private: - entity m_owner; - - string m_strName; /* Full character name */ - int m_iSlot; - int m_iSlotPos; - bool m_bAllowDropping; - int m_iWeight; - - /* generic info */ - int m_iClip1; - int m_iClip2; - float m_flPrimaryNext; - float m_flSecondaryNext; - float m_flLastFired; - -public: - void NSWeapon(void); - - /* inspired by GMOD API https://wiki.facepunch.com/gmod/Weapon */ - /** Returns the model used to attach to players that wield this weapon */ - virtual string GetPlayerModel(void); - /** Returns the model used to display in-world representations of this weapon. */ - virtual string GetWorldModel(void); - /** Returns the name used in printed text for this weapon */ - virtual string GetPrintName(void); - /** Returns if this weapon is allowed to be dropped. */ - virtual bool AllowDropping(void); - /** Returns the framegroup used for the top-half of a player when aiming this weapon. */ - virtual int GetPlayerAnim(void); - /** Returns a formatted obituary message. - Should contain two %s parameters, the first is the attacker and the second is the target. */ - virtual string GetObituaryMessage(void); - /** Returns the weapon type. Check weapontype_t for details. */ - virtual int GetType(void); - - /** Returns primary attack clip */ - virtual int GetClip1(void); - /** Returns secondary attack clip */ - virtual int GetClip2(void); - /** Returns primary max clip size */ - virtual int GetMaxClip1(void); - /** Returns secondary max clip size */ - virtual int GetMaxClip2(void); - /** Returns the next time the primary mode can fire */ - virtual float GetNextPrimaryFire(void); - /** Returns the next time the secondary mode can fire */ - virtual float GetNextSecondaryFire(void); - /** Returns the slot/HUD category the weapon belongs in. */ - virtual int GetSlot(void); - /** Returns the position the weapon belongs in of the slot specified in GetSlot() */ - virtual int GetSlotPos(void); - /** Returns the 'weight', used for deciding what the next best weapon to switch to is. */ - virtual int GetWeight(void); - /** Returns absolute time at which the weapon was last fired */ - virtual float LastFireTime(void); - /** Sets the primary ammo clip count */ - virtual void SetClip1(int); - /** Sets the secondary ammo clip count */ - virtual void SetClip2(int); - /** Returns whether the weapon allows to being switched from when a better weighted weapon is picked up */ - virtual bool AllowsAutoSwitchFrom(void); - /** Returns whether the weapon allows to being switched to when a better weighted weapon is picked up */ - virtual bool AllowsAutoSwitchTo(void); - /** Returns if the weapon is empty, with no reserve ammonition */ - virtual bool IsEmpty(void); - /** Returns if the weapon has ammo left in it. */ - virtual bool HasAmmo(void); - - /* calls */ - /** Called to reload resources utilized by this weapon. */ - virtual void Precache(void); - /** Called when the weapon was switched to from another. */ - virtual void Draw(void); - /** Called right before switching to a new weapon. */ - virtual void Holster(void); - /** Called whenever the command +attack is called by a client. */ - virtual void Primary(void); - /** Called whenever the command +attack2 is called by a client. */ - virtual void Secondary(void); - /** Called whenever the command +reload is called by a client. */ - virtual void Reload(void); - /** Called whenever the no weapon command is called by a client. */ - virtual void Release(void); - -#ifdef CLIENT - /** Called before 3D world rendering is performed. */ - virtual void ClientPredraw(void); - /** Called after 3D world rendering is performed. */ - virtual void ClientPostdraw(void); - - virtual void ReceiveEntity(float, float); -#endif - -#ifdef SERVER - virtual float SendEntity(entity, float); - - virtual void Touch(entity); - virtual void Respawn(void); -#endif -}; \ No newline at end of file diff --git a/src/shared/NSWeapon.qc b/src/shared/NSWeapon.qc deleted file mode 100644 index 1b934a13..00000000 --- a/src/shared/NSWeapon.qc +++ /dev/null @@ -1,246 +0,0 @@ -void -NSWeapon::NSWeapon(void) -{ - -} - -/* calls */ -void -NSWeapon::Precache(void) -{ -} - -void -NSWeapon::Draw(void) -{ -} - -void -NSWeapon::Holster(void) -{ -} - -void -NSWeapon::Primary(void) -{ -} - -void -NSWeapon::Secondary(void) -{ -} - -void -NSWeapon::Reload(void) -{ -} - -void -NSWeapon::Release(void) -{ -} - -#ifdef CLIENT -void -NSWeapon::ClientPredraw(void) -{ -} - -void -NSWeapon::ClientPostdraw(void) -{ -} - -void -NSWeapon::ReceiveEntity(float new, float flChanged) -{ - -} -#endif - -#ifdef SERVER -float -NSWeapon::SendEntity(entity ePEnt, float flChanged) -{ - /* if we have a model, assume we're a pickup */ - if (modelindex) { - return super::SendEntity(ePEnt, flChanged); - } - - /* don't network to anyone but the owner */ - if (ePEnt != owner) { - return (false); - } - -#if 0 - WriteByte(MSG_ENTITY, ENT_WEAPON); - WriteFloat(MSG_ENTITY, flChanged); - WriteInt(MSG_ENTITY, m_iSlot); - WriteInt(MSG_ENTITY, m_iSlotPos); - WriteByte(MSG_ENTITY, m_bAllowDropping); - WriteInt(MSG_ENTITY, m_iWeight); - WriteInt(MSG_ENTITY, m_iClip1); - WriteInt(MSG_ENTITY, m_iClip2); - WriteFloat(MSG_ENTITY, m_flPrimaryNext); - WriteFloat(MSG_ENTITY, m_flSecondaryNext); - WriteFloat(MSG_ENTITY, m_flLastFired); -#endif - - return (true); -} - -void -NSWeapon::Touch(entity eToucher) -{ - Hide(); - SetSolid(SOLID_NOT); -} - -void -NSWeapon::Respawn(void) -{ - /* the weapons gets placed in-world */ - SetModel(GetWorldModel()); - SetSolid(SOLID_TRIGGER); - SetOrigin(GetSpawnOrigin()); -} -#endif - -/* get */ -string -NSWeapon::GetPlayerModel(void) -{ - return "models/error.vvm"; -} - -string -NSWeapon::GetWorldModel(void) -{ - return "models/error.vvm"; -} - -string -NSWeapon::GetPrintName(void) -{ - return m_strName; -} - -int -NSWeapon::GetSlot(void) -{ - return m_iSlot; -} - -int -NSWeapon::GetSlotPos(void) -{ - return m_iSlotPos; -} - -bool -NSWeapon::AllowDropping(void) -{ - return false; -} - -int -NSWeapon::GetWeight(void) -{ - return 0; -} - -int -NSWeapon::GetPlayerAnim(void) -{ - return 0; -} - -bool -NSWeapon::IsEmpty(void) -{ - return false; -} - -string -NSWeapon::GetObituaryMessage(void) -{ - return "%s killed %s with Unknown"; -} - -int -NSWeapon::GetType(void) -{ - return 0; -} - -int -NSWeapon::GetClip1(void) -{ - return 0; -} - -int -NSWeapon::GetClip2(void) -{ - return 0; -} - -int -NSWeapon::GetMaxClip1(void) -{ - return 0; -} - -int -NSWeapon::GetMaxClip2(void) -{ - return 0; -} - -float -NSWeapon::GetNextPrimaryFire(void) -{ - return m_flPrimaryNext; -} - -float -NSWeapon::GetNextSecondaryFire(void) -{ - return m_flSecondaryNext; -} - -float -NSWeapon::LastFireTime(void) -{ - return m_flLastFired; -} - -void -NSWeapon::SetClip1(int new_clip) -{ - m_iClip1 = new_clip; -} - -void -NSWeapon::SetClip2(int new_clip) -{ - m_iClip1 = new_clip; -} - -bool -NSWeapon::AllowsAutoSwitchFrom(void) -{ - return true; -} - -bool -NSWeapon::AllowsAutoSwitchTo(void) -{ - return true; -} - -bool -NSWeapon::HasAmmo(void) -{ - return false; -} diff --git a/src/shared/defs.h b/src/shared/defs.h index d3397454..f81e0450 100644 --- a/src/shared/defs.h +++ b/src/shared/defs.h @@ -62,6 +62,7 @@ string __fullspawndata; #include "NSPointTrigger.h" #include "NSNavAI.h" #include "NSMonster.h" +#include "NSSquadMonster.h" #include "NSTalkMonster.h" #include "NSProjectile.h" #include "NSItem.h" @@ -71,7 +72,6 @@ string __fullspawndata; #include "../xr/defs.h" #include "NSClient.h" #include "NSClientSpectator.h" -#include "NSWeapon.h" #include "NSClientPlayer.h" #include "NSVehicle.h" diff --git a/src/shared/include.src b/src/shared/include.src index fb8d892f..7d6aa77c 100644 --- a/src/shared/include.src +++ b/src/shared/include.src @@ -10,10 +10,10 @@ NSMoverEntity.qc NSPhysicsEntity.qc NSBrushTrigger.qc NSPointTrigger.qc -NSWeapon.qc NSVehicle.qc NSNavAI.qc NSMonster.qc +NSSquadMonster.qc NSTalkMonster.qc NSProjectile.qc NSItem.qc