1998-11-24 00:00:00 +00:00
|
|
|
//==============================================================================
|
|
|
|
//
|
|
|
|
// m_gkrokon.c
|
|
|
|
//
|
|
|
|
// Heretic II
|
|
|
|
// Copyright 1998 Raven Software
|
|
|
|
//
|
|
|
|
//==============================================================================
|
|
|
|
|
|
|
|
#include "g_local.h"
|
|
|
|
#include "Utilities.h"
|
|
|
|
#include "g_DefaultMessageHandler.h"
|
|
|
|
#include "g_monster.h"
|
|
|
|
#include "fx.h"
|
|
|
|
#include "random.h"
|
|
|
|
#include "buoy.h"
|
|
|
|
#include "vector.h"
|
|
|
|
#include "decals.h"
|
|
|
|
#include "g_playstats.h"
|
|
|
|
#include "m_gkrokon.h"
|
|
|
|
#include "m_gkrokon_anim.h"
|
|
|
|
#include "m_stats.h"
|
|
|
|
#include "g_HitLocation.h"
|
|
|
|
|
|
|
|
static int sounds[NUM_SOUNDS];
|
|
|
|
static ClassResourceInfo_t resInfo;
|
|
|
|
|
|
|
|
static animmove_t *Animations[NUM_ANIMS] =
|
|
|
|
{
|
|
|
|
&GkrokonMoveStand1,
|
|
|
|
&GkrokonMoveStand2,
|
|
|
|
&GkrokonMoveStand3,
|
|
|
|
&GkrokonMoveStand4,
|
|
|
|
&GkrokonMoveCrouch1,
|
|
|
|
&GkrokonMoveCrouch2,
|
|
|
|
&GkrokonMoveCrouch3,
|
|
|
|
&GkrokonMoveWalk1,
|
|
|
|
&GkrokonMoveRun1,
|
|
|
|
&GkrokonMoveRun2,
|
|
|
|
&GkrokonMoveJump1,
|
|
|
|
&GkrokonMoveForcedJump,
|
|
|
|
&GkrokonMoveMeleeAttack1,
|
|
|
|
&GkrokonMoveMeleeAttack2,
|
|
|
|
&GkrokonMoveMissileAttack1,
|
|
|
|
&GkrokonMoveEat1,
|
|
|
|
&GkrokonMoveEat2,
|
|
|
|
&GkrokonMoveEat3,
|
|
|
|
&GkrokonMovePain1,
|
|
|
|
&GkrokonMoveDeath1,
|
|
|
|
&GkrokonMoveHop1,
|
|
|
|
&GkrokonMoveRunAway,
|
|
|
|
&GkrokonMoveMissileAttack2,
|
|
|
|
&GkrokonMoveDelay
|
|
|
|
};
|
|
|
|
|
|
|
|
void ThrowBodyPart(edict_t *self, vec3_t *spot, int BodyPart, float damage, int frame);
|
|
|
|
/*
|
|
|
|
|
|
|
|
Spoo functions
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
create_gkrokon_spoo
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void create_gkrokon_spoo(edict_t *Spoo)
|
|
|
|
{
|
|
|
|
Spoo->movetype=PHYSICSTYPE_FLY;
|
|
|
|
Spoo->solid=SOLID_BBOX;
|
|
|
|
Spoo->classname="Gkrokon_Spoo";
|
|
|
|
Spoo->touch=GkrokonSpooTouch;
|
|
|
|
Spoo->isBlocked=GkrokonSpooTouch2;
|
|
|
|
Spoo->dmg=1.0;
|
|
|
|
Spoo->clipmask = MASK_SHOT;
|
|
|
|
Spoo->nextthink = level.time + FRAMETIME;
|
|
|
|
VectorSet(Spoo->mins,-1.0,-1.0,-1.0);
|
|
|
|
VectorSet(Spoo->maxs,1.0,1.0,1.0);
|
|
|
|
Spoo->s.effects = EF_CAMERA_NO_CLIP;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
reflect_spoo
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
1999-03-18 00:00:00 +00:00
|
|
|
edict_t *GkrokonSpooReflect(edict_t *self, edict_t *other, vec3_t vel)
|
1998-11-24 00:00:00 +00:00
|
|
|
{
|
1999-03-18 00:00:00 +00:00
|
|
|
edict_t *Spoo;
|
|
|
|
|
|
|
|
Spoo = G_Spawn();
|
|
|
|
|
1998-11-24 00:00:00 +00:00
|
|
|
create_gkrokon_spoo(Spoo);
|
|
|
|
|
1999-03-18 00:00:00 +00:00
|
|
|
Spoo->enemy=self->owner;
|
|
|
|
Spoo->owner=other;
|
1998-11-24 00:00:00 +00:00
|
|
|
Spoo->s.scale=self->s.scale;
|
|
|
|
Spoo->dmg = self->dmg;
|
|
|
|
|
|
|
|
VectorCopy(self->s.origin, Spoo->s.origin);
|
1999-03-18 00:00:00 +00:00
|
|
|
VectorCopy(vel, Spoo->velocity);
|
|
|
|
VectorNormalize2(vel, Spoo->movedir);
|
|
|
|
AnglesFromDir(Spoo->movedir, Spoo->s.angles);
|
1998-11-24 00:00:00 +00:00
|
|
|
|
|
|
|
gi.linkentity(Spoo);
|
|
|
|
|
|
|
|
gi.CreateEffect(&Spoo->s,
|
|
|
|
FX_SPOO,
|
|
|
|
CEF_OWNERS_ORIGIN,
|
|
|
|
Spoo->s.origin,
|
|
|
|
"");
|
|
|
|
|
|
|
|
Spoo->reflect_debounce_time = self->reflect_debounce_time -1;
|
1999-03-18 00:00:00 +00:00
|
|
|
Spoo->reflected_time=self->reflected_time;
|
|
|
|
|
|
|
|
G_SetToFree(self);
|
|
|
|
|
|
|
|
return(Spoo);
|
1998-11-24 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
GkrokonSpooTouch2
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void GkrokonSpooTouch2(edict_t *self,trace_t *trace)
|
|
|
|
{
|
|
|
|
GkrokonSpooTouch(self,trace->ent,(cplane_t *)&trace->plane,(csurface_t *)trace->surface);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
GkrokonSpooTouch
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void GkrokonSpooTouch(edict_t *self,edict_t *Other,cplane_t *Plane,csurface_t *Surface)
|
|
|
|
{
|
|
|
|
vec3_t Origin, ScorchOrigin, planeDir;
|
|
|
|
|
|
|
|
if(Surface&&(Surface->flags&SURF_SKY))
|
|
|
|
{
|
|
|
|
G_FreeEdict(self);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// are we reflecting ?
|
|
|
|
if (EntReflecting(Other, true, true) && self->reflect_debounce_time)
|
|
|
|
{
|
1999-03-18 00:00:00 +00:00
|
|
|
Create_rand_relect_vect(self->velocity, self->velocity);
|
|
|
|
Vec3ScaleAssign(GKROKON_SPOO_SPEED/2, self->velocity);
|
|
|
|
GkrokonSpooReflect(self, Other, self->velocity);
|
1998-11-24 00:00:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the position for the explosion entity.
|
|
|
|
VectorMA(self->s.origin,-16,self->velocity,Origin);
|
|
|
|
|
|
|
|
if(Other->takedamage)
|
|
|
|
{
|
|
|
|
self->dmg = irand(GKROKON_DMG_SPOO_MIN, GKROKON_DMG_SPOO_MAX);
|
|
|
|
T_Damage(Other,self,self->owner,self->movedir,Other->s.origin,Plane->normal,self->dmg,self->dmg,0,MOD_DIED);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
VectorMA(self->s.origin,-16.0,self->movedir,self->s.origin);
|
|
|
|
}
|
|
|
|
|
|
|
|
T_DamageRadius(self, self->owner, self, 5.0,
|
|
|
|
self->dmg, self->dmg/4, DAMAGE_NO_KNOCKBACK|DAMAGE_ATTACKER_IMMUNE,MOD_DIED);
|
|
|
|
|
|
|
|
gi.RemoveEffects(&self->s, 0);
|
|
|
|
|
|
|
|
if(IsDecalApplicable(self, Other, self->s.origin, Surface, Plane, planeDir))
|
|
|
|
{
|
|
|
|
VectorCopy(self->s.origin, ScorchOrigin);
|
|
|
|
gi.CreateEffect(NULL,
|
|
|
|
FX_SCORCHMARK,
|
|
|
|
0,
|
|
|
|
ScorchOrigin,
|
|
|
|
"d",
|
|
|
|
planeDir);
|
|
|
|
}
|
|
|
|
// Splatter some spoo over the surface.
|
|
|
|
VectorCopy(self->s.origin, ScorchOrigin);
|
|
|
|
|
|
|
|
gi.CreateEffect(NULL,
|
|
|
|
FX_SPOO_SPLAT,
|
|
|
|
0,
|
|
|
|
ScorchOrigin,
|
|
|
|
"d",
|
|
|
|
planeDir);
|
|
|
|
|
|
|
|
gi.sound(self,CHAN_WEAPON,gi.soundindex("monsters/rat/gib.wav"),1,ATTN_NORM,0);
|
|
|
|
|
|
|
|
G_SetToFree(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
GkrokonSpoo
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void GkrokonSpoo(edict_t *self)
|
|
|
|
{
|
|
|
|
vec3_t Forward, up, vf;
|
|
|
|
edict_t *Spoo;
|
|
|
|
float edist;
|
|
|
|
|
|
|
|
if(!self->enemy)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Spawn the spoo globbit.
|
|
|
|
Spoo = G_Spawn();
|
|
|
|
|
|
|
|
create_gkrokon_spoo(Spoo);
|
|
|
|
Spoo->reflect_debounce_time = MAX_REFLECT;
|
|
|
|
|
|
|
|
Spoo->s.scale = 0.5;
|
|
|
|
Spoo->enemy=self->enemy;
|
|
|
|
Spoo->owner=self;
|
|
|
|
AngleVectors(self->s.angles,Forward,NULL,up);
|
|
|
|
VectorCopy(self->s.origin,Spoo->s.origin);
|
|
|
|
VectorMA(Spoo->s.origin,-16,Forward,Spoo->s.origin);
|
|
|
|
Spoo->s.origin[2]+=12.0;
|
|
|
|
VectorCopy(self->movedir,Spoo->movedir);
|
|
|
|
vectoangles(Forward,Spoo->s.angles);
|
|
|
|
|
|
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, vf);
|
|
|
|
edist = VectorNormalize(vf);
|
|
|
|
|
|
|
|
if (DotProduct(vf, Forward) < 0.5)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VectorScale(vf,GKROKON_SPOO_SPEED,Spoo->velocity);
|
|
|
|
Spoo->velocity[2] += GKROKON_SPOO_ARC;
|
|
|
|
Spoo->velocity[2] += (edist - 256);
|
|
|
|
vectoangles(Spoo->velocity, Spoo->s.angles);
|
|
|
|
|
|
|
|
gi.linkentity(Spoo);
|
|
|
|
|
|
|
|
gi.sound(Spoo,CHAN_WEAPON,sounds[SND_SPOO],1,ATTN_NORM,0);
|
|
|
|
|
|
|
|
gi.CreateEffect(&Spoo->s,
|
|
|
|
FX_SPOO,
|
|
|
|
CEF_OWNERS_ORIGIN,
|
|
|
|
Spoo->s.origin,
|
|
|
|
"");
|
|
|
|
|
|
|
|
|
|
|
|
self->monsterinfo.misc_debounce_time = level.time + flrand(0.5, 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Gkrokon Helper Functions
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
SkitterAway
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void SkitterAway(edict_t *self)
|
|
|
|
{
|
|
|
|
if (irand(0,10) < 2 && self->monsterinfo.attack_finished < level.time)
|
|
|
|
{
|
|
|
|
self->monsterinfo.attack_finished = level.time + 5;
|
|
|
|
SetAnim(self, ANIM_SNEEZE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_RUNAWAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
GkrokonBite
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void GkrokonBite(edict_t *self, float value)
|
|
|
|
{
|
|
|
|
trace_t trace;
|
|
|
|
edict_t *victim;
|
|
|
|
vec3_t soff, eoff, mins, maxs, bloodDir, direction;
|
|
|
|
int damage = irand(GKROKON_DMG_BITE_MIN, GKROKON_DMG_BITE_MAX);
|
|
|
|
|
|
|
|
VectorSet(mins, -2, -2, -2);
|
|
|
|
VectorSet(maxs, 2, 2, 2);
|
|
|
|
|
|
|
|
//From the right side
|
|
|
|
if (value)
|
|
|
|
{
|
|
|
|
VectorSet(soff, 32, 48, 32);
|
|
|
|
VectorSet(eoff, 32, 16, 24);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
VectorSet(soff, 32, -24, 32);
|
|
|
|
VectorSet(eoff, 24, 32, 24);
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorSubtract(soff, eoff, bloodDir);
|
|
|
|
VectorNormalize(bloodDir);
|
|
|
|
|
|
|
|
victim = M_CheckMeleeLineHit(self, soff, eoff, mins, maxs, &trace, direction);
|
|
|
|
|
|
|
|
if (victim)
|
|
|
|
{
|
|
|
|
if (victim == self)
|
|
|
|
{
|
|
|
|
//Create a puff effect
|
|
|
|
//gi.CreateEffect(NULL, FX_SPARKS, 0, hitPos, "db", vec3_origin, irand(1,3));
|
|
|
|
if (irand(0,1))
|
|
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_BITEMISS1], 1, ATTN_NORM, 0);
|
|
|
|
else
|
|
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_BITEMISS2], 1, ATTN_NORM, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Hurt whatever we were whacking away at
|
|
|
|
T_Damage(victim, self, self, direction, trace.endpos, bloodDir,
|
|
|
|
damage, damage, DAMAGE_NORMAL,MOD_DIED);
|
|
|
|
|
|
|
|
if (irand(0,1))
|
|
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_BITEHIT1], 1, ATTN_NORM, 0);
|
|
|
|
else
|
|
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_BITEHIT2], 1, ATTN_NORM, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Play swoosh sound?
|
|
|
|
if (irand(0,1))
|
|
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_BITEMISS1], 1, ATTN_NORM, 0);
|
|
|
|
else
|
|
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_BITEMISS2], 1, ATTN_NORM, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
gkrokonSound
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void gkrokonSound(edict_t *self, float channel, float sndindex, float atten)
|
|
|
|
{
|
|
|
|
gi.sound(self, channel, sounds[(int)(sndindex)], 1, atten, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
gkrokonRandomWalkSound
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void gkrokonRandomWalkSound (edict_t *self)
|
|
|
|
{
|
|
|
|
if (irand(0,3))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(!irand(0,1))
|
|
|
|
gkrokonSound(self, CHAN_BODY, SND_WALK1, ATTN_NORM);
|
|
|
|
else
|
|
|
|
gkrokonSound(self, CHAN_BODY, SND_WALK2, ATTN_NORM);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
GkrokonDead
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void GkrokonDead(edict_t *self)
|
|
|
|
{
|
|
|
|
M_EndDeath(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_ai_stand
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_ai_stand(edict_t *self, float dist)
|
|
|
|
{
|
|
|
|
if (M_ValidTarget(self, self->enemy))
|
|
|
|
{
|
|
|
|
MG_FaceGoal(self, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_idle_sound
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_idle_sound(edict_t *self)
|
|
|
|
{
|
|
|
|
int chance = irand(0,20);
|
|
|
|
|
|
|
|
switch (chance)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
gkrokonSound(self, CHAN_BODY, SND_IDLE1, ATTN_NORM);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
gkrokonSound(self, CHAN_BODY, SND_IDLE2, ATTN_NORM);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Message Handlers
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_skitter
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_skitter(edict_t *self, G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
if(self->spawnflags&MSF_FIXED)
|
|
|
|
SetAnim(self, ANIM_DELAY);
|
|
|
|
else
|
|
|
|
SkitterAway(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_check_mood
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_check_mood (edict_t *self, G_Message_t *msg)
|
|
|
|
{
|
|
|
|
ParseMsgParms(msg, "i", &self->ai_mood);
|
|
|
|
|
|
|
|
GkrokonPause(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_walk
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_walk(edict_t *self,G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
if(self->spawnflags&MSF_FIXED)
|
|
|
|
SetAnim(self, ANIM_STAND3);
|
|
|
|
else
|
|
|
|
SetAnim(self,ANIM_WALK1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_run
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_run(edict_t *self, G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
int chance;
|
|
|
|
float dist;
|
|
|
|
|
|
|
|
//Make sure we're not getting up from laying down without the proper animations
|
|
|
|
if(self->ai_mood == AI_MOOD_FLEE)
|
|
|
|
{
|
|
|
|
if(irand(0,1))
|
|
|
|
SetAnim(self, ANIM_RUN1);
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_RUN2);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self->curAnimID == ANIM_STAND1)
|
|
|
|
{
|
|
|
|
SetAnim(self, ANIM_STAND2);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (M_ValidTarget(self, self->enemy))
|
|
|
|
{
|
|
|
|
dist = M_DistanceToTarget(self, self->enemy);
|
|
|
|
|
|
|
|
chance = irand(0,100);
|
|
|
|
|
|
|
|
if (dist < 300)
|
|
|
|
{
|
|
|
|
if (chance < 5)
|
|
|
|
SetAnim(self, ANIM_STAND3);
|
|
|
|
else if (chance < 20)
|
|
|
|
SetAnim(self, ANIM_RUN2);
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_RUN1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetAnim(self, ANIM_RUN1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//If our enemy is dead, we need to stand
|
|
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_stand
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_stand(edict_t *self,G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
if (self->spawnflags & MSF_EXTRA1)
|
|
|
|
SetAnim(self, ANIM_STAND1);
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_STAND3);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_melee
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_missile(edict_t *self,G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
int chance;
|
|
|
|
float dist;
|
|
|
|
|
|
|
|
chance = irand(0,100);
|
|
|
|
|
|
|
|
if(self->enemy)
|
|
|
|
{
|
|
|
|
if(self->monsterinfo.misc_debounce_time < level.time)
|
|
|
|
{
|
|
|
|
dist = M_DistanceToTarget(self, self->enemy);
|
|
|
|
|
|
|
|
if(self->spawnflags&MSF_FIXED)
|
|
|
|
{
|
|
|
|
if (dist < self->missile_range)
|
|
|
|
SetAnim(self, ANIM_MISSILE1);
|
|
|
|
else if(self->curAnimID == ANIM_CROUCH1 && !irand(0, 2))
|
|
|
|
SetAnim(self, ANIM_CROUCH2);//go into stand
|
|
|
|
else if(self->curAnimID == ANIM_STAND3 && !irand(0, 10))
|
|
|
|
SetAnim(self, ANIM_CROUCH3);//go into a crouch
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_DELAY);//stand around
|
|
|
|
}
|
|
|
|
else if (dist < 64)
|
|
|
|
{
|
|
|
|
if (chance < 10)
|
|
|
|
SetAnim(self, ANIM_MISSILE1);
|
|
|
|
else if (chance < 30)
|
|
|
|
{
|
|
|
|
SetAnim(self, ANIM_RUNAWAY);
|
|
|
|
self->monsterinfo.flee_finished = level.time + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (irand(0,1))
|
|
|
|
SetAnim(self, ANIM_MELEE1);
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_MELEE2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ((dist > 64) && (dist < 200))
|
|
|
|
{
|
|
|
|
if (chance < 40)
|
|
|
|
SetAnim(self, ANIM_MISSILE1);
|
|
|
|
else if ((self->monsterinfo.flee_finished < level.time) && (dist > 100))
|
|
|
|
SetAnim(self, ANIM_RUN2);
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_STAND3);
|
|
|
|
}
|
|
|
|
else if (dist < 300)
|
|
|
|
{
|
|
|
|
if (chance < 5)
|
|
|
|
SetAnim(self, ANIM_STAND3);
|
|
|
|
else if (chance < 20)
|
|
|
|
SetAnim(self, ANIM_RUN2);
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_RUN1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetAnim(self, ANIM_RUN1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (irand(0,10) < 6)
|
|
|
|
{
|
|
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(self->spawnflags&MSF_FIXED)
|
|
|
|
SetAnim(self, ANIM_DELAY);
|
|
|
|
else
|
|
|
|
SkitterAway(self);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void beetle_melee(edict_t *self,G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
beetle_missile(self, Msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_pain
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_pain(edict_t *self,G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
int temp, damage;
|
|
|
|
int force_damage;
|
|
|
|
|
|
|
|
ParseMsgParms(Msg, "eeiii", &temp, &temp, &force_damage, &damage, &temp);
|
|
|
|
|
|
|
|
//Weighted random based on health compared to the maximum it was at
|
|
|
|
if (force_damage||((flrand(0, self->max_health+50) > self->health) && irand(0,2)))
|
|
|
|
{
|
|
|
|
if(irand(0,1))
|
|
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_PAIN1], 1, ATTN_NORM, 0);
|
|
|
|
else
|
|
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_PAIN2], 1, ATTN_NORM, 0);
|
|
|
|
|
|
|
|
SetAnim(self, ANIM_PAIN1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!irand(0,2))
|
|
|
|
{
|
|
|
|
self->monsterinfo.aiflags |= AI_FLEE;
|
|
|
|
self->monsterinfo.flee_finished = level.time + 15;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_eat
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_eat(edict_t *self,G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
int chance = irand(0,2);
|
|
|
|
|
|
|
|
switch (chance)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
SetAnim(self, ANIM_EAT1);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
SetAnim(self, ANIM_EAT2);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
SetAnim(self, ANIM_EAT3);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------
|
|
|
|
beetle_death
|
|
|
|
-----------------------------------------------*/
|
|
|
|
|
|
|
|
void beetle_death(edict_t *self,G_Message_t *Msg)
|
|
|
|
{
|
|
|
|
edict_t *targ, *inflictor, *attacker;
|
|
|
|
float damage;
|
|
|
|
|
|
|
|
ParseMsgParms(Msg, "eeei", &targ, &inflictor, &attacker, &damage);
|
|
|
|
|
|
|
|
M_StartDeath(self, ANIM_DIE1);
|
|
|
|
|
|
|
|
if (self->health < -80)
|
|
|
|
{
|
|
|
|
BecomeDebris(self);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(self->health < -10)
|
|
|
|
{
|
|
|
|
vec3_t forward;
|
|
|
|
int i, num_limbs;
|
|
|
|
|
|
|
|
num_limbs = irand(3, 10);
|
|
|
|
for(i = 0; i < num_limbs; i++)
|
|
|
|
{
|
|
|
|
if(self->svflags&SVF_MONSTER)
|
|
|
|
self->monsterinfo.dismember(self, flrand(80, 160), irand(hl_Head, hl_LegLowerRight) | hl_MeleeHit);
|
|
|
|
}
|
|
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
|
|
VectorMA(self->velocity, -400, forward, self->velocity);
|
|
|
|
self->velocity[2] += 150;
|
|
|
|
}
|
|
|
|
SetAnim(self, ANIM_DIE1);
|
|
|
|
}
|
|
|
|
|
|
|
|
gi.sound (self, CHAN_BODY, sounds[SND_DIE], 1, ATTN_NORM, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Bit_for_MeshNode_gk [NUM_MESH_NODES] =
|
|
|
|
{
|
|
|
|
BIT_WAIT1,
|
|
|
|
BIT_SHELLA_P1,
|
|
|
|
BIT_SPIKE_P1,
|
|
|
|
BIT_HEAD_P1,
|
|
|
|
BIT_RPINCHERA_P1,
|
|
|
|
BIT_RPINCHERB_P1,
|
|
|
|
BIT_LPINCHERA_P1,
|
|
|
|
BIT_LPINCHERB_P1,
|
|
|
|
BIT_LARM_P1,
|
|
|
|
BIT_RARM_P1,
|
|
|
|
BIT_ABDOMEN_P1,
|
|
|
|
BIT_LTHIGH_P1,
|
|
|
|
BIT_RTHIGH_P1,
|
|
|
|
BIT_SHELLB_P1,
|
|
|
|
};
|
|
|
|
|
|
|
|
qboolean canthrownode_gk (edict_t *self, int BP, int *throw_nodes)
|
|
|
|
{//see if it's on, if so, add it to throw_nodes
|
|
|
|
//turn it off on thrower
|
|
|
|
if(!(self->s.fmnodeinfo[BP].flags & FMNI_NO_DRAW))
|
|
|
|
{
|
|
|
|
*throw_nodes |= Bit_for_MeshNode_gk[BP];
|
|
|
|
self->s.fmnodeinfo[BP].flags |= FMNI_NO_DRAW;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void beetle_dismember(edict_t *self, int damage, int HitLocation)
|
|
|
|
{
|
|
|
|
int throw_nodes = 0;
|
|
|
|
vec3_t gore_spot, right;
|
|
|
|
qboolean dismember_ok = false;
|
|
|
|
|
|
|
|
if(HitLocation & hl_MeleeHit)
|
|
|
|
{
|
|
|
|
dismember_ok = true;
|
|
|
|
HitLocation &= ~hl_MeleeHit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(HitLocation<1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(HitLocation>hl_Max)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VectorClear(gore_spot);
|
|
|
|
switch(HitLocation)
|
|
|
|
{
|
|
|
|
case hl_Head:
|
|
|
|
if(self->s.fmnodeinfo[MESH__SHELLB_P1].flags & FMNI_NO_DRAW)
|
|
|
|
break;
|
|
|
|
self->s.fmnodeinfo[MESH__SHELLB_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__SHELLB_P1].skin = self->s.skinnum + 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case hl_TorsoFront://split in half?
|
|
|
|
if(!(self->s.fmnodeinfo[MESH__HEAD_P1].flags & FMNI_USE_SKIN))
|
|
|
|
{
|
|
|
|
if(irand(0, 4))
|
|
|
|
{
|
|
|
|
self->s.fmnodeinfo[MESH__HEAD_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__HEAD_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(canthrownode_gk(self, MESH__HEAD_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
T_Damage(self, self->enemy, self->enemy, vec3_origin, vec3_origin, vec3_origin, 9999, 200, DAMAGE_NORMAL,MOD_DIED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!(self->s.fmnodeinfo[MESH__SPIKE_P1].flags & FMNI_USE_SKIN))
|
|
|
|
{
|
|
|
|
if(irand(0, 4))
|
|
|
|
{
|
|
|
|
self->s.fmnodeinfo[MESH__SPIKE_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__SPIKE_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(canthrownode_gk(self, MESH__SPIKE_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!(self->s.fmnodeinfo[MESH__RPINCHERA_P1].flags & FMNI_USE_SKIN))
|
|
|
|
{
|
|
|
|
if(irand(0, 4))
|
|
|
|
{
|
|
|
|
self->s.fmnodeinfo[MESH__RPINCHERA_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__RPINCHERA_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(canthrownode_gk(self, MESH__RPINCHERA_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!(self->s.fmnodeinfo[MESH__LPINCHERA_P1].flags & FMNI_USE_SKIN))
|
|
|
|
{
|
|
|
|
if(irand(0, 4))
|
|
|
|
{
|
|
|
|
self->s.fmnodeinfo[MESH__LPINCHERA_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__LPINCHERA_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(canthrownode_gk(self, MESH__LPINCHERA_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!(self->s.fmnodeinfo[MESH__RPINCHERB_P1].flags & FMNI_USE_SKIN))
|
|
|
|
{
|
|
|
|
if(irand(0, 4))
|
|
|
|
{
|
|
|
|
self->s.fmnodeinfo[MESH__RPINCHERB_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__RPINCHERB_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(canthrownode_gk(self, MESH__RPINCHERB_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!(self->s.fmnodeinfo[MESH__LPINCHERB_P1].flags & FMNI_USE_SKIN))
|
|
|
|
{
|
|
|
|
if(irand(0, 4))
|
|
|
|
{
|
|
|
|
self->s.fmnodeinfo[MESH__LPINCHERB_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__LPINCHERB_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(canthrownode_gk(self, MESH__LPINCHERB_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case hl_TorsoBack://split in half?
|
|
|
|
if(self->s.fmnodeinfo[MESH__ABDOMEN_P1].flags & FMNI_NO_DRAW)
|
|
|
|
break;
|
|
|
|
self->s.fmnodeinfo[MESH__ABDOMEN_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__ABDOMEN_P1].skin = self->s.skinnum+1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case hl_ArmLowerLeft://left arm
|
|
|
|
case hl_ArmUpperLeft:
|
|
|
|
if(self->s.fmnodeinfo[MESH__LARM_P1].flags & FMNI_NO_DRAW)
|
|
|
|
break;
|
|
|
|
if(self->s.fmnodeinfo[MESH__LARM_P1].flags & FMNI_USE_SKIN)
|
|
|
|
damage*=1.5;//greater chance to cut off if previously damaged
|
|
|
|
if(flrand(0,self->health)<damage*0.75&&dismember_ok)
|
|
|
|
{
|
|
|
|
if(canthrownode_gk(self, MESH__LARM_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self->s.fmnodeinfo[MESH__LARM_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__LARM_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case hl_ArmUpperRight:
|
|
|
|
case hl_ArmLowerRight://right arm
|
|
|
|
if(self->s.fmnodeinfo[MESH__RARM_P1].flags & FMNI_NO_DRAW)
|
|
|
|
break;
|
|
|
|
if(flrand(0,self->health)<damage*0.75&&dismember_ok)
|
|
|
|
{
|
|
|
|
canthrownode_gk(self, MESH__RARM_P1, &throw_nodes);
|
|
|
|
if(canthrownode_gk(self, MESH__RARM_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self->s.fmnodeinfo[MESH__RARM_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__RARM_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case hl_LegUpperLeft:
|
|
|
|
case hl_LegLowerLeft://left leg
|
|
|
|
if(self->health>0)
|
|
|
|
{
|
|
|
|
if(self->s.fmnodeinfo[MESH__LTHIGH_P1].flags & FMNI_USE_SKIN)
|
|
|
|
break;
|
|
|
|
self->s.fmnodeinfo[MESH__LTHIGH_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__LTHIGH_P1].skin = self->s.skinnum+1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(self->s.fmnodeinfo[MESH__LTHIGH_P1].flags & FMNI_NO_DRAW)
|
|
|
|
break;
|
|
|
|
if(canthrownode_gk(self, MESH__LTHIGH_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case hl_LegUpperRight:
|
|
|
|
case hl_LegLowerRight://right leg
|
|
|
|
if(self->health>0)
|
|
|
|
{
|
|
|
|
if(self->s.fmnodeinfo[MESH__RTHIGH_P1].flags & FMNI_USE_SKIN)
|
|
|
|
break;
|
|
|
|
self->s.fmnodeinfo[MESH__RTHIGH_P1].flags |= FMNI_USE_SKIN;
|
|
|
|
self->s.fmnodeinfo[MESH__RTHIGH_P1].skin = self->s.skinnum+1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(self->s.fmnodeinfo[MESH__RTHIGH_P1].flags & FMNI_NO_DRAW)
|
|
|
|
break;
|
|
|
|
if(canthrownode_gk(self, MESH__RTHIGH_P1, &throw_nodes))
|
|
|
|
{
|
|
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(self->s.fmnodeinfo[MESH__SPIKE_P1].flags&FMNI_NO_DRAW)
|
|
|
|
{
|
|
|
|
self->monsterinfo.aiflags |= AI_COWARD;
|
|
|
|
self->monsterinfo.aiflags |= AI_NO_MELEE;
|
|
|
|
self->monsterinfo.aiflags |= AI_NO_MISSILE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void beetle_dead_pain (edict_t *self, G_Message_t *msg)
|
|
|
|
{
|
|
|
|
if(self->health<=-80)
|
|
|
|
{
|
|
|
|
// We get gibbed.
|
|
|
|
gi.sound(self,CHAN_BODY,sounds[SND_GIB],1,ATTN_NORM,0);
|
|
|
|
|
|
|
|
self->think = BecomeDebris;
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(msg)
|
|
|
|
if(!(self->svflags & SVF_PARTS_GIBBED))
|
|
|
|
MG_parse_dismember_msg(self, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ****************************************************************************
|
|
|
|
// GkrokonStaticsInit -
|
|
|
|
// ****************************************************************************
|
|
|
|
|
|
|
|
void GkrokonStaticsInit(void)
|
|
|
|
{
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_STAND]=beetle_stand;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_RUN]=beetle_run;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_DEATH_PAIN] = beetle_dead_pain;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_CHECK_MOOD] = beetle_check_mood;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_FALLBACK] = beetle_skitter;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_MELEE]=beetle_melee;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_MISSILE]=beetle_missile;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_PAIN]=beetle_pain;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_EAT]=beetle_eat;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_DEATH]=beetle_death;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_DEATH_PAIN] = beetle_dead_pain;
|
|
|
|
classStatics[CID_GKROKON].msgReceivers[MSG_DISMEMBER] = MG_parse_dismember_msg;
|
|
|
|
|
|
|
|
resInfo.numAnims=NUM_ANIMS;
|
|
|
|
resInfo.animations=Animations;
|
|
|
|
resInfo.modelIndex=gi.modelindex("models/monsters/gkrokon/tris.fm");
|
|
|
|
|
|
|
|
sounds[SND_PAIN1]=gi.soundindex("monsters/beetle/pain1.wav");
|
|
|
|
sounds[SND_PAIN2]=gi.soundindex("monsters/beetle/pain2.wav");
|
|
|
|
sounds[SND_DIE]=gi.soundindex("monsters/beetle/death.wav");
|
|
|
|
sounds[SND_GIB]=gi.soundindex("monsters/insect/gib.wav");
|
|
|
|
sounds[SND_SPOO]=gi.soundindex("monsters/beetle/spoo.wav");
|
|
|
|
sounds[SND_IDLE1]=gi.soundindex("monsters/beetle/idle1.wav");
|
|
|
|
sounds[SND_IDLE2]=gi.soundindex("monsters/beetle/idle2.wav");
|
|
|
|
sounds[SND_SIGHT]=gi.soundindex("monsters/beetle/sight.wav");
|
|
|
|
sounds[SND_WALK1]=gi.soundindex("monsters/beetle/walk1.wav");
|
|
|
|
sounds[SND_WALK2]=gi.soundindex("monsters/beetle/walk2.wav");
|
|
|
|
sounds[SND_FLEE]=gi.soundindex("monsters/beetle/flee.wav");
|
|
|
|
sounds[SND_ANGRY]=gi.soundindex("monsters/beetle/angry.wav");
|
|
|
|
sounds[SND_EATING]=gi.soundindex("monsters/beetle/eating.wav");
|
|
|
|
sounds[SND_BITEHIT1]=gi.soundindex("monsters/beetle/meleehit1.wav");
|
|
|
|
sounds[SND_BITEHIT2]=gi.soundindex("monsters/beetle/meleehit2.wav");
|
|
|
|
sounds[SND_BITEMISS1]=gi.soundindex("monsters/beetle/meleemiss1.wav");
|
|
|
|
sounds[SND_BITEMISS2]=gi.soundindex("monsters/beetle/meleemiss2.wav");
|
|
|
|
|
|
|
|
resInfo.numSounds=NUM_SOUNDS;
|
|
|
|
resInfo.sounds=sounds;
|
|
|
|
|
|
|
|
classStatics[CID_GKROKON].resInfo=&resInfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ****************************************************************************
|
|
|
|
// SP_Monster_Gkrokon - The Gkrokon's spawn function.
|
|
|
|
// ****************************************************************************
|
|
|
|
|
|
|
|
/*QUAKED monster_gkrokon (1 .5 0) (-20 -20 -0) (20 20 32) AMBUSH ASLEEP EATING 8 16 32 64 FIXED WANDER MELEE_LEAD STALK COWARD RESTING EXTRA2 EXTRA3 EXTRA4
|
|
|
|
|
|
|
|
AMBUSH - Will not be woken up by other monsters or shots from player
|
|
|
|
|
|
|
|
ASLEEP - will not appear until triggered
|
|
|
|
|
|
|
|
EATING - Chomp chomp... chewie chomp
|
|
|
|
|
|
|
|
WANDER - Monster will wander around aimlessly (but follows buoys)
|
|
|
|
|
|
|
|
MELEE_LEAD - Monster will try to cut you off when you're running and fighting him, works well if there are a few monsters in a group, half doing this, half not
|
|
|
|
|
|
|
|
STALK - Monster will only approach and attack from behind- if you're facing the monster it will just stand there. Once the monster takes pain, however, it will stop this behaviour and attack normally
|
|
|
|
|
|
|
|
COWARD - Monster starts off in flee mode- runs away from you when woken up
|
|
|
|
|
|
|
|
"homebuoy" - monsters will head to this buoy if they don't have an enemy ("homebuoy" should be targetname of the buoy you want them to go to)
|
|
|
|
|
|
|
|
"wakeup_target" - monsters will fire this target the first time it wakes up (only once)
|
|
|
|
|
|
|
|
"pain_target" - monsters will fire this target the first time it gets hurt (only once)
|
|
|
|
|
|
|
|
mintel - monster intelligence- this basically tells a monster how many buoys away an enemy has to be for it to give up.
|
|
|
|
|
|
|
|
melee_range - How close the player has to be, maximum, for the monster to go into melee. If this is zero, the monster will never melee. If it is negative, the monster will try to keep this distance from the player. If the monster has a backup, he'll use it if too clode, otherwise, a negative value here means the monster will just stop running at the player at this distance.
|
|
|
|
Examples:
|
|
|
|
melee_range = 60 - monster will start swinging it player is closer than 60
|
|
|
|
melee_range = 0 - monster will never do a mele attack
|
|
|
|
melee_range = -100 - monster will never do a melee attack and will back away (if it has that ability) when player gets too close
|
|
|
|
|
|
|
|
missile_range - Maximum distance the player can be from the monster to be allowed to use it's ranged attack.
|
|
|
|
|
|
|
|
min_missile_range - Minimum distance the player can be from the monster to be allowed to use it's ranged attack.
|
|
|
|
|
|
|
|
bypass_missile_chance - Chance that a monster will NOT fire it's ranged attack, even when it has a clear shot. This, in effect, will make the monster come in more often than hang back and fire. A percentage (0 = always fire/never close in, 100 = never fire/always close in).- must be whole number
|
|
|
|
|
|
|
|
jump_chance - every time the monster has the opportunity to jump, what is the chance (out of 100) that he will... (100 = jump every time)- must be whole number
|
|
|
|
|
|
|
|
wakeup_distance - How far (max) the player can be away from the monster before it wakes up. This just means that if the monster can see the player, at what distance should the monster actually notice him and go for him.
|
|
|
|
|
|
|
|
DEFAULTS:
|
|
|
|
mintel = 12
|
|
|
|
melee_range = 0
|
|
|
|
missile_range = 256
|
|
|
|
min_missile_range = 48
|
|
|
|
bypass_missile_chance = 0
|
|
|
|
jump_chance = 100
|
|
|
|
wakeup_distance = 1024
|
|
|
|
|
|
|
|
NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1
|
|
|
|
*/
|
|
|
|
|
|
|
|
void SP_Monster_Gkrokon(edict_t *self)
|
|
|
|
{
|
|
|
|
if (!walkmonster_start(self)) // Failed initialization
|
|
|
|
return;
|
|
|
|
|
|
|
|
self->msgHandler=DefaultMsgHandler;
|
|
|
|
self->classID=CID_GKROKON;
|
|
|
|
|
|
|
|
if (!self->health)
|
|
|
|
self->health=GKROKON_HEALTH;
|
|
|
|
|
1999-03-18 00:00:00 +00:00
|
|
|
self->max_health = self->health = MonsterHealth(self->health);
|
|
|
|
|
1998-11-24 00:00:00 +00:00
|
|
|
self->materialtype = MAT_INSECT;
|
|
|
|
self->mass=GKROKON_MASS;
|
|
|
|
self->yaw_speed=20;
|
|
|
|
self->movetype=PHYSICSTYPE_STEP;
|
|
|
|
VectorClear(self->knockbackvel);
|
|
|
|
self->solid=SOLID_BBOX;
|
|
|
|
self->monsterinfo.dismember = beetle_dismember;
|
|
|
|
|
|
|
|
self->s.renderfx |= RF_FRAMELERP;
|
|
|
|
|
|
|
|
self->touch = M_Touch;
|
|
|
|
|
|
|
|
VectorCopy(STDMinsForClass[self->classID], self->mins);
|
|
|
|
VectorCopy(STDMaxsForClass[self->classID], self->maxs);
|
|
|
|
|
|
|
|
self->monsterinfo.attack_finished = level.time;
|
|
|
|
|
|
|
|
self->s.modelindex=classStatics[CID_GKROKON].resInfo->modelIndex;
|
|
|
|
|
|
|
|
self->s.skinnum = (irand(0, 1)) ? 0 : 2;
|
|
|
|
|
|
|
|
self->monsterinfo.otherenemyname = "";
|
|
|
|
|
|
|
|
self->timestamp = 0;
|
|
|
|
|
|
|
|
self->monsterinfo.searchType = SEARCH_COMMON;
|
|
|
|
|
|
|
|
MG_InitMoods(self);
|
|
|
|
|
|
|
|
// Check our spawnflags to see what our initial behaviour should be.
|
|
|
|
if(self->spawnflags & MSF_EATING)
|
|
|
|
{
|
|
|
|
self->monsterinfo.aiflags|=AI_EATING;
|
|
|
|
QPostMessage(self,MSG_EAT,PRI_DIRECTIVE,NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
QPostMessage(self,MSG_STAND,PRI_DIRECTIVE,NULL);
|
|
|
|
|
|
|
|
self->count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GkrokonPause(edict_t *self)
|
|
|
|
{
|
|
|
|
if(self->spawnflags & MSF_FIXED && self->curAnimID == ANIM_DELAY && self->enemy)
|
|
|
|
{
|
|
|
|
self->monsterinfo.searchType = SEARCH_COMMON;
|
|
|
|
MG_FaceGoal(self, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
self->mood_think(self);
|
|
|
|
|
|
|
|
switch (self->ai_mood)
|
|
|
|
{
|
|
|
|
case AI_MOOD_ATTACK:
|
|
|
|
QPostMessage(self, MSG_MISSILE, PRI_DIRECTIVE, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AI_MOOD_PURSUE:
|
|
|
|
case AI_MOOD_NAVIGATE:
|
|
|
|
case AI_MOOD_FLEE:
|
|
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AI_MOOD_WALK:
|
|
|
|
case AI_MOOD_WANDER:
|
|
|
|
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AI_MOOD_BACKUP:
|
|
|
|
QPostMessage(self, MSG_FALLBACK, PRI_DIRECTIVE, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AI_MOOD_STAND:
|
|
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AI_MOOD_JUMP:
|
|
|
|
if(self->spawnflags&MSF_FIXED || self->jump_chance<=0)
|
|
|
|
SetAnim(self, ANIM_DELAY);
|
|
|
|
else
|
|
|
|
SetAnim(self, ANIM_FJUMP);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AI_MOOD_EAT:
|
|
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default :
|
|
|
|
#ifdef _DEVEL
|
|
|
|
gi.dprintf("Unusable mood %d for Gkrokon\n", self->ai_mood);
|
|
|
|
#endif
|
|
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void beetle_to_stand (edict_t *self)
|
|
|
|
{
|
|
|
|
SetAnim(self, ANIM_STAND3);
|
|
|
|
GkrokonPause(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
void beetle_to_crouch (edict_t *self)
|
|
|
|
{
|
|
|
|
SetAnim(self, ANIM_CROUCH1);
|
|
|
|
GkrokonPause(self);
|
|
|
|
}
|