NSMonster: Initial work towards reworking states.

This commit is contained in:
Marco Cawthorne 2022-07-17 00:04:01 -07:00
parent 725a32a4d6
commit 6c895d73b7
Signed by: eukara
GPG key ID: CE2032F0A2882A22
6 changed files with 145 additions and 47 deletions

View file

@ -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).
*/

View file

@ -110,18 +110,19 @@ typedef enumflags
MSF_MULTIPLAYER, MSF_MULTIPLAYER,
MSF_FALLING, MSF_FALLING,
MSF_HORDE MSF_HORDE
} monsterFlag_t; } monsterFlag_e;
/* movement states */ /* movement states */
typedef enum typedef enum
{ {
MONSTER_IDLE, MONSTER_IDLE,
MONSTER_ALERT,
MONSTER_FOLLOWING, MONSTER_FOLLOWING,
MONSTER_CHASING, MONSTER_CHASING,
MONSTER_AIMING, MONSTER_AIMING,
MONSTER_DEAD, MONSTER_DEAD,
MONSTER_GIBBED MONSTER_GIBBED
} monsterState_t; } monsterState_e;
/* scripted sequence states */ /* scripted sequence states */
typedef enum typedef enum
@ -130,7 +131,7 @@ typedef enum
SEQUENCESTATE_IDLE, SEQUENCESTATE_IDLE,
SEQUENCESTATE_ACTIVE, SEQUENCESTATE_ACTIVE,
SEQUENCESTATE_ENDING SEQUENCESTATE_ENDING
} sequenceState_t; } sequenceState_e;
/* alliance state */ /* alliance state */
typedef enum typedef enum
@ -139,14 +140,14 @@ typedef enum
MAL_ENEMY, /* unfriendly towards the player */ MAL_ENEMY, /* unfriendly towards the player */
MAL_ALIEN, /* unfriendly towards anyone but themselves */ MAL_ALIEN, /* unfriendly towards anyone but themselves */
MAL_ROGUE /* no allies, not even amongst themselves */ MAL_ROGUE /* no allies, not even amongst themselves */
} allianceState_t; } allianceState_e;
typedef enum typedef enum
{ {
MOVESTATE_IDLE, MOVESTATE_IDLE,
MOVESTATE_WALK, MOVESTATE_WALK,
MOVESTATE_RUN MOVESTATE_RUN
} movementState_t; } movementState_e;
/* These numerations involve the m_iTriggerCondition attribute. /* These numerations involve the m_iTriggerCondition attribute.
* Basically these conditions are being checked and triggered depending on what * 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_HEARWEAPONS, /* we hear weapons being fired */
MTRIG_SEEPLAYER, /* we see a player, don't have to be angry at him. */ 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 */ 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() /* FIXME: I'd like to move this into NSMonster, but our current IsFriend()
* check is currently only checking on a .takedamage basis. */ * check is currently only checking on a .takedamage basis. */
@ -192,7 +193,7 @@ class NSMonster:NSSurfacePropEntity
vector m_vecSequenceAngle; vector m_vecSequenceAngle;
vector m_vecTurnAngle; vector m_vecTurnAngle;
int m_iSequenceFlags; int m_iSequenceFlags;
movementState_t m_iMoveState; movementState_e m_iMoveState;
int m_iTriggerCondition; int m_iTriggerCondition;
string m_strTriggerTarget; string m_strTriggerTarget;
@ -203,7 +204,8 @@ class NSMonster:NSSurfacePropEntity
/* attack/alliance system */ /* attack/alliance system */
entity m_eEnemy; entity m_eEnemy;
float m_flAttackThink; float m_flAttackThink;
int m_iMState; monsterState_e m_iMState;
monsterState_e m_iOldMState;
vector m_vecLKPos; /* last-known pos */ vector m_vecLKPos; /* last-known pos */
/* pathfinding */ /* pathfinding */
@ -233,6 +235,9 @@ class NSMonster:NSSurfacePropEntity
virtual void(string) Sound; virtual void(string) Sound;
virtual void(string, string) SpawnKey; virtual void(string, string) SpawnKey;
virtual bool(void) IsAlive;
virtual bool(int) IsFriend;
/* see/hear subsystem */ /* see/hear subsystem */
float m_flSeeTime; float m_flSeeTime;
virtual void(void) SeeThink; virtual void(void) SeeThink;
@ -274,6 +279,11 @@ class NSMonster:NSSurfacePropEntity
virtual void(float) AnimPlay; virtual void(float) AnimPlay;
virtual void(void) AnimationUpdate; virtual void(void) AnimationUpdate;
/* states */
virtual void(monsterState_e, monsterState_e) StateChanged;
virtual void(monsterState_e) SetState;
virtual monsterState_e(void) GetState;
/* TriggerTarget/Condition */ /* TriggerTarget/Condition */
virtual int(void) GetTriggerCondition; virtual int(void) GetTriggerCondition;
virtual void(void) TriggerTargets; virtual void(void) TriggerTargets;

