772 lines
20 KiB
C
772 lines
20 KiB
C
//==============================================================================
|
|
//
|
|
// m_mssithra.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 "m_mssithra.h"
|
|
#include "m_mssithra_anim.h"
|
|
#include "g_misc.h"
|
|
#include "g_HitLocation.h"
|
|
#include "p_anim_branch2.h"
|
|
#include "m_stats.h"
|
|
#include "g_playstats.h"
|
|
|
|
void extrapolateFiredir (edict_t *self,vec3_t p1,float pspeed,edict_t *targ,float accept,vec3_t vec2);
|
|
H2COMMON_API void KnockDownPlayer(playerinfo_t *playerinfo);
|
|
void create_ssithra_arrow(edict_t *Arrow);
|
|
|
|
#define MSSITHRA_JUMP_VELOCITY 300.0
|
|
#define MSSITHRA_HOP_VELOCITY 128.0
|
|
#define MSSITHRA_SF_NAMOR 8
|
|
#define MSSITHRA_SF_SPIN 16
|
|
|
|
//========================================
|
|
//INITIALIZE
|
|
//========================================
|
|
static animmove_t *animations[NUM_ANIMS] =
|
|
{
|
|
&mssithra_move_claw1,
|
|
&mssithra_move_death1,
|
|
&mssithra_move_idle1,
|
|
&mssithra_move_jump1,
|
|
&mssithra_move_fjump,
|
|
// &mssithra_move_pain1,
|
|
&mssithra_move_roar,
|
|
&mssithra_move_shoota1,
|
|
&mssithra_move_shootb1,
|
|
&mssithra_move_walk1,
|
|
&mssithra_move_backpedal1,
|
|
&mssithra_move_run1,
|
|
&mssithra_move_delay,
|
|
&mssithra_move_shoot1_trans,
|
|
&mssithra_move_shoot1_loop,
|
|
&mssithra_move_shoot1_detrans
|
|
};
|
|
|
|
static int Sounds[NUM_SOUNDS];
|
|
|
|
static ClassResourceInfo_t resInfo;
|
|
/*
|
|
|
|
//========================================
|
|
//MOVEMENT
|
|
//========================================
|
|
|
|
*/
|
|
|
|
void mssithra_stand(edict_t *self, G_Message_t *msg)
|
|
{
|
|
SetAnim(self, ANIM_IDLE1);
|
|
}
|
|
|
|
void mssithra_decide_stand(edict_t *self)
|
|
{
|
|
if(mssithraCheckMood(self))
|
|
return;
|
|
|
|
SetAnim(self, ANIM_IDLE1);
|
|
}
|
|
|
|
void mssithra_pain(edict_t *self, G_Message_t *msg)
|
|
{//fixme - make part fly dir the vector from hit loc to sever loc
|
|
int temp, damage;
|
|
qboolean force_pain;
|
|
|
|
if(self->deadflag == DEAD_DEAD) //Dead but still being hit
|
|
return;
|
|
|
|
ParseMsgParms(msg, "eeiii", &temp, &temp, &force_pain, &damage, &temp);
|
|
|
|
if(!force_pain)
|
|
{
|
|
if(irand(0,10)<5||!self->groundentity)
|
|
return;
|
|
|
|
if(self->pain_debounce_time > level.time)
|
|
return;
|
|
}
|
|
|
|
if(self->curAnimID == ANIM_CLAW1)
|
|
return;
|
|
|
|
self->pain_debounce_time = level.time + 2;
|
|
|
|
if(irand(0,10)<5)
|
|
gi.sound (self, CHAN_VOICE, Sounds[SND_PAIN1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound (self, CHAN_VOICE, Sounds[SND_PAIN2], 1, ATTN_NORM, 0);
|
|
|
|
if (self->flags&FL_INWATER)
|
|
self->flags |= FL_SWIM;
|
|
else
|
|
self->flags &= ~FL_SWIM;
|
|
|
|
if(irand(0,10)<1)
|
|
SetAnim(self, ANIM_ROAR1);//make him tougher? more aggressive?
|
|
}
|
|
|
|
void mssithra_pain_react (edict_t *self)
|
|
{
|
|
if(!self->enemy)
|
|
{
|
|
mssithra_decide_stand(self);
|
|
return;
|
|
}
|
|
|
|
if(self->enemy->health<=0||self->enemy == self||!self->enemy->takedamage)
|
|
{
|
|
self->enemy=NULL;
|
|
mssithra_decide_stand(self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//===========================================
|
|
//DEATHS
|
|
//===========================================
|
|
|
|
void mssithra_death(edict_t *self, G_Message_t *msg)
|
|
{
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
self->msgHandler=DyingMsgHandler;
|
|
|
|
if(self->deadflag == DEAD_DEAD)
|
|
return;
|
|
|
|
self->deadflag = DEAD_DEAD;
|
|
|
|
gi.sound (self, CHAN_VOICE, Sounds[SND_DIE], 1, ATTN_NORM, 0);
|
|
if (self->flags&FL_INWATER)
|
|
self->flags |= FL_SWIM;
|
|
else
|
|
self->flags &= ~FL_SWIM;
|
|
|
|
SetAnim(self, ANIM_DEATH1);
|
|
|
|
//Remove the life bar once dead
|
|
M_ShowLifeMeter( self, 0, 0);
|
|
self->post_think = NULL;
|
|
self->next_post_think = -1;
|
|
}
|
|
|
|
void mssithra_dead(edict_t *self)
|
|
{//maybe allow dead bodies to be chopped? Make BBOX small?
|
|
self->msgHandler = DeadMsgHandler;
|
|
self->deadState = DEAD_DEAD;
|
|
|
|
self->think = NULL;
|
|
self->nextthink = 0;
|
|
self->flags |= FL_DONTANIMATE;
|
|
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void mssithraKillSelf (edict_t *self)
|
|
{
|
|
vec3_t gore_spot;
|
|
|
|
self->svflags &= ~SVF_DEADMONSTER; // now treat as a different content type
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->deadflag = false;
|
|
VectorCopy(self->s.origin,gore_spot);
|
|
gore_spot[2]+=12;
|
|
self->health = 1;
|
|
T_Damage (self, self, self, vec3_origin, gore_spot, vec3_origin, 10, 0,0,MOD_DIED);
|
|
self->health = -69;
|
|
}
|
|
|
|
//===========================================
|
|
//SOUNDS
|
|
//===========================================
|
|
|
|
void mssithraSound(edict_t *self, float soundnum, float channel, float attenuation)
|
|
{
|
|
return;
|
|
if(!channel)
|
|
channel = CHAN_AUTO;
|
|
|
|
if(!attenuation)
|
|
attenuation = ATTN_NORM;
|
|
else if(attenuation == -1)
|
|
attenuation = ATTN_NONE;
|
|
|
|
gi.sound(self, (int)channel, Sounds[(int)(soundnum)], 1, (int)attenuation, 0);
|
|
}
|
|
|
|
//===========================================
|
|
//ATTACKS
|
|
//===========================================
|
|
|
|
void mssithra_melee(edict_t *self, G_Message_t *msg)
|
|
{
|
|
vec3_t v;
|
|
float len, melee_range, min_seperation, jump_range;
|
|
|
|
if(M_ValidTarget(self, self->enemy))
|
|
{
|
|
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
|
len = VectorLength (v);
|
|
melee_range = 64;
|
|
jump_range = 128;
|
|
min_seperation = self->maxs[0] + self->enemy->maxs[0];
|
|
|
|
if (len < min_seperation + melee_range) // A hit
|
|
{
|
|
SetAnim(self, ANIM_CLAW1);
|
|
}
|
|
}
|
|
else
|
|
QPostMessage(self, MSG_STAND,PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
void mssithra_missile(edict_t *self, G_Message_t *msg)
|
|
{//NEWSTUFF: jump closer to claw, loop shooting anims
|
|
vec3_t v;
|
|
float len, min_seperation, jump_range;
|
|
|
|
if(M_ValidTarget(self, self->enemy))
|
|
{
|
|
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
|
len = VectorLength (v);
|
|
jump_range = 128;
|
|
min_seperation = self->maxs[0] + self->enemy->maxs[0];
|
|
|
|
if (irand(0,(skill->value+1)*2))
|
|
{
|
|
SetAnim(self, ANIM_SHOOT_TRANS);
|
|
}
|
|
else
|
|
{
|
|
SetAnim(self, ANIM_IDLE1);
|
|
}
|
|
}
|
|
else
|
|
QPostMessage(self, MSG_STAND,PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
void mssithraSwipe (edict_t *self)
|
|
{
|
|
vec3_t v, off, dir, org, ang;
|
|
float len;
|
|
|
|
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
|
len = VectorLength (v);
|
|
|
|
if (len < (self->maxs[0] + self->enemy->maxs[0] + 45) ) // A hit
|
|
{
|
|
if (infront(self, self->enemy))
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, Sounds[SND_SWIPEHIT], 1, ATTN_NORM, 0);
|
|
VectorSet(off, 35.0, 0.0, 32.0);
|
|
VectorGetOffsetOrigin(off, self->s.origin, self->s.angles[YAW], org);
|
|
VectorCopy(self->s.angles, ang);
|
|
ang[YAW] += DEGREE_90;
|
|
AngleVectors(ang, dir, NULL, NULL);
|
|
T_Damage (self->enemy, self, self, dir, org, vec3_origin,
|
|
MSSITHRA_DMG_SWIPE, 0, DAMAGE_DISMEMBER,MOD_DIED);
|
|
|
|
if(self->enemy->health>0)//else don't gib?
|
|
{
|
|
if(!irand(0,5))
|
|
{
|
|
if(!stricmp(self->enemy->classname, "player"))
|
|
KnockDownPlayer(&self->enemy->client->playerinfo);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
gi.sound (self, CHAN_WEAPON, Sounds[SND_SWIPE], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void mssithra_missile_explode(edict_t *self)
|
|
{
|
|
int damage = irand(8, 16);
|
|
|
|
//TODO: Spawn an explosion effect
|
|
gi.CreateEffect( NULL,
|
|
FX_M_EFFECTS,
|
|
0,
|
|
self->s.origin,
|
|
"bv",
|
|
FX_MSSITHRA_EXPLODE,
|
|
self->movedir);
|
|
|
|
T_DamageRadius(self, self->owner, self->owner, 128, damage, damage/2, DAMAGE_ATTACKER_IMMUNE, MOD_DIED);
|
|
|
|
G_FreeEdict(self);
|
|
}
|
|
|
|
void mssithraAlphaArrowTouch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surface)
|
|
{
|
|
edict_t *Arrow;
|
|
|
|
// are we reflecting ?
|
|
if (self->reflect_debounce_time)
|
|
{
|
|
if(EntReflecting(other, true, true))
|
|
{
|
|
Arrow = G_Spawn();
|
|
|
|
create_ssithra_arrow(Arrow);
|
|
VectorCopy(self->s.origin, Arrow->s.origin);
|
|
Arrow->owner = self->owner;
|
|
|
|
Arrow->nextthink=self->nextthink;
|
|
Arrow->think=G_FreeEdict;
|
|
|
|
Create_rand_relect_vect(self->velocity, Arrow->velocity);
|
|
|
|
vectoangles(Arrow->velocity, Arrow->s.angles);
|
|
Arrow->s.angles[YAW]+=90;
|
|
|
|
Vec3ScaleAssign(MSSITHRA_ARROW_SPEED/2,Arrow->velocity);
|
|
|
|
Arrow->reflect_debounce_time = self->reflect_debounce_time - 1;
|
|
|
|
G_LinkMissile(Arrow);
|
|
//gi.CreateEffect(&Arrow->s, FX_WEAPON_REDRAINMISSILE, CEF_OWNERS_ORIGIN|CEF_FLAG8|CEF_FLAG7, NULL, "t", Arrow->velocity);
|
|
|
|
G_SetToFree(self);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(other->takedamage)
|
|
{
|
|
if (plane->normal)
|
|
VectorCopy(plane->normal, self->movedir);
|
|
|
|
self->dmg = irand(MSSITHRA_DMG_MIN*2, MSSITHRA_DMG_MAX*2);
|
|
mssithra_missile_explode(self);
|
|
}
|
|
else
|
|
{
|
|
gi.sound(self, CHAN_WEAPON, Sounds[SND_INWALL], 0.5, ATTN_NORM, 0);
|
|
|
|
self->s.effects |= EF_ALTCLIENTFX;
|
|
|
|
VectorClear(self->velocity);
|
|
|
|
if (plane->normal)
|
|
VectorCopy(plane->normal, self->movedir);
|
|
|
|
self->dmg = irand(MSSITHRA_DMG_MIN, MSSITHRA_DMG_MAX);
|
|
self->think = mssithra_missile_explode;
|
|
self->nextthink = level.time + flrand(0.5, 1.5);
|
|
}
|
|
}
|
|
|
|
// create the guts of the ssithra arrow
|
|
void create_ssithra_arrow(edict_t *Arrow)
|
|
{
|
|
Arrow->s.modelindex = gi.modelindex("models/objects/exarrow/tris.fm");
|
|
Arrow->movetype = MOVETYPE_FLYMISSILE;
|
|
Arrow->solid = SOLID_BBOX;
|
|
Arrow->classname = "mssithra_Arrow";
|
|
|
|
Arrow->touch = mssithraAlphaArrowTouch;
|
|
|
|
Arrow->clipmask = MASK_SHOT;
|
|
|
|
Arrow->s.effects |= EF_ALWAYS_ADD_EFFECTS | EF_CAMERA_NO_CLIP;
|
|
Arrow->svflags |= SVF_ALWAYS_SEND;
|
|
|
|
VectorSet(Arrow->mins,-1.0,-1.0,-1.0);
|
|
VectorSet(Arrow->maxs,1.0,1.0,1.0);
|
|
Arrow->s.scale = 1.5;
|
|
}
|
|
|
|
void mssithraArrow(edict_t *self)
|
|
{//fixme; adjust for up/down
|
|
vec3_t Forward, targ_pos;
|
|
vec3_t Right, fire_spot, fire_dir;// , up;
|
|
edict_t *Arrow;
|
|
float spread;
|
|
int num_shots = 3;
|
|
|
|
if (!self->enemy)
|
|
return;
|
|
|
|
if(self->enemy->health<=0)
|
|
{
|
|
self->enemy=NULL;
|
|
mssithra_decide_stand(self);
|
|
return;
|
|
}
|
|
|
|
if(self->monsterinfo.attack_finished>level.time)
|
|
return;
|
|
|
|
gi.sound(self,CHAN_WEAPON,Sounds[SND_ARROW],1,ATTN_NORM,0);
|
|
self->monsterinfo.attack_finished = level.time + 0.4;
|
|
|
|
VectorCopy(self->s.origin,fire_spot);
|
|
fire_spot[2]+=self->maxs[2]*0.5;
|
|
AngleVectors(self->s.angles,Forward,Right,NULL);
|
|
VectorMA(fire_spot,72,Forward,fire_spot);
|
|
VectorMA(fire_spot,16,Right,fire_spot);
|
|
fire_spot[2] += 24;
|
|
|
|
VectorRandomCopy(self->enemy->s.origin, targ_pos, 16);
|
|
VectorSubtract(targ_pos, fire_spot, Forward);
|
|
VectorNormalize2(Forward, fire_dir);
|
|
|
|
while(num_shots)
|
|
{
|
|
Arrow = G_Spawn();
|
|
|
|
create_ssithra_arrow(Arrow);
|
|
Arrow->reflect_debounce_time = MAX_REFLECT;
|
|
|
|
VectorCopy(fire_spot,Arrow->s.origin);
|
|
VectorCopy(self->movedir,Arrow->movedir);
|
|
|
|
//Increase the spread for lower levels
|
|
switch((int)skill->value)
|
|
{
|
|
case 0:
|
|
spread = 0.35;
|
|
break;
|
|
|
|
case 1:
|
|
spread = 0.2;
|
|
break;
|
|
|
|
case 2:
|
|
default:
|
|
spread = 0.1;
|
|
break;
|
|
}
|
|
|
|
AngleVectors(self->s.angles,NULL,Right,NULL);
|
|
switch (num_shots)
|
|
{
|
|
case 3:
|
|
VectorScale(Right, spread,Right);
|
|
break;
|
|
|
|
case 1:
|
|
VectorScale(Right,-spread,Right);
|
|
break;
|
|
|
|
case 2:
|
|
default:
|
|
VectorClear(Right);
|
|
break;
|
|
}
|
|
|
|
VectorAdd(fire_dir, Right, Arrow->movedir);
|
|
VectorNormalize(Arrow->movedir);
|
|
VectorScale(Arrow->movedir,MSSITHRA_ARROW_SPEED,Arrow->velocity);
|
|
|
|
vectoangles(Arrow->velocity, Arrow->s.angles);
|
|
Arrow->s.angles[YAW] += 90;
|
|
|
|
Arrow->owner=self;
|
|
Arrow->enemy=self->enemy;
|
|
|
|
gi.CreateEffect(&Arrow->s,
|
|
FX_M_EFFECTS,
|
|
CEF_OWNERS_ORIGIN | CEF_FLAG6,
|
|
NULL,
|
|
"bv",
|
|
FX_MSSITHRA_ARROW,
|
|
Arrow->velocity);
|
|
|
|
G_LinkMissile(Arrow);
|
|
|
|
Arrow->nextthink=level.time+5;
|
|
Arrow->think=G_FreeEdict;//mssithraArrowThink;
|
|
|
|
num_shots--;
|
|
}
|
|
}
|
|
|
|
void mssithraCheckLoop (edict_t *self, float frame)
|
|
{//see if should fire again
|
|
vec3_t v;
|
|
float len, melee_range, min_seperation, jump_range;
|
|
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
ai_charge2(self, 0);
|
|
|
|
if(!visible(self, self->enemy))
|
|
return;
|
|
|
|
if(!infront(self, self->enemy))
|
|
return;
|
|
|
|
if(irand(0,10)<5)
|
|
return;
|
|
|
|
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
|
len = VectorLength (v);
|
|
melee_range = 64;
|
|
jump_range = 128;
|
|
min_seperation = self->maxs[0] + self->enemy->maxs[0];
|
|
|
|
if (infront(self, self->enemy))
|
|
{//don't loop if enemy close enough
|
|
if (len < min_seperation + melee_range)
|
|
return;
|
|
else if (len < min_seperation + jump_range && irand(0,10)<3)
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.currframeindex = (int)frame;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
mssithra_pause
|
|
-------------------------------------------------------------------------*/
|
|
qboolean mssithraCheckMood (edict_t *self)
|
|
{
|
|
if(self->monsterinfo.aiflags&AI_OVERRIDE_GUIDE)
|
|
return false;
|
|
|
|
self->mood_think(self);
|
|
|
|
if (self->ai_mood == AI_MOOD_NORMAL)
|
|
return false;
|
|
|
|
switch (self->ai_mood)
|
|
{
|
|
case AI_MOOD_ATTACK:
|
|
if(self->ai_mood_flags & AI_MOOD_FLAG_MISSILE)
|
|
QPostMessage(self, MSG_MISSILE, PRI_DIRECTIVE, NULL);
|
|
else
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
return true;
|
|
break;
|
|
case AI_MOOD_PURSUE:
|
|
case AI_MOOD_NAVIGATE:
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
return true;
|
|
break;
|
|
case AI_MOOD_WALK:
|
|
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
|
|
return true;
|
|
break;
|
|
|
|
case AI_MOOD_STAND:
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
return true;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void mssithra_postthink(edict_t *self)
|
|
{
|
|
//Only display a lifemeter if we have an enemy
|
|
if (self->enemy)
|
|
{
|
|
if (self->dmg < self->max_health)
|
|
{
|
|
M_ShowLifeMeter( self, self->dmg, self->dmg);
|
|
self->dmg+=50;
|
|
}
|
|
else
|
|
{
|
|
M_ShowLifeMeter( self, self->health, self->max_health);
|
|
}
|
|
}
|
|
|
|
self->next_post_think = level.time + 0.05;
|
|
}
|
|
|
|
void mmssithraRandomGrowlSound (edict_t *self)
|
|
{
|
|
if(!irand(0,2))
|
|
gi.sound(self, CHAN_VOICE, Sounds[SND_GROWL1], 1, ATTN_NORM, 0);
|
|
else if(!irand(0,1))
|
|
gi.sound(self, CHAN_VOICE, Sounds[SND_GROWL2], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, Sounds[SND_GROWL3], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void mssithra_ShotLoop(edict_t *self)
|
|
{
|
|
SetAnim(self, ANIM_SHOOT_LOOP);
|
|
}
|
|
|
|
void mssithraCheckShotLoop(edict_t *self)
|
|
{
|
|
//TODO: Check to keep shooting or to stop shooting
|
|
if (irand(0,(skill->value+1)*2))
|
|
{
|
|
//Just keep playing the animation
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
SetAnim(self, ANIM_SHOOT_DETRANS);
|
|
}
|
|
}
|
|
|
|
void MssithraStaticsInit()
|
|
{
|
|
classStatics[CID_MSSITHRA].msgReceivers[MSG_STAND] = mssithra_stand;
|
|
classStatics[CID_MSSITHRA].msgReceivers[MSG_MISSILE] = mssithra_missile;
|
|
classStatics[CID_MSSITHRA].msgReceivers[MSG_MELEE] = mssithra_melee;
|
|
classStatics[CID_MSSITHRA].msgReceivers[MSG_DEATH] = mssithra_death;
|
|
classStatics[CID_MSSITHRA].msgReceivers[MSG_PAIN] = mssithra_pain;
|
|
classStatics[CID_MSSITHRA].msgReceivers[MSG_RUN] = mssithra_missile;
|
|
|
|
resInfo.numAnims = NUM_ANIMS;
|
|
resInfo.animations = animations;
|
|
resInfo.modelIndex = gi.modelindex("models/monsters/mutantsithra/tris.fm");
|
|
|
|
Sounds[SND_PAIN1]=gi.soundindex("monsters/mssithra/pain1.wav");
|
|
Sounds[SND_PAIN2]=gi.soundindex("monsters/mssithra/pain2.wav");
|
|
Sounds[SND_DIE]=gi.soundindex("monsters/mssithra/death1.wav");
|
|
Sounds[SND_SWIPE] = gi.soundindex ("monsters/mssithra/swipe.wav");
|
|
Sounds[SND_SWIPEHIT]=gi.soundindex("monsters/mssithra/swipehit.wav");
|
|
Sounds[SND_ARROW]=gi.soundindex("weapons/RedRainPowerFire.wav");
|
|
Sounds[SND_AEXPLODE]=gi.soundindex("weapons/FlyingFistImpact.wav");
|
|
Sounds[SND_GROWL1]=gi.soundindex("monsters/mssithra/growl1.wav");
|
|
Sounds[SND_GROWL2] = gi.soundindex ("monsters/mssithra/growl2.wav");
|
|
Sounds[SND_GROWL3] = gi.soundindex ("monsters/mssithra/growl3.wav");
|
|
Sounds[SND_ROAR]=gi.soundindex("monsters/mssithra/roar.wav");
|
|
Sounds[SND_INWALL]=gi.soundindex("weapons/staffhitwall.wav");
|
|
|
|
resInfo.numSounds = NUM_SOUNDS;
|
|
resInfo.sounds = Sounds;
|
|
|
|
classStatics[CID_MSSITHRA].resInfo = &resInfo;
|
|
}
|
|
|
|
/*QUAKED monster_mssithra (1 .5 0) (-36 -36 0) (36 36 96) AMBUSH ASLEEP 4 8 16 32 64 FIXED WANDER MELEE_LEAD STALK COWARD EXTRA1 EXTRA2 EXTRA3 EXTRA4
|
|
|
|
The mssithra
|
|
|
|
AMBUSH - Will not be woken up by other monsters or shots from player
|
|
|
|
ASLEEP - will not appear until triggered
|
|
|
|
WALKING - Use WANDER instead
|
|
|
|
WANDER - Monster will wander around aimlessly (but follows buoys)
|
|
|
|
MELEE_LEAD - Monster will tryto 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)
|
|
|
|
"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 = 16
|
|
melee_range = 100
|
|
missile_range = 400
|
|
min_missile_range = 100
|
|
bypass_missile_chance = 25
|
|
jump_chance = 25
|
|
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_mssithra (edict_t *self)
|
|
{
|
|
// Generic Monster Initialization
|
|
if (!walkmonster_start (self)) // failed initialization
|
|
return;
|
|
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->classID = CID_MSSITHRA;
|
|
self->materialtype = MAT_FLESH;
|
|
self->flags |= FL_IMMUNE_SLIME;
|
|
if(self->flags&FL_INWATER||
|
|
gi.pointcontents(self->s.origin)&CONTENTS_WATER||
|
|
self->waterlevel >= 3)
|
|
self->flags|=FL_SWIM;
|
|
|
|
if(self->health<=0)
|
|
self->health = MSSITHRA_HEALTH;
|
|
|
|
//Apply to the end result (whether designer set or not)
|
|
self->max_health = self->health = MonsterHealth(self->health);
|
|
|
|
self->mass = MSSITHRA_MASS;
|
|
self->yaw_speed = 20;
|
|
self->viewheight = 88;
|
|
self->monsterinfo.aiflags |= AI_BRUTAL|AI_AGRESSIVE;
|
|
|
|
self->movetype=PHYSICSTYPE_STEP;//MOVETYPE_STEP
|
|
VectorClear(self->knockbackvel);
|
|
|
|
self->solid=SOLID_BBOX;
|
|
|
|
VectorCopy(STDMinsForClass[self->classID], self->mins);
|
|
VectorCopy(STDMaxsForClass[self->classID], self->maxs);
|
|
|
|
self->s.modelindex = classStatics[CID_MSSITHRA].resInfo->modelIndex;
|
|
|
|
self->s.skinnum = 0;
|
|
|
|
if (!self->monsterinfo.scale)
|
|
{
|
|
self->s.scale = self->monsterinfo.scale = (MODEL_SCALE + 0.25);
|
|
}
|
|
|
|
self->monsterinfo.otherenemyname = "obj_barrel";
|
|
|
|
//set up my mood function
|
|
MG_InitMoods(self);
|
|
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
self->post_think = mssithra_postthink;
|
|
self->next_post_think = level.time + 0.1;
|
|
|
|
//Turn the goofy bolts off!
|
|
self->s.fmnodeinfo[MESH__BOLTS].flags |= FMNI_NO_DRAW;
|
|
|
|
self->dmg = 0;
|
|
self->svflags|=SVF_BOSS;
|
|
}
|