2256 lines
No EOL
66 KiB
C
2256 lines
No EOL
66 KiB
C
//==============================================================================
|
|
//
|
|
// m_plagueElf.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_plagueElf.h"
|
|
#include "m_plagueElf_anim.h"
|
|
#include "g_HitLocation.h"
|
|
#include "g_misc.h"
|
|
#include "angles.h"
|
|
#include "c_ai.h"
|
|
#include "m_stats.h"
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
plagueElf Base Info
|
|
-----------------------------------------------------------------------*/
|
|
static animmove_t *animations[ NUM_ANIMS] =
|
|
{
|
|
&plagueElf_move_stand1,
|
|
&plagueElf_move_walk1,
|
|
&plagueElf_move_walk2,
|
|
&plagueElf_move_run1,
|
|
&plagueElf_move_runatk1,
|
|
&plagueElf_move_fjump,
|
|
&plagueElf_move_inair,
|
|
&plagueElf_move_land,
|
|
&plagueElf_move_melee1,
|
|
&plagueElf_move_melee2,
|
|
&plagueElf_move_death1,
|
|
&plagueElf_move_death2,
|
|
&plagueElf_move_death3,
|
|
&plagueElf_move_death4,
|
|
&plagueElf_fist1,
|
|
&plagueElf_lean1,
|
|
&plagueElf_shake1,
|
|
&plagueElf_move_pain1,
|
|
&plagueElf_delay,
|
|
&plagueElf_move_missile,
|
|
//
|
|
&plagueElf_move_kdeath_go,
|
|
&plagueElf_move_kdeath_loop,
|
|
&plagueElf_move_kdeath_end,
|
|
|
|
&plagueElf_crazy_A,
|
|
&plagueElf_crazy_B,
|
|
&plagueElf_cursing,
|
|
&plagueElf_point,
|
|
&plagueElf_scared,
|
|
|
|
// Cinematics
|
|
&plagueElf_move_c_idle1,
|
|
&plagueElf_move_c_idle2,
|
|
&plagueElf_move_c_idle3,
|
|
&plagueElf_move_c_walk,
|
|
&plagueElf_move_c_walk2,
|
|
&plagueElf_move_c_run,
|
|
&plagueElf_move_c_attack1,
|
|
&plagueElf_move_c_attack2,
|
|
&plagueElf_move_c_attack3,
|
|
&plagueElf_move_c_attack4,
|
|
&plagueElf_move_c_pain1,
|
|
&plagueElf_move_c_death1,
|
|
&plagueElf_move_c_death2,
|
|
&plagueElf_move_c_death3,
|
|
&plagueElf_move_c_death4,
|
|
&plagueElf_move_run1,
|
|
|
|
// &plagueElf_move_stand1,
|
|
|
|
};
|
|
|
|
static int sounds[NUM_SOUNDS];
|
|
static ClassResourceInfo_t resInfo;
|
|
|
|
int Bit_for_MeshNode_pe [12] =
|
|
{
|
|
BIT_BASE,
|
|
BIT_HANDLE,
|
|
BIT_HOE,
|
|
BIT_GAFF,
|
|
BIT_HAMMER,
|
|
BIT_BODY,
|
|
BIT_L_LEG,
|
|
BIT_R_LEG,
|
|
BIT_R_ARM,
|
|
BIT_L_ARM,
|
|
BIT_HEAD
|
|
};
|
|
|
|
float pelf_VoiceTimes[] =
|
|
{
|
|
0.0, //FIRST_SIGHT_GROUP
|
|
1.0, //VOICE_SIGHT_AFTER_HIM1
|
|
0.6, //VOICE_SIGHT_AFTER_HIM2
|
|
1.3, //VOICE_SIGHT_CUT_HIM2
|
|
1.0, //VOICE_SIGHT_CUT_HIM1
|
|
1.2, //VOICE_SIGHT_EAT_FLESH1
|
|
1.2, //VOICE_SIGHT_EAT_FLESH2
|
|
0.9, //VOICE_SIGHT_GET_HIM1
|
|
0.9, //VOICE_SIGHT_GET_HIM2
|
|
0.9, //VOICE_SIGHT_GET_HIM3
|
|
1.0, //VOICE_SIGHT_KILL_HIM1
|
|
0.9, //VOICE_SIGHT_KILL_HIM2
|
|
1.3, //VOICE_SIGHT_KILL_HIM3
|
|
1.2, //VOICE_SIGHT_OVER_THERE
|
|
1.2, //VOICE_SIGHT_THERES_ONE
|
|
0.7, //VOICE_SUPPORT_FOLLOW_ME
|
|
1.5, //VOICE_SUPPORT_CURE
|
|
1.6, //VOICE_SUPPORT_LIVER
|
|
2.0, //VOICE_SUPPORT_SLASH
|
|
1.1, //VOICE_SUPPORT_SURROUND_HIM
|
|
1.8, //VOICE_SUPPORT_UNAFFECTED1
|
|
2.0, //VOICE_SUPPORT_UNAFFECTED2
|
|
1.3, //VOICE_SUPPORT_YEAH_GET_HIM1
|
|
1.0, //VOICE_SUPPORT_YEAH_GET_HIM2
|
|
0.0, //VOICE_FIRST_ALONE
|
|
1.1, //VOICE_MISC_DIE
|
|
1.3, //VOICE_MISC_FLESH
|
|
1.1, //VOICE_SUPPORT_GONNA_DIE1
|
|
1.5, //VOICE_SUPPORT_GONNA_DIE2
|
|
1.5, //VOICE_SUPPORT_GONNA_DIE3
|
|
1.2, //VOICE_SUPPORT_GONNA_DIE4
|
|
2.0, //VOICE_MISC_MUST_KILL
|
|
1.1, //VOICE_SUPPORT_MUST_DIE
|
|
1.1, //VOICE_SUPPORT_YES
|
|
0.0, //VOICE_LAST_GROUP
|
|
1.6, //VOICE_MISC_GET_AWAY1
|
|
0.9, //VOICE_MISC_GET_AWAY2
|
|
1.2, //VOICE_MISC_GO_AWAY
|
|
0.6, //VOICE_MISC_HELP_ME1
|
|
2.3, //VOICE_MISC_HELP_ME2
|
|
2.2, //VOICE_MISC_HELP_ME3
|
|
1.5, //VOICE_MISC_LEAVE_ME1
|
|
1.0, //VOICE_MISC_LEAVE_ME2
|
|
0.6, //VOICE_MISC_NO
|
|
0.9, //VOICE_MISC_TRAPPED
|
|
0.8, //VOICE_MISC_COME_BACK1
|
|
1.0, //VOICE_MISC_COME_BACK2
|
|
0.9, //VOICE_MISC_DONT_HURT
|
|
};
|
|
|
|
|
|
void dying_elf_sounds (edict_t *self, int type);
|
|
/*----------------------------------------------------------------------
|
|
Cinematic Functions for the monster
|
|
-----------------------------------------------------------------------*/
|
|
|
|
void plagueElf_c_gib(edict_t *self, G_Message_t *msg)
|
|
{
|
|
gi.sound(self, CHAN_BODY, sounds[SND_GIB], 1, ATTN_NORM, 0);
|
|
self->think = BecomeDebris;
|
|
self->nextthink = level.time + 0.1;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_c_anims
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElf_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_ATTACK1:
|
|
self->monsterinfo.c_anim_flag |= C_ANIM_MOVE;
|
|
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_ATTACK4:
|
|
self->monsterinfo.c_anim_flag |= C_ANIM_REPEAT;
|
|
curr_anim = ANIM_C_ATTACK4;
|
|
break;
|
|
case MSG_C_DEATH1:
|
|
self->monsterinfo.c_anim_flag |= C_ANIM_DONE;
|
|
curr_anim = ANIM_C_DEATH1;
|
|
break;
|
|
case MSG_C_DEATH2:
|
|
self->monsterinfo.c_anim_flag |= C_ANIM_DONE;
|
|
curr_anim = ANIM_C_DEATH2;
|
|
break;
|
|
case MSG_C_DEATH3:
|
|
self->monsterinfo.c_anim_flag |= C_ANIM_DONE;
|
|
curr_anim = ANIM_C_DEATH3;
|
|
break;
|
|
case MSG_C_DEATH4:
|
|
self->monsterinfo.c_anim_flag |= C_ANIM_DONE;
|
|
curr_anim = ANIM_C_DEATH4;
|
|
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_THINKAGAIN: // Think for yourself, elf.
|
|
self->monsterinfo.c_mode = 0;
|
|
self->enemy = self->monsterinfo.c_ent;
|
|
FoundTarget(self, true);
|
|
// self->takedamage = DAMAGE_YES;
|
|
curr_anim = ANIM_C_THINKAGAIN;
|
|
break;
|
|
case MSG_C_WALK1:
|
|
self->monsterinfo.c_anim_flag |= C_ANIM_MOVE;
|
|
curr_anim = ANIM_C_WALK1;
|
|
break;
|
|
case MSG_C_WALK2:
|
|
self->monsterinfo.c_anim_flag |= C_ANIM_MOVE;
|
|
curr_anim = ANIM_C_WALK2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
SetAnim(self, curr_anim);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueelf_death_loop
|
|
-------------------------------------------------------------------------*/
|
|
|
|
void plagueelf_death_loop ( edict_t *self )
|
|
{
|
|
SetAnim(self, ANIM_KDEATH_LOOP);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueelf_check_land
|
|
-------------------------------------------------------------------------*/
|
|
|
|
void plagueelf_check_land ( edict_t *self )
|
|
{
|
|
vec3_t endpos;
|
|
trace_t trace;
|
|
|
|
if(self->s.frame == FRAME_death7)
|
|
MG_NoBlocking(self);
|
|
|
|
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.allsolid || trace.startsolid ) && self->curAnimID != ANIM_KDEATH_END && self->curAnimID != ANIM_KDEATH_GO)
|
|
{
|
|
self->elasticity = 1.25;
|
|
self->friction = 0.5;
|
|
SetAnim(self, ANIM_KDEATH_END);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_strike
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElf_strike (edict_t *self)
|
|
{
|
|
trace_t trace;
|
|
edict_t *victim;
|
|
vec3_t soff, eoff, mins, maxs, bloodDir, direction;
|
|
int damage;
|
|
|
|
//FIXME: Account for weapon being knocked out of hand?
|
|
if(self->s.fmnodeinfo[MESH__HANDLE].flags & FMNI_NO_DRAW)
|
|
return;
|
|
|
|
switch ( self->curAnimID )
|
|
{
|
|
case ANIM_MELEE1:
|
|
VectorSet(soff, -8, 0, 32);
|
|
VectorSet(eoff, 36, 8, 16);
|
|
break;
|
|
|
|
case ANIM_MELEE2:
|
|
VectorSet(soff, -8, -16, 32);
|
|
VectorSet(eoff, 36, 0, 0);
|
|
break;
|
|
|
|
case ANIM_RUNATK1:
|
|
VectorSet(soff, 2, -4, 24);
|
|
VectorSet(eoff, 50, 4, 4);
|
|
break;
|
|
}
|
|
|
|
VectorSet(mins, -4, -4, -4);
|
|
VectorSet(maxs, 4, 4, 4);
|
|
|
|
VectorSubtract(soff, eoff, bloodDir);
|
|
VectorNormalize(bloodDir);
|
|
|
|
victim = M_CheckMeleeLineHit(self, soff, eoff, mins, maxs, &trace, direction);
|
|
|
|
if (victim)
|
|
{
|
|
if (victim == self)
|
|
{
|
|
//Create a spark effect
|
|
gi.CreateEffect(NULL, FX_SPARKS, 0, trace.endpos, "d", bloodDir);
|
|
}
|
|
else
|
|
{
|
|
if (!(self->s.fmnodeinfo[MESH__GAFF].flags & FMNI_NO_DRAW) || !(self->s.fmnodeinfo[MESH__HOE].flags & FMNI_NO_DRAW))
|
|
{//it's the hoe or the hook
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_ATTACKHIT1], 1, ATTN_NORM, 0);
|
|
}
|
|
else //it's the hammer or handle
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_ATTACKHIT2], 1, ATTN_NORM, 0);
|
|
|
|
damage = irand(PLAGUEELF_DMG_MIN, PLAGUEELF_DMG_MAX);
|
|
|
|
if(!(self->s.fmnodeinfo[MESH__HOE].flags & FMNI_NO_DRAW))
|
|
damage+=PLAGUEELF_DMG_HOE;
|
|
else if(!(self->s.fmnodeinfo[MESH__GAFF].flags & FMNI_NO_DRAW))
|
|
damage+=PLAGUEELF_DMG_GAFF;
|
|
else if(self->s.fmnodeinfo[MESH__HAMMER].flags & FMNI_NO_DRAW)
|
|
damage+=PLAGUEELF_DMG_HAMMER;
|
|
|
|
//Hurt whatever we were whacking away at
|
|
T_Damage(victim, self, self, direction, trace.endpos, bloodDir, damage, damage*2, DAMAGE_DISMEMBER,MOD_DIED);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Play a swish sound
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_ATTACKMISS1], 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_death
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElf_death(edict_t *self, G_Message_t *msg)
|
|
{
|
|
edict_t *targ, *inflictor, *attacker;
|
|
float damage;
|
|
vec3_t dVel, vf, yf;
|
|
int chance;
|
|
|
|
pelf_init_phase_in(self);
|
|
|
|
ParseMsgParms(msg, "eeei", &targ, &inflictor, &attacker, &damage);
|
|
|
|
// Big enough death to be thrown back
|
|
if(self->monsterinfo.aiflags&AI_DONT_THINK)
|
|
{
|
|
if (irand(0,10) < 5)
|
|
SetAnim(self, ANIM_DIE2);
|
|
else
|
|
SetAnim(self, ANIM_DIE1);
|
|
return;
|
|
}
|
|
|
|
self->msgHandler = DeadMsgHandler;
|
|
|
|
//Dead but still being hit
|
|
if(self->deadflag == DEAD_DEAD)
|
|
return;
|
|
|
|
self->deadflag = DEAD_DEAD;
|
|
|
|
plagueElf_dropweapon (self, (int)self->health*-1);
|
|
|
|
if (self->health <= -80)//gib death
|
|
{
|
|
gi.sound(self, CHAN_BODY, sounds[SND_GIB], 1, ATTN_NORM, 0);
|
|
BecomeDebris(self);
|
|
return;
|
|
}
|
|
else if (self->health < -10)
|
|
{
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
SetAnim(self, ANIM_KDEATH_GO);
|
|
|
|
VectorCopy(targ->velocity, vf);
|
|
VectorNormalize(vf);
|
|
|
|
VectorScale(vf, -1, yf);
|
|
|
|
self->ideal_yaw = vectoyaw( yf );
|
|
self->yaw_speed = 48;
|
|
|
|
VectorScale(vf, 250, dVel);
|
|
dVel[2] = irand(150,200);
|
|
|
|
VectorCopy(dVel, self->velocity);
|
|
// self->groundentity = NULL;
|
|
return;
|
|
}
|
|
|
|
//regular death
|
|
if(self->count == 0)
|
|
{
|
|
chance = irand(0,3);
|
|
if(chance == 0)
|
|
SetAnim(self, ANIM_DIE1);
|
|
else if(chance == 1)
|
|
SetAnim(self, ANIM_DIE2);
|
|
else if(chance == 2)
|
|
SetAnim(self, ANIM_DIE3);
|
|
else
|
|
SetAnim(self, ANIM_DIE4);
|
|
}
|
|
else
|
|
{
|
|
if(self->count == 1)
|
|
SetAnim(self, ANIM_DIE1);
|
|
else if(self->count == 2)
|
|
SetAnim(self, ANIM_DIE2);
|
|
else if(self->count == 3)
|
|
SetAnim(self, ANIM_DIE3);
|
|
else
|
|
SetAnim(self, ANIM_DIE4);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElfdeathsqueal
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElfdeathsqueal (edict_t *self)
|
|
{
|
|
int sound;
|
|
|
|
sound = irand(SND_DIE1, SND_DIE3);
|
|
|
|
gi.sound(self, CHAN_VOICE, sounds[sound], 1, ATTN_NORM, 0);
|
|
return;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElfgrowl
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElfgrowl (edict_t *self)
|
|
{
|
|
int chance;
|
|
|
|
chance = irand(0, 10);
|
|
|
|
if (chance <= 2 )
|
|
{
|
|
chance = irand(0, 12);
|
|
if (chance < 3)
|
|
return;
|
|
//gi.sound (self, CHAN_WEAPON, sounds[SND_GASP], 1, ATTN_IDLE, 0);
|
|
else if ( chance < 6)
|
|
gi.sound (self, CHAN_VOICE, sounds[SND_PANT], 1, ATTN_IDLE, 0);
|
|
else if (chance < 9)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_MOAN1], 1, ATTN_IDLE, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_MOAN2], 1, ATTN_IDLE, 0);
|
|
}
|
|
}
|
|
|
|
void plagueElfattack(edict_t *self)
|
|
{
|
|
int chance, sound;
|
|
|
|
chance = irand(0, 10);
|
|
|
|
if (chance < 5)
|
|
{
|
|
sound = irand(SND_ATTACK1, SND_ATTACK2);
|
|
gi.sound (self, CHAN_VOICE, sounds[sound], 1, ATTN_IDLE, 0);
|
|
}
|
|
}
|
|
|
|
void create_pe_spell(edict_t *Spell)
|
|
{
|
|
Spell->movetype=MOVETYPE_FLYMISSILE;
|
|
Spell->solid=SOLID_BBOX;
|
|
Spell->classname="plagueElf_Spell";
|
|
Spell->touch=plagueElfSpellTouch;
|
|
Spell->enemy=NULL;
|
|
Spell->clipmask=MASK_MONSTERSOLID|MASK_PLAYERSOLID|MASK_SHOT;
|
|
Spell->s.scale = 0.5;
|
|
Spell->s.effects |= EF_MARCUS_FLAG1|EF_CAMERA_NO_CLIP;
|
|
Spell->svflags |= SVF_ALWAYS_SEND;
|
|
}
|
|
|
|
// the spell needs to bounce
|
|
void make_pe_spell_reflect(edict_t *self, edict_t *spell)
|
|
{
|
|
create_pe_spell(spell);
|
|
spell->s.modelindex = self->s.modelindex;
|
|
VectorCopy(self->s.origin, spell->s.origin);
|
|
spell->owner = self->owner;
|
|
spell->enemy = self->enemy;
|
|
spell->touch=self->touch;
|
|
spell->nextthink=self->nextthink;
|
|
spell->think=G_FreeEdict;
|
|
spell->health = self->health;
|
|
spell->red_rain_count = self->red_rain_count;
|
|
|
|
Create_rand_relect_vect(self->velocity, spell->velocity);
|
|
|
|
vectoangles(spell->velocity, spell->s.angles);
|
|
spell->s.angles[YAW]+=90;
|
|
|
|
Vec3ScaleAssign(500, spell->velocity);
|
|
|
|
G_LinkMissile(spell);
|
|
}
|
|
|
|
void plagueElfSpellTouch (edict_t *self, edict_t *Other, cplane_t *Plane, csurface_t *Surface)
|
|
{
|
|
vec3_t normal;
|
|
edict_t *Spell;
|
|
|
|
if(Surface&&(Surface->flags&SURF_SKY))
|
|
{
|
|
SkyFly(self);
|
|
return;
|
|
}
|
|
|
|
if(EntReflecting(Other, true, true))
|
|
{
|
|
Spell = G_Spawn();
|
|
|
|
make_pe_spell_reflect(self,Spell);
|
|
|
|
gi.CreateEffect(&Spell->s,
|
|
FX_PE_SPELL,
|
|
CEF_OWNERS_ORIGIN,
|
|
NULL,
|
|
"bv",
|
|
self->red_rain_count,
|
|
Spell->velocity);
|
|
|
|
G_SetToFree(self);
|
|
|
|
return;
|
|
}
|
|
|
|
VectorNormalize(self->velocity);
|
|
switch(self->red_rain_count)
|
|
{
|
|
case FX_PE_MAKE_SPELL:
|
|
gi.CreateEffect(NULL,
|
|
FX_PE_SPELL,
|
|
0,
|
|
self->s.origin,
|
|
"bv",
|
|
FX_PE_EXPLODE_SPELL,
|
|
self->velocity);
|
|
self->dmg = irand(PLAGUEELF_DMG_SPELL_MIN, PLAGUEELF_DMG_SPELL_MAX) + skill->value;
|
|
break;
|
|
case FX_PE_MAKE_SPELL2:
|
|
gi.CreateEffect(NULL,
|
|
FX_PE_SPELL,
|
|
0,
|
|
self->s.origin,
|
|
"bv",
|
|
FX_PE_EXPLODE_SPELL2,
|
|
self->velocity);
|
|
self->dmg = irand(PLAGUEELF_GUARD_DMG_SPELL_MIN, PLAGUEELF_GUARD_DMG_SPELL_MAX);
|
|
break;
|
|
case FX_PE_MAKE_SPELL3:
|
|
gi.CreateEffect(NULL,
|
|
FX_PE_SPELL,
|
|
0,
|
|
self->s.origin,
|
|
"bv",
|
|
FX_PE_EXPLODE_SPELL3,
|
|
self->velocity);
|
|
self->dmg = irand(PLAGUEELF_GUARD_DMG_SPELL_MIN+2, PLAGUEELF_GUARD_DMG_SPELL_MAX+2);
|
|
self->dmg_radius = 40;
|
|
break;
|
|
}
|
|
|
|
if(Other->takedamage)
|
|
{
|
|
VectorSet(normal, 0, 0, 1);
|
|
if(Plane)
|
|
{
|
|
if(Plane->normal)
|
|
{
|
|
VectorCopy(Plane->normal, normal);
|
|
}
|
|
}
|
|
T_Damage(Other,self,self->owner,self->movedir,self->s.origin,normal,self->dmg,0,DAMAGE_SPELL,MOD_DIED);
|
|
}
|
|
|
|
if(self->dmg_radius)
|
|
T_DamageRadius(self, self->owner, self->owner, self->dmg_radius, self->dmg, 0, DAMAGE_SPELL,MOD_DIED);
|
|
|
|
VectorClear(self->velocity);
|
|
|
|
G_FreeEdict(self);
|
|
}
|
|
|
|
void plagueElf_spell(edict_t *self)
|
|
{//fixme; adjust for up/down
|
|
vec3_t Forward, right, firedir;
|
|
edict_t *Spell;
|
|
|
|
if (M_ValidTarget(self, self->enemy))
|
|
{
|
|
if(self->s.fmnodeinfo[MESH__R_ARM].flags&FMNI_NO_DRAW)
|
|
return;
|
|
|
|
// gi.sound(self, CHAN_WEAPON, Sounds[SND_SPELL], 1, ATTN_NORM, 0);
|
|
self->monsterinfo.attack_finished = level.time + 0.4;
|
|
Spell = G_Spawn();
|
|
|
|
create_pe_spell(Spell);
|
|
|
|
Spell->touch=plagueElfSpellTouch;
|
|
|
|
Spell->owner=self;
|
|
Spell->enemy=self->enemy;
|
|
|
|
Spell->health = 0; // tell the touch function what kind of Spell we are;
|
|
|
|
AngleVectors(self->s.angles, Forward, right, NULL);
|
|
VectorCopy(self->s.origin,Spell->s.origin);
|
|
VectorMA(Spell->s.origin, 4, Forward, Spell->s.origin);
|
|
VectorMA(Spell->s.origin, 8, right, Spell->s.origin);
|
|
Spell->s.origin[2] += 12;
|
|
|
|
VectorCopy(self->movedir,Spell->movedir);
|
|
vectoangles(Forward,Spell->s.angles);
|
|
|
|
VectorSubtract(self->enemy->s.origin, Spell->s.origin, firedir);
|
|
VectorNormalize(firedir);
|
|
Forward[2] = firedir[2];
|
|
VectorNormalize(Forward);
|
|
VectorScale(Forward, 400, Spell->velocity);
|
|
|
|
VectorCopy(Spell->velocity, Spell->movedir);
|
|
VectorNormalize(Spell->movedir);
|
|
vectoangles(Spell->movedir, Spell->s.angles);
|
|
Spell->s.angles[PITCH] = anglemod(Spell->s.angles[PITCH] * -1);
|
|
|
|
if(stricmp(self->classname, "monster_plagueElf"))//one of the special dudes
|
|
{
|
|
if(irand(0, 3) || skill->value < 2 || !stricmp(self->classname, "monster_palace_plague_guard_invisible"))
|
|
Spell->red_rain_count = FX_PE_MAKE_SPELL2;
|
|
else
|
|
Spell->red_rain_count = FX_PE_MAKE_SPELL3;
|
|
}
|
|
else
|
|
Spell->red_rain_count = FX_PE_MAKE_SPELL;
|
|
|
|
gi.CreateEffect(&Spell->s,
|
|
FX_PE_SPELL,
|
|
CEF_OWNERS_ORIGIN,
|
|
NULL,
|
|
"bv",
|
|
Spell->red_rain_count,
|
|
Spell->velocity);
|
|
|
|
G_LinkMissile(Spell);
|
|
|
|
Spell->nextthink=level.time+3;
|
|
Spell->think=G_FreeEdict;//plagueElfSpellThink;
|
|
}
|
|
else//If our enemy is dead, we need to stand
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
}
|
|
|
|
|
|
void plagueElf_c_spell(edict_t *self)
|
|
{//fixme; adjust for up/down
|
|
vec3_t Forward, right, firedir, holdpos;
|
|
edict_t *Spell;
|
|
|
|
if(self->s.fmnodeinfo[MESH__R_ARM].flags&FMNI_NO_DRAW) // Was his arm lopped off?
|
|
return;
|
|
|
|
self->monsterinfo.attack_finished = level.time + 0.4;
|
|
Spell = G_Spawn();
|
|
|
|
create_pe_spell(Spell);
|
|
|
|
Spell->touch=plagueElfSpellTouch;
|
|
|
|
Spell->owner=self;
|
|
// Spell->enemy=self->enemy;
|
|
|
|
Spell->health = 0; // tell the touch function what kind of Spell we are;
|
|
|
|
AngleVectors(self->s.angles, Forward, right, NULL);
|
|
VectorCopy(self->s.origin,Spell->s.origin);
|
|
VectorMA(Spell->s.origin, 4, Forward, Spell->s.origin);
|
|
VectorMA(Spell->s.origin, 8, right, Spell->s.origin);
|
|
Spell->s.origin[2] += 12;
|
|
|
|
VectorCopy(self->movedir,Spell->movedir);
|
|
vectoangles(Forward,Spell->s.angles);
|
|
|
|
|
|
VectorCopy(self->s.origin,holdpos);
|
|
VectorMA(Spell->s.origin, 40, Forward, Spell->s.origin);
|
|
|
|
VectorSubtract(holdpos, Spell->s.origin, firedir);
|
|
VectorNormalize(firedir);
|
|
Forward[2] = firedir[2];
|
|
VectorNormalize(Forward);
|
|
VectorScale(Forward, 500, Spell->velocity);
|
|
|
|
VectorCopy(Spell->velocity, Spell->movedir);
|
|
VectorNormalize(Spell->movedir);
|
|
vectoangles(Spell->movedir, Spell->s.angles);
|
|
Spell->s.angles[PITCH] = anglemod(Spell->s.angles[PITCH] * -1);
|
|
|
|
if(stricmp(self->classname, "monster_plagueElf"))//one of the special dudes
|
|
{
|
|
if(irand(0, 3))
|
|
Spell->red_rain_count = FX_PE_MAKE_SPELL2;
|
|
else
|
|
Spell->red_rain_count = FX_PE_MAKE_SPELL3;
|
|
}
|
|
else
|
|
Spell->red_rain_count = FX_PE_MAKE_SPELL;
|
|
|
|
gi.CreateEffect(&Spell->s,
|
|
FX_PE_SPELL,
|
|
CEF_OWNERS_ORIGIN,
|
|
NULL,
|
|
"bv",
|
|
Spell->red_rain_count,
|
|
Spell->velocity);
|
|
|
|
G_LinkMissile(Spell);
|
|
|
|
Spell->nextthink=level.time+3;
|
|
Spell->think=G_FreeEdict;//plagueElfSpellThink;
|
|
|
|
}
|
|
|
|
void plagueElf_runaway (edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(self->spawnflags&MSF_FIXED)
|
|
return;
|
|
|
|
self->monsterinfo.aiflags |= AI_FLEE;
|
|
self->monsterinfo.flee_finished = level.time + flrand(2, 4);
|
|
}
|
|
|
|
void plagueElf_missile(edict_t *self, G_Message_t *msg)
|
|
{
|
|
pelf_init_phase_in(self);
|
|
|
|
SetAnim(self, ANIM_MISSILE);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_melee
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElf_melee(edict_t *self, G_Message_t *msg)
|
|
{
|
|
vec3_t attackVel, vf;
|
|
int ret;
|
|
|
|
if (M_ValidTarget(self, self->enemy))
|
|
{//A monster in melee will continue too long if the player backs away, this prevents it
|
|
if(self->spawnflags&MSF_FIXED||self->monsterinfo.aiflags&AI_NO_MELEE)
|
|
{
|
|
SetAnim(self, ANIM_MISSILE);
|
|
return;
|
|
}
|
|
AngleVectors(self->s.angles, vf, NULL, NULL);
|
|
VectorMA(vf, 1, vf, attackVel);
|
|
ret = M_PredictTargetEvasion( self, self->enemy, attackVel, self->enemy->velocity, self->melee_range, 5 );
|
|
|
|
if (ret)
|
|
{
|
|
if(irand(0,10)<5)
|
|
SetAnim(self, ANIM_MELEE1);
|
|
else
|
|
SetAnim(self, ANIM_MELEE2);
|
|
}
|
|
else
|
|
{
|
|
if(self->spawnflags&MSF_FIXED)
|
|
SetAnim(self, ANIM_DELAY);
|
|
else
|
|
SetAnim(self, ANIM_RUNATK1);
|
|
}
|
|
}
|
|
else//If our enemy is dead, we need to stand
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_pain
|
|
-------------------------------------------------------------------------*/
|
|
|
|
void pelf_dismember_sound(edict_t *self)
|
|
{
|
|
if(self->health <= 0)
|
|
return;
|
|
|
|
if(irand(0, 1))
|
|
gi.sound(self, CHAN_BODY, sounds[SND_DISMEMBER1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_BODY, sounds[SND_DISMEMBER2], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
qboolean canthrownode_pe (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_pe[BP];
|
|
self->s.fmnodeinfo[BP].flags |= FMNI_NO_DRAW;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void plagueElf_chicken (edict_t *self, int coward, int flee, float fleetime)
|
|
{
|
|
float chance;
|
|
|
|
chance = flrand(0, 20 + skill->value * 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;
|
|
}
|
|
}
|
|
|
|
void plagueElf_dead_pain (edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(msg)
|
|
if(!(self->svflags & SVF_PARTS_GIBBED))
|
|
MG_parse_dismember_msg(self, msg);
|
|
}
|
|
|
|
//THROWS weapon, turns off those nodes, sets that weapon as gone
|
|
qboolean plagueElf_dropweapon (edict_t *self, int damage)
|
|
{//NO PART FLY FRAME!
|
|
vec3_t handspot, forward, right, up;
|
|
float chance;
|
|
|
|
if(self->s.fmnodeinfo[MESH__HANDLE].flags & FMNI_NO_DRAW)
|
|
return false;
|
|
|
|
VectorClear(handspot);
|
|
AngleVectors(self->s.angles,forward,right,up);
|
|
VectorMA(handspot,5,forward,handspot);
|
|
VectorMA(handspot,8,right,handspot);
|
|
VectorMA(handspot,-6,up,handspot);
|
|
|
|
if(self->deadflag == DEAD_DEAD||(self->s.fmnodeinfo[MESH__R_ARM].flags & FMNI_NO_DRAW))
|
|
chance = 0;
|
|
else
|
|
chance = 3;
|
|
if(!(self->s.fmnodeinfo[MESH__HOE].flags & FMNI_NO_DRAW))
|
|
{
|
|
if(irand(0,10)<chance)
|
|
{//just take off top
|
|
ThrowWeapon(self, &handspot, BIT_HOE, 0, FRAME_torsooff);
|
|
self->s.fmnodeinfo[MESH__HOE].flags |= FMNI_NO_DRAW;
|
|
plagueElf_chicken(self,2,5,flrand(2,7));
|
|
}
|
|
else
|
|
{
|
|
ThrowWeapon(self, &handspot, BIT_HANDLE|BIT_HOE, 0, FRAME_partfly);
|
|
self->s.fmnodeinfo[MESH__HOE].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__HANDLE].flags |= FMNI_NO_DRAW;
|
|
plagueElf_chicken(self,4,8,flrand(3,8));
|
|
}
|
|
return true;
|
|
}
|
|
if(!(self->s.fmnodeinfo[MESH__GAFF].flags & FMNI_NO_DRAW))
|
|
{
|
|
if(irand(0,10)<chance)
|
|
{//just take off top
|
|
ThrowWeapon(self, &handspot, BIT_GAFF, 0, FRAME_partfly);
|
|
self->s.fmnodeinfo[MESH__GAFF].flags |= FMNI_NO_DRAW;
|
|
plagueElf_chicken(self,2,6,flrand(2,7));
|
|
}
|
|
else
|
|
{
|
|
ThrowWeapon(self, &handspot, BIT_HANDLE|BIT_GAFF, 0, FRAME_partfly);
|
|
self->s.fmnodeinfo[MESH__GAFF].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__HANDLE].flags |= FMNI_NO_DRAW;
|
|
plagueElf_chicken(self,4,8,flrand(3,8));
|
|
}
|
|
return true;
|
|
}
|
|
if(!(self->s.fmnodeinfo[MESH__HAMMER].flags & FMNI_NO_DRAW))
|
|
{
|
|
if(irand(0,10)<chance)
|
|
{//just take off top
|
|
ThrowWeapon(self, &handspot, BIT_HAMMER, 0, FRAME_partfly);
|
|
self->s.fmnodeinfo[MESH__HAMMER].flags |= FMNI_NO_DRAW;
|
|
plagueElf_chicken(self,2,6,flrand(2,7));
|
|
}
|
|
else
|
|
{
|
|
ThrowWeapon(self, &handspot, BIT_HANDLE|BIT_HAMMER, 0, FRAME_partfly);
|
|
self->s.fmnodeinfo[MESH__HAMMER].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__HANDLE].flags |= FMNI_NO_DRAW;
|
|
plagueElf_chicken(self,4,8,flrand(3,8));
|
|
}
|
|
return true;
|
|
}
|
|
ThrowWeapon(self, &handspot, BIT_HANDLE, 0, FRAME_partfly);
|
|
self->s.fmnodeinfo[MESH__HANDLE].flags |= FMNI_NO_DRAW;
|
|
if(self->deadflag != DEAD_DEAD)
|
|
plagueElf_chicken(self,6,8,flrand(5,10));
|
|
return true;
|
|
}
|
|
|
|
void plagueElf_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
|
|
|
|
VectorCopy(vec3_origin,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)
|
|
plagueElf_dropweapon (self, (int)damage);
|
|
if(flrand(0,self->health)<damage*0.3&&dismember_ok)
|
|
{
|
|
canthrownode_pe(self, MESH__HEAD,&throw_nodes);
|
|
|
|
gore_spot[2]+=18;
|
|
pelf_dismember_sound(self);
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
|
|
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
|
|
{
|
|
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__BODY].flags & FMNI_NO_DRAW)
|
|
break;
|
|
if(self->s.fmnodeinfo[MESH__BODY].flags & FMNI_USE_SKIN)
|
|
damage*=1.5;//greater chance to cut off if previously damaged
|
|
if(self->health > 0 && flrand(0,self->health)<damage*0.3 && dismember_ok)
|
|
{
|
|
gore_spot[2]+=12;
|
|
canthrownode_pe(self, MESH__BODY,&throw_nodes);
|
|
canthrownode_pe(self, MESH__L_ARM,&throw_nodes);
|
|
canthrownode_pe(self, MESH__R_ARM,&throw_nodes);
|
|
canthrownode_pe(self, MESH__HEAD,&throw_nodes);
|
|
|
|
plagueElf_dropweapon (self, (int)damage);
|
|
pelf_dismember_sound(self);
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, FRAME_torsooff);
|
|
VectorAdd(self->s.origin, gore_spot, gore_spot);
|
|
SprayDebris(self,gore_spot,12,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(flrand(0,self->health)<damage*0.5)
|
|
plagueElf_dropweapon (self, (int)damage);
|
|
self->s.fmnodeinfo[MESH__BODY].flags |= FMNI_USE_SKIN;
|
|
self->s.fmnodeinfo[MESH__BODY].skin = self->s.skinnum+1;
|
|
}
|
|
break;
|
|
case hl_ArmUpperLeft:
|
|
case hl_ArmLowerLeft://left arm
|
|
if(self->s.fmnodeinfo[MESH__L_ARM].flags & FMNI_NO_DRAW)
|
|
break;
|
|
if(self->s.fmnodeinfo[MESH__L_ARM].flags & FMNI_USE_SKIN)
|
|
damage*=1.5;//greater chance to cut off if previously damaged
|
|
if(flrand(0,self->health)<damage*0.4)
|
|
plagueElf_dropweapon (self, (int)damage);
|
|
if(flrand(0,self->health)<damage*0.75&&dismember_ok)
|
|
{
|
|
if(canthrownode_pe(self, MESH__L_ARM, &throw_nodes))
|
|
{
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
VectorMA(gore_spot,-10,right,gore_spot);
|
|
plagueElf_chicken(self,6,8,flrand(7,15));
|
|
pelf_dismember_sound(self);
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->s.fmnodeinfo[MESH__L_ARM].flags |= FMNI_USE_SKIN;
|
|
self->s.fmnodeinfo[MESH__L_ARM].skin = self->s.skinnum+1;
|
|
}
|
|
break;
|
|
case hl_ArmUpperRight:
|
|
case hl_ArmLowerRight://right arm
|
|
//Knock weapon out of hand?
|
|
if(self->s.fmnodeinfo[MESH__R_ARM].flags & FMNI_NO_DRAW)
|
|
break;
|
|
if(self->s.fmnodeinfo[MESH__R_ARM].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_pe(self, MESH__R_ARM, &throw_nodes))
|
|
{
|
|
AngleVectors(self->s.angles,NULL,right,NULL);
|
|
gore_spot[2]+=self->maxs[2]*0.3;
|
|
VectorMA(gore_spot,10,right,gore_spot);
|
|
plagueElf_dropweapon (self, (int)damage);
|
|
pelf_dismember_sound(self);
|
|
ThrowBodyPart(self, &gore_spot, throw_nodes, damage, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(flrand(0,self->health)<damage*0.75)
|
|
plagueElf_dropweapon (self, (int)damage);
|
|
self->s.fmnodeinfo[MESH__R_ARM].flags |= FMNI_USE_SKIN;
|
|
self->s.fmnodeinfo[MESH__R_ARM].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__L_LEG].flags & FMNI_USE_SKIN)
|
|
break;
|
|
self->s.fmnodeinfo[MESH__L_LEG].flags |= FMNI_USE_SKIN;
|
|
self->s.fmnodeinfo[MESH__L_LEG].skin = self->s.skinnum+1;
|
|
}
|
|
else
|
|
{
|
|
if(self->s.fmnodeinfo[MESH__L_LEG].flags & FMNI_NO_DRAW)
|
|
break;
|
|
if(canthrownode_pe(self, MESH__L_LEG, &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;
|
|
}
|
|
break;
|
|
case hl_LegUpperRight:
|
|
case hl_LegLowerRight://right leg
|
|
if(self->health>0)
|
|
{//still alive
|
|
if(self->s.fmnodeinfo[MESH__R_LEG].flags & FMNI_USE_SKIN)
|
|
break;
|
|
self->s.fmnodeinfo[MESH__R_LEG].flags |= FMNI_USE_SKIN;
|
|
self->s.fmnodeinfo[MESH__R_LEG].skin = self->s.skinnum+1;
|
|
}
|
|
else
|
|
{
|
|
if(self->s.fmnodeinfo[MESH__R_LEG].flags & FMNI_NO_DRAW)
|
|
break;
|
|
if(canthrownode_pe(self, MESH__R_LEG, &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;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if(flrand(0,self->health)<damage*0.25)
|
|
plagueElf_dropweapon (self, (int)damage);
|
|
break;
|
|
}
|
|
if(throw_nodes)
|
|
self->pain_debounce_time = 0;
|
|
|
|
if(self->s.fmnodeinfo[MESH__R_ARM].flags&FMNI_NO_DRAW)
|
|
{
|
|
self->monsterinfo.aiflags |= AI_NO_MELEE;
|
|
self->monsterinfo.aiflags |= AI_NO_MISSILE;
|
|
}
|
|
else if(self->s.fmnodeinfo[MESH__HANDLE].flags & FMNI_NO_DRAW)
|
|
{
|
|
self->monsterinfo.aiflags |= AI_NO_MELEE;
|
|
if(self->missile_range)
|
|
{
|
|
if(!(self->s.fmnodeinfo[MESH__R_ARM].flags&FMNI_NO_DRAW))
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_NO_MISSILE;
|
|
self->melee_range = 0;
|
|
}
|
|
else
|
|
self->monsterinfo.aiflags |= AI_NO_MISSILE;
|
|
}
|
|
}
|
|
|
|
if(self->monsterinfo.aiflags&AI_NO_MISSILE &&
|
|
self->monsterinfo.aiflags&AI_NO_MELEE)
|
|
self->monsterinfo.aiflags |= AI_COWARD;
|
|
}
|
|
|
|
|
|
void plagueElf_pain(edict_t *self, G_Message_t *msg)
|
|
{
|
|
int temp, damage;
|
|
qboolean force_pain;
|
|
|
|
ParseMsgParms(msg, "eeiii", &temp, &temp, &force_pain, &damage, &temp);
|
|
|
|
pelf_init_phase_in(self);
|
|
|
|
if(!(self->monsterinfo.aiflags & AI_COWARD) && !(self->monsterinfo.aiflags & AI_FLEE))
|
|
if(!force_pain)
|
|
if(flrand(0,self->health)>damage)
|
|
return;
|
|
|
|
if(self->fire_damage_time > level.time)
|
|
self->monsterinfo.aiflags |= AI_COWARD;
|
|
|
|
if (self->pain_debounce_time < level.time)
|
|
{
|
|
self->pain_debounce_time = level.time + 1;
|
|
SetAnim(self, ANIM_PAIN1);
|
|
}
|
|
}
|
|
|
|
void plagueElfApplyJump (edict_t *self)
|
|
{
|
|
self->jump_time = level.time + 0.5;
|
|
VectorCopy(self->movedir, self->velocity);
|
|
VectorNormalize(self->movedir);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_pause
|
|
-------------------------------------------------------------------------*/
|
|
void pelf_phase_out (edict_t *self);
|
|
void pelf_phase_in (edict_t *self);
|
|
void plagueElf_pause (edict_t *self)
|
|
{
|
|
self->monsterinfo.misc_debounce_time = 0;
|
|
|
|
if(self->ai_mood == AI_MOOD_FLEE)
|
|
{
|
|
if(self->s.color.a != 255 && self->pre_think!=pelf_phase_in)
|
|
pelf_init_phase_in(self);
|
|
}
|
|
else
|
|
{
|
|
if(!skill->value)
|
|
{
|
|
if(self->s.color.a > 50 && self->pre_think!=pelf_phase_out)
|
|
pelf_init_phase_out(self);
|
|
}
|
|
else if(self->s.color.a && self->pre_think!=pelf_phase_out)
|
|
pelf_init_phase_out(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:
|
|
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:
|
|
if(self->ai_mood_flags&AI_MOOD_FLAG_DUMB_FLEE)
|
|
SetAnim(self, ANIM_SCARED);
|
|
else
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
case AI_MOOD_NAVIGATE:
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
case AI_MOOD_WALK:
|
|
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
case AI_MOOD_FLEE:
|
|
if(self->ai_mood_flags&AI_MOOD_FLAG_DUMB_FLEE || (!(self->ai_mood_flags&AI_MOOD_FLAG_FORCED_BUOY) && !irand(0, 20)))
|
|
{
|
|
if(self->enemy)
|
|
{
|
|
if(M_DistanceToTarget(self, self->enemy) < 100)
|
|
{
|
|
if(self->curAnimID == ANIM_SCARED)
|
|
dying_elf_sounds (self, DYING_ELF_PAIN_VOICE);
|
|
if(irand(0,1))
|
|
SetAnim(self, ANIM_CRAZY_A);
|
|
else
|
|
SetAnim(self, ANIM_CRAZY_B);
|
|
break;
|
|
}
|
|
}
|
|
SetAnim(self, ANIM_SCARED);
|
|
}
|
|
else if(irand(0,1))
|
|
SetAnim(self, ANIM_CRAZY_A);
|
|
else
|
|
SetAnim(self, ANIM_CRAZY_B);
|
|
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:
|
|
if(self->spawnflags&MSF_FIXED)
|
|
SetAnim(self, ANIM_DELAY);
|
|
else
|
|
SetAnim(self, ANIM_WALK1);
|
|
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("plague elf: Unusable mood %d!\n", self->ai_mood);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
void pelf_land(edict_t *self)
|
|
{
|
|
gi.sound(self, CHAN_BODY, gi.soundindex("misc/land.wav"), 1, ATTN_NORM, 0);
|
|
gi.CreateEffect(&self->s,
|
|
FX_DUST_PUFF,
|
|
CEF_OWNERS_ORIGIN,
|
|
self->s.origin,
|
|
NULL);
|
|
}
|
|
|
|
void pelf_go_inair(edict_t *self)
|
|
{
|
|
SetAnim(self, ANIM_INAIR);
|
|
}
|
|
|
|
void pelf_jump (edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(self->spawnflags&MSF_FIXED)
|
|
SetAnim(self, ANIM_DELAY);
|
|
else
|
|
SetAnim(self, ANIM_FJUMP);
|
|
}
|
|
|
|
void pelf_check_mood (edict_t *self, G_Message_t *msg)
|
|
{
|
|
ParseMsgParms(msg, "i", &self->ai_mood);
|
|
|
|
plagueElf_pause(self);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_run
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElf_run(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(self->curAnimID == ANIM_CURSING || self->curAnimID == ANIM_POINT)
|
|
return;
|
|
|
|
if (M_ValidTarget(self, self->enemy))
|
|
{
|
|
if(self->spawnflags&MSF_FIXED)
|
|
SetAnim(self, ANIM_DELAY);
|
|
else
|
|
SetAnim(self, ANIM_RUN1);
|
|
}
|
|
else//If our enemy is dead, we need to stand
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
plagueElf runorder - order the plagueElf to choose an run animation
|
|
-----------------------------------------------------------------------*/
|
|
void plagueElf_runorder(edict_t *self)
|
|
{
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElfsqueal
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElfsqueal (edict_t *self)
|
|
{
|
|
if(self->monsterinfo.aiflags & AI_COWARD || self->monsterinfo.aiflags & AI_FLEE)
|
|
dying_elf_sounds (self, DYING_ELF_PAIN_VOICE);
|
|
else
|
|
{
|
|
int sound = irand(SND_PAIN1, SND_PAIN3);
|
|
|
|
gi.sound(self, CHAN_VOICE, sounds[sound], 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_stand
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElf_stand(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if (self->ai_mood == AI_MOOD_DELAY)
|
|
SetAnim(self, ANIM_DELAY);
|
|
else
|
|
SetAnim(self, ANIM_SHAKE1);
|
|
|
|
return;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
plagueElf_walk
|
|
-------------------------------------------------------------------------*/
|
|
void plagueElf_walk(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(self->curAnimID == ANIM_CURSING || self->curAnimID == ANIM_POINT)
|
|
return;
|
|
|
|
if(self->spawnflags&MSF_FIXED)
|
|
SetAnim(self, ANIM_DELAY);
|
|
else if(irand(0, 1))
|
|
SetAnim(self, ANIM_WALK1);
|
|
else
|
|
SetAnim(self, ANIM_WALK2);
|
|
return;
|
|
}
|
|
|
|
void plagueElf_go_run(edict_t *self)
|
|
{
|
|
if(self->spawnflags&MSF_FIXED)
|
|
SetAnim(self, ANIM_DELAY);
|
|
else
|
|
SetAnim(self, ANIM_RUN1);
|
|
}
|
|
|
|
/*
|
|
|
|
Plagued Elf voice support functions
|
|
|
|
*/
|
|
|
|
/*-----------------------------------------------
|
|
pelf_ChooseSightSound
|
|
-----------------------------------------------*/
|
|
|
|
#define SE_ALONE 0
|
|
#define SE_PAIR 1
|
|
#define SE_GROUP 2
|
|
|
|
int pelf_ChooseSightSound ( edict_t *self, int event )
|
|
{
|
|
int sound;
|
|
|
|
switch (event)
|
|
{
|
|
case SE_ALONE:
|
|
sound = irand( FIRST_SIGHT_ALONE, LAST_SIGHT_ALONE);
|
|
break;
|
|
|
|
case SE_PAIR:
|
|
case SE_GROUP:
|
|
sound = irand( FIRST_SIGHT_GROUP, LAST_SIGHT_GROUP );
|
|
break;
|
|
}
|
|
|
|
return sound;
|
|
}
|
|
|
|
/*-----------------------------------------------
|
|
pelf_ChooseReponseSound
|
|
-----------------------------------------------*/
|
|
|
|
int pelf_ChooseResponseSound ( edict_t *self, int event, int sound_id )
|
|
{
|
|
int sound;
|
|
|
|
switch (event)
|
|
{
|
|
case SE_PAIR:
|
|
case SE_GROUP:
|
|
sound = irand( FIRST_SIGHT_GROUP, LAST_SIGHT_GROUP);
|
|
break;
|
|
}
|
|
|
|
return sound;
|
|
}
|
|
//plague elf has seen first target (usually player)
|
|
|
|
/*-----------------------------------------------
|
|
pelf_SightSound
|
|
-----------------------------------------------*/
|
|
#define PLAGUEELF_SUPPORT_RADIUS 200
|
|
|
|
void pelf_SightSound ( edict_t *self, G_Message_t *msg )
|
|
{
|
|
edict_t *enemy = NULL;
|
|
byte sight_type;
|
|
int support, sound;
|
|
|
|
if(self->targetname || self->monsterinfo.c_mode)
|
|
return;//cinematic waiting to be activated, don't do this
|
|
|
|
//Have we already said something?
|
|
if (self->monsterinfo.supporters != -1)
|
|
return;
|
|
|
|
ParseMsgParms(msg, "be", &sight_type, &enemy);
|
|
|
|
//Find out how many elves are around (save this if we want it later)
|
|
support = self->monsterinfo.supporters = M_FindSupport(self, PLAGUEELF_SUPPORT_RADIUS);
|
|
|
|
//See if we are the first to see the player
|
|
if(M_CheckAlert(self, PLAGUEELF_SUPPORT_RADIUS))
|
|
{
|
|
//gi.dprintf("pelf_SightSound: elf has %d supporters\n", support);
|
|
|
|
if (support < 1) //Loner
|
|
{
|
|
sound = pelf_ChooseSightSound(self, SE_ALONE);
|
|
gi.sound(self, CHAN_VOICE, sounds[sound], 1, ATTN_NORM, 0);
|
|
}
|
|
else if (support < 2) //Paired
|
|
{
|
|
sound = pelf_ChooseSightSound(self, SE_PAIR);
|
|
self->monsterinfo.sound_finished = level.time + pelf_VoiceTimes[sound];
|
|
gi.sound(self, CHAN_VOICE, sounds[sound], 1, ATTN_NORM, 0);
|
|
|
|
pelf_PollResponse( self, SE_PAIR, sound, self->monsterinfo.sound_finished - flrand(0.5, 0.25) );
|
|
}
|
|
else //Grouped
|
|
{
|
|
sound = pelf_ChooseSightSound(self, SE_GROUP);
|
|
self->monsterinfo.sound_finished = level.time + pelf_VoiceTimes[sound];
|
|
gi.sound(self, CHAN_VOICE, sounds[sound], 1, ATTN_NORM, 0);
|
|
|
|
pelf_PollResponse( self, SE_GROUP, sound, self->monsterinfo.sound_finished - flrand(0.5, 0.25) );
|
|
}
|
|
|
|
if(support)
|
|
{//FIXME: make sure enemy is far enough away to anim!
|
|
if(irand(0, 1))
|
|
SetAnim(self, ANIM_CURSING);
|
|
else
|
|
SetAnim(self, ANIM_POINT);
|
|
}
|
|
}
|
|
}
|
|
|
|
//The plague elf has said something and is looking for a response
|
|
void pelf_PollResponse ( edict_t *self, int sound_event, int sound_id, float time )
|
|
{
|
|
edict_t *ent = NULL, *last_valid = NULL;
|
|
int numSupport = 0;
|
|
|
|
while((ent = findradius(ent, self->s.origin, PLAGUEELF_SUPPORT_RADIUS)) != NULL)
|
|
{
|
|
//Not us
|
|
if (ent==self)
|
|
continue;
|
|
|
|
//Same class
|
|
if (ent->classID != self->classID)
|
|
continue;
|
|
|
|
//Not already talking
|
|
if (ent->monsterinfo.sound_pending || ent->monsterinfo.sound_finished > level.time)
|
|
continue;
|
|
|
|
//Not going after different goals
|
|
if ((ent->enemy) && ent->enemy != self->enemy)
|
|
continue;
|
|
|
|
//Alive
|
|
if (ent->health <= 0)
|
|
continue;
|
|
|
|
if(!ok_to_wake(ent, false, false))
|
|
continue;
|
|
|
|
//Save this as valid!
|
|
last_valid = ent;
|
|
|
|
//Random chance to continue on
|
|
if (irand(0,1))
|
|
continue;
|
|
|
|
if(!ent->enemy)
|
|
{
|
|
ent->enemy = self->enemy;
|
|
FoundTarget(ent, false);
|
|
}
|
|
|
|
//This is the elf to respond, so post the message
|
|
QPostMessage(ent, MSG_VOICE_POLL, PRI_DIRECTIVE, "bbf", sound_event, sound_id, time);
|
|
last_valid = NULL;
|
|
break;
|
|
}
|
|
|
|
//We skipped the last valid elf, so just make the last valid one talk
|
|
if (last_valid)
|
|
{
|
|
QPostMessage(last_valid, MSG_VOICE_POLL, PRI_DIRECTIVE, "bbf", sound_event, sound_id, time);
|
|
last_valid = NULL;
|
|
}
|
|
}
|
|
|
|
//A plague elf has been polled for a response, now choose a reply and echo it back
|
|
void pelf_EchoResponse ( edict_t *self, G_Message_t *msg )
|
|
{
|
|
float time;
|
|
int sound_event, sound_id;
|
|
|
|
if(self->targetname || self->monsterinfo.c_mode)
|
|
return;//cinematic waiting to be activated, don't do this
|
|
|
|
//gi.dprintf("pelf_EchoResponse: Echoing alert response\n");
|
|
|
|
ParseMsgParms(msg, "bbf", &sound_event, &sound_id, &time);
|
|
|
|
switch ( sound_event )
|
|
{
|
|
case SE_PAIR:
|
|
self->monsterinfo.sound_pending = pelf_ChooseResponseSound( self, SE_PAIR, sound_id );
|
|
self->monsterinfo.sound_start = time;
|
|
self->monsterinfo.sound_finished = level.time + pelf_VoiceTimes[self->monsterinfo.sound_pending];
|
|
|
|
if(irand(0, 4))
|
|
{//FIXME: make sure enemy is far enough away to anim!
|
|
if(irand(0, 1))
|
|
SetAnim(self, ANIM_CURSING);
|
|
else
|
|
SetAnim(self, ANIM_POINT);
|
|
}
|
|
break;
|
|
|
|
case SE_GROUP:
|
|
self->monsterinfo.sound_pending = pelf_ChooseResponseSound( self, SE_GROUP, sound_id );
|
|
self->monsterinfo.sound_start = time;
|
|
self->monsterinfo.sound_finished = level.time + pelf_VoiceTimes[self->monsterinfo.sound_pending];
|
|
|
|
if (irand(0,2))
|
|
pelf_PollResponse( self, SE_GROUP, self->monsterinfo.sound_pending, self->monsterinfo.sound_finished );
|
|
|
|
if(irand(0, 2))
|
|
{//FIXME: make sure enemy is far enough away to anim!
|
|
if(irand(0, 1))
|
|
SetAnim(self, ANIM_CURSING);
|
|
else
|
|
SetAnim(self, ANIM_POINT);
|
|
|
|
}
|
|
break;
|
|
|
|
default:
|
|
self->monsterinfo.sound_pending = pelf_ChooseResponseSound( self, SE_GROUP, sound_id );
|
|
self->monsterinfo.sound_start = time;
|
|
self->monsterinfo.sound_finished = level.time + pelf_VoiceTimes[self->monsterinfo.sound_pending];
|
|
|
|
if (irand(0,2))
|
|
pelf_PollResponse( self, SE_GROUP, self->monsterinfo.sound_pending, self->monsterinfo.sound_finished );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Play a sound from a trigger or a pending sound event
|
|
void pelf_EchoSound ( edict_t *self, G_Message_t *msg )
|
|
{
|
|
int sound;
|
|
|
|
ParseMsgParms(msg, "i", &sound);
|
|
|
|
gi.sound(self, CHAN_VOICE, sounds[sound], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
//
|
|
void pelf_check_too_close(edict_t *self)
|
|
{
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
if(M_DistanceToTarget(self, self->enemy) < flrand(0, 100))
|
|
{
|
|
if(irand(0,1))
|
|
SetAnim(self, ANIM_CRAZY_A);
|
|
else
|
|
SetAnim(self, ANIM_CRAZY_B);
|
|
}
|
|
}
|
|
|
|
|
|
void pelf_phase_out (edict_t *self)
|
|
{
|
|
int interval = 60;
|
|
|
|
if(self->s.color.a > interval)
|
|
{
|
|
self->s.color.a -= irand(interval/2, interval);
|
|
self->pre_think = pelf_phase_out;
|
|
self->next_pre_think = level.time + 0.05;
|
|
}
|
|
else
|
|
{
|
|
if(!skill->value)
|
|
self->s.color.a = 50;
|
|
else
|
|
self->s.color.a = 0;
|
|
self->pre_think = NULL;
|
|
self->next_pre_think = -1;
|
|
}
|
|
}
|
|
|
|
void pelf_init_phase_out (edict_t *self);
|
|
void pelf_phase_in (edict_t *self)
|
|
{
|
|
int interval = 60;
|
|
|
|
if(self->s.color.a < 255 - interval)
|
|
{
|
|
self->s.color.a += irand(interval/2, interval);
|
|
self->pre_think = pelf_phase_in;
|
|
self->next_pre_think = level.time + 0.05;
|
|
}
|
|
else
|
|
{
|
|
self->svflags &= ~SVF_NO_AUTOTARGET;
|
|
self->s.color.c = 0xffffffff;
|
|
if(self->health <= 0)
|
|
{
|
|
self->pre_think = NULL;
|
|
self->next_pre_think = -1;
|
|
}
|
|
else
|
|
pelf_init_phase_out(self);
|
|
}
|
|
}
|
|
|
|
void pelf_init_phase_out (edict_t *self)
|
|
{
|
|
if(stricmp(self->classname, "monster_palace_plague_guard_invisible"))
|
|
return;
|
|
|
|
// gi.dprintf("Elf phasing out\n");
|
|
self->pre_think = pelf_phase_out;
|
|
self->next_pre_think = level.time + FRAMETIME;
|
|
self->svflags |= SVF_NO_AUTOTARGET;
|
|
}
|
|
|
|
void pelf_init_phase_in (edict_t *self)
|
|
{
|
|
if(stricmp(self->classname, "monster_palace_plague_guard_invisible"))
|
|
return;
|
|
|
|
// gi.dprintf("Elf phasing in\n");
|
|
self->pre_think = pelf_phase_in;
|
|
self->next_pre_think = level.time + FRAMETIME;
|
|
}
|
|
/*-------------------------------------------------------------------------
|
|
PlagueElfStaticsInit
|
|
-------------------------------------------------------------------------*/
|
|
void PlagueElfStaticsInit()
|
|
{
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_STAND] = plagueElf_stand;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_WALK] = plagueElf_walk;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_RUN] = plagueElf_run;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_MELEE] = plagueElf_melee;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_MISSILE] = plagueElf_missile;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_PAIN] = plagueElf_pain;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_DEATH] = plagueElf_death;
|
|
// classStatics[CID_PLAGUEELF].msgReceivers[MSG_BLOCKED] = plagueElf_blocked;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_DISMEMBER] = MG_parse_dismember_msg;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_JUMP] = pelf_jump;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_DEATH_PAIN] = plagueElf_dead_pain;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_FALLBACK] = plagueElf_run;//away
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_CHECK_MOOD] = pelf_check_mood;
|
|
|
|
//Sound support
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_VOICE_SIGHT] = pelf_SightSound;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_VOICE_POLL] = pelf_EchoResponse;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_VOICE_PUPPET] = pelf_EchoSound;
|
|
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_IDLE1] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_IDLE2] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_IDLE3] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_WALK1] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_WALK2] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_RUN1] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_ATTACK1] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_ATTACK2] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_ATTACK3] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_ATTACK4] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_DEATH1] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_DEATH2] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_DEATH3] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_DEATH4] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_THINKAGAIN] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_PAIN1] = plagueElf_c_anims;
|
|
classStatics[CID_PLAGUEELF].msgReceivers[MSG_C_GIB1] = plagueElf_c_gib;
|
|
|
|
resInfo.numAnims = NUM_ANIMS;
|
|
resInfo.animations = animations;
|
|
|
|
//note that the name is different in the path
|
|
resInfo.modelIndex = gi.modelindex("models/monsters/plaguelf/tris.fm");
|
|
|
|
sounds[SND_PAIN1] = gi.soundindex("monsters/plagueElf/pain1.wav");
|
|
sounds[SND_PAIN2] = gi.soundindex("monsters/plagueElf/pain2.wav");
|
|
sounds[SND_PAIN3] = gi.soundindex("monsters/plagueElf/pain3.wav");
|
|
sounds[SND_DIE1] = gi.soundindex("monsters/plagueElf/death1.wav");
|
|
sounds[SND_DIE2] = gi.soundindex("monsters/plagueElf/death2.wav");
|
|
sounds[SND_DIE3] = gi.soundindex("monsters/plagueElf/death3.wav");
|
|
sounds[SND_GIB] = gi.soundindex("monsters/plagueElf/gib2.wav");
|
|
sounds[SND_ATTACKHIT1] = gi.soundindex("monsters/plagueElf/hookhit.wav");
|
|
sounds[SND_ATTACKHIT2] = gi.soundindex("monsters/plagueElf/hamhit.wav");
|
|
sounds[SND_ATTACKMISS1] = gi.soundindex("weapons/staffswing.wav");
|
|
sounds[SND_MOAN1] = gi.soundindex("monsters/plagueElf/pelfgrn1.wav");
|
|
sounds[SND_MOAN2] = gi.soundindex("monsters/plagueElf/pelfgron.wav");
|
|
sounds[SND_SHIVER] = gi.soundindex("monsters/plagueElf/pelfshiv.wav");
|
|
sounds[SND_PANT] = gi.soundindex("monsters/plagueElf/pelfpant.wav");
|
|
sounds[SND_GASP] = gi.soundindex("monsters/plagueElf/pelfgasp.wav");
|
|
sounds[SND_SIGH] = gi.soundindex("monsters/plagueElf/pelfsigh.wav");
|
|
sounds[SND_ATTACK1] = gi.soundindex("monsters/plagueElf/attack1.wav");
|
|
sounds[SND_ATTACK2] = gi.soundindex("monsters/plagueElf/attack2.wav");
|
|
sounds[SND_DISMEMBER1] = gi.soundindex("monsters/plagueElf/loselimb1.wav");
|
|
sounds[SND_DISMEMBER2] = gi.soundindex("monsters/plagueElf/loselimb2.wav");
|
|
|
|
//Plague elf voices
|
|
sounds[VOICE_SIGHT_EAT_FLESH1] = gi.soundindex("monsters/plagueElf/voices/eatfleshb.wav");
|
|
sounds[VOICE_SIGHT_GET_HIM1] = gi.soundindex("monsters/plagueElf/voices/gethimb.wav");
|
|
sounds[VOICE_SIGHT_GET_HIM2] = gi.soundindex("monsters/plagueElf/voices/gethimk.wav");
|
|
sounds[VOICE_SIGHT_GET_HIM3] = gi.soundindex("monsters/plagueElf/voices/gethimm.wav");
|
|
sounds[VOICE_SIGHT_THERES_ONE] = gi.soundindex("monsters/plagueElf/voices/theresonep.wav");
|
|
|
|
sounds[VOICE_SUPPORT_GONNA_DIE1] = gi.soundindex("monsters/plagueElf/voices/gonnadieb.wav");
|
|
sounds[VOICE_SUPPORT_LIVER] = gi.soundindex("monsters/plagueElf/voices/liverm.wav");
|
|
sounds[VOICE_SUPPORT_YES] = gi.soundindex("monsters/plagueElf/voices/yesb.wav");
|
|
|
|
sounds[VOICE_MISC_LEAVE_ME1] = gi.soundindex("monsters/plagueElf/voices/leavemeb.wav");
|
|
sounds[VOICE_MISC_NO] = gi.soundindex("monsters/plagueElf/voices/nomrj.wav");
|
|
|
|
resInfo.numSounds = NUM_SOUNDS;
|
|
resInfo.sounds = sounds;
|
|
|
|
classStatics[CID_PLAGUEELF].resInfo = &resInfo;
|
|
}
|
|
|
|
/*QUAKED monster_plagueElf (1 .5 0) (-17 -25 -1) (22 12 63) AMBUSH ASLEEP WALKING CINEMATIC Missile 32 64 FIXED WANDER MELEE_LEAD STALK COWARD EXTRA1 EXTRA2 EXTRA3 EXTRA4
|
|
|
|
Missile - can fire a ranged attack
|
|
|
|
The plagueElf
|
|
|
|
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)
|
|
|
|
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(melee P.E.):
|
|
mintel = 16
|
|
melee_range = 0
|
|
missile_range = 512
|
|
min_missile_range = 0
|
|
bypass_missile_chance = 0
|
|
jump_chance = 50
|
|
wakeup_distance = 1024
|
|
|
|
DEFAULTS(missile P.E.):
|
|
mintel = 16
|
|
melee_range = 0
|
|
missile_range = 512
|
|
min_missile_range = 0
|
|
bypass_missile_chance = 60
|
|
jump_chance = 50
|
|
wakeup_distance = 1024
|
|
|
|
NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1
|
|
*/
|
|
|
|
#define PALACE_ELF_SKIN 4
|
|
/*-------------------------------------------------------------------------
|
|
SP_monster_plagueElf
|
|
-------------------------------------------------------------------------*/
|
|
void SP_monster_plagueElf (edict_t *self)
|
|
{
|
|
int chance;//, scale;
|
|
|
|
if(self->spawnflags & MSF_WALKING)
|
|
{
|
|
self->spawnflags |= MSF_WANDER;
|
|
self->spawnflags &= ~MSF_WALKING;
|
|
}
|
|
|
|
self->classID = CID_PLAGUEELF;
|
|
|
|
if (!walkmonster_start(self)) // Failed initialization
|
|
return;
|
|
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->monsterinfo.dismember = plagueElf_dismember;
|
|
|
|
if (!self->health)
|
|
{
|
|
self->health = PLAGUEELF_HEALTH;
|
|
}
|
|
|
|
//Apply to the end result (whether designer set or not)
|
|
self->max_health = self->health = MonsterHealth(self->health);
|
|
|
|
self->mass = PLAGUEELF_MASS;
|
|
self->yaw_speed = 20;
|
|
|
|
self->movetype = PHYSICSTYPE_STEP;
|
|
VectorClear(self->knockbackvel);
|
|
|
|
self->solid=SOLID_BBOX;
|
|
|
|
if(irand(0,1))
|
|
self->ai_mood_flags |= AI_MOOD_FLAG_PREDICT;
|
|
//FIXME: Hack to account for new origin with old QuakEd header
|
|
self->s.origin[2] += 32;
|
|
|
|
VectorCopy(STDMinsForClass[self->classID], self->mins);
|
|
VectorCopy(STDMaxsForClass[self->classID], self->maxs);
|
|
self->viewheight = self->maxs[2]*0.8;
|
|
|
|
self->s.modelindex = classStatics[CID_PLAGUEELF].resInfo->modelIndex;
|
|
|
|
// All skins are even numbers, pain skins are skin+1.
|
|
if (!self->s.skinnum)
|
|
{ // If the skin hasn't been touched, set it.
|
|
if (irand(0,1))
|
|
self->s.skinnum = 0;
|
|
else
|
|
self->s.skinnum = 2;
|
|
}
|
|
|
|
if (!self->s.scale)
|
|
{
|
|
self->s.scale = self->monsterinfo.scale = MODEL_SCALE;
|
|
}
|
|
|
|
self->materialtype = MAT_FLESH;
|
|
|
|
|
|
//FIXME (somewhere: otherenemy should be more than just *one* kind
|
|
self->monsterinfo.otherenemyname = "monster_box";
|
|
|
|
if(self->spawnflags & MSF_WANDER)
|
|
{
|
|
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
|
|
}
|
|
else if(self->spawnflags & MSF_PELF_CINEMATIC)
|
|
{
|
|
self->svflags|=SVF_FLOAT;
|
|
self->monsterinfo.c_mode = 1;
|
|
// self->takedamage = DAMAGE_NO;
|
|
QPostMessage(self, MSG_C_IDLE1, PRI_DIRECTIVE, "iiige",0,0,0,NULL,NULL);
|
|
}
|
|
else
|
|
{
|
|
QPostMessage(self,MSG_STAND,PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
if(self->spawnflags&MSF_FIXED || self->spawnflags&MSF_PELF_MISSILE)
|
|
{
|
|
// if(!self->melee_range)
|
|
self->melee_range = 0;
|
|
|
|
if(!self->missile_range)
|
|
self->missile_range = 512;
|
|
|
|
// if(!self->min_missile_range)
|
|
self->min_missile_range = 0;
|
|
|
|
if(!self->bypass_missile_chance)
|
|
self->bypass_missile_chance = 60;
|
|
|
|
self->s.fmnodeinfo[MESH__HOE].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__GAFF].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__HAMMER].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__HANDLE].flags |= FMNI_NO_DRAW;
|
|
|
|
self->monsterinfo.aiflags |= AI_NO_MELEE;
|
|
}
|
|
else
|
|
{
|
|
//turn on/off the weapons that aren't used
|
|
chance = irand(0, 3);
|
|
if(chance < 1)
|
|
{
|
|
//show the hammer
|
|
self->s.fmnodeinfo[MESH__HOE].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__GAFF].flags |= FMNI_NO_DRAW;
|
|
}
|
|
else if(chance < 2)
|
|
{
|
|
//show the hoe
|
|
self->s.fmnodeinfo[MESH__HAMMER].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__GAFF].flags |= FMNI_NO_DRAW;
|
|
}
|
|
else
|
|
{
|
|
//show the gaff (that hook thingie)
|
|
self->s.fmnodeinfo[MESH__HAMMER].flags |= FMNI_NO_DRAW;
|
|
self->s.fmnodeinfo[MESH__HOE].flags |= FMNI_NO_DRAW;
|
|
}
|
|
|
|
self->monsterinfo.aiflags |= AI_NO_MISSILE;
|
|
}
|
|
|
|
self->monsterinfo.supporters = -1;
|
|
|
|
//set up my mood function
|
|
MG_InitMoods(self);
|
|
self->svflags |= SVF_WAIT_NOTSOLID;
|
|
|
|
if(!stricmp(self->classname, "monster_palace_plague_guard_invisible"))
|
|
{
|
|
self->melee_range = -64;
|
|
self->min_missile_range = 30;
|
|
self->bypass_missile_chance = 80;
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
/*QUAKED monster_palace_plague_guard (1 .5 0) (-17 -25 -1) (22 12 63) AMBUSH ASLEEP WALKING CINEMATIC Missile 32 64 FIXED WANDER MELEE_LEAD STALK COWARD EXTRA1 EXTRA2 EXTRA3 EXTRA4
|
|
|
|
Can fire 2 ranged attacks, has a new skin, toucgher, has armor?
|
|
|
|
The plagueElf
|
|
|
|
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)
|
|
|
|
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 = 0
|
|
missile_range = 512
|
|
min_missile_range = 0
|
|
bypass_missile_chance = 60
|
|
jump_chance = 50
|
|
wakeup_distance = 1024
|
|
|
|
NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1
|
|
*/
|
|
/*-------------------------------------------------------------------------
|
|
SP_monster_palace_plague_guard
|
|
-------------------------------------------------------------------------*/
|
|
void SP_monster_palace_plague_guard (edict_t *self)
|
|
{
|
|
if(!self->health)
|
|
self->health = PLAGUEELF_HEALTH * 2;
|
|
|
|
self->spawnflags |= MSF_PELF_MISSILE;
|
|
|
|
if (!self->s.scale)
|
|
self->monsterinfo.scale = self->s.scale = flrand(1.0, 1.3);
|
|
|
|
SP_monster_plagueElf(self);
|
|
|
|
self->s.skinnum = PALACE_ELF_SKIN;
|
|
}
|
|
|
|
/*QUAKED monster_palace_plague_guard_invisible (1 .5 0) (-17 -25 -1) (22 12 63) AMBUSH ASLEEP WALKING CINEMATIC Missile 32 64 FIXED WANDER MELEE_LEAD STALK COWARD EXTRA1 EXTRA2 EXTRA3 StartVisible
|
|
|
|
Can fire 2 ranged attacks, has a new skin, toucgher, has armor?
|
|
|
|
Is invisible unless firing or hit
|
|
|
|
The plagueElf
|
|
|
|
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)
|
|
|
|
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 = -64
|
|
missile_range = 512
|
|
min_missile_range = 30
|
|
bypass_missile_chance = 80
|
|
jump_chance = 50
|
|
wakeup_distance = 1024
|
|
|
|
NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1
|
|
*/
|
|
/*-------------------------------------------------------------------------
|
|
SP_monster_palace_plague_guard_invisible
|
|
-------------------------------------------------------------------------*/
|
|
void SP_monster_palace_plague_guard_invisible (edict_t *self)
|
|
{
|
|
if(!self->health)
|
|
self->health = PLAGUEELF_HEALTH * 2;
|
|
|
|
self->spawnflags |= MSF_PELF_MISSILE;
|
|
|
|
SP_monster_plagueElf(self);
|
|
|
|
self->s.skinnum = PALACE_ELF_SKIN;
|
|
|
|
self->s.color.c = 0xFFFFFFFF;
|
|
|
|
if(!(self->spawnflags&MSF_EXTRA4))//these guys start visible
|
|
pelf_init_phase_out(self);
|
|
|
|
} |