View file

@ -14,6 +14,8 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * 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 #ifdef SERVER
int int
NSMonster::GetTriggerCondition(void) NSMonster::GetTriggerCondition(void)
@ -113,15 +115,15 @@ NSMonster::IdleNoise(void)
{ {
} }
int bool
NSMonster::IsFriend(int al) NSMonster::IsFriend(int al)
{ {
if (m_iAlliance == MAL_ROGUE) if (m_iAlliance == MAL_ROGUE)
return (0); return (false);
else if (al == m_iAlliance) else if (al == m_iAlliance)
return (1); return (true);
return (0); return (false);
} }
/* The maximum distance to which we should attempt an attack */ /* The maximum distance to which we should attempt an attack */
@ -201,8 +203,9 @@ NSMonster::SeeThink(void)
/* check if we should invalidate current enemy */ /* check if we should invalidate current enemy */
if (IsValidEnemy(m_eEnemy)) if (IsValidEnemy(m_eEnemy))
return; return;
/* enemy is not valid anymore, reset it, clear route and search for new enemy */ /* enemy is not valid anymore, reset it, clear route and search for new enemy */
m_iMState = MONSTER_IDLE; SetState(MONSTER_ALERT);
m_eEnemy = __NULL__; m_eEnemy = __NULL__;
ClearRoute(); ClearRoute();
m_flSeeTime = 0; m_flSeeTime = 0;
@ -286,7 +289,7 @@ NSMonster::AttackThink(void)
/* something is blocking us */ /* something is blocking us */
if (trace_fraction < 1.0f) { if (trace_fraction < 1.0f) {
m_iMState = MONSTER_IDLE; SetState(MONSTER_ALERT);
/* FIXME: This is unreliable, but unlikely that a player ever is here */ /* FIXME: This is unreliable, but unlikely that a player ever is here */
if (m_vecLKPos != [0,0,0]) { if (m_vecLKPos != [0,0,0]) {
@ -296,13 +299,17 @@ NSMonster::AttackThink(void)
m_vecLKPos = [0,0,0]; m_vecLKPos = [0,0,0];
} }
} else { } else {
m_iMState = MONSTER_AIMING; SetState(MONSTER_AIMING);
/* make sure we remember the last known position. */ /* make sure we remember the last known position. */
m_vecLKPos = m_eEnemy.origin; 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; int m;
if (MeleeCondition() == TRUE) if (MeleeCondition() == TRUE)
m = AttackMelee(); m = AttackMelee();
@ -311,7 +318,7 @@ NSMonster::AttackThink(void)
/* if we don't have the desired attack mode, walk */ /* if we don't have the desired attack mode, walk */
if (m == FALSE) if (m == FALSE)
m_iMState = MONSTER_CHASING; SetState(MONSTER_CHASING);
} }
} }
@ -508,12 +515,12 @@ NSMonster::WalkRoute(void)
vector endangles; vector endangles;
/* we're busy shooting at something, don't walk */ /* 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); endangles = vectoangles(m_eEnemy.origin - origin);
/* TODO: lerp */ /* TODO: lerp */
m_vecTurnAngle[1] = endangles[1]; 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 */ /* we're on our last node */
if (m_iCurNode < 0) { if (m_iCurNode < 0) {
endangles = vectoangles(m_vecLastNode - origin); endangles = vectoangles(m_vecLastNode - origin);
@ -522,7 +529,7 @@ NSMonster::WalkRoute(void)
} }
m_vecTurnAngle[1] = endangles[1]; m_vecTurnAngle[1] = endangles[1];
input_movevalues = [m_flSequenceSpeed, 0, 0]; 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 */ /* we've got 'em in our sights, just need to walk closer */
endangles = vectoangles(m_eEnemy.origin - origin); endangles = vectoangles(m_eEnemy.origin - origin);
input_movevalues = [GetChaseSpeed(), 0, 0]; input_movevalues = [GetChaseSpeed(), 0, 0];
@ -543,9 +550,9 @@ NSMonster::WalkRoute(void)
makevectors(angles); makevectors(angles);
old_ang = v_forward; 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[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); angles = vectoangles(tmp);
#endif #endif
} }
@ -604,7 +611,7 @@ NSMonster::AnimationUpdate(void)
int fr = 0; int fr = 0;
int act = 0; int act = 0;
if (style == MONSTER_DEAD) if (GetState() == MONSTER_DEAD)
return; return;
float spvel = vlen(velocity); float spvel = vlen(velocity);
@ -652,10 +659,41 @@ NSMonster::AnimationUpdate(void)
AnimPlay(act); AnimPlay(act);
else else
SetFrame(fr); 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_RunPlayerPhysics(entity target);
void PMoveCustom_RunCrouchPhysics(entity target); void PMoveCustom_RunCrouchPhysics(entity target);
void void
@ -733,9 +771,26 @@ NSMonster::Touch(entity eToucher)
void void
NSMonster::Pain(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)) if (!m_eEnemy || (random() < 0.5))
m_eEnemy = g_dmg_eAttacker; m_eEnemy = g_dmg_eAttacker;
/* an alert monster will take a while to calm back down */
SetState(MONSTER_ALERT);
/* alert all nearby friendlies */
AlertNearby(); AlertNearby();
} }
@ -743,7 +798,7 @@ void
NSMonster::Death(void) NSMonster::Death(void)
{ {
/* we were already dead before, so gib */ /* we were already dead before, so gib */
if (style == MONSTER_DEAD) { if (GetState() == MONSTER_DEAD) {
Gib(); Gib();
return; return;
} }
@ -751,21 +806,21 @@ NSMonster::Death(void)
m_iFlags = 0x0; m_iFlags = 0x0;
/* if we make more than 50 damage, gib immediately */ /* if we make more than 50 damage, gib immediately */
if (health < -50) { if (GetHealth() < -50) {
Gib(); Gib();
return; return;
} }
/* make sure we're not causing any more obituaries */ /* make sure we're not causing any more obituaries */
flags &= ~FL_MONSTER; RemoveFlags(FL_MONSTER);
m_iFlags = 0x0;
/* gibbing action */ /* set the monster up for getting gibbed */
SetMovetype(MOVETYPE_NONE); SetMovetype(MOVETYPE_NONE);
SetSolid(SOLID_CORPSE); SetSolid(SOLID_CORPSE);
health = 50 + health; SetHealth(50 + GetHealth());
style = MONSTER_DEAD; SetState(MONSTER_DEAD);
/* monsters trigger their targets when dead */
if (GetTriggerCondition() == MTRIG_DEATH) if (GetTriggerCondition() == MTRIG_DEATH)
TriggerTargets(); TriggerTargets();
} }
@ -786,16 +841,16 @@ NSMonster::Respawn(void)
v_angle[0] = Math_FixDelta(v_angle[0]); v_angle[0] = Math_FixDelta(v_angle[0]);
v_angle[1] = Math_FixDelta(v_angle[1]); v_angle[1] = Math_FixDelta(v_angle[1]);
v_angle[2] = Math_FixDelta(v_angle[2]); 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; iBleeds = TRUE;
customphysics = Physics; customphysics = Physics;
velocity = [0,0,0];
m_iFlags = 0x0;
SendFlags = 0xff;
style = MONSTER_IDLE;
health = base_health;
m_eEnemy = __NULL__;
SetAngles(v_angle); SetAngles(v_angle);
SetSolid(SOLID_SLIDEBOX); SetSolid(SOLID_SLIDEBOX);
@ -804,7 +859,7 @@ NSMonster::Respawn(void)
SetSize(base_mins, base_maxs); SetSize(base_mins, base_maxs);
SetOrigin(GetSpawnOrigin()); SetOrigin(GetSpawnOrigin());
droptofloor(); DropToFloor();
} }
void void
@ -1109,6 +1164,7 @@ NSMonster_AlertEnemyAlliance(vector pos, float radius, int alliance)
/* if they're our friend... ignore*/ /* if they're our friend... ignore*/
if (f.IsFriend(alliance)) if (f.IsFriend(alliance))
continue; continue;
/* if the monster is dead... ignore */ /* if the monster is dead... ignore */
if (f.health <= 0) if (f.health <= 0)
continue; continue;

