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_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;

View file

@ -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;

View file

@ -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;

View file

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

View file

@ -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);