heretic2-sdk/Toolkit/Programming/GameCode/game/m_tcheckrik.c
1999-03-18 00:00:00 +00:00

1896 lines
56 KiB
C

//==============================================================================
//
// m_tcheckrik.c
//
// Heretic II
// Copyright 1998 Raven Software
//
//
// AI :
//
// STAND1 : Looking straight ahead
//
// WALK1 : a normal straight line
// WALK2 : another normal straight line
//
// MELEE1 : Attack
// MELEE2 : Attack
//
// RUNATTACK : Running and swinging
// RUN1 : chasing an enemy straight ahead
// SHAKE : stand and spaz
// DIE1 : Fall back dead
// LEAN1 : lean agains the wall
// FIST1 : Beat against the wall in rage and desperation
//
//
//==============================================================================
#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_tcheckrik.h"
#include "m_tcheckrik_anim.h"
#include "g_HitLocation.h"
#include "g_misc.h"
#include "angles.h"
#include "g_HitLocation.h"
#include "c_ai.h"
#include "m_stats.h"
void insect_dismember(edict_t *self, int damage, int HitLocation);
/*----------------------------------------------------------------------
insect Base Info
-----------------------------------------------------------------------*/
static animmove_t *animations[ NUM_ANIMS] =
{
&insect_move_back,// = {10, insect_frames_back, insect_pause},
&insect_move_deathfr,// = {23, insect_frames_deathfr, insect_dead},
&insect_move_idle,// = { 50, insect_frames_idle, NULL},
&insect_move_land,// = {6, insect_frames_land, insect_pause},
&insect_move_inair,// = {1, insect_frames_inair, NULL},
&insect_move_forcedjump,// = {9, insect_frames_forcedjump, insect_go_inair},
&insect_move_finair,// = {1, insect_frames_inair, NULL},
&insect_move_fjump,// = {9, insect_frames_forcedjump, insect_go_inair},
&insect_move_paina,// = {7, insect_frames_paina, insect_pause},
&insect_move_painc,// = {6, insect_frames_painc, insect_pause},
&insect_move_run,// = {10, insect_frames_run, insect_pause},
&insect_move_spear,// = {11, insect_frames_spear, insect_pause},
&insect_move_sword,// = {9, insect_frames_sword, insect_pause},
&insect_move_spell,// = {8, insect_frames_spell, insect_pause},
&insect_move_spell2,// = {8, insect_frames_spell, insect_pause},
&insect_move_walk,// = {20, insect_frames_walk, insect_pause},
&insect_delay,// = {50 , insect_frames_delay, insect_pause},
&insect_move_knock1_go,
&insect_move_knock1_loop,
&insect_move_knock1_end,
&insect_move_twitch,
// Cinematic
&insect_move_c_action1,
&insect_move_c_action2,
&insect_move_c_action3,
&insect_move_c_action4,
&insect_move_c_attack1,
&insect_move_c_attack2,
&insect_move_c_attack3,
&insect_move_c_backpedal,
&insect_move_c_death1,
&insect_move_c_idle1,
&insect_move_c_idle2,
&insect_move_c_idle3,
&insect_move_c_pain1,
&insect_move_c_run,
&insect_move_c_walk,
};
static int Sounds[NUM_SOUNDS];
static ClassResourceInfo_t resInfo;
/*----------------------------------------------------------------------
Cinematic Functions for the monster
-----------------------------------------------------------------------*/
/*-------------------------------------------------------------------------
plagueElf_c_anims
-------------------------------------------------------------------------*/
void insect_c_anims(edict_t *self, G_Message_t *msg)
{
int int_msg;
int curr_anim;
ai_c_readmessage(self, msg);
int_msg = (int) msg->ID;
self->monsterinfo.c_anim_flag = 0;
switch(int_msg)
{
case MSG_C_ACTION1:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_ACTION1;
break;
case MSG_C_ACTION2:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_ACTION2;
break;
case MSG_C_ACTION3:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_ACTION3;
break;
case MSG_C_ACTION4:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_ACTION4;
break;
case MSG_C_ATTACK1:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_ATTACK1;
break;
case MSG_C_ATTACK2:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_ATTACK2;
break;
case MSG_C_ATTACK3:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_ATTACK3;
break;
case MSG_C_BACKPEDAL1:
self->monsterinfo.c_anim_flag |= C_ANIM_MOVE;
curr_anim = ANIM_C_BACKPEDAL;
break;
case MSG_C_DEATH1:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_DEATH1;
break;
case MSG_C_IDLE1:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT | C_ANIM_IDLE;
curr_anim = ANIM_C_IDLE1;
break;
case MSG_C_IDLE2:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_IDLE2;
break;
case MSG_C_IDLE3:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_IDLE3;
break;
case MSG_C_PAIN1:
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
curr_anim = ANIM_C_PAIN1;
break;
case MSG_C_RUN1:
self->monsterinfo.c_anim_flag |= C_ANIM_MOVE;
curr_anim = ANIM_C_RUN1;
break;
case MSG_C_WALK1:
self->monsterinfo.c_anim_flag |= C_ANIM_MOVE;
curr_anim = ANIM_C_WALK1;
break;
default:
self = self;
break;
}
SetAnim(self, curr_anim);
}
/*-------------------------------------------------------------------------
insect_c_pause
-------------------------------------------------------------------------*/
void insect_c_reallydead (edict_t *self)
{
self->nextthink = level.time;
self->think = NULL;
}
/*----------------------------------------------------------------------
Action Functions for the monster
-----------------------------------------------------------------------*/
/*-------------------------------------------------------------------------
insectCut
-------------------------------------------------------------------------*/
vec3_t TC_WPN_MINS = {-2.0, -2.0, -1.0};
vec3_t TC_WPN_MAXS = {2.0, 2.0, 1.0};
void insectCut (edict_t *self, float attacktype)
{
trace_t trace;
vec3_t pos1, pos2, dir, forward, right, hitangles;
float damage;
AngleVectors(self->s.angles, forward, right, NULL);
VectorCopy(self->s.origin, pos1);
VectorMA (pos1, 72, forward, pos2);
switch((int)attacktype)
{
case TC_ATK_STAB:
if(self->s.fmnodeinfo[MESH__SPEAR].flags & FMNI_NO_DRAW)
return;
VectorMA(pos2, -16, right, pos2);
damage = irand(TC_DMG_STAB_MIN, TC_DMG_STAB_MAX);
break;
case TC_ATK_HACK:
// if(self->s.fmnodeinfo[MESH__SWORD].flags & FMNI_NO_DRAW)
if(self->s.fmnodeinfo[MESH__MALEHAND].flags & FMNI_NO_DRAW)
{
VectorMA (pos1, 12, forward, pos1);
pos1[2]+= 28;
VectorMA(pos2, 24, right, pos2);
damage = irand(TC_MALE_DMG_HACK_MIN, TC_MALE_DMG_HACK_MAX);
}
else
{
VectorMA (pos1, 12, forward, pos1);
pos1[2]+= 18;
VectorMA(pos2, 12, right, pos2);
damage = irand(TC_FEMALE_DMG_HACK_MIN, TC_FEMALE_DMG_HACK_MAX);
}
break;
default:
return;
break;
}
gi.trace(pos1, TC_WPN_MINS, TC_WPN_MAXS, pos2, self, MASK_MONSTERSOLID|MASK_SHOT,&trace);
//sfs--do this check before the allsolid check, because trace is screwy--fraction should be valid in all cases, so shouldn't be a problem
if(trace.fraction == 1.0)
{//missed totally
return;
}
if(trace.allsolid || trace.startsolid || !trace.ent->takedamage)
{//ping!
vectoangles(trace.plane.normal, hitangles);
gi.CreateEffect(NULL, FX_SPARKS, 0, trace.endpos, "d", hitangles);
gi.sound (self, CHAN_AUTO, Sounds[SND_SWIPEHITW], 1, ATTN_NORM, 0);
// gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/staffhitwall.wav"), 1, ATTN_NORM, 0);
return;
}
//hit someone, cut em!
VectorSubtract(pos2, pos1, dir);
gi.sound (self, CHAN_AUTO, Sounds[SND_SWIPEHITF], 1, ATTN_NORM, 0);
// gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/staffhit.wav"), 1, ATTN_NORM, 0);
T_Damage (trace.ent, self, self, dir, trace.endpos, vec3_origin, damage, damage*2, DAMAGE_DISMEMBER,MOD_DIED);
/* gi.CreateEffect(&self->s,
FX_I_EFFECTS,
0,
vec3_origin,
"bv",
FX_I_SWORD,
vec3_origin);*/
}
/*-------------------------------------------------------------------------
insect_dead
-------------------------------------------------------------------------*/
void insect_dead(edict_t *self)
{
self->s.effects |= EF_DISABLE_EXTRA_FX;
self->msgHandler = DeadMsgHandler;
self->deadState = DEAD_DEAD;
M_EndDeath(self);
}
/*-------------------------------------------------------------------------
insect_death
-------------------------------------------------------------------------*/
void insect_random_death_sound (edict_t *self)
{
if(self->mass==MASS_TC_MALE)//irand(0,1))
gi.sound(self, CHAN_VOICE, Sounds[SND_DIEM], 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, Sounds[SND_DIEF], 1, ATTN_NORM, 0);
}
void insect_random_idle_sound (edict_t *self)
{
if(self->mass==MASS_TC_MALE)//
{
if(irand(0,1))
gi.sound(self, CHAN_VOICE, Sounds[SND_GROWLM1], 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, Sounds[SND_GROWLM2], 1, ATTN_NORM, 0);
}
else
{
if(irand(0,1))
gi.sound(self, CHAN_VOICE, Sounds[SND_GROWLF1], 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, Sounds[SND_GROWLF2], 1, ATTN_NORM, 0);
}
}
void M_MoveFrame (edict_t *self);
void insect_dead_pain (edict_t *self, G_Message_t *msg)
{
if(msg)
if(!(self->svflags & SVF_PARTS_GIBBED))
MG_parse_dismember_msg(self, msg);
if(self->curAnimID!=ANIM_TWITCH&&self->deadState!=DEAD_DEAD)
return;//still dying
if(self->s.frame==FRAME_knock15)
{
self->think = M_MoveFrame;
SetAnim(self, ANIM_TWITCH);
self->nextthink = level.time + 0.1;
}
}
void insect_wait_twitch (edict_t *self)
{
if(self->curAnimID!=ANIM_TWITCH&&!irand(0,1))
{
SetAnim(self, ANIM_TWITCH);
self->nextthink = level.time + 0.1;
}
else if(irand(0, 5))
{
SetAnim(self, ANIM_TWITCH);
self->nextthink = level.time + flrand(0.2, 10);
}
else
{
self->s.effects |= EF_DISABLE_EXTRA_FX;
self->msgHandler = DeadMsgHandler;
self->deadState = DEAD_DEAD;
M_EndDeath(self);
self->think = NULL;
}
}
void insect_flyback_loop(edict_t *self)
{
SetAnim(self, ANIM_KNOCK1_LOOP);
}
void insect_flyback_move(edict_t *self)
{
vec3_t endpos;
trace_t trace;
M_ChangeYaw(self);
VectorCopy(self->s.origin, endpos);
endpos[2] -= 48;
gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID,&trace);
if ( ( trace.fraction < 1 || trace.startsolid || trace.allsolid ) &&
self->curAnimID != ANIM_KNOCK1_END &&
self->curAnimID != ANIM_KNOCK1_GO)
{
self->elasticity = 1.1;
self->friction = 0.5;
SetAnim(self, ANIM_KNOCK1_END);
}
}
void insect_death(edict_t *self, G_Message_t *msg)
{
if(self->monsterinfo.aiflags&AI_DONT_THINK)
{
SetAnim(self, ANIM_DEATHFR);
return;
}
self->msgHandler = DeadMsgHandler;
if(self->deadflag == DEAD_DEAD) //Dead but still being hit
return;
gi.RemoveEffects(&self->s, FX_I_EFFECTS);
self->s.effects |= EF_DISABLE_EXTRA_FX;
self->deadflag = DEAD_DEAD;
insect_dropweapon (self, BIT_SPEAR);
insect_dropweapon (self, BIT_STAFF);
// insect_dropweapon (self, BIT_SWORD);
if(self->health <= -80) //gib death
{
gi.sound(self, CHAN_BODY, Sounds[SND_GIB], 1, ATTN_NORM, 0);
self->think = BecomeDebris;
self->nextthink = level.time + 0.1;
return;
}
else
{
insect_random_death_sound(self);
if(self->health<-20)
{
vec3_t vf, yf, dVel;
SetAnim(self, ANIM_KNOCK1_GO);
VectorSet(self->knockbackvel, 0, 0, 0);
VectorCopy(self->velocity, vf);
VectorNormalize(vf);
VectorScale(vf, -1, yf);
self->ideal_yaw = vectoyaw( yf );
self->yaw_speed = 16;
VectorScale(vf, 300, dVel);
dVel[2] = irand(150,250);
VectorCopy(dVel, self->velocity);
return;
}
SetAnim(self, ANIM_DEATHFR);
return;
}
}
/*-------------------------------------------------------------------------
insectdeathsqueal
-------------------------------------------------------------------------*/
void insectdeathsqueal (edict_t *self)
{
// gi.sound(self, CHAN_WEAPON, Sounds[SND_DIE1], 1, ATTN_IDLE, 0);
return;
}
/*-------------------------------------------------------------------------
insectgrowl
-------------------------------------------------------------------------*/
void insectgrowl (edict_t *self)
{
int chance;
chance = irand(0, 10);
if (chance <= 2 )
{
if(irand(0, 1))
{
if(self->mass == MASS_TC_MALE)
gi.sound (self, CHAN_WEAPON, Sounds[SND_GROWLM1], 1, ATTN_IDLE, 0);
else
gi.sound (self, CHAN_WEAPON, Sounds[SND_GROWLF1], 1, ATTN_IDLE, 0);
}
else
{
if(self->mass == MASS_TC_MALE)
gi.sound (self, CHAN_WEAPON, Sounds[SND_GROWLM2], 1, ATTN_IDLE, 0);
else
gi.sound (self, CHAN_WEAPON, Sounds[SND_GROWLF2], 1, ATTN_IDLE, 0);
}
}
}
/*-------------------------------------------------------------------------
insect_melee
-------------------------------------------------------------------------*/
/*
void insect_melee(edict_t *self, G_Message_t *msg)
{
if (!self->enemy)
return;
if (self->enemy->health <= 0)
{
if (!FindTarget(self))
{
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
return;
}
}
if (anglemod(self->s.angles[YAW] - self->ideal_yaw) > self->yaw_speed)
{
M_ChangeYaw(self);
SetAnim(self, ANIM_RUN);
return;
}
if(self->mass == MASS_TC_MALE)
{//male
if(irand(0, 1)&&self->s.fmnodeinfo[MESH__SPEAR].flags & FMNI_NO_DRAW)
SetAnim(self, ANIM_SPEAR);
else// if(self->s.fmnodeinfo[MESH__SWORD].flags & FMNI_NO_DRAW)
SetAnim(self, ANIM_SWORD);
}
else//if no staff, diff spell?
SetAnim(self, ANIM_SPELL);
}
*/
void insect_backpedal(edict_t *self, G_Message_t *msg)
{
if (M_ValidTarget(self, self->enemy))
{
if(self->spawnflags&MSF_FIXED)
SetAnim(self, ANIM_DELAY);
else
SetAnim(self, ANIM_BACK);//not male?
}
else
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
}
void insect_sound(edict_t *self, float channel, float soundnum, float attn)
{
gi.sound (self, (int)(channel), Sounds[(int)(soundnum)], 1, (int)(attn), 0);
}
void insect_melee(edict_t *self, G_Message_t *msg)
{
if (M_ValidTarget(self, self->enemy))
{
if(self->mass == MASS_TC_MALE)
{//male
if(self->enemy->classID == CID_TBEAST && self->enemy->enemy == self)
{
if(M_DistanceToTarget(self, self->enemy) < 250)
{
self->monsterinfo.aiflags |= AI_FLEE;
self->monsterinfo.flee_finished = level.time + 3;
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
return;
}
}
SetAnim(self, ANIM_SWORD);
}
else//too close for female, back away(maybe attack anyway?)
{
if(!irand(0,5)&&!(self->monsterinfo.aiflags&AI_NO_MELEE)||(self->spawnflags&MSF_FIXED))
SetAnim(self, ANIM_SPELL);
else
SetAnim(self, ANIM_BACK);
}
}
else
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
}
void insect_missile(edict_t *self, G_Message_t *msg)
{
if (M_ValidTarget(self, self->enemy))
{
if(self->mass == MASS_TC_MALE)
{//male
if(self->enemy->classID == CID_TBEAST && self->enemy->enemy == self)
{
if(M_DistanceToTarget(self, self->enemy) < 250)
{
if(self->enemy->enemy != self)
{
self->enemy->oldenemy = self->enemy->enemy;
self->enemy->enemy = self;
}
self->monsterinfo.aiflags |= AI_FLEE;
self->monsterinfo.flee_finished = level.time + 3;
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
return;
}
}
SetAnim(self, ANIM_SPEAR);
}
else
{
if(self->spawnflags & MSF_INSECT_ALTERNATE)
SetAnim(self, ANIM_SPELL2);
else
SetAnim(self, ANIM_SPELL);
}
}
else
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
}
extern void SpellCastInsectSpear(edict_t *Caster,vec3_t StartPos,vec3_t AimAngles,int offset);
//extern void SpellCastFlyingFist(edict_t *Caster,vec3_t StartPos,vec3_t AimAngles,vec3_t AimDir,float Value);
void insectStaff(edict_t *self)
{
vec3_t org, forward, right;
AngleVectors(self->s.angles, forward, right, NULL);
VectorCopy(self->s.origin, org);
VectorMA(org, 12, forward, org);
VectorMA(org, 4, right, org);
if(self->spawnflags & MSF_INSECT_YELLOWJACKET)
{
if(self->s.frame == FRAME_SpearB4)
{
if(!skill->value)
SpellCastInsectSpear(self, org, self->s.angles, irand(1, 3));
else if(skill->value == 1)
{
SpellCastInsectSpear(self, org, self->s.angles, 1);
SpellCastInsectSpear(self, org, self->s.angles, 2);
}
else
{
SpellCastInsectSpear(self, org, self->s.angles, 1);
SpellCastInsectSpear(self, org, self->s.angles, 2);
SpellCastInsectSpear(self, org, self->s.angles, 3);
}
gi.sound (self, CHAN_WEAPON, Sounds[SND_SPELLM2], 1, ATTN_NORM, 0);
}
}
else
{
SpellCastInsectSpear(self, org, self->s.angles, false);
gi.sound (self, CHAN_WEAPON, Sounds[SND_SPELLM], 1, ATTN_NORM, 0);
}
}
void imp_fireball (edict_t *self);
void insectSpell(edict_t *self, float whichspell)
{
vec3_t org, forward, right;
if (!(self->spawnflags & MSF_INSECT_CINEMATIC))
ai_charge(self, 0);
AngleVectors(self->s.angles, forward, right, NULL);
switch((int)whichspell)
{
case TC_SPL_GLOW:
if(self->s.fmnodeinfo[MESH__STAFF].flags & FMNI_NO_DRAW)
{//no staff, weaker spell
VectorCopy(self->s.origin, org);
VectorMA(org, 8, forward, org);
VectorMA(org, 2, right, org);
org[2] += 6;
SpellCastInsectStaff(self, org, self->s.angles, forward, 0);
gi.sound (self, CHAN_WEAPON, Sounds[SND_SPELLF], 1, ATTN_NORM, 0);
//SpellCastFlyingFist(self, org, self->s.angles, forward, 0);
}
else
{//staff, bigger spell
VectorCopy(self->s.origin, org);
VectorMA(org, 16, forward, org);
VectorMA(org, 4, right, org);
org[2] += 8;
self->s.effects &= ~(EF_DISABLE_EXTRA_FX | EF_MARCUS_FLAG1);
self->damage_debounce_time = false;
SpellCastGlobeOfOuchiness(self, org, self->s.angles, forward);
gi.sound (self, CHAN_ITEM, Sounds[SND_SPLPWRUPF], 1, ATTN_NORM, 0);
}
break;
case TC_SPL_FIRE:
if(self->s.fmnodeinfo[MESH__STAFF].flags & FMNI_NO_DRAW)
{//no staff
VectorCopy(self->s.origin, org); // We need to initialize org, don't we?
VectorMA(org, 16, forward, org);
org[2] += 12;
SpellCastInsectStaff(self, org, self->s.angles, forward, 0);
gi.sound (self, CHAN_AUTO, Sounds[SND_SPELLF], 1, ATTN_NORM, 0);
return;
}
else
{
VectorCopy(self->s.origin, org);
VectorMA(org, 16, forward, org);
org[2] += 12;
SpellCastInsectStaff(self, org, self->s.angles, forward, 0);
gi.sound (self, CHAN_AUTO, Sounds[SND_SPELLF], 1, ATTN_NORM, 0);
}
break;
case TC_SPL_FIRE2:
if(!(self->s.fmnodeinfo[MESH__STAFF].flags & FMNI_NO_DRAW))
{//no staff
VectorCopy(self->s.origin, org);
VectorMA(org, 16, forward, org);
org[2] += 12;
SpellCastInsectStaff(self, org, self->s.angles, forward, 1);
gi.sound(self, CHAN_AUTO, gi.soundindex("monsters/imp/fireball.wav"), 1, ATTN_NORM, 0);
}
else
{
VectorCopy(self->s.origin, org);
VectorMA(org, 16, forward, org);
org[2] += 12;
SpellCastInsectStaff(self, org, self->s.angles, forward, 0);
gi.sound (self, CHAN_AUTO, Sounds[SND_SPELLF], 1, ATTN_NORM, 0);
}
break;
}
}
void insectReleaseSpell (edict_t *self)
{
gi.RemoveEffects(&self->s, FX_I_EFFECTS);
self->s.effects |= EF_DISABLE_EXTRA_FX | EF_MARCUS_FLAG1;
self->damage_debounce_time = true;
}
/*-------------------------------------------------------------------------
insect_pain
-------------------------------------------------------------------------*/
int Bit_for_MeshNode_tc [16] =
{
BIT_MASTER,
BIT_LLEG,
BIT_HEAD,
BIT_LMANDIBLE,
BIT_RMANDIBLE,
BIT_CROWN,
BIT_L2NDARM,
BIT_SPEAR,
BIT_FEMHAND,
BIT_SWORD,
BIT_STAFF,
BIT_GEM,
BIT_R2NDARM,
BIT_RWINGS,
BIT_LWINGS,
BIT_RLEG
};
qboolean canthrownode_tc (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_tc[BP];
self->s.fmnodeinfo[BP].flags |= FMNI_NO_DRAW;
return true;
}
return false;
}
void insect_chicken (edict_t *self, int coward, int flee, float fleetime)
{
float chance;
chance = flrand(0,10);
if(chance<coward)
{
self->monsterinfo.aiflags |= AI_COWARD;
return;
}
if(chance<flee)
{
self->monsterinfo.aiflags |= AI_FLEE;
self->monsterinfo.flee_finished = level.time + fleetime;
}
}
//THROWS weapon, turns off those nodes, sets that weapon as gone
void insect_dropweapon (edict_t *self, int weapon)
{//NO PART FLY FRAME!
vec3_t handspot, forward, right, up;
// float chance;
VectorClear(handspot);
AngleVectors(self->s.angles,forward,right,up);
// if(self->deadflag == DEAD_DEAD||(self->s.fmnodeinfo[MESH__R_ARM].flags & FMNI_NO_DRAW))
// chance = 0;
// else
// chance = 3;
if((!weapon || weapon&BIT_STAFF) &&
!(self->s.fmnodeinfo[MESH__STAFF].flags & FMNI_NO_DRAW))
{
VectorMA(handspot,8,forward,handspot);
VectorMA(handspot,5,right,handspot);
VectorMA(handspot,12,up,handspot);
ThrowWeapon(self, &handspot, BIT_STAFF, 0, FRAME_partfly);
self->s.fmnodeinfo[MESH__STAFF].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__GEM].flags |= FMNI_NO_DRAW;
// insect_chicken(self,2,4,flrand(3,8));
return;
}
if((!weapon || weapon&BIT_SPEAR)&&
!(self->s.fmnodeinfo[MESH__SPEAR].flags & FMNI_NO_DRAW))
{
VectorMA(handspot,6,forward,handspot);
VectorMA(handspot,4,right,handspot);
ThrowWeapon(self, &handspot, BIT_SPEAR, 0, FRAME_partfly);
self->s.fmnodeinfo[MESH__SPEAR].flags |= FMNI_NO_DRAW;
// insect_chicken(self,2,4,flrand(3,8));
return;
}
if((!weapon || weapon&BIT_SWORD)&&
!(self->s.fmnodeinfo[MESH__MALEHAND].flags & FMNI_NO_DRAW))
// !(self->s.fmnodeinfo[MESH__SWORD].flags & FMNI_NO_DRAW))
{
VectorMA(handspot,6,forward,handspot);
VectorMA(handspot,-6,right,handspot);
VectorMA(handspot,-6,up,handspot);
ThrowWeapon(self, &handspot, BIT_SWORD, 0, FRAME_partfly);
self->s.fmnodeinfo[MESH__MALEHAND].flags |= FMNI_NO_DRAW;
// self->s.fmnodeinfo[MESH__SWORD].flags |= FMNI_NO_DRAW;
// insect_chicken(self,2,4,flrand(3,8));
return;
}
}
void insect_dismember(edict_t *self, int damage, int HitLocation)
{//fixme: throw current weapon
//fixme - make part fly dir the vector from hit loc to sever loc
//remember- turn on caps!
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;
// gi.dprintf("HL: %d",HitLocation);
/* if(self->curAnimID==ANIM_MELEE1||self->curAnimID==ANIM_MELEE1)
{//Hit chest during melee, may have hit arms
if(HitLocation == hl_TorsoFront&&irand(0,10)<4)
{
if(irand(0,10)<7)
HitLocation = hl_ArmLowerRight;
else
HitLocation = hl_ArmLowerLeft;
}
}
if(
(HitLocation == hl_ArmUpperLeft&& self->s.fmnodeinfo[MESH__L_ARM].flags & FMNI_NO_DRAW) ||
(HitLocation == hl_ArmUpperRight&& self->s.fmnodeinfo[MESH__R_ARM].flags & FMNI_NO_DRAW)||
(
(HitLocation == hl_TorsoFront|| HitLocation == hl_TorsoBack) &&
self->s.fmnodeinfo[MESH__R_ARM].flags & FMNI_NO_DRAW &&
self->s.fmnodeinfo[MESH__L_ARM].flags & FMNI_NO_DRAW &&
irand(0,10)<4)
)
HitLocation = hl_Head;//Decap
*/
self->monsterinfo.aiflags &= ~AI_OVERRIDE_GUIDE;
VectorClear(gore_spot);
switch(HitLocation)
{
case hl_Head:
if(self->s.fmnodeinfo[MESH__HEAD].flags & FMNI_NO_DRAW)
break;
if(self->s.fmnodeinfo[MESH__HEAD].flags & FMNI_USE_SKIN)
damage*=1.5;//greater chance to cut off if previously damaged
if(flrand(0,self->health)<damage*0.25)
insect_dropweapon (self, 0);
if(flrand(0,self->health)<damage*0.3&&dismember_ok)
{
canthrownode_tc(self, MESH__HEAD,&throw_nodes);
canthrownode_tc(self, MESH__CROWN,&throw_nodes);
canthrownode_tc(self, MESH__LMANDIBLE,&throw_nodes);
canthrownode_tc(self, MESH__RMANDIBLE,&throw_nodes);
gore_spot[2]+=24;
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, FRAME_partfly);
VectorAdd(self->s.origin, gore_spot, gore_spot);
SprayDebris(self,gore_spot,8,damage);
if(self->health>0)
{
self->health = 1;
T_Damage (self, self, self, vec3_origin, vec3_origin, vec3_origin, 10, 20,0,MOD_DIED);
}
return;
}
else
{
if(irand(0,1)||self->s.fmnodeinfo[MESH__HEAD].flags & FMNI_USE_SKIN)
{
if(self->mass == MASS_TC_MALE)
{
if(irand(0,1)||self->s.fmnodeinfo[MESH__RMANDIBLE].flags & FMNI_USE_SKIN)
{
self->s.fmnodeinfo[MESH__LMANDIBLE].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__LMANDIBLE].skin = self->s.skinnum+1;
}
else
{
self->s.fmnodeinfo[MESH__RMANDIBLE].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__RMANDIBLE].skin = self->s.skinnum+1;
}
}
else
{
self->s.fmnodeinfo[MESH__CROWN].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__CROWN].skin = self->s.skinnum+1;
}
}
else
{
self->s.fmnodeinfo[MESH__HEAD].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__HEAD].skin = self->s.skinnum+1;
}
}
break;
case hl_TorsoFront://split in half?
case hl_TorsoBack://split in half?
if(self->s.fmnodeinfo[MESH_MASTER].flags & FMNI_USE_SKIN)
break;
if(flrand(0,self->health)<damage*0.5)
insect_dropweapon (self, 0);
self->s.fmnodeinfo[MESH_MASTER].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH_MASTER].skin = self->s.skinnum+1;
break;
case hl_ArmUpperLeft:
if(self->mass == MASS_TC_FEMALE)
{//female
if(self->s.fmnodeinfo[MESH_MASTER].flags & FMNI_USE_SKIN)
return;
self->s.fmnodeinfo[MESH_MASTER].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH_MASTER].skin = self->s.skinnum+1;
return;
}
else
{//male - sword arm
if(self->s.fmnodeinfo[MESH__L2NDARM].flags & FMNI_USE_SKIN)
damage*=1.5;//greater chance to cut off if previously damaged
if(flrand(0,self->health)<damage*0.4)
insect_dropweapon (self, BIT_SWORD);
self->s.fmnodeinfo[MESH__L2NDARM].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__L2NDARM].skin = self->s.skinnum+1;
}
break;
case hl_ArmLowerLeft://left arm
if(self->mass == MASS_TC_FEMALE)
{//female
if(self->s.fmnodeinfo[MESH_MASTER].flags & FMNI_USE_SKIN)
return;
self->s.fmnodeinfo[MESH_MASTER].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH_MASTER].skin = self->s.skinnum+1;
return;
}
else
{//male - left spear arm
if(self->s.fmnodeinfo[MESH__L2NDARM].flags & FMNI_NO_DRAW)
break;
if(self->s.fmnodeinfo[MESH__L2NDARM].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_tc(self, MESH__L2NDARM, &throw_nodes))
{
AngleVectors(self->s.angles,NULL,right,NULL);
gore_spot[2]+=self->maxs[2]*0.3;
VectorMA(gore_spot,-10,right,gore_spot);
// insect_chicken(self,6,8,flrand(7,15));
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, FRAME_partfly);
if(self->s.fmnodeinfo[MESH__R2NDARM].flags & FMNI_NO_DRAW&&
!(self->s.fmnodeinfo[MESH__SPEAR].flags & FMNI_NO_DRAW))
insect_dropweapon (self, BIT_SPEAR);
}
}
else
{
if(flrand(0,self->health)<damage*0.4)
insect_dropweapon (self, BIT_SPEAR);
self->s.fmnodeinfo[MESH__L2NDARM].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__L2NDARM].skin = self->s.skinnum+1;
}
}
break;
case hl_ArmUpperRight:
if(self->mass == MASS_TC_FEMALE)
{//female
if(self->s.fmnodeinfo[MESH_MASTER].flags & FMNI_USE_SKIN)
damage*=1.5;//greater chance to cut off if previously damaged
else
{
self->s.fmnodeinfo[MESH_MASTER].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH_MASTER].skin = self->s.skinnum+1;
}
if(flrand(0,self->health)<damage*0.4)
insect_dropweapon (self, BIT_STAFF);
}
else
{
if(self->s.fmnodeinfo[MESH_MASTER].flags & FMNI_USE_SKIN)
return;
self->s.fmnodeinfo[MESH_MASTER].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH_MASTER].skin = self->s.skinnum+1;
return;
}
//male - right upper arm- nothing in it
break;
case hl_ArmLowerRight://right arm
if(self->mass == MASS_TC_FEMALE)
{//female
if(self->s.fmnodeinfo[MESH_MASTER].flags & FMNI_USE_SKIN)
damage*=1.5;//greater chance to cut off if previously damaged
else
{
self->s.fmnodeinfo[MESH_MASTER].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH_MASTER].skin = self->s.skinnum+1;
}
if(flrand(0,self->health)<damage*0.4)
insect_dropweapon (self, BIT_STAFF);
}
else
{//male - right spear arm
if(self->s.fmnodeinfo[MESH__R2NDARM].flags & FMNI_NO_DRAW)
break;
if(self->s.fmnodeinfo[MESH__R2NDARM].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_tc(self, MESH__R2NDARM, &throw_nodes))
{
AngleVectors(self->s.angles,NULL,right,NULL);
gore_spot[2]+=self->maxs[2]*0.3;
VectorMA(gore_spot,-10,right,gore_spot);
// insect_chicken(self,6,8,flrand(7,15));
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, FRAME_partfly);
if(self->s.fmnodeinfo[MESH__L2NDARM].flags & FMNI_NO_DRAW&&
!(self->s.fmnodeinfo[MESH__SPEAR].flags & FMNI_NO_DRAW))
insect_dropweapon (self, BIT_SPEAR);
}
}
else
{
if(flrand(0,self->health)<damage*0.4)
insect_dropweapon (self, BIT_SPEAR);
self->s.fmnodeinfo[MESH__R2NDARM].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__R2NDARM].skin = self->s.skinnum+1;
}
}
break;
case hl_LegUpperLeft:
case hl_LegLowerLeft://left leg
if(self->health>0)
{//still alive
if(self->s.fmnodeinfo[MESH__LLEG].flags & FMNI_USE_SKIN)
break;
self->s.fmnodeinfo[MESH__LLEG].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__LLEG].skin = self->s.skinnum+1;
break;
}
else
{
if(self->s.fmnodeinfo[MESH__LLEG].flags & FMNI_NO_DRAW)
break;
if(canthrownode_tc(self, MESH__LLEG, &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, FRAME_partfly);
}
}
break;
case hl_LegUpperRight:
case hl_LegLowerRight://right leg
if(self->health>0)
{//still alive
if(self->s.fmnodeinfo[MESH__RLEG].flags & FMNI_USE_SKIN)
break;
self->s.fmnodeinfo[MESH__RLEG].flags |= FMNI_USE_SKIN;
self->s.fmnodeinfo[MESH__RLEG].skin = self->s.skinnum+1;
break;
}
else
{
if(self->s.fmnodeinfo[MESH__RLEG].flags & FMNI_NO_DRAW)
break;
if(canthrownode_tc(self, MESH__RLEG, &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, FRAME_partfly);
}
}
break;
default:
if(flrand(0,self->health)<damage*0.25)
insect_dropweapon (self, 0);
break;
}
if(throw_nodes)
self->pain_debounce_time = 0;
/*if(self->mass == MASS_TC_FEMALE)
{
if(self->s.fmnodeinfo[MESH__STAFF].flags & FMNI_NO_DRAW)
self->monsterinfo.aiflags |= AI_NO_MISSILE;
}
else*/
if(self->mass == MASS_TC_MALE)
{
// if(self->s.fmnodeinfo[MESH__SWORD].flags & FMNI_NO_DRAW)
if(self->s.fmnodeinfo[MESH__MALEHAND].flags & FMNI_NO_DRAW)
self->monsterinfo.aiflags |= AI_NO_MELEE;
if(self->s.fmnodeinfo[MESH__SPEAR].flags & FMNI_NO_DRAW)
self->monsterinfo.aiflags |= AI_NO_MISSILE;
}
}
void insect_random_pain_sound (edict_t *self)
{
if(self->mass==MASS_TC_MALE)//irand(0,1))
gi.sound(self, CHAN_VOICE, Sounds[SND_PAINM], 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, Sounds[SND_PAINF], 1, ATTN_NORM, 0);
}
void insect_pain(edict_t *self, G_Message_t *msg)
{
int temp, damage;
int force_damage;
ParseMsgParms(msg, "eeiii", &temp, &temp, &force_damage, &damage, &temp);
if(!force_damage&&flrand(0,self->health)>damage)
return;
gi.RemoveEffects(&self->s, FX_I_EFFECTS);
self->s.effects |= EF_DISABLE_EXTRA_FX;
//sound
insect_random_pain_sound(self);
//remove spell effects
self->monsterinfo.aiflags &= ~AI_OVERRIDE_GUIDE;
if (force_damage || self->pain_debounce_time < level.time)
{
self->pain_debounce_time = level.time + 1;
if(irand(0,1))
SetAnim(self, ANIM_PAINA);
else
SetAnim(self, ANIM_PAINC);
}
}
/*-------------------------------------------------------------------------
insect_pause
-------------------------------------------------------------------------*/
void insect_pause (edict_t *self)
{
vec3_t v;
float len;
if(self->spawnflags & MSF_INSECT_BEAST_FODDER)
{
edict_t *found = NULL;
if(found = G_Find(found, FOFS(classname), "monster_trial_beast"))
{
if(found->health > 0)
{
self->enemy = found;
found->oldenemy = found->enemy;
found->enemy = self;
self->spawnflags &= ~MSF_INSECT_BEAST_FODDER;
self->monsterinfo.aiflags &= ~AI_NO_MISSILE;
self->missile_range = 3000;
self->min_missile_range = 250;
//self->monsterinfo.aiflags |= AI_NO_MELEE;
//self->melee_range = -250;
self->monsterinfo.aiflags &= ~AI_NO_MELEE;
self->melee_range = 48;
self->bypass_missile_chance = 0;
self->s.fmnodeinfo[MESH__MALEHAND].flags &= ~FMNI_NO_DRAW;
}
}
}
if(self->monsterinfo.aiflags&AI_OVERRIDE_GUIDE)
{
if(self->groundentity)
self->monsterinfo.aiflags &= ~AI_OVERRIDE_GUIDE;
else
return;
}
if(self->spawnflags & MSF_FIXED && self->curAnimID == ANIM_DELAY && self->enemy)
{
self->monsterinfo.searchType = SEARCH_COMMON;
MG_FaceGoal(self, true);
}
self->mood_think(self);
if (self->ai_mood == AI_MOOD_NORMAL)
{
FindTarget(self);
if(self->enemy)
{
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
len = VectorLength (v);
if ((len > 80) || (self->monsterinfo.aiflags & AI_FLEE)) // Far enough to run after
{
QPostMessage(self, MSG_RUN,PRI_DIRECTIVE, NULL);
}
else // Close enough to Attack
{
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
}
}
}
else
{
//gi.dprintf("plageElf_pause: Using High Level Functionality\n");
if(self->enemy)
{
if(self->enemy->classID == CID_TBEAST)
{
if(visible(self, self->enemy))
{
if(M_DistanceToTarget(self, self->enemy) > 250)
{
if(infront(self, self->enemy))
{
self->ai_mood = AI_MOOD_ATTACK;
self->monsterinfo.flee_finished = -1;
self->ai_mood_flags |= AI_MOOD_FLAG_MISSILE;
}
else
{
self->ai_mood = AI_MOOD_PURSUE;
self->ai_mood_flags &= ~AI_MOOD_FLAG_DUMB_FLEE;
self->monsterinfo.aiflags &= ~AI_FLEE;
self->monsterinfo.flee_finished = -1;
}
}
}
}
}
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);
break;
case AI_MOOD_PURSUE:
case AI_MOOD_NAVIGATE:
case AI_MOOD_FLEE:
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
break;
case AI_MOOD_STAND:
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
break;
case AI_MOOD_DELAY:
SetAnim(self, ANIM_DELAY);
break;
case AI_MOOD_WANDER:
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
break;
case AI_MOOD_WALK:
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
break;
case AI_MOOD_BACKUP:
QPostMessage(self, MSG_FALLBACK, PRI_DIRECTIVE, NULL);
break;
case AI_MOOD_JUMP:
if(self->spawnflags&MSF_FIXED)
SetAnim(self, ANIM_DELAY);
else
SetAnim(self, ANIM_FJUMP);
break;
default :
#ifdef _DEVEL
gi.dprintf("insect: Unusable mood %d!\n", self->ai_mood);
#endif
break;
}
}
}
void insect_check_mood (edict_t *self, G_Message_t *msg)
{
ParseMsgParms(msg, "i", &self->ai_mood);
insect_pause(self);
}
/*-------------------------------------------------------------------------
insect_run
-------------------------------------------------------------------------*/
void insect_run(edict_t *self, G_Message_t *msg)
{
//turn on lerpflags
if (M_ValidTarget(self, self->enemy))
{
if(self->enemy->classID == CID_TBEAST && self->enemy->enemy != self)
{
if(M_DistanceToTarget(self, self->enemy) < 250)
{
self->monsterinfo.aiflags |= AI_FLEE;
self->monsterinfo.flee_finished = level.time + 3;
}
}
if(self->spawnflags&MSF_FIXED)
SetAnim(self, ANIM_DELAY);
else
SetAnim(self, ANIM_RUN);
}
else
{
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
}
}
/*----------------------------------------------------------------------
insect runorder - order the insect to choose an run animation
-----------------------------------------------------------------------*/
void insect_runorder(edict_t *self)
{
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
}
/*-------------------------------------------------------------------------
insectsqueal
-------------------------------------------------------------------------*/
void insectsqueal (edict_t *self)
{
/* 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);*/
}
/*-------------------------------------------------------------------------
insect_stand
-------------------------------------------------------------------------*/
void insect_stand(edict_t *self, G_Message_t *msg)
{
if (self->ai_mood == AI_MOOD_DELAY)
SetAnim(self, ANIM_DELAY);
else
SetAnim(self, ANIM_IDLE);
return;
}
/*-------------------------------------------------------------------------
insect_walk
-------------------------------------------------------------------------*/
void insect_walk(edict_t *self, G_Message_t *msg)
{
if(self->spawnflags&MSF_FIXED)
SetAnim(self, ANIM_DELAY);
else
SetAnim(self, ANIM_WALK);
}
/*void Monster_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t pos1, pos2;
float zdiff;
if ((other->svflags & SVF_MONSTER) || (!stricmp(other->classname, "player")))
{
VectorCopy(other->s.origin, pos1);
pos1[2] += other->mins[2];
VectorCopy(self->s.origin, pos2);
pos2[2] += self->maxs[2];
zdiff = pos1[2] - pos2[2];
if (zdiff >= 0 ) // On top
{
other->velocity[0] = flrand(75.0, 100.0);
other->velocity[1] = flrand(75.0, 100.0);
other->velocity[2] += 101;
if (irand(0,1))
other->velocity[0] *= -1;
if (irand(0,1))
other->velocity[1] *= -1;
self->velocity[0] = -(other->velocity[0]);
self->velocity[1] = -(other->velocity[1]);
self->velocity[2] += 100;
other->groundentity = NULL;
self->groundentity = NULL;
}
}
}*/
void insect_go_inair(edict_t *self)
{
SetAnim(self, ANIM_INAIR);
}
void insect_go_finair(edict_t *self)
{
SetAnim(self, ANIM_FINAIR);
}
void insectCheckLoop (edict_t *self, float frame)
{//see if should fire again
vec3_t v;
float len, melee_range, min_seperation, missile_range;
if(!self->enemy)
return;
if(self->enemy->health <= 0)
return;
if(!visible(self, self->enemy))
return;
if(!infront(self, self->enemy))
return;
ai_charge2(self, 0);
if(irand(0,100)<self->bypass_missile_chance)
return;
if(self->monsterinfo.attack_finished > level.time)
return;
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
len = VectorLength (v);
melee_range = 64;
missile_range = 384;
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 + missile_range && irand(0,10)<3)
return;
}
self->monsterinfo.currframeindex = (int)frame;
}
void insect_jump (edict_t *self, G_Message_t *msg)
{
if(self->spawnflags&MSF_FIXED)
{
SetAnim(self, ANIM_DELAY);
return;
}
SetAnim(self, ANIM_FORCED_JUMP);
self->monsterinfo.aiflags |= AI_OVERRIDE_GUIDE;
}
/*-------------------------------------------------------------------------
insectStaticsInit
-------------------------------------------------------------------------*/
void TcheckrikStaticsInit()
{
classStatics[CID_TCHECKRIK].msgReceivers[MSG_STAND] = insect_stand;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_WALK] = insect_walk;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_RUN] = insect_run;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_MELEE] = insect_melee;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_MISSILE] = insect_missile;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_PAIN] = insect_pain;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_DEATH] = insect_death;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_FALLBACK] = insect_backpedal;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_DISMEMBER] = MG_parse_dismember_msg;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_JUMP] = insect_jump;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_DEATH_PAIN] = insect_dead_pain;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_CHECK_MOOD] = insect_check_mood;
// Cinematics
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_ACTION1] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_ACTION2] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_ACTION3] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_ACTION4] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_ATTACK1] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_ATTACK2] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_ATTACK3] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_BACKPEDAL1] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_DEATH1] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_IDLE1] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_IDLE2] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_IDLE3] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_PAIN1] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_RUN1] = insect_c_anims;
classStatics[CID_TCHECKRIK].msgReceivers[MSG_C_WALK1] = insect_c_anims;
resInfo.numAnims = NUM_ANIMS;
resInfo.animations = animations;
//note that the name is different in the path
resInfo.modelIndex = gi.modelindex("models/monsters/tcheckrik/male/tris.fm");
Sounds[SND_PAINM]=gi.soundindex("monsters/insect/painm.wav");
Sounds[SND_PAINF]=gi.soundindex("monsters/insect/painf.wav");
Sounds[SND_DIEM]=gi.soundindex("monsters/insect/deathm.wav");
Sounds[SND_DIEF]=gi.soundindex("monsters/insect/deathf.wav");
Sounds[SND_GIB]=gi.soundindex("monsters/insect/gib.wav");
Sounds[SND_SWIPE]=gi.soundindex("monsters/insect/swipe.wav");
Sounds[SND_SWIPEHITF]=gi.soundindex("monsters/plagueelf/hookhit.wav");
Sounds[SND_SWIPEHITW]=gi.soundindex("monsters/insect/swipehitw.wav");
Sounds[SND_SPELLM]=gi.soundindex("monsters/insect/spellm.wav");
Sounds[SND_SPELLM2]=gi.soundindex("monsters/insect/spellm2.wav");
Sounds[SND_SPLPWRUPF]=gi.soundindex("monsters/insect/splpwrupf.wav");
Sounds[SND_SPELLF]=gi.soundindex("monsters/insect/spellf.wav");
Sounds[SND_GROWLM1]=gi.soundindex("monsters/insect/growlm1.wav");
Sounds[SND_GROWLM2] = gi.soundindex ("monsters/insect/growlm2.wav");
Sounds[SND_GROWLF1]=gi.soundindex("monsters/insect/growlf1.wav");
Sounds[SND_GROWLF2] = gi.soundindex ("monsters/insect/growlf2.wav");
Sounds[SND_THUD] = gi.soundindex ("monsters/insect/thud.wav");
resInfo.numSounds = NUM_SOUNDS;
resInfo.sounds = Sounds;
classStatics[CID_TCHECKRIK].resInfo = &resInfo;
}
/*QUAKED monster_tcheckrik_male (1 .5 0) (-16 -16 -32) (16 16 32) AMBUSH ASLEEP WALKING CINEMATIC BEAST_FODDER YELLOWJACKET 64 FIXED WANDER LEAD STALK COWARD EXTRA1 EXTRA2 EXTRA3 EXTRA4
The insect
AMBUSH - Will not be woken up by other monsters or shots from player
ASLEEP - will not appear until triggered
WALKING - use WANDER instead
BEAST_FODDER - Will run away if a Trial Beast is present
YELLOWJACKET - uses black and yellow skin, shoots a spread of three yellow stinger projectiles
WANDER - Monster will wander around aimlessly (but follows buoys)
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)
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 = -10self->classID == CID_MOTHER0 - 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 = 32
melee_range = 30
missile_range = 512
min_missile_range = 48
bypass_missile_chance = 30
jump_chance = 40
wakeup_distance = 1024
*/
/*-------------------------------------------------------------------------
SP_monster_insect
-------------------------------------------------------------------------*/
void SP_monster_tcheckrik_male (edict_t *self)
{
if(irand(0, 1))//made random again
self->spawnflags |= MSF_INSECT_YELLOWJACKET;
if(self->spawnflags & MSF_WALKING)
{
self->spawnflags |= MSF_WANDER;
self->spawnflags &= ~MSF_WALKING;
}
if (!monster_start(self))
return; // Failed initialization
self->msgHandler = DefaultMsgHandler;
self->classID = CID_TCHECKRIK;
self->think = walkmonster_start_go;
self->monsterinfo.dismember = insect_dismember;
if (!self->health)
{
self->health = TC_HEALTH_MALE;
}
self->max_health = self->health = MonsterHealth(self->health);
self->mass = MASS_TC_MALE;
self->yaw_speed = 20;
self->movetype = PHYSICSTYPE_STEP;
VectorClear(self->knockbackvel);
self->solid=SOLID_BBOX;
VectorCopy(STDMinsForClass[self->classID], self->mins);
VectorCopy(STDMaxsForClass[self->classID], self->maxs);
// VectorSet (self->mins, -16, -16, -32);
// VectorSet (self->maxs, 16, 16, 32);
self->viewheight = self->maxs[2]*0.8;
self->touch = M_Touch;
self->s.modelindex = gi.modelindex("models/monsters/tcheckrik/male/tris.fm");
// All skins are even numbers, pain skins are skin+1.
if(self->spawnflags & MSF_INSECT_YELLOWJACKET)
self->s.skinnum = 2;
else
self->s.skinnum = 0;
if (!self->s.scale)
{
self->s.scale = self->monsterinfo.scale = MODEL_SCALE;
}
self->materialtype = MAT_INSECT;
//turn on/off the weapons that aren't used
self->s.fmnodeinfo[MESH__CROWN].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__RWINGS].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__LWINGS].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__STAFF].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__GEM].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__FEMHAND].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__CROWN].flags |= FMNI_NO_DRAW;
if(self->spawnflags & MSF_INSECT_BEAST_FODDER)
self->s.fmnodeinfo[MESH__MALEHAND].flags |= FMNI_NO_DRAW;//?
//FIXME (somewhere: otherenemy should be more than just *one* kind
self->monsterinfo.otherenemyname = "monster_rat";
//override female's settings in m_stats.c
if(!self->bypass_missile_chance)
self->bypass_missile_chance = 30;
if(!self->melee_range)
self->melee_range = 48;
//set up my mood function
MG_InitMoods(self);
if(!irand(0,2))
self->ai_mood_flags |= AI_MOOD_FLAG_PREDICT;
if(self->spawnflags & MSF_WANDER)
{
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
}
else if(self->spawnflags & MSF_INSECT_CINEMATIC)
{
self->monsterinfo.c_mode = 1;
QPostMessage(self, MSG_C_IDLE1, PRI_DIRECTIVE, "iiige",0,0,0,NULL,NULL);
}
else
{
QPostMessage(self,MSG_STAND,PRI_DIRECTIVE, NULL);
}
gi.CreateEffect(&self->s,
FX_I_EFFECTS,
CEF_OWNERS_ORIGIN,
vec3_origin,
"bv",
FX_I_RREFS,
vec3_origin);
}
/*QUAKED monster_tcheckrik_female (1 .5 0) (-16 -16 -32) (16 16 32) AMBUSH ASLEEP WALKING CINEMATIC 16 ALTERNATE 64 FIXED WANDER MELEE_LEAD STALK COWARD EXTRA1 EXTRA2 EXTRA3 EXTRA4
The insect
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)
ALTERNATE - fires alternate projectile, more damage, not multiple though
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)
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 = 32
melee_range = -72
missile_range = 512
min_missile_range = 48
bypass_missile_chance = 0
jump_chance = 40
wakeup_distance = 1024
NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1
*/
/*-------------------------------------------------------------------------
SP_monster_insect
-------------------------------------------------------------------------*/
void SP_monster_tcheckrik_female (edict_t *self)
{
if(self->spawnflags & MSF_WALKING)
{
self->spawnflags |= MSF_WANDER;
self->spawnflags &= ~MSF_WALKING;
}
if (!monster_start(self))
return; // Failed initialization
self->msgHandler = DefaultMsgHandler;
self->classID = CID_TCHECKRIK;
self->think = walkmonster_start_go;
self->monsterinfo.dismember = insect_dismember;
if (!self->health)
{
self->health = TC_HEALTH_FEMALE;
}
self->max_health = self->health = MonsterHealth(self->health);
self->mass = MASS_TC_FEMALE;
self->yaw_speed = 20;
self->movetype = PHYSICSTYPE_STEP;
VectorClear(self->knockbackvel);
self->solid=SOLID_BBOX;
VectorCopy(STDMinsForClass[self->classID], self->mins);
VectorCopy(STDMaxsForClass[self->classID], self->maxs);
self->viewheight = self->maxs[2]*0.4;
self->touch = M_Touch;
self->s.modelindex = gi.modelindex("models/monsters/tcheckrik/female/tris.fm");
// All skins are even numbers, pain skins are skin+1.
self->s.skinnum = 0;
// self->s.skinnum = 2;
if (!self->s.scale)
{
self->s.scale = self->monsterinfo.scale = MODEL_SCALE;
}
self->materialtype = MAT_INSECT;
//turn on/off the weapons that aren't used
self->s.fmnodeinfo[MESH__LMANDIBLE].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__RMANDIBLE].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__L2NDARM].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__R2NDARM].flags |= FMNI_NO_DRAW;
self->s.fmnodeinfo[MESH__MALEHAND].flags |= FMNI_NO_DRAW;//?
// self->s.fmnodeinfo[MESH__SWORD].flags |= FMNI_NO_DRAW;//?
self->s.fmnodeinfo[MESH__SPEAR].flags |= FMNI_NO_DRAW;//?
// self->s.fmnodeinfo[MESH__GEM].flags |= FMNI_NO_DRAW;
//FIXME (somewhere: otherenemy should be more than just *one* kind
self->monsterinfo.otherenemyname = "monster_rat";
//set up my mood function
MG_InitMoods(self);
if(!irand(0,2))
self->ai_mood_flags |= AI_MOOD_FLAG_PREDICT;
if(self->spawnflags & MSF_WANDER)
{
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
}
else
{
QPostMessage(self,MSG_STAND,PRI_DIRECTIVE, NULL);
}
self->monsterinfo.aiflags |= AI_NO_MELEE;
gi.CreateEffect(&self->s,
FX_I_EFFECTS,
CEF_OWNERS_ORIGIN,
vec3_origin,
"bv",
FX_I_RREFS,
vec3_origin);
self->svflags |= SVF_WAIT_NOTSOLID;
if(self->spawnflags & MSF_INSECT_ALTERNATE)
{
self->s.color.a = 255;
self->s.color.r = 250;
self->s.color.g = 150;
self->s.color.b = 100;
}
}