View file

@ -79,6 +79,7 @@ class NSSurfacePropEntity:NSRenderableEntity
float m_oldHealth; float m_oldHealth;
virtual void(void) Pain; virtual void(void) Pain;
virtual void(void) Death; virtual void(void) Death;
virtual bool(void) IsAlive;
/* Generic Damage */ /* Generic Damage */
nonvirtual void(float) SetTakedamage; nonvirtual void(float) SetTakedamage;

View file

@ -33,6 +33,12 @@ NSSurfacePropEntity::Spawned(void)
/* networking */ /* networking */
#ifdef SERVER #ifdef SERVER
bool
NSSurfacePropEntity::IsAlive(void)
{
return (health > 0) ? true : false;
}
#if INDEV #if INDEV
typedef enum typedef enum
{ {

View file

@ -139,7 +139,7 @@ NSTalkMonster::StartleAllies(void)
void void
NSTalkMonster::Sentence(string sentence) NSTalkMonster::Sentence(string sentence)
{ {
if (style == MONSTER_DEAD) if (GetState() == MONSTER_DEAD)
return; return;
string seq = Sentences_GetSamples(sentence); string seq = Sentences_GetSamples(sentence);
@ -158,7 +158,7 @@ NSTalkMonster::Sentence(string sentence)
void void
NSTalkMonster::Speak(string sentence) NSTalkMonster::Speak(string sentence)
{ {
if (style == MONSTER_DEAD) if (GetState() == MONSTER_DEAD)
return; return;
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
@ -463,7 +463,7 @@ NSTalkMonster::Physics(void)
/* make sure we're forgetting about enemies and attack states in sequence */ /* make sure we're forgetting about enemies and attack states in sequence */
if (m_iSequenceState != SEQUENCESTATE_NONE) { if (m_iSequenceState != SEQUENCESTATE_NONE) {
m_eEnemy = __NULL__; m_eEnemy = __NULL__;
m_iMState = MONSTER_IDLE; SetState(MONSTER_IDLE);
} }
/* override whatever we did above with this */ /* override whatever we did above with this */
@ -471,7 +471,7 @@ NSTalkMonster::Physics(void)
input_angles = v_angle = angles = m_vecSequenceAngle; input_angles = v_angle = angles = m_vecSequenceAngle;
SetFrame(m_flSequenceEnd); SetFrame(m_flSequenceEnd);
} else { } else {
if (style != MONSTER_DEAD) { if (GetState() != MONSTER_DEAD) {
if (m_iSequenceState == SEQUENCESTATE_NONE) { if (m_iSequenceState == SEQUENCESTATE_NONE) {
SeeThink(); SeeThink();
AttackThink(); AttackThink();
@ -612,9 +612,9 @@ NSTalkMonster::SendEntity(entity ePEnt, float fChanged)
WriteCoord(MSG_ENTITY, origin[2]); WriteCoord(MSG_ENTITY, origin[2]);
} }
if (fChanged & BASEFL_CHANGED_ANGLES) { 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[1] * 32767 / 360);
WriteShort(MSG_ENTITY, angles[2] * 32767 / 360); WriteShort(MSG_ENTITY, 0);
} }
if (fChanged & BASEFL_CHANGED_MODELINDEX) { if (fChanged & BASEFL_CHANGED_MODELINDEX) {
WriteShort(MSG_ENTITY, modelindex); WriteShort(MSG_ENTITY, modelindex);