/* * Copyright (c) 2016-2020 Marco Cawthorne * * 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. */ #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) { SaveInt(handle, "TriggerCondition", m_iTriggerCondition); SaveString(handle, "TriggerTarget", m_strTriggerTarget); SaveFloat(handle, "flPitch", m_flPitch); SaveInt(handle, "iFlags", m_iFlags); super::Save(handle); } void NSMonster::Restore(string strKey, string strValue) { switch (strKey) { case "TriggerCondition": m_iTriggerCondition = stoi(strValue); break; case "TriggerTarget": m_strTriggerTarget = strValue; break; case "flPitch": m_flPitch = stof(strValue); break; case "iFlags": m_iFlags = stoi(strValue); break; } } int NSMonster::AnimIdle(void) { return -1; } int NSMonster::AnimWalk(void) { return -1; } int NSMonster::AnimRun(void) { return -1; } void NSMonster::AnimPlay(float seq) { /* forces a client-side update */ SetSendFlags(BASEFL_CHANGED_FRAME); SetFrame(seq); m_flAnimTime = time + frameduration(modelindex, frame); } void NSMonster::Sound(string msg) { sound(this, CHAN_VOICE, msg, 1.0, ATTN_NORM); } void NSMonster::Gib(void) { takedamage = DAMAGE_NO; FX_GibHuman(origin, vectoangles(origin - g_dmg_eAttacker.origin), g_dmg_iDamage * 2.0f); Hide(); } void NSMonster::FallNoise(void) { } void NSMonster::IdleNoise(void) { } int NSMonster::IsFriend(int al) { if (m_iAlliance == MAL_ROGUE) return (0); else if (al == m_iAlliance) return (1); return (0); } /* 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 */ int 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; //NSLog("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; } } void NSMonster::SeeThink(void) { if (m_eEnemy) return; 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 = Entity_FindClosest(this, "player"); if (m_eEnemy) NewRoute(m_eEnemy.origin); return; } for (entity w = world; (w = findfloat(w, ::takedamage, DAMAGE_YES));) { /* prevent them from shooting non-sentient stuff */ if (!(w.flags & FL_MONSTER) && !(w.flags & FL_CLIENT)) continue; /* if they're our friend... ignore*/ if (IsFriend(w.m_iAlliance)) continue; /* is the target dead? */ if (w.health <= 0) continue; /* some monsters will ignore players */ if ((w.flags & FL_CLIENT) && HasSpawnFlags(MSF_IGNOREPLAYER)) continue; /* first, is the potential enemy in our field of view? */ makevectors(v_angle); vector v = normalize(w.origin - origin); float flDot = v * v_forward; if (flDot < SeeFOV()/180) continue; traceline(origin, w.origin, MOVE_EVERYTHING, this); /* we have line of sight with the player */ if (trace_fraction == 1.0f || trace_ent == w) { if (m_eEnemy != w) { m_eEnemy = w; AlertNearby(); } return; } } } float NSMonster::GetWalkSpeed(void) { return 64; } float NSMonster::GetChaseSpeed(void) { return 240; } float NSMonster::GetRunSpeed(void) { return 140; } void NSMonster::AttackThink(void) { if (m_iSequenceState != SEQUENCESTATE_NONE) return; if (m_flAttackThink > time) return; if (!m_eEnemy) return; /* reset */ if (m_eEnemy.solid == SOLID_CORPSE || (m_eEnemy && m_eEnemy.health <= 0)) { m_eEnemy = __NULL__; ClearRoute(); } /* 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) { m_iMState = MONSTER_IDLE; /* FIXME: This is unreliable, but unlikely that a player ever is here */ if (m_vecLKPos != [0,0,0]) { ClearRoute(); NewRoute(m_vecLKPos); m_flSequenceSpeed = 140; m_vecLKPos = [0,0,0]; } } else { m_iMState = MONSTER_AIMING; /* make sure we remember the last known position. */ m_vecLKPos = m_eEnemy.origin; } if (m_iMState == MONSTER_AIMING) { int m; if (MeleeCondition() == TRUE) m = AttackMelee(); else { m = AttackRanged(); /* if we don't have the desired attack mode, walk */ if (m == FALSE) m_iMState = 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) { NSLog("^1%s::AttackDraw: Not defined!", classname); m_flAttackThink = time + 0.5f; } void NSMonster::AttackHolster(void) { NSLog("^1%s::AttackHolster: Not defined!", classname); m_flAttackThink = time + 0.5f; } void NSMonster::ClearRoute(void) { if (m_iNodes) { m_iNodes = 0; memfree(m_pRoute); } } 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(); } dprint("NSMonster::FreeState\n"); } void NSMonster::FreeStateMoved(void) { vector new_origin; dprint("NSMonster::FreeStateMoved\n"); new_origin = gettaginfo(this, 1); SetOrigin(new_origin); droptofloor(); FreeState(); } void NSMonster::CheckRoute(void) { float flDist; vector evenpos; if (!m_iNodes) return; /* level out position/node stuff */ if (m_iCurNode < 0) { evenpos = m_vecLastNode; evenpos[2] = origin[2]; } else { evenpos = m_pRoute[m_iCurNode].dest; evenpos[2] = origin[2]; } flDist = floor(vlen(evenpos - origin)); if (flDist < 8) { NSLog("^2NSMonster::^3CheckRoute^7: " \ "%s reached node\n", this.targetname); m_iCurNode--; velocity = [0,0,0]; /* clamp friction */ /* we've still traveling and from this node we may be able to walk * directly to our end-destination */ if (m_iCurNode > -1) { tracebox(origin, mins, maxs, m_vecLastNode, MOVE_NORMAL, this); /* can we walk directly to our target destination? */ if (trace_fraction == 1.0) { dprint("^2NSMonster::^3CheckRoute^7: " \ "Walking directly to last node\n"); m_iCurNode = -1; } } } if (m_iCurNode < -1) { ClearRoute(); NSLog("^2NSMonster::^3CheckRoute^7: %s reached end", this.targetname); /* mark that we've ended a sequence, if we're in one and que anim */ if (m_iSequenceState == SEQUENCESTATE_ACTIVE) { if (m_flSequenceEnd) { float duration = frameduration(modelindex, m_flSequenceEnd); m_iSequenceState = SEQUENCESTATE_ENDING; think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved; nextthink = time + duration; NSLog("^2NSMonster::^3CheckRoute^7: %s overriding anim for %f seconds (modelindex %d, frame %d)", this.targetname, duration, modelindex, m_flSequenceEnd); } else { /* we still need to trigger targets */ think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved; nextthink = time; NSLog("^2NSMonster::^3CheckRoute^7: %s has no anim, finished sequence.", this.targetname); } } } /* crouch attempt */ if (CanCrouch()) { vector src; bool shouldcrouch = false; /* test up */ src = origin + [0,0,24]; makevectors(angles); traceline(src, src + v_forward * 128, MOVE_NORMAL, this); /* we hit something */ if (trace_fraction < 1.0) { src = origin + [0,0, -8]; traceline(src, src + v_forward * 128, MOVE_NORMAL, this); /* we can crouch here, so let's do it */ if (trace_fraction >= 1.0) shouldcrouch = true; } /* entire way-link needs to be crouched. that's the law of the land */ if (shouldcrouch || Route_GetNodeFlags(&m_pRoute[m_iCurNode]) & LF_CROUCH) input_buttons |= INPUT_BUTTON8; } /*if (flDist == m_flLastDist) { m_flNodeGiveup += frametime; } else { m_flNodeGiveup = bound(0, m_flNodeGiveup - frametime, 1.0); } m_flLastDist = flDist; if (m_flNodeGiveup >= 1.0f) { print(sprintf("NSMonster::CheckNode: %s gave up route\n", this.netname)); ClearRoute(); }*/ } void NSMonster::WalkRoute(void) { vector endangles; /* we're busy shooting at something, don't walk */ if (m_iMState == MONSTER_AIMING) { endangles = vectoangles(m_eEnemy.origin - origin); /* TODO: lerp */ m_vecTurnAngle[1] = endangles[1]; } else if (m_iNodes && m_iMState == MONSTER_IDLE) { /* we're on our last node */ if (m_iCurNode < 0) { endangles = vectoangles(m_vecLastNode - origin); } else { endangles = vectoangles(m_pRoute[m_iCurNode].dest - origin); } m_vecTurnAngle[1] = endangles[1]; input_movevalues = [m_flSequenceSpeed, 0, 0]; } else if (m_iMState == MONSTER_CHASING) { /* we've got 'em in our sights, just need to walk closer */ endangles = vectoangles(m_eEnemy.origin - origin); input_movevalues = [GetChaseSpeed(), 0, 0]; m_vecTurnAngle[1] = endangles[1]; } else return; /* functional */ input_angles[1] = v_angle[1] = m_vecTurnAngle[1]; #if 1 /* cosmetic */ vector new_ang; vector old_ang; vector tmp; makevectors(m_vecTurnAngle); new_ang = v_forward; makevectors(angles); old_ang = v_forward; tmp[0] = Math_Lerp(old_ang[0], new_ang[0], 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); angles = vectoangles(tmp); #endif } void NSMonster::NewRoute(vector destination) { /* engine calls this upon successfully creating a route */ static void NewRoute_RouteCB(entity ent, vector dest, int numnodes, nodeslist_t *nodelist) { NSMonster p = (NSMonster)ent; p.m_iNodes = numnodes; p.m_iCurNode = numnodes - 1; p.m_pRoute = nodelist; //traceline(p.origin, dest, MOVE_NORMAL, this); tracebox(p.origin, p.mins, p.maxs, dest, MOVE_NORMAL, this); /* can we walk directly to our target destination? */ if (trace_fraction == 1.0) { dprint("^2NSMonster::^3NewRoute^7: " \ "Walking directly to last node\n"); p.m_iCurNode = -1; } else { dprint("^2NSMonster::^3NewRoute^7: " \ "Path obstructed, calculating route\n"); /* run through all nodes, mark the closest direct path possible */ for (int i = 0; i < p.m_iNodes; i++) { vector vecDest = p.m_pRoute[i].dest; tracebox(p.origin, p.mins, p.maxs, vecDest, TRUE, p); //traceline(p.origin, vecDest, MOVE_NORMAL, this); if (trace_fraction == 1.0) { p.m_iCurNode = i; break; } } } } if (!g_nodes_present) return; ClearRoute(); if (!m_iNodes) { route_calculate(this, destination, 0, NewRoute_RouteCB); m_vecLastNode = destination; } } void NSMonster::AnimationUpdate(void) { int fr = 0; int act = 0; if (style == MONSTER_DEAD) return; float spvel = vlen(velocity); float midspeed = GetWalkSpeed() + ((GetRunSpeed() - GetWalkSpeed()) * 0.5f); if (spvel < 5) { fr = AnimIdle(); 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); } const int CONTENTBITS_MONSTER = CONTENTBIT_SOLID|CONTENTBIT_BODY|CONTENTBIT_MONSTERCLIP|CONTENTBIT_BOTCLIP; 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_angles = v_angle; input_timelength = frametime; /* override whatever we did above with this */ if (m_iSequenceState == SEQUENCESTATE_ENDING) { input_angles = v_angle = angles = m_vecSequenceAngle; SetFrame(m_flSequenceEnd); } else if (movetype == MOVETYPE_WALK) { if (m_iSequenceState == SEQUENCESTATE_NONE) { SeeThink(); AttackThink(); } CheckRoute(); WalkRoute(); hitcontentsmaski = CONTENTBITS_MONSTER; if (CanCrouch()) PMoveCustom_RunCrouchPhysics(this); else PMoveCustom_RunPlayerPhysics(this); SetOrigin(origin); IdleNoise(); button8 = input_buttons & INPUT_BUTTON8; // duck AnimationUpdate(); } if (!(flags & 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(); } } frame1time += frametime; } bool NSMonster::CanCrouch(void) { return false; } 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::Pain(void) { if (!m_eEnemy || (random() < 0.5)) m_eEnemy = g_dmg_eAttacker; AlertNearby(); } void NSMonster::Death(void) { /* we were already dead before, so gib */ if (style == MONSTER_DEAD) { Gib(); return; } m_iFlags = 0x0; /* if we make more than 50 damage, gib immediately */ if (health < -50) { Gib(); return; } /* make sure we're not causing any more obituaries */ flags &= ~FL_MONSTER; m_iFlags = 0x0; /* gibbing action */ SetMovetype(MOVETYPE_NONE); SetSolid(SOLID_CORPSE); health = 50 + health; style = MONSTER_DEAD; if (GetTriggerCondition() == MTRIG_DEATH) TriggerTargets(); } void NSMonster::Hide(void) { SetModelindex(0); SetSolid(SOLID_NOT); SetMovetype(MOVETYPE_NONE); customphysics = __NULL__; } 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]); flags |= FL_MONSTER; takedamage = DAMAGE_YES; 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); 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; } } /* Make sure StartFrame calls this */ float NSMonster::SendEntity(entity ePEnt, float fChanged) { if (!modelindex) return (0); if (clienttype(ePEnt) != CLIENTTYPE_REAL) return (0); WriteByte(MSG_ENTITY, ENT_MONSTER); /* newly popped into the PVS, sadly this is the only hacky way to check * for this right now. convince the engine maintainer to make this more sensible */ if (fChanged == 0xFFFFFF) { /* check for defaults. if these are predictable fields, don't even bother * networking them! you're just wasting bandwidth. */ if (frame == 0) fChanged &= ~BASEFL_CHANGED_FRAME; if (skin == 0) fChanged &= ~BASEFL_CHANGED_SKIN; if (effects == 0) fChanged &= ~BASEFL_CHANGED_EFFECTS; if (m_iBody == 0) fChanged &= ~BASEFL_CHANGED_BODY; if (scale == 0.0 || scale == 1.0) fChanged &= ~BASEFL_CHANGED_SCALE; if (origin == [0,0,0]) fChanged &= ~BASEFL_CHANGED_ORIGIN; if (angles == [0,0,0]) fChanged &= ~BASEFL_CHANGED_ANGLES; if (velocity == [0,0,0]) fChanged &= ~BASEFL_CHANGED_VELOCITY; if (mins == [0,0,0] && maxs == [0,0,0]) fChanged &= ~BASEFL_CHANGED_SIZE; if (solid == SOLID_NOT) fChanged &= ~BASEFL_CHANGED_SOLID; if (movetype == MOVETYPE_NONE) fChanged &= ~BASEFL_CHANGED_MOVETYPE; #ifdef GS_RENDERFX if (m_iRenderMode == RM_NORMAL) fChanged &= ~BASEFL_CHANGED_RENDERMODE; #endif } /* don't network triggers unless provoked */ /*if (cvar("developer") == 0 && m_iRenderMode == RM_TRIGGER) return (0);*/ /* broadcast how much data is expected to be read */ WriteFloat(MSG_ENTITY, fChanged); /* really trying to get our moneys worth with 23 bits of mantissa */ if (fChanged & BASEFL_CHANGED_ORIGIN) { WriteCoord(MSG_ENTITY, origin[0]); WriteCoord(MSG_ENTITY, origin[1]); WriteCoord(MSG_ENTITY, origin[2]); } if (fChanged & BASEFL_CHANGED_ANGLES) { WriteShort(MSG_ENTITY, angles[0] * 32767 / 360); WriteShort(MSG_ENTITY, angles[1] * 32767 / 360); WriteShort(MSG_ENTITY, angles[2] * 32767 / 360); } if (fChanged & BASEFL_CHANGED_MODELINDEX) { WriteShort(MSG_ENTITY, modelindex); } if (fChanged & BASEFL_CHANGED_SOLID) { WriteByte(MSG_ENTITY, solid); } if (fChanged & BASEFL_CHANGED_MOVETYPE) { WriteByte(MSG_ENTITY, movetype); } if (fChanged & BASEFL_CHANGED_SIZE) { WriteCoord(MSG_ENTITY, mins[0]); WriteCoord(MSG_ENTITY, mins[1]); WriteCoord(MSG_ENTITY, mins[2]); WriteCoord(MSG_ENTITY, maxs[0]); WriteCoord(MSG_ENTITY, maxs[1]); WriteCoord(MSG_ENTITY, maxs[2]); } if (fChanged & BASEFL_CHANGED_FRAME) { WriteByte(MSG_ENTITY, frame); WriteFloat(MSG_ENTITY, frame1time); } if (fChanged & BASEFL_CHANGED_SKIN) { WriteByte(MSG_ENTITY, skin + 128); } if (fChanged & BASEFL_CHANGED_EFFECTS) { WriteFloat(MSG_ENTITY, effects); } if (fChanged & BASEFL_CHANGED_BODY) { WriteByte(MSG_ENTITY, m_iBody); } if (fChanged & BASEFL_CHANGED_SCALE) { WriteFloat(MSG_ENTITY, scale); } if (fChanged & BASEFL_CHANGED_VELOCITY) { WriteFloat(MSG_ENTITY, velocity[0]); WriteFloat(MSG_ENTITY, velocity[1]); WriteFloat(MSG_ENTITY, velocity[2]); } #ifdef GS_RENDERFX if (fChanged & BASEFL_CHANGED_RENDERMODE) { WriteByte(MSG_ENTITY, m_iRenderMode); WriteByte(MSG_ENTITY, m_iRenderFX); } if (fChanged & BASEFL_CHANGED_RENDERCOLOR) { WriteFloat(MSG_ENTITY, m_vecRenderColor[0]); WriteFloat(MSG_ENTITY, m_vecRenderColor[1]); WriteFloat(MSG_ENTITY, m_vecRenderColor[2]); } if (fChanged & BASEFL_CHANGED_RENDERAMT) { WriteFloat(MSG_ENTITY, m_flRenderAmt); } #else if (fChanged & BASEFL_CHANGED_ALPHA) { WriteFloat(MSG_ENTITY, alpha); } #endif 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; } return render; } /* ============ NSMonster::ReceiveEntity ============ */ void NSMonster::ReceiveEntity(float flNew, float flChanged) { if (flChanged & BASEFL_CHANGED_ORIGIN) { origin[0] = readcoord(); origin[1] = readcoord(); origin[2] = readcoord(); } if (flChanged & BASEFL_CHANGED_ANGLES) { angles[0] = readshort() / (32767 / 360); angles[1] = readshort() / (32767 / 360); angles[2] = readshort() / (32767 / 360); } if (flChanged & BASEFL_CHANGED_MODELINDEX) { setmodelindex(this, readshort()); } if (flChanged & BASEFL_CHANGED_SOLID) { solid = readbyte(); } if (flChanged & BASEFL_CHANGED_MOVETYPE) { movetype = readbyte(); if (movetype == MOVETYPE_PHYSICS) { movetype = MOVETYPE_NONE; } } if (flChanged & BASEFL_CHANGED_SIZE) { mins[0] = readcoord(); mins[1] = readcoord(); mins[2] = readcoord(); maxs[0] = readcoord(); maxs[1] = readcoord(); maxs[2] = readcoord(); setsize(this, mins, maxs); } if (flChanged & BASEFL_CHANGED_FRAME) { frame = readbyte(); frame1time = readfloat(); } if (flChanged & BASEFL_CHANGED_SKIN) { skin = readbyte() - 128; } if (flChanged & BASEFL_CHANGED_EFFECTS) { effects = readfloat(); } if (flChanged & BASEFL_CHANGED_BODY) { m_iBody = readbyte(); setcustomskin(this, "", sprintf("geomset 1 %i\n", m_iBody)); } if (flChanged & BASEFL_CHANGED_SCALE) { scale = readfloat(); } if (flChanged & BASEFL_CHANGED_VELOCITY) { velocity[0] = readfloat(); velocity[1] = readfloat(); velocity[2] = readfloat(); } #ifdef GS_RENDERFX if (flChanged & BASEFL_CHANGED_RENDERMODE) { m_iRenderMode = readbyte(); m_iRenderFX = readbyte(); } if (flChanged & BASEFL_CHANGED_RENDERCOLOR) { m_vecRenderColor[0] = readfloat(); m_vecRenderColor[1] = readfloat(); m_vecRenderColor[2] = readfloat(); } if (flChanged & BASEFL_CHANGED_RENDERAMT) { m_flRenderAmt = readfloat(); } #else if (flChanged & BASEFL_CHANGED_ALPHA) { alpha = readfloat(); } #endif if (modelindex) { drawmask = MASK_ENGINE; } else { drawmask = 0; } if (scale == 0.0) scale = 1.0f; setorigin(this, origin); } #endif void NSMonster::NSMonster(void) { #ifdef SERVER if (!HasSpawnFlags(MSF_MULTIPLAYER)) if (g_grMode.MonstersSpawn() == FALSE) { remove(this); return; } #endif } #ifdef CLIENT void NSMonster_ReadEntity(float new) { NSMonster me = (NSMonster)self; if (new) { spawnfunc_NSMonster(); } me.ReceiveEntity(new, readfloat()); } #else 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; 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)) continue; NSMonster f = (NSMonster)w; /* they already got a target of some kind */ if (f.m_eEnemy) continue; /* if they're our friend... ignore*/ if (f.IsFriend(alliance)) continue; /* if the monster is dead... ignore */ if (f.health <= 0) continue; /* we've heard a noise. investigate the location */ f.ClearRoute(); f.NewRoute(pos); f.m_flSequenceSpeed = 140; } g_monsteralert_timer = time + 0.5f; } #endif