nuclide/src/shared/NSMonster.qc

1353 lines
31 KiB
Plaintext

/*
* 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.
*/
void
NSMonster::NSMonster(void)
{
#ifdef SERVER
if (!HasSpawnFlags(MSF_MULTIPLAYER))
if (g_grMode.MonstersSpawn() == FALSE) {
remove(this);
return;
}
m_ssLast = __NULL__;
oldnet_velocity = g_vec_null;
m_flPitch = 1.0f;
m_iFlags = 0i;
//base_mins = g_vec_null;
//base_maxs = g_vec_null;
//base_health = 100;
m_strRouteEnded = __NULL__;
m_iSequenceRemove = 0i;
m_iSequenceState = 0i;
m_flSequenceEnd = 0.0f;
m_flSequenceSpeed = 0.0f;
m_vecSequenceAngle = g_vec_null;
m_iSequenceFlags = 0i;
m_iMoveState = 0i;
m_iTriggerCondition = 0i;
m_strTriggerTarget = __NULL__;
m_flBaseTime = 0.0f;
m_eEnemy = __NULL__;
m_flAttackThink = 0.0f;
m_iMState = 0i;
m_iOldMState = 0i;
m_vecLKPos = g_vec_null;
m_flSeeTime = 0.0f;
m_flAnimTime = 0.0f;
m_flTrackingTime = 0.0f;
m_actIdle = -1;
#endif
}
#ifdef SERVER
int
NSMonster::GetTriggerCondition(void)
{
return m_iTriggerCondition;
}
void
NSMonster::TriggerTargets(void)
{
for (entity f = world; (f = find(f, ::targetname, m_strTriggerTarget));) {
NSTrigger trigger = (NSTrigger)f;
if (trigger.Trigger != __NULL__) {
trigger.Trigger(this, TRIG_TOGGLE);
}
}
}
void
NSMonster::Save(float handle)
{
super::Save(handle);
SaveEntity(handle, "m_eEnemy", m_eEnemy);
SaveEntity(handle, "m_ssLast", m_ssLast);
SaveFloat(handle, "base_health", base_health);
SaveFloat(handle, "m_flAttackThink", m_flAttackThink);
SaveFloat(handle, "m_flBaseTime", m_flBaseTime);
SaveFloat(handle, "m_flPitch", m_flPitch);
SaveFloat(handle, "m_flSequenceEnd", m_flSequenceEnd);
SaveFloat(handle, "m_flSequenceSpeed", m_flSequenceSpeed);
SaveInt(handle, "m_iFlags", m_iFlags);
SaveInt(handle, "m_iSequenceFlags", m_iSequenceFlags);
SaveInt(handle, "m_iSequenceRemove", m_iSequenceRemove);
SaveInt(handle, "m_iSequenceState", m_iSequenceState);
SaveInt(handle, "m_iTriggerCondition", m_iTriggerCondition);
SaveFloat(handle, "m_iMState", m_iMState);
SaveFloat(handle, "m_iOldMState", m_iOldMState);
SaveFloat(handle, "m_iMoveState", m_iMoveState);
SaveString(handle, "m_strRouteEnded", m_strRouteEnded);
SaveString(handle, "m_strTriggerTarget", m_strTriggerTarget);
SaveVector(handle, "base_maxs", base_maxs);
SaveVector(handle, "base_mins", base_mins);
SaveVector(handle, "m_vecLKPos", m_vecLKPos);
SaveVector(handle, "m_vecSequenceAngle", m_vecSequenceAngle);
SaveFloat(handle, "m_flSeeTime", m_flSeeTime);
SaveFloat(handle, "m_flAnimTime", m_flAnimTime);
}
void
NSMonster::Restore(string strKey, string strValue)
{
switch (strKey) {
case "m_eEnemy":
m_eEnemy = ReadEntity(strValue);
break;
case "m_ssLast":
m_ssLast = ReadEntity(strValue);
break;
case "base_health":
base_health = ReadFloat(strValue);
break;
case "m_flAttackThink":
m_flAttackThink = ReadFloat(strValue);
break;
case "m_flBaseTime":
m_flBaseTime = ReadFloat(strValue);
break;
case "m_flPitch":
m_flPitch = ReadFloat(strValue);
break;
case "m_flSequenceEnd":
m_flSequenceEnd = ReadFloat(strValue);
break;
case "m_flSequenceSpeed":
m_flSequenceSpeed = ReadFloat(strValue);
break;
case "m_iFlags":
m_iFlags = ReadInt(strValue);
break;
case "m_iSequenceFlags":
m_iSequenceFlags = ReadInt(strValue);
break;
case "m_iSequenceRemove":
m_iSequenceRemove = ReadInt(strValue);
break;
case "m_iSequenceState":
m_iSequenceState = ReadInt(strValue);
break;
case "m_iTriggerCondition":
m_iTriggerCondition = ReadInt(strValue);
break;
case "m_iMState":
m_iMState = ReadFloat(strValue);
break;
case "m_iOldMState":
m_iOldMState = ReadFloat(strValue);
break;
case "m_iMoveState":
m_iMoveState = ReadFloat(strValue);
break;
case "m_strRouteEnded":
m_strRouteEnded = ReadString(strValue);
break;
case "m_strTriggerTarget":
m_strTriggerTarget = ReadString(strValue);
break;
case "base_maxs":
base_maxs = ReadVector(strValue);
break;
case "base_mins":
base_mins = ReadVector(strValue);
break;
case "m_vecLKPos":
m_vecLKPos = ReadVector(strValue);
break;
case "m_vecSequenceAngle":
m_vecSequenceAngle = ReadVector(strValue);
break;
case "m_flSeeTime":
m_flSeeTime = ReadFloat(strValue);
break;
case "m_flAnimTime":
m_flAnimTime = ReadFloat(strValue);
break;
default:
super::Restore(strKey, strValue);
break;
}
}
int
NSMonster::AnimIdle(void)
{
return frameforaction(modelindex, ACT_IDLE);
}
int
NSMonster::AnimWalk(void)
{
return frameforaction(modelindex, ACT_WALK);
}
int
NSMonster::AnimRun(void)
{
return frameforaction(modelindex, ACT_RUN);
}
void
NSMonster::AnimPlay(float seq)
{
/* forces a client-side update */
SetSendFlags(MONFL_CHANGED_FRAME);
SetFrame(seq);
m_flAnimTime = time + frameduration(modelindex, frame);
}
bool
NSMonster::InAnimation(void)
{
return (m_flAnimTime > time) ? true : false;
}
void
NSMonster::Sound(string msg)
{
sound(this, CHAN_VOICE, msg, 1.0, ATTN_NORM);
}
void
NSMonster::Gib(void)
{
vector vecDir = vectoangles(GetOrigin() - g_dmg_vecLocation);
SetState(MONSTER_DEAD);
SetTakedamage(DAMAGE_NO);
FX_GibHuman(origin, vecDir, g_dmg_iDamage * 2.5f);
Disappear();
}
void
NSMonster::FallNoise(void)
{
}
void
NSMonster::IdleNoise(void)
{
}
void
NSMonster::AlertNoise(void)
{
}
bool
NSMonster::IsFriend(int al)
{
if (m_iAlliance == MAL_ROGUE)
return (false);
else if (al == m_iAlliance)
return (true);
return (false);
}
/* The maximum distance to which we should attempt an attack */
float
NSMonster::MeleeMaxDistance(void)
{
return 96;
}
/* Whether or not we should attempt a melee attack */
bool
NSMonster::MeleeCondition(void)
{
return (vlen(origin - m_eEnemy.origin) < MeleeMaxDistance()) ? true : false;
}
float
NSMonster::SeeFOV(void)
{
return 90;
}
void
NSMonster::AlertNearby(void)
{
if (m_eEnemy == __NULL__)
return;
for (entity w = world; (w = findfloat(w, ::takedamage, DAMAGE_YES));) {
if (!IsFriend(w.m_iAlliance))
continue;
/* only bother if within 512 unit radius */
if (vlen(origin - w.origin) > 512)
continue;
//NSMonster_Log("Alert! %s get %s", w.classname, m_eEnemy.classname);
NSMonster f = (NSMonster)w;
/* we shouldn't override this when they already got a target */
if (f.m_eEnemy != __NULL__)
continue;
f.m_eEnemy = m_eEnemy;
f._Alerted();
}
}
/* returns TRUE if 'enemy' should be considered a valid target for killing */
bool
NSMonster::IsValidEnemy(entity enny)
{
if (enny == __NULL__)
return FALSE;
/* dead enny should not be considered valid */
if ((enny.solid == SOLID_CORPSE) || (enny.health <= 0))
return FALSE;
/* such monster should ignore players */
if ((enny.flags & FL_CLIENT) && HasSpawnFlags(MSF_IGNOREPLAYER))
return FALSE;
/* monsters ignore enny who uses notarget cheat, useful for development */
if (enny.flags & FL_NOTARGET)
return FALSE;
/* if they're our friend... ignore */
if (IsFriend(enny.m_iAlliance))
return FALSE;
/* prevent from shooting non-sentient stuff */
if (!(enny.flags & (FL_MONSTER | FL_CLIENT)))
return FALSE;
return TRUE;
}
static bool
NSMonster_TraceAgainsTarget(NSMonster monster, NSEntity target)
{
traceline(monster.GetEyePos(), target.GetOrigin(), MOVE_NORMAL, monster);
/* we have line of sight with the player */
if (trace_fraction == 1.0f || trace_ent == target) {
return true;
}
return false;
}
void
NSMonster::SeeThink(void)
{
if (m_flAttackThink < time)
if (m_eEnemy) {
/* check if we should invalidate current enemy */
if (IsValidEnemy(m_eEnemy)) {
/* only update 1/4th of a second */
if (m_flSeeTime > time)
return;
m_flSeeTime = time + 0.25f;
/* see if we can trace our target, if yes, update our timestamp */
if (NSMonster_TraceAgainsTarget(this, (NSEntity) m_eEnemy) == true) {
m_flTrackingTime = time;
}
/* if we haven't gotten a trace in 5 seconds, give up. */
if ((m_flTrackingTime + 5.0) > time)
return;
}
/* enemy is not valid anymore, reset it, clear route and search for new enemy */
RouteClear();
makevectors(angles);
RouteToPosition(m_eEnemy.origin + (v_forward * -64));
m_flSequenceSpeed = GetWalkSpeed();
SetState(MONSTER_ALERT);
m_eEnemy = __NULL__;
m_flSeeTime = 0;
}
if (m_flSeeTime > time)
return;
m_flSeeTime = time + 0.25f;
/* a horse type monster always knows where the nearest player is */
if (m_iFlags & MSF_HORDE) {
m_eEnemy = NSMonster_FindClosestPlayer(this);
if (m_eEnemy)
RouteToPosition(m_eEnemy.origin);
return;
}
for (entity w = world; (w = findfloat(w, ::takedamage, DAMAGE_YES));) {
/* check if 'w' could be a valid enemy */
if (!IsValidEnemy(w))
continue;
/* first, is the potential enemy in our field of view? */
makevectors(angles);
vector v = normalize(w.origin - GetEyePos());
float flDot = v * v_forward;
if (flDot < SeeFOV()/180)
continue;
/* we have line of sight with the player */
if (NSMonster_TraceAgainsTarget(this, (NSEntity)w) == true) {
if (m_eEnemy != w) {
m_eEnemy = w;
m_flTrackingTime = time;
_Alerted();
AlertNearby();
}
return;
}
}
}
var float autocvar_ai_stepSize = 128;
float
NSMonster::GetWalkSpeed(void)
{
float speed = autocvar_ai_stepSize / frameduration(modelindex, AnimWalk());
return speed;
}
float
NSMonster::GetChaseSpeed(void)
{
float speed = autocvar_ai_stepSize / frameduration(modelindex, AnimRun());
return speed;
}
float
NSMonster::GetRunSpeed(void)
{
float speed = autocvar_ai_stepSize / frameduration(modelindex, AnimRun());
return speed;
}
float
NSMonster::GetYawSpeed(void)
{
if (GetState() == MONSTER_AIMING)
return 128;
return 90;
}
void
NSMonster::_LerpTurnToEnemy(void)
{
/* only continue if we're in one of the three states. */
if (GetState() != MONSTER_AIMING)
if (GetState() != MONSTER_CHASING)
if (GetState() != MONSTER_FOLLOWING)
return;
if (!m_eEnemy)
return;
float turnSpeed = GetYawSpeed();
vector vecWishAngle = vectoangles(m_eEnemy.origin - origin);
float yawDiff = anglesub(vecWishAngle[1], v_angle[1]);
/* min/max out the diff */
if (yawDiff > 0) {
v_angle[1] += turnSpeed * frametime;
if (v_angle[1] > vecWishAngle[1])
v_angle[1] = vecWishAngle[1];
} else if (yawDiff < 0) {
v_angle[1] -= turnSpeed * frametime;
if (v_angle[1] < vecWishAngle[1])
v_angle[1] = vecWishAngle[1];
}
/* fix angles */
makevectors(v_angle);
vecWishAngle = vectoangles( v_forward );
angles[1] = input_angles[1] = v_angle[1] = vecWishAngle[1];
}
void
NSMonster::AttackThink(void)
{
if (InSequence())
return;
if (m_flAttackThink > time)
return;
if (!m_eEnemy)
return;
/* do we have a clear shot? */
other = world;
traceline(origin, m_eEnemy.origin, MOVE_OTHERONLY, this);
/* something is blocking us */
if (trace_fraction < 1.0f) {
SetState(MONSTER_ALERT);
/* FIXME: This is unreliable, but unlikely that a player ever is here */
if (m_vecLKPos != [0,0,0]) {
RouteClear();
RouteToPosition(m_vecLKPos);
m_flSequenceSpeed = 140;
m_vecLKPos = [0,0,0];
}
} else {
SetState(MONSTER_AIMING);
/* make sure we remember the last known position. */
m_vecLKPos = m_eEnemy.origin;
}
/* the state may have switched */
if (m_flAttackThink > time)
return;
if (GetState() == MONSTER_AIMING) {
int m;
_LerpTurnToEnemy();
if (MeleeCondition() == TRUE)
m = AttackMelee();
else {
m = AttackRanged();
/* if we don't have the desired attack mode, walk */
if (m == FALSE)
SetState(MONSTER_CHASING);
}
}
}
int
NSMonster::AttackMelee(void)
{
m_flAttackThink = time + 0.5f;
return (0);
}
int
NSMonster::AttackRanged(void)
{
m_flAttackThink = time + 0.5f;
return (0);
}
void
NSMonster::AttackDraw(void)
{
NSMonster_Log("^1%s::AttackDraw: Not defined!", classname);
m_flAttackThink = time + 0.5f;
}
void
NSMonster::AttackHolster(void)
{
NSMonster_Log("^1%s::AttackHolster: Not defined!", classname);
m_flAttackThink = time + 0.5f;
}
void
NSMonster::FreeState(void)
{
string to_trigger;
m_flSequenceEnd = 0;
m_iSequenceState = SEQUENCESTATE_NONE;
m_iSequenceFlags = 0;
if (m_ssLast) {
scripted_sequence seq = (scripted_sequence)m_ssLast;
seq.m_iValue = TRUE;
}
/* we're clearing m_strRouteEnded early, because m_strRouteEnded
might change when .Trigger is executed. It could be another scripted
sequence triggering another sequence. Hence the caching */
to_trigger = m_strRouteEnded;
m_strRouteEnded = __NULL__;
m_ssLast = __NULL__;
/* trigger when required */
if (to_trigger) {
for (entity f = world; (f = find(f, ::targetname, to_trigger));) {
NSEntity trigger = (NSEntity)f;
if (trigger.Trigger != __NULL__) {
trigger.Trigger(this, TRIG_TOGGLE);
}
}
}
if (m_iSequenceRemove) {
Hide();
}
NSMonster_Log("^2%s::^3FreeState^7: (%i, %S)", classname, m_iSequenceRemove, to_trigger);
}
void
NSMonster::FreeStateMoved(void)
{
vector new_origin;
new_origin = gettaginfo(this, 1);
NSMonster_Log("^2%s::^3FreeStateMoved^7: moved to %v", classname, new_origin);
SetOrigin(new_origin);
DropToFloor();
FreeState();
}
void
NSMonster::RouteEnded(void)
{
super::RouteEnded();
if (GetSequenceState() != SEQUENCESTATE_ACTIVE)
return;
/* mark that we've ended a sequence, if we're in one and que anim */
if (m_flSequenceEnd) {
float duration = frameduration(modelindex, m_flSequenceEnd);
m_iSequenceState = SEQUENCESTATE_ENDING;
think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved;
nextthink = time + duration;
NSMonster_Log("^2%s::^3CheckRoute^7: %s overriding anim for %f seconds (modelindex %d, frame %d)", \
classname, this.targetname, duration, modelindex, m_flSequenceEnd);
} else {
/* we still need to trigger targets */
think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved;
nextthink = time;
NSMonster_Log("^2%s::^3CheckRoute^7: %s has no anim, finished sequence", \
classname, this.targetname);
}
}
void
NSMonster::WalkRoute(void)
{
/* we're busy shooting at something, don't walk */
if (GetState() == MONSTER_AIMING && m_eEnemy) {
input_angles = vectoangles(m_eEnemy.origin - origin);
input_angles[0] = input_angles[2] = 0;
} else if (m_iNodes && (GetState() == MONSTER_IDLE || GetState() == MONSTER_ALERT)) {
input_angles = GetRouteDirection();
input_angles[0] = input_angles[2] = 0;
input_movevalues = GetRouteMovevalues() * m_flSequenceSpeed;
} else if (GetState() == MONSTER_CHASING && m_eEnemy) {
/* we've got 'em in our sights, just need to walk closer */
input_angles = vectoangles(m_eEnemy.origin - origin);
input_angles[0] = input_angles[2] = 0;
input_movevalues = [GetChaseSpeed(), 0, 0];
} else
return;
/* yaw interpolation */
{
float turnSpeed = GetYawSpeed();
vector vecWishAngle = input_angles;
float yawDiff = anglesub(vecWishAngle[1], v_angle[1]);
/* min/max out the diff */
if (yawDiff > 0) {
v_angle[1] += turnSpeed * frametime;
if (v_angle[1] > vecWishAngle[1])
v_angle[1] = vecWishAngle[1];
} else if (yawDiff < 0) {
v_angle[1] -= turnSpeed * frametime;
if (v_angle[1] < vecWishAngle[1])
v_angle[1] = vecWishAngle[1];
}
/* fix angles */
makevectors(v_angle);
vecWishAngle = vectoangles( v_forward );
angles[1] = input_angles[1] = v_angle[1] = vecWishAngle[1];
}
}
void
NSMonster::AnimationUpdate(void)
{
int fr = 0;
int act = 0;
if (GetState() == MONSTER_DEAD)
return;
if (GetState() == MONSTER_AIMING)
return;
float spvel = vlen(velocity);
float midspeed = GetWalkSpeed() + ((GetRunSpeed() - GetWalkSpeed()) * 0.5f);
if (spvel < 5) {
if (m_actIdle == -1)
m_actIdle = AnimIdle();
fr = m_actIdle;
if (m_iMoveState != MOVESTATE_IDLE)
m_flAnimTime = 0.0f;
if (fr == -1)
act = (frameforaction(modelindex, ACT_IDLE));
m_iMoveState = MOVESTATE_IDLE;
} else if (spvel < midspeed) {
fr = AnimWalk();
if (m_iMoveState != MOVESTATE_WALK)
m_flAnimTime = 0.0f;
if (fr == -1)
act = (frameforaction(modelindex, ACT_WALK));
m_iMoveState = MOVESTATE_WALK;
} else {
fr = AnimRun();
if (m_iMoveState != MOVESTATE_RUN)
m_flAnimTime = 0.0f;
if (fr == -1)
act = (frameforaction(modelindex, ACT_RUN));
m_iMoveState = MOVESTATE_RUN;
}
if (m_flAnimTime > 0.0f) {
return;
}
if (fr == -1)
AnimPlay(act);
else
SetFrame(fr);
}
/* 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_t oldState, monsterState_t newState)
{
NSMonster_Log("^2%s::^3StateChanged^7: state changed from %d to %d", \
classname, oldState, newState);
}
void
NSMonster::SetState(monsterState_t newState)
{
if (newState == m_iMState)
return;
m_iOldMState = m_iMState;
m_iMState = newState;
StateChanged(m_iOldMState, m_iMState);
}
monsterState_t
NSMonster::GetState(void)
{
return m_iMState;
}
int
NSMonster::GetSequenceState(void)
{
return m_iSequenceState;
}
bool
NSMonster::InSequence(void)
{
return (GetSequenceState() == SEQUENCESTATE_NONE) ? false : true;
}
void
NSMonster::RunAI(void)
{
IdleNoise();
SeeThink();
AttackThink();
}
void PMoveCustom_RunPlayerPhysics(entity target);
void PMoveCustom_RunCrouchPhysics(entity target);
void
NSMonster::Physics(void)
{
input_movevalues = [0,0,0];
input_impulse = 0;
input_buttons = 0;
input_timelength = frametime;
input_angles = angles;
/* when stuck in a sequence, forget enemies, combat stance */
if (GetSequenceState() != SEQUENCESTATE_NONE) {
m_eEnemy = __NULL__;
SetState(MONSTER_IDLE);
}
/* we're ending a scripted sequence, so play its animation */
if (GetSequenceState() == SEQUENCESTATE_ENDING) {
angles[1] = input_angles[1] = m_vecSequenceAngle[1];
SetFrame(m_flSequenceEnd);
} else {
/* if still alive... */
if (IsAlive()) {
/* only run AI functions when not in a scripted sequence */
if (InSequence() == false) {
RunAI();
}
_LerpTurnToEnemy();
AnimationUpdate();
}
/* suppress movement when playing an animation outside
a scripted sequence */
if (InAnimation() == true && InSequence() == false) {
input_movevalues = [0,0,0];
} else {
CheckRoute();
WalkRoute();
}
hitcontentsmaski = CONTENTBITS_MONSTER;
if (CanCrouch())
PMoveCustom_RunCrouchPhysics(this);
else
PMoveCustom_RunPlayerPhysics(this);
SetOrigin(origin);
}
if (!(GetFlags() & FL_ONGROUND) && velocity[2] < -415) {
if (!(m_iFlags & MSF_FALLING)) {
FallNoise();
}
m_iFlags |= MSF_FALLING;
} else {
m_iFlags &= ~MSF_FALLING;
}
/* support for think/nextthink */
if (think && nextthink > 0.0f) {
if (nextthink < time) {
nextthink = 0.0f;
think();
}
}
m_flBaseTime = frame1time;
frame1time += frametime;
processmodelevents(modelindex, frame, m_flBaseTime,
frame1time, HandleAnimEvent);
}
void
NSMonster::Touch(entity eToucher)
{
if (movetype != MOVETYPE_WALK)
return;
if (eToucher.movetype == MOVETYPE_WALK) {
if (eToucher.absmin[2] < origin[2])
velocity = normalize(eToucher.origin - origin) * -128;
}
}
void
NSMonster::HasBeenHit(void)
{
/* to be filled in by the sub-class */
}
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 */
if (GetState() != MONSTER_ALERT)
if (GetState() != MONSTER_FOLLOWING)
if (GetState() != MONSTER_CHASING)
SetState(MONSTER_ALERT);
/* alert all nearby friendlies */
AlertNearby();
HasBeenHit();
}
void
NSMonster::HasBeenKilled(void)
{
/* to be filled in by the sub-class */
}
void
NSMonster::HasBeenGibbed(void)
{
/* to be filled in by the sub-class */
}
void
NSMonster::HasBeenAlerted(void)
{
/* to be filled in by the sub-class */
}
void
NSMonster::_Alerted(void)
{
HasBeenAlerted();
}
void
NSMonster::Death(void)
{
/* we were already dead before, so gib */
if (GetState() == MONSTER_DEAD) {
HasBeenGibbed();
Gib();
return;
}
m_iFlags = 0x0;
/* if we make more than 50 damage, gib immediately */
if (GetHealth() < -50) {
HasBeenGibbed();
Gib();
return;
}
/* make sure we're not causing any more obituaries */
HasBeenKilled();
RemoveFlags(FL_MONSTER);
/* set the monster up for getting gibbed */
SetMovetype(MOVETYPE_NONE);
SetSolid(SOLID_CORPSE);
SetHealth(50 + GetHealth());
SetState(MONSTER_DEAD);
/* monsters trigger their targets when dead */
if (GetTriggerCondition() == MTRIG_DEATH)
TriggerTargets();
}
#if 0
void
NSMonster::Hide(void)
{
SetModelindex(0);
SetSolid(SOLID_NOT);
SetMovetype(MOVETYPE_NONE);
customphysics = __NULL__;
}
#endif
void
NSMonster::Respawn(void)
{
v_angle = GetSpawnAngles();
v_angle[0] = Math_FixDelta(v_angle[0]);
v_angle[1] = Math_FixDelta(v_angle[1]);
v_angle[2] = Math_FixDelta(v_angle[2]);
AddFlags(FL_MONSTER);
SetTakedamage(DAMAGE_YES);
SetVelocity([0,0,0]);
SetState(MONSTER_IDLE);
SetHealth(base_health);
m_eEnemy = __NULL__;
m_iFlags = 0x0;
SetCanBleed(true);
customphysics = Physics;
SetAngles(v_angle);
SetSolid(SOLID_SLIDEBOX);
SetMovetype(MOVETYPE_WALK);
SetModel(GetSpawnModel());
SetSize(base_mins, base_maxs);
SetOrigin(GetSpawnOrigin());
DropToFloor();
}
void
NSMonster::SpawnKey(string strKey, string strValue)
{
switch (strKey) {
/* The legacy GoldSrc trigger condition system */
case "TriggerCondition":
m_iTriggerCondition = stoi(strValue);
break;
case "TriggerTarget":
m_strTriggerTarget = strValue;
break;
default:
NSSurfacePropEntity::SpawnKey(strKey, strValue);
break;
}
}
void
NSMonster::EvaluateEntity(void)
{
EVALUATE_VECTOR(origin, 0, MONFL_CHANGED_ORIGIN_X)
EVALUATE_VECTOR(origin, 1, MONFL_CHANGED_ORIGIN_Y)
EVALUATE_VECTOR(origin, 2, MONFL_CHANGED_ORIGIN_Z)
EVALUATE_VECTOR(angles, 0, MONFL_CHANGED_ANGLES_X)
EVALUATE_VECTOR(angles, 1, MONFL_CHANGED_ANGLES_Y)
EVALUATE_VECTOR(angles, 2, MONFL_CHANGED_ANGLES_Z)
EVALUATE_FIELD(modelindex, MONFL_CHANGED_MODELINDEX)
EVALUATE_VECTOR(view_ofs, 2, MONFL_CHANGED_MODELINDEX)
EVALUATE_FIELD(solid, MONFL_CHANGED_SOLID)
EVALUATE_FIELD(movetype, MONFL_CHANGED_FLAGS)
EVALUATE_FIELD(flags, MONFL_CHANGED_FLAGS)
EVALUATE_VECTOR(mins, 0, MONFL_CHANGED_SIZE)
EVALUATE_VECTOR(mins, 1, MONFL_CHANGED_SIZE)
EVALUATE_VECTOR(mins, 2, MONFL_CHANGED_SIZE)
EVALUATE_VECTOR(maxs, 0, MONFL_CHANGED_SIZE)
EVALUATE_VECTOR(maxs, 1, MONFL_CHANGED_SIZE)
EVALUATE_VECTOR(maxs, 2, MONFL_CHANGED_SIZE)
EVALUATE_FIELD(frame, MONFL_CHANGED_FRAME)
EVALUATE_FIELD(skin, MONFL_CHANGED_SKINHEALTH)
EVALUATE_FIELD(health, MONFL_CHANGED_SKINHEALTH)
EVALUATE_FIELD(effects, MONFL_CHANGED_EFFECTS)
EVALUATE_FIELD(m_iBody, MONFL_CHANGED_BODY)
EVALUATE_FIELD(scale, MONFL_CHANGED_SCALE)
EVALUATE_VECTOR(velocity, 0, MONFL_CHANGED_VELOCITY)
EVALUATE_VECTOR(velocity, 1, MONFL_CHANGED_VELOCITY)
EVALUATE_VECTOR(velocity, 2, MONFL_CHANGED_VELOCITY)
EVALUATE_FIELD(m_iRenderMode, MONFL_CHANGED_RENDERMODE)
EVALUATE_FIELD(m_iRenderFX, MONFL_CHANGED_RENDERMODE)
EVALUATE_VECTOR(m_vecRenderColor, 0, MONFL_CHANGED_RENDERCOLOR)
EVALUATE_VECTOR(m_vecRenderColor, 1, MONFL_CHANGED_RENDERCOLOR)
EVALUATE_VECTOR(m_vecRenderColor, 2, MONFL_CHANGED_RENDERCOLOR)
EVALUATE_FIELD(m_flRenderAmt, MONFL_CHANGED_RENDERAMT)
}
/* Make sure StartFrame calls this */
float
NSMonster::SendEntity(entity ePEnt, float flChanged)
{
if (!modelindex)
return (0);
if (clienttype(ePEnt) != CLIENTTYPE_REAL)
return (0);
WriteByte(MSG_ENTITY, ENT_MONSTER);
/* broadcast how much data is expected to be read */
WriteFloat(MSG_ENTITY, flChanged);
SENDENTITY_COORD(origin[0], MONFL_CHANGED_ORIGIN_X)
SENDENTITY_COORD(origin[1], MONFL_CHANGED_ORIGIN_Y)
SENDENTITY_COORD(origin[2], MONFL_CHANGED_ORIGIN_Z)
SENDENTITY_ANGLE(angles[0], MONFL_CHANGED_ANGLES_X)
SENDENTITY_ANGLE(angles[1], MONFL_CHANGED_ANGLES_Y)
SENDENTITY_ANGLE(angles[2], MONFL_CHANGED_ANGLES_Z)
SENDENTITY_SHORT(modelindex, MONFL_CHANGED_MODELINDEX)
SENDENTITY_BYTE(view_ofs[2], MONFL_CHANGED_MODELINDEX)
SENDENTITY_BYTE(solid, MONFL_CHANGED_SOLID)
SENDENTITY_BYTE(movetype, MONFL_CHANGED_FLAGS)
SENDENTITY_INT(flags, MONFL_CHANGED_FLAGS)
SENDENTITY_COORD(mins[0], MONFL_CHANGED_SIZE)
SENDENTITY_COORD(mins[1], MONFL_CHANGED_SIZE)
SENDENTITY_COORD(mins[2], MONFL_CHANGED_SIZE)
SENDENTITY_COORD(maxs[0], MONFL_CHANGED_SIZE)
SENDENTITY_COORD(maxs[1], MONFL_CHANGED_SIZE)
SENDENTITY_COORD(maxs[2], MONFL_CHANGED_SIZE)
SENDENTITY_BYTE(frame, MONFL_CHANGED_FRAME)
SENDENTITY_FLOAT(skin, MONFL_CHANGED_SKINHEALTH)
SENDENTITY_FLOAT(health, MONFL_CHANGED_SKINHEALTH)
SENDENTITY_FLOAT(effects, MONFL_CHANGED_EFFECTS)
SENDENTITY_BYTE(m_iBody, MONFL_CHANGED_BODY)
SENDENTITY_FLOAT(scale, MONFL_CHANGED_SCALE)
SENDENTITY_COORD(velocity[0], MONFL_CHANGED_VELOCITY)
SENDENTITY_COORD(velocity[1], MONFL_CHANGED_VELOCITY)
SENDENTITY_COORD(velocity[2], MONFL_CHANGED_VELOCITY)
SENDENTITY_BYTE(m_iRenderMode, MONFL_CHANGED_RENDERMODE)
SENDENTITY_BYTE(m_iRenderFX, MONFL_CHANGED_RENDERMODE)
SENDENTITY_ANGLE(m_vecRenderColor[0], MONFL_CHANGED_RENDERCOLOR)
SENDENTITY_ANGLE(m_vecRenderColor[1], MONFL_CHANGED_RENDERCOLOR)
SENDENTITY_ANGLE(m_vecRenderColor[2], MONFL_CHANGED_RENDERCOLOR)
SENDENTITY_ANGLE(m_flRenderAmt, MONFL_CHANGED_RENDERAMT)
return (1);
}
#else
void
NSMonster::customphysics(void)
{
/* Page intentionally left blank */
}
float
NSMonster::predraw(void)
{
float render;
render = super::predraw();
/* are we inside of an interpolation? */
if (frame != frame2) {
/* we're done lerping */
if (lerpfrac <= 0.0f)
frame2 = frame;
lerpfrac -= frametime * 4.0f;
} else {
/* make sure we're set up for next lerp */
lerpfrac = 1.0f;
frame2time = frame1time;
}
_RenderDebugViewCone();
return render;
}
/*
============
NSMonster::ReceiveEntity
============
*/
void
NSMonster::ReceiveEntity(float flNew, float flChanged)
{
READENTITY_COORD(origin[0], MONFL_CHANGED_ORIGIN_X)
READENTITY_COORD(origin[1], MONFL_CHANGED_ORIGIN_Y)
READENTITY_COORD(origin[2], MONFL_CHANGED_ORIGIN_Z)
READENTITY_ANGLE(angles[0], MONFL_CHANGED_ANGLES_X)
READENTITY_ANGLE(angles[1], MONFL_CHANGED_ANGLES_Y)
READENTITY_ANGLE(angles[2], MONFL_CHANGED_ANGLES_Z)
READENTITY_SHORT(modelindex, MONFL_CHANGED_MODELINDEX)
READENTITY_BYTE(view_ofs[2], MONFL_CHANGED_MODELINDEX)
READENTITY_BYTE(solid, MONFL_CHANGED_SOLID)
READENTITY_BYTE(movetype, MONFL_CHANGED_FLAGS)
READENTITY_INT(flags, MONFL_CHANGED_FLAGS)
READENTITY_COORD(mins[0], MONFL_CHANGED_SIZE)
READENTITY_COORD(mins[1], MONFL_CHANGED_SIZE)
READENTITY_COORD(mins[2], MONFL_CHANGED_SIZE)
READENTITY_COORD(maxs[0], MONFL_CHANGED_SIZE)
READENTITY_COORD(maxs[1], MONFL_CHANGED_SIZE)
READENTITY_COORD(maxs[2], MONFL_CHANGED_SIZE)
READENTITY_BYTE(frame, MONFL_CHANGED_FRAME)
READENTITY_FLOAT(skin, MONFL_CHANGED_SKINHEALTH)
READENTITY_FLOAT(health, MONFL_CHANGED_SKINHEALTH)
READENTITY_FLOAT(effects, MONFL_CHANGED_EFFECTS)
READENTITY_BYTE(m_iBody, MONFL_CHANGED_BODY)
READENTITY_FLOAT(scale, MONFL_CHANGED_SCALE)
READENTITY_COORD(velocity[0], MONFL_CHANGED_VELOCITY)
READENTITY_COORD(velocity[1], MONFL_CHANGED_VELOCITY)
READENTITY_COORD(velocity[2], MONFL_CHANGED_VELOCITY)
READENTITY_BYTE(m_iRenderMode, MONFL_CHANGED_RENDERMODE)
READENTITY_BYTE(m_iRenderFX, MONFL_CHANGED_RENDERMODE)
READENTITY_ANGLE(m_vecRenderColor[0], MONFL_CHANGED_RENDERCOLOR)
READENTITY_ANGLE(m_vecRenderColor[1], MONFL_CHANGED_RENDERCOLOR)
READENTITY_ANGLE(m_vecRenderColor[2], MONFL_CHANGED_RENDERCOLOR)
READENTITY_ANGLE(m_flRenderAmt, MONFL_CHANGED_RENDERAMT)
if (scale == 0.0)
scale = 1.0f;
if (flChanged & MONFL_CHANGED_FRAME)
frame1time = 0.0f;
if (flChanged & MONFL_CHANGED_SIZE)
setsize(this, mins * scale, maxs * scale);
if (flChanged & MONFL_CHANGED_BODY)
setcustomskin(this, "", sprintf("geomset 0 %i\ngeomset 1 %i\n", m_iBody, m_iBody));
setorigin(this, origin);
}
void
NSMonster::_RenderDebugViewCone(void)
{
vector v;
float flDot;
vector testOrg;
if (health <= 0 || solid == SOLID_CORPSE)
return;
if (autocvar(r_showViewCone, 0) == 0)
return;
makevectors(angles);
testOrg = pSeat->m_ePlayer.origin;
v = normalize(testOrg - GetEyePos());
flDot = v * v_forward;
/* not inside our FoV at all */
if (flDot < 90.0f/180) {
drawcone(GetEyePos(), angles, 16, 60, 90, [0.25,0,0], 0.25f);
return;
}
traceline(GetEyePos(), testOrg, MOVE_EVERYTHING, this);
/* we have line of sight with the client */
if (trace_fraction == 1.0f || trace_ent == pSeat->m_ePlayer) {
drawcone(GetEyePos(), angles, 16, 60, 90, [1,0,0], 0.75f);
} else { /* in FoV, no line of sight */
drawcone(GetEyePos(), angles, 16, 60, 90, [1,1,1], 0.25f);
}
}
#endif
#ifdef CLIENT
void
NSMonster_ReadEntity(bool new)
{
NSMonster me = (NSMonster)self;
if (new) {
spawnfunc_NSMonster();
}
me.ReceiveEntity(new, readfloat());
}
#else
var int autocvar_ai_alertdebug = 0;
var float g_monsteralert_timer;
void
NSMonster_AlertEnemyAlliance(vector pos, float radius, int alliance)
{
/* sometimes many alert-sounds happen at once... we don't really want that */
if (g_monsteralert_timer > time)
return;
if (autocvar_ai_alertdebug)
print(sprintf("AI alert from %v with radius %f and alliance %i\n", pos, radius, alliance));
for (entity w = world; (w = findfloat(w, ::takedamage, DAMAGE_YES));) {
/* out of radius */
if (vlen(pos - w.origin) > radius) {
continue;
}
/* only target monsters */
if (!(w.flags & FL_MONSTER)) {
if (autocvar_ai_alertdebug)
print(sprintf("\t%S is not a monster\n", w.classname));
continue;
}
NSMonster f = (NSMonster)w;
/* they already got a target of some kind */
if (f.m_eEnemy) {
if (autocvar_ai_alertdebug)
print(sprintf("\t%S already has a target\n", w.classname));
continue;
}
/* if they're our friend... ignore*/
if (f.IsFriend(alliance)) {
if (autocvar_ai_alertdebug)
print(sprintf("\t%S is friend of alliance %i\n", w.classname, alliance));
continue;
}
/* if the monster is dead... ignore */
if (f.health <= 0) {
if (autocvar_ai_alertdebug)
print(sprintf("\t%S is dead, cannot be alerted\n", w.classname));
continue;
}
if (autocvar_ai_alertdebug)
print(sprintf("\twe're alerting %S to go to %v\n", w.classname, pos));
/* we've heard a noise. investigate the location */
f.RouteClear();
f.RouteToPosition(pos);
f.m_flSequenceSpeed = f.GetWalkSpeed();
f.AlertNoise();
}
g_monsteralert_timer = time + 0.5f;
}
entity NSMonster_FindClosestPlayer(entity target) {
NSMonster t = (NSMonster)target;
entity best = world;
float bestdist;
float dist;
bestdist = 9999999;
for (entity e = world; (e = find(e, classname, "player"));) {
/* hack: don't ever return dead players. they're invisible. */
if (!t.IsValidEnemy(e))
continue;
dist = vlen(target.origin - e.origin);
if (dist < bestdist) {
bestdist = dist;
best = e;
}
}
return best;
}
#endif