From 6c895d73b7748f8629538f2da42079821b39b420 Mon Sep 17 00:00:00 2001 From: Marco Cawthorne Date: Sun, 17 Jul 2022 00:04:01 -0700 Subject: [PATCH] NSMonster: Initial work towards reworking states. --- src/gs-entbase/server/info_node.qc | 25 ++++ src/gs-entbase/shared/NSMonster.h | 26 ++-- src/gs-entbase/shared/NSMonster.qc | 122 ++++++++++++++----- src/gs-entbase/shared/NSSurfacePropEntity.h | 1 + src/gs-entbase/shared/NSSurfacePropEntity.qc | 6 + src/gs-entbase/shared/NSTalkMonster.qc | 12 +- 6 files changed, 145 insertions(+), 47 deletions(-) create mode 100644 src/gs-entbase/server/info_node.qc diff --git a/src/gs-entbase/server/info_node.qc b/src/gs-entbase/server/info_node.qc new file mode 100644 index 00000000..37d07634 --- /dev/null +++ b/src/gs-entbase/server/info_node.qc @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016-2022 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. +*/ + +/*QUAKED info_node (0 0 0) (-8 -8 -8) (8 8 8) +It's a node, helping monsters navigate on the ground. + +-------- KEYS -------- +"targetname" : Name + +-------- TRIVIA -------- +This entity was introduced in Half-Life (1998). +*/ \ No newline at end of file diff --git a/src/gs-entbase/shared/NSMonster.h b/src/gs-entbase/shared/NSMonster.h index 9c6e8546..590e56e3 100644 --- a/src/gs-entbase/shared/NSMonster.h +++ b/src/gs-entbase/shared/NSMonster.h @@ -110,18 +110,19 @@ typedef enumflags MSF_MULTIPLAYER, MSF_FALLING, MSF_HORDE -} monsterFlag_t; +} monsterFlag_e; /* movement states */ typedef enum { MONSTER_IDLE, + MONSTER_ALERT, MONSTER_FOLLOWING, MONSTER_CHASING, MONSTER_AIMING, MONSTER_DEAD, MONSTER_GIBBED -} monsterState_t; +} monsterState_e; /* scripted sequence states */ typedef enum @@ -130,7 +131,7 @@ typedef enum SEQUENCESTATE_IDLE, SEQUENCESTATE_ACTIVE, SEQUENCESTATE_ENDING -} sequenceState_t; +} sequenceState_e; /* alliance state */ typedef enum @@ -139,14 +140,14 @@ typedef enum MAL_ENEMY, /* unfriendly towards the player */ MAL_ALIEN, /* unfriendly towards anyone but themselves */ MAL_ROGUE /* no allies, not even amongst themselves */ -} allianceState_t; +} allianceState_e; typedef enum { MOVESTATE_IDLE, MOVESTATE_WALK, MOVESTATE_RUN -} movementState_t; +} movementState_e; /* These numerations involve the m_iTriggerCondition attribute. * Basically these conditions are being checked and triggered depending on what @@ -166,7 +167,7 @@ typedef enum MTRIG_HEARWEAPONS, /* we hear weapons being fired */ MTRIG_SEEPLAYER, /* we see a player, don't have to be angry at him. */ MTRIG_SEEPLAYER_RELAXED, /* we see a player and we're currently attacking anything */ -} triggerCondition_t; +} triggerCondition_e; /* FIXME: I'd like to move this into NSMonster, but our current IsFriend() * check is currently only checking on a .takedamage basis. */ @@ -192,7 +193,7 @@ class NSMonster:NSSurfacePropEntity vector m_vecSequenceAngle; vector m_vecTurnAngle; int m_iSequenceFlags; - movementState_t m_iMoveState; + movementState_e m_iMoveState; int m_iTriggerCondition; string m_strTriggerTarget; @@ -203,7 +204,8 @@ class NSMonster:NSSurfacePropEntity /* attack/alliance system */ entity m_eEnemy; float m_flAttackThink; - int m_iMState; + monsterState_e m_iMState; + monsterState_e m_iOldMState; vector m_vecLKPos; /* last-known pos */ /* pathfinding */ @@ -233,6 +235,9 @@ class NSMonster:NSSurfacePropEntity virtual void(string) Sound; virtual void(string, string) SpawnKey; + virtual bool(void) IsAlive; + virtual bool(int) IsFriend; + /* see/hear subsystem */ float m_flSeeTime; virtual void(void) SeeThink; @@ -274,6 +279,11 @@ class NSMonster:NSSurfacePropEntity virtual void(float) AnimPlay; virtual void(void) AnimationUpdate; + /* states */ + virtual void(monsterState_e, monsterState_e) StateChanged; + virtual void(monsterState_e) SetState; + virtual monsterState_e(void) GetState; + /* TriggerTarget/Condition */ virtual int(void) GetTriggerCondition; virtual void(void) TriggerTargets; diff --git a/src/gs-entbase/shared/NSMonster.qc b/src/gs-entbase/shared/NSMonster.qc index 94abc7f3..b20262a6 100644 --- a/src/gs-entbase/shared/NSMonster.qc +++ b/src/gs-entbase/shared/NSMonster.qc @@ -14,6 +14,8 @@ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +const int CONTENTBITS_MONSTER = CONTENTBIT_SOLID|CONTENTBIT_BODY|CONTENTBIT_MONSTERCLIP|CONTENTBIT_BOTCLIP; + #ifdef SERVER int NSMonster::GetTriggerCondition(void) @@ -113,15 +115,15 @@ NSMonster::IdleNoise(void) { } -int +bool NSMonster::IsFriend(int al) { if (m_iAlliance == MAL_ROGUE) - return (0); + return (false); else if (al == m_iAlliance) - return (1); + return (true); - return (0); + return (false); } /* The maximum distance to which we should attempt an attack */ @@ -201,8 +203,9 @@ NSMonster::SeeThink(void) /* check if we should invalidate current enemy */ if (IsValidEnemy(m_eEnemy)) return; + /* enemy is not valid anymore, reset it, clear route and search for new enemy */ - m_iMState = MONSTER_IDLE; + SetState(MONSTER_ALERT); m_eEnemy = __NULL__; ClearRoute(); m_flSeeTime = 0; @@ -286,7 +289,7 @@ NSMonster::AttackThink(void) /* something is blocking us */ if (trace_fraction < 1.0f) { - m_iMState = MONSTER_IDLE; + SetState(MONSTER_ALERT); /* FIXME: This is unreliable, but unlikely that a player ever is here */ if (m_vecLKPos != [0,0,0]) { @@ -296,13 +299,17 @@ NSMonster::AttackThink(void) m_vecLKPos = [0,0,0]; } } else { - m_iMState = MONSTER_AIMING; + SetState(MONSTER_AIMING); /* make sure we remember the last known position. */ m_vecLKPos = m_eEnemy.origin; } - if (m_iMState == MONSTER_AIMING) { + /* the state may have switched */ + if (m_flAttackThink > time) + return; + + if (GetState() == MONSTER_AIMING) { int m; if (MeleeCondition() == TRUE) m = AttackMelee(); @@ -311,7 +318,7 @@ NSMonster::AttackThink(void) /* if we don't have the desired attack mode, walk */ if (m == FALSE) - m_iMState = MONSTER_CHASING; + SetState(MONSTER_CHASING); } } @@ -508,12 +515,12 @@ NSMonster::WalkRoute(void) vector endangles; /* we're busy shooting at something, don't walk */ - if (m_iMState == MONSTER_AIMING && m_eEnemy) { + if (GetState() == MONSTER_AIMING && m_eEnemy) { endangles = vectoangles(m_eEnemy.origin - origin); /* TODO: lerp */ m_vecTurnAngle[1] = endangles[1]; - } else if (m_iNodes && m_iMState == MONSTER_IDLE) { + } else if (m_iNodes && GetState() == MONSTER_IDLE) { /* we're on our last node */ if (m_iCurNode < 0) { endangles = vectoangles(m_vecLastNode - origin); @@ -522,7 +529,7 @@ NSMonster::WalkRoute(void) } m_vecTurnAngle[1] = endangles[1]; input_movevalues = [m_flSequenceSpeed, 0, 0]; - } else if (m_iMState == MONSTER_CHASING && m_eEnemy) { + } else if (GetState() == MONSTER_CHASING && m_eEnemy) { /* we've got 'em in our sights, just need to walk closer */ endangles = vectoangles(m_eEnemy.origin - origin); input_movevalues = [GetChaseSpeed(), 0, 0]; @@ -543,9 +550,9 @@ NSMonster::WalkRoute(void) makevectors(angles); old_ang = v_forward; - tmp[0] = Math_Lerp(old_ang[0], new_ang[0], frametime * 5); + tmp[0] = 0; tmp[1] = Math_Lerp(old_ang[1], new_ang[1], frametime * 5); - tmp[2] = Math_Lerp(old_ang[2], new_ang[2], frametime * 5); + tmp[2] = 0; angles = vectoangles(tmp); #endif } @@ -604,7 +611,7 @@ NSMonster::AnimationUpdate(void) int fr = 0; int act = 0; - if (style == MONSTER_DEAD) + if (GetState() == MONSTER_DEAD) return; float spvel = vlen(velocity); @@ -652,10 +659,41 @@ NSMonster::AnimationUpdate(void) AnimPlay(act); else SetFrame(fr); - } -const int CONTENTBITS_MONSTER = CONTENTBIT_SOLID|CONTENTBIT_BODY|CONTENTBIT_MONSTERCLIP|CONTENTBIT_BOTCLIP; +/* for an NSMonster, health doesn't matter that much, as we could be a corpse */ +bool +NSMonster::IsAlive(void) +{ + if (GetState() == MONSTER_DEAD) + return false; + + return true; +} + +void +NSMonster::StateChanged(monsterState_e oldState, monsterState_e newState) +{ + print(sprintf("%s state changed from %d to %d\n", classname, oldState, newState)); +} + +void +NSMonster::SetState(monsterState_e newState) +{ + if (newState == m_iMState) + return; + + m_iOldMState = m_iMState; + m_iMState = newState; + StateChanged(m_iOldMState, m_iMState); +} + +monsterState_e +NSMonster::GetState(void) +{ + return m_iMState; +} + void PMoveCustom_RunPlayerPhysics(entity target); void PMoveCustom_RunCrouchPhysics(entity target); void @@ -733,9 +771,26 @@ NSMonster::Touch(entity eToucher) void NSMonster::Pain(void) { + /* dead things tell nuthin */ + if (IsAlive() == false) + return; + + if (GetHealth() <= (base_health / 2)) { + if (IsFriend(g_dmg_eAttacker.m_iAlliance) == true) + m_iAlliance = MAL_ROGUE; + } + + if (IsFriend(g_dmg_eAttacker.m_iAlliance) == true) + return; + + /* if don't have an enemy, set one; else make it random */ if (!m_eEnemy || (random() < 0.5)) m_eEnemy = g_dmg_eAttacker; + /* an alert monster will take a while to calm back down */ + SetState(MONSTER_ALERT); + + /* alert all nearby friendlies */ AlertNearby(); } @@ -743,7 +798,7 @@ void NSMonster::Death(void) { /* we were already dead before, so gib */ - if (style == MONSTER_DEAD) { + if (GetState() == MONSTER_DEAD) { Gib(); return; } @@ -751,21 +806,21 @@ NSMonster::Death(void) m_iFlags = 0x0; /* if we make more than 50 damage, gib immediately */ - if (health < -50) { + if (GetHealth() < -50) { Gib(); return; } /* make sure we're not causing any more obituaries */ - flags &= ~FL_MONSTER; - m_iFlags = 0x0; + RemoveFlags(FL_MONSTER); - /* gibbing action */ + /* set the monster up for getting gibbed */ SetMovetype(MOVETYPE_NONE); SetSolid(SOLID_CORPSE); - health = 50 + health; - style = MONSTER_DEAD; + SetHealth(50 + GetHealth()); + SetState(MONSTER_DEAD); + /* monsters trigger their targets when dead */ if (GetTriggerCondition() == MTRIG_DEATH) TriggerTargets(); } @@ -786,16 +841,16 @@ NSMonster::Respawn(void) v_angle[0] = Math_FixDelta(v_angle[0]); v_angle[1] = Math_FixDelta(v_angle[1]); v_angle[2] = Math_FixDelta(v_angle[2]); - flags |= FL_MONSTER; - takedamage = DAMAGE_YES; + + AddFlags(FL_MONSTER); + SetTakedamage(DAMAGE_YES); + SetVelocity([0,0,0]); + SetState(MONSTER_IDLE); + SetHealth(base_health); + m_eEnemy = __NULL__; + m_iFlags = 0x0; iBleeds = TRUE; customphysics = Physics; - velocity = [0,0,0]; - m_iFlags = 0x0; - SendFlags = 0xff; - style = MONSTER_IDLE; - health = base_health; - m_eEnemy = __NULL__; SetAngles(v_angle); SetSolid(SOLID_SLIDEBOX); @@ -804,7 +859,7 @@ NSMonster::Respawn(void) SetSize(base_mins, base_maxs); SetOrigin(GetSpawnOrigin()); - droptofloor(); + DropToFloor(); } void @@ -1109,6 +1164,7 @@ NSMonster_AlertEnemyAlliance(vector pos, float radius, int alliance) /* if they're our friend... ignore*/ if (f.IsFriend(alliance)) continue; + /* if the monster is dead... ignore */ if (f.health <= 0) continue; diff --git a/src/gs-entbase/shared/NSSurfacePropEntity.h b/src/gs-entbase/shared/NSSurfacePropEntity.h index 29d157a8..5823171c 100644 --- a/src/gs-entbase/shared/NSSurfacePropEntity.h +++ b/src/gs-entbase/shared/NSSurfacePropEntity.h @@ -79,6 +79,7 @@ class NSSurfacePropEntity:NSRenderableEntity float m_oldHealth; virtual void(void) Pain; virtual void(void) Death; + virtual bool(void) IsAlive; /* Generic Damage */ nonvirtual void(float) SetTakedamage; diff --git a/src/gs-entbase/shared/NSSurfacePropEntity.qc b/src/gs-entbase/shared/NSSurfacePropEntity.qc index 2ffbea66..55ff0f25 100644 --- a/src/gs-entbase/shared/NSSurfacePropEntity.qc +++ b/src/gs-entbase/shared/NSSurfacePropEntity.qc @@ -33,6 +33,12 @@ NSSurfacePropEntity::Spawned(void) /* networking */ #ifdef SERVER +bool +NSSurfacePropEntity::IsAlive(void) +{ + return (health > 0) ? true : false; +} + #if INDEV typedef enum { diff --git a/src/gs-entbase/shared/NSTalkMonster.qc b/src/gs-entbase/shared/NSTalkMonster.qc index 1be4cf09..3998460c 100644 --- a/src/gs-entbase/shared/NSTalkMonster.qc +++ b/src/gs-entbase/shared/NSTalkMonster.qc @@ -139,7 +139,7 @@ NSTalkMonster::StartleAllies(void) void NSTalkMonster::Sentence(string sentence) { - if (style == MONSTER_DEAD) + if (GetState() == MONSTER_DEAD) return; string seq = Sentences_GetSamples(sentence); @@ -158,7 +158,7 @@ NSTalkMonster::Sentence(string sentence) void NSTalkMonster::Speak(string sentence) { - if (style == MONSTER_DEAD) + if (GetState() == MONSTER_DEAD) return; WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); @@ -463,7 +463,7 @@ NSTalkMonster::Physics(void) /* make sure we're forgetting about enemies and attack states in sequence */ if (m_iSequenceState != SEQUENCESTATE_NONE) { m_eEnemy = __NULL__; - m_iMState = MONSTER_IDLE; + SetState(MONSTER_IDLE); } /* override whatever we did above with this */ @@ -471,7 +471,7 @@ NSTalkMonster::Physics(void) input_angles = v_angle = angles = m_vecSequenceAngle; SetFrame(m_flSequenceEnd); } else { - if (style != MONSTER_DEAD) { + if (GetState() != MONSTER_DEAD) { if (m_iSequenceState == SEQUENCESTATE_NONE) { SeeThink(); AttackThink(); @@ -612,9 +612,9 @@ NSTalkMonster::SendEntity(entity ePEnt, float fChanged) WriteCoord(MSG_ENTITY, origin[2]); } if (fChanged & BASEFL_CHANGED_ANGLES) { - WriteShort(MSG_ENTITY, angles[0] * 32767 / 360); + WriteShort(MSG_ENTITY, 0); WriteShort(MSG_ENTITY, angles[1] * 32767 / 360); - WriteShort(MSG_ENTITY, angles[2] * 32767 / 360); + WriteShort(MSG_ENTITY, 0); } if (fChanged & BASEFL_CHANGED_MODELINDEX) { WriteShort(MSG_ENTITY, modelindex);