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

1701 lines
40 KiB
C

//==============================================================================
//
// m_priestess.c
//
// Heretic II
// Copyright 1998 Raven Software
//
//==============================================================================
#include "g_local.h"
#include "m_priestess.h"
#include "m_priestess_anim.h"
#include "Utilities.h"
#include "g_DefaultMessageHandler.h"
#include "g_monster.h"
#include "Random.h"
#include "vector.h"
#include "fx.h"
#include "g_HitLocation.h"
#include "m_stats.h"
static int sounds[NUM_SOUNDS];
static ClassResourceInfo_t resInfo;
void create_priestess_proj(edict_t *self,edict_t *proj);
enum {
HPMISSILE1,
HPMISSILE2,
HPMISSILE3,
HPMISSILE4,
HPMISSILE5,
HPMISSILE1_EXPLODE,
HPMISSILE2_EXPLODE,
HPMISSILE3_EXPLODE,
HPMISSILE4_EXPLODE,
HPMISSILE5_EXPLODE,
HPMISSILE1_LIGHT,
HPMISSILE2_LIGHT,
HPMISSILE3_LIGHT,
HPMISSILE4_LIGHT,
HPMISSILE5_LIGHT,
HPTELEPORT_START,
HPTELEPORT_END,
HPLIGHTNING_BOLT,
};
enum {
AS_QUEENS_FURY,
AS_BROODS_SACRIFICE,
AS_HEAVENS_RAIN,
AS_LIGHT_MISSILE,
AS_POUNCE,
AS_JUMP_RIGHT,
AS_JUMP_LEFT,
} HighPriestessAttackStates_e;
enum
{
HP_STAFF_INIT,
HP_STAFF_TRAIL,
} HighPriestessStaff_e;
static animmove_t *animations[NUM_ANIMS] =
{
&priestess_move_stand1,
&priestess_move_attack1_go,
&priestess_move_attack1_loop,
&priestess_move_attack1_end,
&priestess_move_attack2,
&priestess_move_backup,
&priestess_move_death,
&priestess_move_idle,
&priestess_move_jump,
&priestess_move_pain,
&priestess_move_idle_pose,
&priestess_move_pose_trans,
&priestess_move_shield_go,
&priestess_move_shield_end,
&priestess_move_dodge_left,
&priestess_move_dodge_right,
&priestess_move_walk,
&priestess_move_jump_forward,
&priestess_move_jump_back,
&priestess_move_jump_right,
&priestess_move_jump_left,
&priestess_move_jump_pounce,
&priestess_move_pounce_attack,
&priestess_move_attack3_go,
&priestess_move_attack3_loop,
&priestess_move_attack3_end,
&priestess_move_jump_attack
};
/*
Priestess Teleport Functions
*/
/*-----------------------------------------------
priestess_teleport_go
-----------------------------------------------*/
void priestess_teleport_go ( edict_t *self )
{
gi.sound (self, CHAN_AUTO, sounds[SND_TPORT_OUT], 1, ATTN_NORM, 0);
self->takedamage = DAMAGE_NO;
gi.CreateEffect(NULL,
FX_HP_MISSILE,
0,
self->s.origin,
"vb",
self->s.origin,
HPTELEPORT_START);
}
/*-----------------------------------------------
priestess_teleport_end
-----------------------------------------------*/
void priestess_teleport_end ( edict_t *self )
{
gi.sound (self, CHAN_AUTO, sounds[SND_TPORT_IN], 1, ATTN_NORM, 0);
gi.CreateEffect(NULL,
FX_HP_MISSILE,
0,
self->s.origin,
"vb",
self->s.origin,
HPTELEPORT_END);
}
/*-----------------------------------------------
priestess_teleport_move
-----------------------------------------------*/
void blocker_throw ( edict_t *self, trace_t *trace )
{
#ifdef _DEVEL
gi.dprintf("ERROR blocker_throw : Entity touched teleport destination!\n");
#endif
}
void priestess_teleport_move ( edict_t *self )
{
edict_t *moveLocation = NULL, *bestLocation = NULL, *blocker = NULL;
trace_t trace;
vec3_t testPos;
vec3_t mins = { -24, -24, -36 };
vec3_t maxs = { -24, -24, -36 };
float bestDist = 9999999;
float dist, startDist;
while ( (moveLocation = G_Find( moveLocation, FOFS( classname ), "path_corner")) != NULL)
{
if (stricmp(moveLocation->targetname, "priestess"))
continue;
dist = vhlen(self->enemy->s.origin, moveLocation->s.origin);
if (dist < 64)
continue;
startDist = vhlen(self->s.origin, moveLocation->s.origin);
if (startDist < 64)
continue;
if ( (dist < bestDist) && (visible(moveLocation, self->enemy)) )
{
VectorCopy(moveLocation->s.origin, testPos);
testPos[2] += 36;
gi.trace(testPos, mins, maxs, testPos, self, MASK_MONSTERSOLID,&trace);
if (trace.startsolid || trace.allsolid)
continue;
if (trace.ent && !stricmp(trace.ent->classname, "player"))
continue;
bestDist = dist;
bestLocation = moveLocation;
}
}
if (bestLocation)
{
//ULTRA HACK!
VectorCopy(bestLocation->s.origin, self->monsterinfo.nav_goal);
self->s.origin[0] += 2000;
gi.linkentity(self);
//Spawn a fake entity to sit where the priestess will teleport to assure there's no telefragging
blocker = G_Spawn();
VectorSet(blocker->mins, -24, -24, -36);
VectorSet(blocker->maxs, 24, 24, 36);
blocker->solid = SOLID_BBOX;
blocker->movetype = PHYSICSTYPE_NONE;
//If the player touches this entity somehow, he's thrown back
blocker->isBlocked = blocker_throw;
blocker->isBlocking = blocker_throw;
blocker->bounced = blocker_throw;
self->movetarget = blocker;
gi.linkentity(blocker);
}
else
{
#ifdef _DEVEL
gi.dprintf("ERROR priestess_teleport_move :Priestess unable to move to new teleport destination!\n");
#endif
SetAnim(self, ANIM_SHIELD_END);
}
}
/*-----------------------------------------------
priestess_teleport_self_effects
-----------------------------------------------*/
void priestess_teleport_self_effects( edict_t *self )
{
self->s.renderfx |= RF_ALPHA_TEXTURE;
self->s.color.r = 255;
self->s.color.g = 255;
self->s.color.b = 255;
self->s.color.a = 255;
}
/*-----------------------------------------------
priestess_delta_alpha
-----------------------------------------------*/
void priestess_delta_alpha( edict_t *self, float amount )
{
if (self->s.color.a + amount > 255)
self->s.color.a = 255;
else if (self->s.color.a + amount < 0)
self->s.color.a = 0;
else
self->s.color.a += amount;
}
/*-----------------------------------------------
priestess_stop_alpha
-----------------------------------------------*/
void priestess_stop_alpha ( edict_t *self )
{
self->takedamage = DAMAGE_YES;
self->s.renderfx &= ~RF_ALPHA_TEXTURE;
self->s.color.r = 255;
self->s.color.g = 255;
self->s.color.b = 255;
self->s.color.a = 255;
}
/*-----------------------------------------------
priestess_teleport_return
-----------------------------------------------*/
void priestess_teleport_return ( edict_t *self )
{
trace_t trace;
vec3_t start, end;
if ( (self->movetarget) && (self->movetarget != self->enemy) )
G_FreeEdict(self->movetarget);
VectorCopy(self->monsterinfo.nav_goal, start);
VectorCopy(self->monsterinfo.nav_goal, end);
start[2] += 36;
end[2] -= 128;
gi.trace(start, self->mins, self->maxs, end, self, MASK_MONSTERSOLID,&trace);
if (trace.allsolid || trace.startsolid)
{
//The priestess has become lodged in something!
assert(0);
return;
}
VectorCopy(trace.endpos, end);
VectorCopy(end, self->s.origin);
gi.linkentity(self);
SetAnim(self, ANIM_SHIELD_END);
}
/*
Priestess Projectile Functions
*/
/*-----------------------------------------------
priestess_proj1_drunken
-----------------------------------------------*/
void priestess_proj1_drunken( edict_t *self )
{
VectorRandomCopy(self->velocity, self->velocity, 64);
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
priestess_proj1_think
-----------------------------------------------*/
void priestess_proj1_think( edict_t *self )
{
vec3_t olddir, newdir, huntdir;
float oldvelmult , newveldiv, speed_mod;
//No enemy, stop tracking
if (!self->enemy)
{
self->think = NULL;
return;
}
//Enemy is dead, stop tracking
if (self->enemy->health <= 0)
{
self->think = NULL;
return;
}
//Timeout?
if (self->monsterinfo.attack_finished < level.time)
{
gi.sound (self, CHAN_BODY, sounds[SND_BALLHIT], 1, ATTN_NORM, 0);
gi.CreateEffect(&self->s,
FX_HP_MISSILE,
CEF_OWNERS_ORIGIN,
self->s.origin,
"vb",
vec3_origin,
HPMISSILE1_EXPLODE);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
return;
}
VectorCopy(self->velocity, olddir);
VectorNormalize(olddir);
VectorSubtract(self->enemy->s.origin, self->s.origin, huntdir);
VectorNormalize(huntdir);
oldvelmult = 1.2;
newveldiv = 1/(oldvelmult + 1);
VectorScale(olddir, oldvelmult, olddir);
VectorAdd(olddir, huntdir, newdir);
VectorScale(newdir, newveldiv, newdir);
speed_mod = DotProduct( olddir , newdir );
if (speed_mod < 0.05)
speed_mod = 0.05;
newveldiv *= self->ideal_yaw * speed_mod;
VectorScale(olddir, oldvelmult, olddir);
VectorAdd(olddir, huntdir, newdir);
VectorScale(newdir, newveldiv, newdir);
VectorCopy(newdir, self->velocity);
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
priestess_proj2_die
-----------------------------------------------*/
int priestess_proj2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
gi.sound (self, CHAN_AUTO, sounds[SND_BUGHIT], 1, ATTN_NORM, 0);
gi.CreateEffect(&self->s,
FX_HP_MISSILE,
CEF_OWNERS_ORIGIN,
self->s.origin,
"vb",
vec3_origin,
HPMISSILE3_EXPLODE);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
return 1;
}
/*-----------------------------------------------
priestess_proj2_think
-----------------------------------------------*/
void priestess_proj2_think( edict_t *self )
{
//Timeout?
if (self->monsterinfo.attack_finished < level.time)
{
gi.sound (self, CHAN_AUTO, sounds[SND_BUGHIT], 1, ATTN_NORM, 0);
gi.CreateEffect(&self->s,
FX_HP_MISSILE,
CEF_OWNERS_ORIGIN,
self->s.origin,
"vb",
vec3_origin,
HPMISSILE3_EXPLODE);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
return;
}
VectorScale(self->velocity, self->missile_range, self->velocity);
self->velocity[0] += irand(-8, 8);
self->velocity[1] += irand(-8, 8);
self->velocity[2] += irand(-8, 8);
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
priestess_proj1_blocked
-----------------------------------------------*/
void priestess_proj1_blocked( edict_t *self, trace_t *trace )
{
edict_t *proj;
vec3_t hitDir;
int damage;
byte exp;
if (trace->ent == self->owner)
return;
if (!stricmp(trace->ent->classname, "HPriestess_Missile"))
return;
//Reflection stuff
if(EntReflecting(trace->ent, true, true))
{
proj = G_Spawn();
create_priestess_proj(self,proj);
proj->owner = self->owner;
proj->ideal_yaw = self->ideal_yaw;
Create_rand_relect_vect(self->velocity, proj->velocity);
Vec3ScaleAssign(proj->ideal_yaw,proj->velocity);
vectoangles(proj->velocity, proj->s.angles);
switch ( self->monsterinfo.attack_state )
{
case AS_QUEENS_FURY:
case AS_LIGHT_MISSILE:
exp = HPMISSILE1_EXPLODE;
break;
case AS_BROODS_SACRIFICE:
exp = HPMISSILE3_EXPLODE;
break;
case AS_HEAVENS_RAIN:
exp = HPMISSILE1_EXPLODE;
break;
}
gi.CreateEffect(&self->s,
FX_HP_MISSILE,
CEF_OWNERS_ORIGIN,
self->s.origin,
"vb",
vec3_origin,
(unsigned char) exp);
gi.linkentity(proj);
G_SetToFree(self);
return;
}
//Do the rest of the stuff
switch ( self->monsterinfo.attack_state )
{
case AS_QUEENS_FURY:
exp = HPMISSILE1_EXPLODE;
damage = irand(HP_DMG_FURY_MIN, HP_DMG_FURY_MAX);
gi.sound (self, CHAN_AUTO, sounds[SND_HOMINGHIT], 1, ATTN_NORM, 0);
break;
case AS_BROODS_SACRIFICE:
exp = HPMISSILE3_EXPLODE;
damage = irand(HP_DMG_BROOD_MIN, HP_DMG_BROOD_MAX);
gi.sound (self, CHAN_AUTO, sounds[SND_BUGHIT], 1, ATTN_NORM, 0);
break;
case AS_HEAVENS_RAIN:
exp = HPMISSILE2_EXPLODE;
damage = HP_DMG_RAIN;
gi.sound (self, CHAN_AUTO, sounds[SND_ZAPHIT], 1, ATTN_NORM, 0);
break;
case AS_LIGHT_MISSILE:
exp = HPMISSILE1_EXPLODE;
damage = irand(HP_DMG_MISSILE_MIN, HP_DMG_MISSILE_MAX);
gi.sound (self, CHAN_AUTO, sounds[SND_BALLHIT], 1, ATTN_NORM, 0);
break;
default:
assert(0);
break;
}
if ( trace->ent->takedamage )
{
VectorCopy( self->velocity, hitDir );
VectorNormalize( hitDir );
T_Damage( trace->ent, self, self->owner, hitDir, self->s.origin, trace->plane.normal, damage, 0, DAMAGE_SPELL | DAMAGE_NO_KNOCKBACK,MOD_DIED );
}
gi.CreateEffect(&self->s,
FX_HP_MISSILE,
CEF_OWNERS_ORIGIN,
self->s.origin,
"vb",
vec3_origin,
(unsigned char) exp);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
priestess_proj1_touch
-----------------------------------------------*/
void priestess_proj1_touch( edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surface )
{
vec3_t hitDir;
byte exp;
int damage;
if (other == self->owner)
return;
if (!stricmp(other->classname, "HPriestess_Missile"))
return;
//Do the rest of the stuff
switch ( self->monsterinfo.attack_state )
{
case AS_QUEENS_FURY:
exp = HPMISSILE1_EXPLODE;
damage = irand(HP_DMG_FURY_MIN, HP_DMG_FURY_MAX);
gi.sound (self, CHAN_AUTO, sounds[SND_HOMINGHIT], 1, ATTN_NORM, 0);
break;
case AS_BROODS_SACRIFICE:
exp = HPMISSILE3_EXPLODE;
damage = irand(HP_DMG_BROOD_MIN, HP_DMG_BROOD_MAX);
gi.sound (self, CHAN_AUTO, sounds[SND_BUGHIT], 1, ATTN_NORM, 0);
break;
case AS_HEAVENS_RAIN:
exp = HPMISSILE2_EXPLODE;
damage = HP_DMG_RAIN;
gi.sound (self, CHAN_AUTO, sounds[SND_ZAPHIT], 1, ATTN_NORM, 0);
break;
case AS_LIGHT_MISSILE:
exp = HPMISSILE1_EXPLODE;
damage = irand(HP_DMG_MISSILE_MIN, HP_DMG_MISSILE_MAX);
gi.sound (self, CHAN_AUTO, sounds[SND_BALLHIT], 1, ATTN_NORM, 0);
break;
default:
assert(0);
break;
}
if ( other->takedamage )
{
VectorCopy( self->velocity, hitDir );
VectorNormalize( hitDir );
T_Damage( other, self, self->owner, hitDir, self->s.origin, plane->normal, damage, 0, DAMAGE_SPELL | DAMAGE_NO_KNOCKBACK,MOD_DIED );
}
if (other == self->owner)
return;
if (!stricmp(other->classname, "HPriestess_Missile"))
return;
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
create_priestess_proj
-----------------------------------------------*/
// create the guts of the high priestess projectile
void create_priestess_proj(edict_t *self,edict_t *proj)
{
proj->svflags |= SVF_ALWAYS_SEND;
proj->movetype = PHYSICSTYPE_FLY;
proj->gravity = 0;
proj->solid = SOLID_BBOX;
proj->classname = "HPriestess_Missile";
proj->dmg = 1.0;
proj->s.scale = 1.0;
proj->clipmask = MASK_SHOT;
proj->nextthink = level.time + 0.1;
proj->bounced = priestess_proj1_blocked;
proj->isBlocking = priestess_proj1_blocked;
proj->isBlocked = priestess_proj1_blocked;
//proj->touch = priestess_proj1_touch;
proj->s.effects=EF_MARCUS_FLAG1|EF_CAMERA_NO_CLIP;
proj->enemy = self->enemy;
VectorSet(proj->mins, -2.0, -2.0, -2.0);
VectorSet(proj->maxs, 2.0, 2.0, 2.0);
VectorCopy(self->s.origin, proj->s.origin);
}
/*-----------------------------------------------
priestess_fire1
-----------------------------------------------*/
//Hand thrown light missiles
void priestess_fire1( edict_t *self, float pitch_ofs, float yaw_ofs, float roll_ofs )
{
edict_t *proj;
vec3_t vf, vr, predPos;
vec3_t ang, vel, startOfs, angles;
int i;
if (!self->enemy)
return;
//Only predict once for all the missiles
M_PredictTargetPosition ( self->enemy, self->enemy->velocity, 1, predPos );
AngleVectors(self->s.angles, vf, vr, NULL);
VectorMA(self->s.origin, -8, vf, startOfs);
VectorMA(startOfs, -16, vr, startOfs);
startOfs[2] += 32;
VectorSubtract(predPos, startOfs, vf);
VectorNormalize(vf);
vectoangles( vf, angles );
i = irand(2,3);
while (i--)
{
// Spawn the projectile
proj = G_Spawn();
proj->monsterinfo.attack_state = AS_LIGHT_MISSILE;
create_priestess_proj(self,proj);
proj->owner = self;
VectorCopy(startOfs, proj->s.origin);
VectorCopy(angles, ang);
ang[PITCH] = flrand( -4, 4 ) + -ang[PITCH];
ang[YAW] += flrand( -4, 4 );
AngleVectors( ang, vel, NULL, NULL );
VectorScale(vel, irand(500,600), proj->velocity);
vectoangles(proj->velocity, proj->s.angles);
gi.sound (self, CHAN_AUTO, sounds[SND_3BALLATK], 1, ATTN_NORM, 0);
//One in ten wander off drunkenly
if (!irand(0,10))
proj->think = priestess_proj1_drunken;
gi.CreateEffect(&proj->s,
FX_HP_MISSILE,
CEF_OWNERS_ORIGIN,
NULL,
"vb",
proj->velocity,
HPMISSILE2);
gi.linkentity(proj);
}
}
/*-----------------------------------------------
priestess_fire2
-----------------------------------------------*/
//Tracking, anime style missiles
void priestess_fire2( edict_t *self, float pitch_ofs, float yaw_ofs, float roll_ofs )
{
edict_t *proj;
vec3_t vf, vr, ang;
// Spawn the projectile
proj = G_Spawn();
create_priestess_proj(self,proj);
proj->monsterinfo.attack_state = AS_QUEENS_FURY;
proj->owner = self;
AngleVectors(self->s.angles, vf, vr, NULL);
VectorCopy(self->s.origin, proj->s.origin);
VectorMA(self->s.origin, 30, vf, proj->s.origin);
VectorMA(proj->s.origin, 8, vr, proj->s.origin);
proj->s.origin[2] += 56;
proj->ideal_yaw = 400;
vectoangles( vf, ang );
ang[PITCH] -= irand( 5, 75 );
ang[YAW] += irand( -60, 60 );
AngleVectors( ang, vf, NULL, NULL );
VectorScale( vf, proj->ideal_yaw, proj->velocity );
proj->monsterinfo.attack_finished= level.time + 2;
vectoangles(proj->velocity, proj->s.angles);
if (!irand(0,15))
proj->think = priestess_proj1_drunken;
else
proj->think=priestess_proj1_think;
gi.sound (self, CHAN_AUTO, sounds[SND_HOMINGATK], 1, ATTN_NORM, 0);
gi.CreateEffect(&proj->s,
FX_HP_MISSILE,
CEF_OWNERS_ORIGIN,
proj->s.origin,
"vb",
proj->s.origin,
HPMISSILE1);
gi.linkentity(proj);
}
/*-----------------------------------------------
priestess_fire3
-----------------------------------------------*/
//The light bugs
void priestess_fire3( edict_t *self, float pitch_ofs, float yaw_ofs, float roll_ofs )
{
edict_t *proj;
vec3_t vf, vr, ang, startPos;
float len;
// Spawn the projectile
proj = G_Spawn();
create_priestess_proj(self,proj);
proj->takedamage = DAMAGE_YES;
proj->die = priestess_proj2_die;
proj->monsterinfo.attack_state = AS_BROODS_SACRIFICE;
proj->owner = self;
AngleVectors(self->s.angles, vf, vr, NULL);
VectorNormalize(vf);
VectorCopy(self->s.origin, proj->s.origin);
VectorMA(self->s.origin, 30, vf, proj->s.origin);
VectorMA(proj->s.origin, 8, vr, proj->s.origin);
proj->s.origin[2] += 56;
VectorCopy(proj->s.origin, startPos);
len = M_DistanceToTarget(self, self->enemy);
len /= 200;
proj->ideal_yaw = irand(500*len,750*len);
proj->missile_range = flrand(0.65, 0.75);
VectorSubtract(self->enemy->s.origin, proj->s.origin, vf);
VectorNormalize(vf);
vectoangles( vf, ang );
ang[PITCH] *= -1;
ang[PITCH] += irand( -10, 5 );
ang[YAW] += irand( -35, 35 );
AngleVectors( ang, vf, NULL, NULL );
VectorScale( vf, proj->ideal_yaw, proj->velocity );
proj->monsterinfo.attack_finished= level.time + 5;
vectoangles(proj->velocity, proj->s.angles);
proj->think=priestess_proj2_think;
gi.sound (self, CHAN_AUTO, sounds[SND_BUGS], 1, ATTN_NORM, 0);
gi.CreateEffect(&proj->s,
FX_HP_MISSILE,
CEF_OWNERS_ORIGIN,
startPos,
"vb",
proj->velocity,
HPMISSILE3);
gi.linkentity(proj);
}
/*-----------------------------------------------
priestess_fire4
-----------------------------------------------*/
//Big special light show of doom and chaos and destruction... or something...
void priestess_fire4( edict_t *self, float pitch_ofs, float yaw_ofs, float roll_ofs )
{
trace_t trace;
vec3_t vf, vr, /*ang,*/ startPos, endPos;
vec3_t mins = { -1, -1, -1 };
vec3_t maxs = { 1, 1, 1 };
float len;
if (self->monsterinfo.sound_finished < level.time)
{
gi.sound (self, CHAN_AUTO, sounds[SND_ZAP], 1, ATTN_NORM, 0);
self->monsterinfo.sound_finished = level.time + 5;
}
AngleVectors(self->s.angles, vf, vr, NULL);
VectorCopy(self->s.origin, startPos);
VectorMA(self->s.origin, 30, vf, startPos);
VectorMA(startPos, 8, vr, startPos);
startPos[2] += 56;
//The 5 to 8 effects are spawn on the other side, no reason to send each one
gi.CreateEffect(NULL,
FX_HP_MISSILE,
0,
startPos,
"vb",
startPos,
HPMISSILE4);
AngleVectors(self->s.angles, vf, vr, NULL);
VectorCopy(self->s.origin, startPos);
VectorMA(self->s.origin, 30, vf, startPos);
VectorMA(startPos, 8, vr, startPos);
startPos[2] += 56;
if ( self->monsterinfo.misc_debounce_time < level.time )
{
VectorSubtract(self->enemy->s.origin, startPos, vf);
len = VectorNormalize(vf);
VectorMA( startPos, len, vf, endPos );
gi.trace( startPos, mins, maxs, endPos, self, MASK_SHOT ,&trace);
if (trace.ent == self->enemy)
{
T_Damage(trace.ent, self, self, vf, trace.endpos, trace.plane.normal,
irand(HP_DMG_FIRE_MIN, HP_DMG_FIRE_MAX), 0, DAMAGE_DISMEMBER,MOD_DIED);
gi.sound (self, CHAN_AUTO, sounds[SND_ZAPHIT], 1, ATTN_NORM, 0);
}
gi.CreateEffect(NULL,
FX_HP_MISSILE,
0,
startPos,
"vb",
trace.endpos,
HPMISSILE5);
self->monsterinfo.misc_debounce_time = level.time + flrand(0.2, 0.4);
}
}
/*
Priestess Helper Functions
*/
/*-----------------------------------------------
priestess_attack1_pause
-----------------------------------------------*/
void priestess_attack1_pause( edict_t *self )
{
if (self->monsterinfo.search_time--)
{
}
else
{
priestess_pause(self);
}
}
/*-----------------------------------------------
priestess_attack3_loop
-----------------------------------------------*/
void priestess_attack3_loop ( edict_t *self )
{
vec3_t spawnSpot, vf, vr;
SetAnim(self, ANIM_ATTACK3_LOOP);
self->monsterinfo.attack_finished = level.time + 4;
AngleVectors(self->s.angles, vf, vr, NULL);
VectorCopy(self->s.origin, spawnSpot);
VectorMA(self->s.origin, 30, vf, spawnSpot);
VectorMA(spawnSpot, 8, vr, spawnSpot);
spawnSpot[2] += 56;
//RIGHT HERE!
self->monsterinfo.jump_time = level.time + 10;
self->monsterinfo.attack_state = irand(0,2);
//Don't repeat an attack (people want to see them all!)
if (self->monsterinfo.lefty == self->monsterinfo.attack_state)
{
switch ( self->monsterinfo.attack_state )
{
case AS_QUEENS_FURY:
self->monsterinfo.attack_state += irand(1,2);
break;
case AS_BROODS_SACRIFICE:
if (irand(0,1))
self->monsterinfo.attack_state = AS_QUEENS_FURY;
else
self->monsterinfo.attack_state = AS_HEAVENS_RAIN;
break;
case AS_HEAVENS_RAIN:
self->monsterinfo.attack_state -= irand(1,2);
break;
}
}
self->monsterinfo.lefty = self->monsterinfo.attack_state;
switch ( self->monsterinfo.attack_state )
{
case AS_QUEENS_FURY:
gi.CreateEffect(NULL,
FX_HP_MISSILE,
0,
spawnSpot,
"vb",
vec3_origin,
HPMISSILE1_LIGHT);
break;
case AS_BROODS_SACRIFICE:
self->monsterinfo.attack_finished = level.time + 2;
gi.CreateEffect(NULL,
FX_HP_MISSILE,
0,
spawnSpot,
"vb",
vec3_origin,
HPMISSILE3_LIGHT);
break;
case AS_HEAVENS_RAIN:
gi.CreateEffect(NULL,
FX_HP_MISSILE,
0,
spawnSpot,
"vb",
vec3_origin,
HPMISSILE4_LIGHT);
gi.CreateEffect(NULL,
FX_LENSFLARE,
CEF_FLAG8,
spawnSpot,
"bbbf",
(byte) 128,
(byte) 128,
(byte) 128,
0.9);
break;
}
}
/*-----------------------------------------------
priestess_attackc_loop_fire
-----------------------------------------------*/
void priestess_attack3_loop_fire ( edict_t *self )
{
if (self->monsterinfo.attack_finished < level.time)
{
SetAnim(self, ANIM_ATTACK3_END);
return;
}
//NOTE: These effects are not necessarily called each frame (hence the irands)
switch ( self->monsterinfo.attack_state )
{
case AS_QUEENS_FURY:
if (self->monsterinfo.search_time < level.time)
{
priestess_fire2( self, 0, 0, 0 );
self->monsterinfo.search_time = level.time + 0.25;
}
break;
case AS_BROODS_SACRIFICE:
if (self->monsterinfo.search_time < level.time)
{
priestess_fire3( self, 0, 0, 0 );
self->monsterinfo.search_time = level.time + 0.15;
}
break;
case AS_HEAVENS_RAIN:
priestess_fire4( self, 0, 0, 0 );
break;
}
}
/*-----------------------------------------------
priestess_pounce_attack
-----------------------------------------------*/
void priestess_pounce_attack ( edict_t *self )
{
float len;
if (M_ValidTarget(self, self->enemy))
{
len = M_DistanceToTarget(self, self->enemy);
if (len < 64)
{
SetAnim(self, ANIM_POUNCE_ATTACK);
}
else if (len < 128)
{
SetAnim(self, ANIM_ATTACK2);
}
else
{
priestess_pause(self);
}
}
}
/*-----------------------------------------------
priestess_pounce
-----------------------------------------------*/
//Number of frames the priestess is in the air
#define PRIESTESS_JUMPFRAMES 10
#define PRIESTESS_HOPDIST 0
void priestess_jump_attack ( edict_t *self )
{
vec3_t predPos, jumpVel;
float jumpDist, moveDist, hopDist;
//Find out where the player will be when we would probably land
M_PredictTargetPosition( self->enemy, self->enemy->velocity, PRIESTESS_JUMPFRAMES+2, predPos);
//Find the vector to that spot and the length
VectorSubtract(predPos, self->s.origin, jumpVel);
moveDist = VectorNormalize(jumpVel);
//Velocity is applied per tenth of a frame, so take the distance, divide by the number of frames in the air, and FRAMETIME
jumpDist = ( moveDist * PRIESTESS_JUMPFRAMES ) * FRAMETIME;
//Now get the height to keep her in the air long enough to complete this jump
hopDist = ( PRIESTESS_HOPDIST + ( ( sv_gravity->value * PRIESTESS_JUMPFRAMES ) / 4 ) ) * FRAMETIME;
//Setup the vector for the jump
VectorScale( jumpVel, jumpDist, jumpVel );
jumpVel[2] = hopDist;
//Set the priestess in motion
VectorCopy( jumpVel, self->velocity );
// self->groundentity = NULL;
}
void priestess_pounce ( edict_t *self )
{
vec3_t predPos, jumpVel;
float jumpDist, moveDist, hopDist;
if (!self->enemy)
return;
//Find out where the player will be when we would probably land
M_PredictTargetPosition( self->enemy, self->enemy->velocity, PRIESTESS_JUMPFRAMES+2, predPos);
//Find the vector to that spot and the length
VectorSubtract(predPos, self->s.origin, jumpVel);
moveDist = VectorNormalize(jumpVel);
//Velocity is applied per tenth of a frame, so take the distance, divide by the number of frames in the air, and FRAMETIME
jumpDist = ( moveDist * PRIESTESS_JUMPFRAMES ) * FRAMETIME;
//Now get the height to keep her in the air long enough to complete this jump
hopDist = ( PRIESTESS_HOPDIST + ( ( sv_gravity->value * PRIESTESS_JUMPFRAMES ) / 4 ) ) * FRAMETIME;
//Setup the vector for the jump
VectorScale( jumpVel, jumpDist, jumpVel );
jumpVel[2] = hopDist;
//Set her in motion
VectorCopy( jumpVel, self->velocity );
// self->groundentity = NULL;
}
/*-----------------------------------------------
priestess_strike
-----------------------------------------------*/
void priestess_strike ( edict_t *self, float damage )
{
trace_t trace;
edict_t *victim;
vec3_t soff, eoff, mins, maxs, bloodDir, direction;
//FIXME: Take out the mults here, done this way to speed up tweaking (sue me)
switch ( self->s.frame )
{
case FRAME_attackB8:
VectorSet(soff, 16*4, -16*5, 16*3);
VectorSet(eoff, 16*3, 16*5, -8);
break;
case FRAME_attackB14:
VectorSet(soff, 16*2, 16*5, 16*4);
VectorSet(eoff, 16*5, -16*5,-16*2);
break;
case FRAME_jumpatt12:
VectorSet(soff, 16*2, 0, 16*5);
VectorSet(eoff, 16*5, 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);
//Did something get hit?
if (victim)
{
if (victim == self)
{
//Create a spark effect
gi.CreateEffect(NULL, FX_SPARKS, CEF_FLAG6, trace.endpos, "d", direction);
gi.sound (self, CHAN_WEAPON, sounds[SND_SWIPEWALL], 1, ATTN_NORM, 0);
}
else
{
//Hurt whatever we were whacking away at
T_Damage(victim, self, self, direction, trace.endpos, bloodDir, damage, damage*2, DAMAGE_DISMEMBER,MOD_DIED);
gi.sound (self, CHAN_WEAPON, sounds[SND_SWIPE], 1, ATTN_NORM, 0);
}
}
else
{
//Play swoosh sound
gi.sound (self, CHAN_AUTO, sounds[SND_SWIPEMISS], 1, ATTN_NORM, 0);
}
}
/*-----------------------------------------------
priestess_move
-----------------------------------------------*/
void priestess_move( edict_t *self, float vf, float vr, float vu )
{
}
/*-----------------------------------------------
priestess_jump_right
-----------------------------------------------*/
void priestess_jump_right( edict_t *self )
{
vec3_t vr;
AngleVectors(self->s.angles, NULL, vr, NULL);
VectorScale( vr, 300, vr );
vr[2] = 150;
VectorCopy(vr, self->velocity);
// self->groundentity = NULL;
}
/*-----------------------------------------------
priestess_jump_left
-----------------------------------------------*/
void priestess_jump_left( edict_t *self )
{
vec3_t vr;
AngleVectors(self->s.angles, NULL, vr, NULL);
VectorScale( vr, -300, vr );
vr[2] = 150;
VectorCopy(vr, self->velocity);
// self->groundentity = NULL;
}
/*-----------------------------------------------
priestess_jump_forward
-----------------------------------------------*/
void priestess_jump_forward( edict_t *self )
{
vec3_t vf;
AngleVectors(self->s.angles, vf, NULL, NULL);
VectorScale( vf, 300, vf );
vf[2] = 150;
VectorCopy(vf, self->velocity);
// self->groundentity = NULL;
}
/*-----------------------------------------------
priestess_jump_back
-----------------------------------------------*/
void priestess_jump_back( edict_t *self )
{
vec3_t vf;
AngleVectors(self->s.angles, vf, NULL, NULL);
VectorScale( vf, -300, vf );
vf[2] = 150;
VectorCopy(vf, self->velocity);
// self->groundentity = NULL;
}
/*-----------------------------------------------
priestess_pause
-----------------------------------------------*/
void priestess_pause( edict_t *self )
{
qboolean clear_LOS;
float len;
int chance;
chance = irand(0,100);
if (M_ValidTarget(self, self->enemy))
{
len = M_DistanceToTarget(self, self->enemy);
clear_LOS = visible(self, self->enemy);
if (!clear_LOS && chance < 75)
{
SetAnim(self, ANIM_SHIELD_GO);
return;
}
chance = irand(0,100);
if (len < 64)
{
if (chance < 20)
SetAnim(self, ANIM_ATTACK2);
else if (chance < 40)
SetAnim(self, ANIM_BACKUP);
else
SetAnim(self, ANIM_JUMP_BACK);
}
else
{
if (chance < 40 && self->monsterinfo.jump_time < level.time)
{
SetAnim(self, ANIM_ATTACK3_GO);
}
else if (chance < 40 && self->monsterinfo.attack_state != AS_LIGHT_MISSILE)
{
self->monsterinfo.search_time = 2;
self->monsterinfo.attack_state = AS_LIGHT_MISSILE;
SetAnim(self, ANIM_ATTACK1_GO);
}
else if (chance < 80 && self->monsterinfo.attack_state != AS_POUNCE)
{
self->monsterinfo.attack_state = AS_POUNCE;
if (len > 256)
SetAnim(self, ANIM_JUMP_POUNCE);
else
SetAnim(self, ANIM_JUMP_ATTACK);
}
else if (chance < 90 && self->monsterinfo.attack_finished < level.time)
{
SetAnim(self, ANIM_SHIELD_GO);
self->monsterinfo.attack_finished = level.time + 5;
}
else if (self->monsterinfo.attack_state != AS_JUMP_RIGHT)
{
self->monsterinfo.attack_state = AS_JUMP_RIGHT;
SetAnim(self, ANIM_JUMP_RIGHT);
}
else
{
self->monsterinfo.attack_state = AS_JUMP_LEFT;
SetAnim(self, ANIM_JUMP_LEFT);
}
}
//SetAnim(self, ANIM_SHIELD_GO);
//self->monsterinfo.attack_state = AS_LIGHT_MISSILE;
//self->monsterinfo.search_time = 2;
//SetAnim(self, ANIM_STAND1);
//SetAnim(self, ANIM_ATTACK3_GO);
//SetAnim(self, ANIM_ATTACK1_GO);
//SetAnim(self, ANIM_JUMP_ATTACK);
//SetAnim(self, ANIM_JUMP_POUNCE);
return;
}
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
}
/*-----------------------------------------------
priestess_idle
-----------------------------------------------*/
void priestess_idle(edict_t *self)
{
}
/*-----------------------------------------------
priestess_dead
-----------------------------------------------*/
void priestess_dead( edict_t *self )
{
/* CINEMATICS HERE */
self->mood_nextthink = -1;//never mood_think again
self->maxs[2] = self->mins[2] + 16;
if (self->PersistantCFX)
{
gi.RemovePersistantEffect(self->PersistantCFX, REMOVE_PRIESTESS);
self->PersistantCFX = 0;
}
gi.RemoveEffects(&self->s, 0);
gi.linkentity (self);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
}
/*
Priestess Message Functions
*/
/*-----------------------------------------------
priestess_death
-----------------------------------------------*/
void priestess_death( edict_t *self, G_Message_t *msg )
{
self->msgHandler = DeadMsgHandler;
if(self->deadflag == DEAD_DEAD)
return;
self->deadflag = DEAD_DEAD;
self->takedamage = DAMAGE_NO;
self->dmg = 0;
self->health = 0;
self->max_health = 0;
M_ShowLifeMeter( self, 0, 0);
SetAnim(self, ANIM_DEATH);
}
/*-----------------------------------------------
priestess_evade
-----------------------------------------------*/
void priestess_evade( edict_t *self, G_Message_t *msg )
{
if (irand(0,1))
SetAnim(self, ANIM_DODGE_LEFT);
else
SetAnim(self, ANIM_DODGE_RIGHT);
}
/*-----------------------------------------------
priestess_stand
-----------------------------------------------*/
void priestess_stand(edict_t *self, G_Message_t *msg)
{
SetAnim(self, ANIM_STAND1);
}
/*-----------------------------------------------
priestess_missile
-----------------------------------------------*/
void priestess_missile(edict_t *self, G_Message_t *msg)
{
SetAnim(self, ANIM_ATTACK2);
}
/*-----------------------------------------------
priestess_run
-----------------------------------------------*/
void priestess_run(edict_t *self, G_Message_t *msg)
{
SetAnim(self, ANIM_WALK);
}
/*-----------------------------------------------
priestess_pain
-----------------------------------------------*/
void priestess_pain(edict_t *self, G_Message_t *msg)
{
int temp, damage;
int force_pain;
ParseMsgParms(msg, "eeiii", &temp, &temp, &force_pain, &damage, &temp);
if (self->curAnimID == ANIM_ATTACK3_GO || self->curAnimID == ANIM_ATTACK3_LOOP ||
self->curAnimID == ANIM_SHIELD_GO)
return;
//Weighted random based on health compared to the maximum it was at
if (force_pain || ((irand(0, self->max_health+50) > self->health) && !irand(0,2)))
{
if (irand(0,1))
gi.sound (self, CHAN_AUTO, sounds[SND_PAIN1], 1, ATTN_NORM, 0);
else
gi.sound (self, CHAN_AUTO, sounds[SND_PAIN2], 1, ATTN_NORM, 0);
SetAnim(self, ANIM_PAIN);
}
}
void priestess_postthink(edict_t *self)
{
//Only display a lifemeter if we have an enemy
if (self->enemy)
{
if (self->dmg < self->max_health)
{
M_ShowLifeMeter( self, self->dmg, self->dmg);
self->dmg+=50;
}
else
{
M_ShowLifeMeter( self, self->health, self->max_health);
}
}
self->next_post_think = level.time + 0.05;
}
/*
Priestess Spawn Functions
*/
void HighPriestessStaticsInit()
{
classStatics[CID_HIGHPRIESTESS].msgReceivers[MSG_STAND] = priestess_stand;
classStatics[CID_HIGHPRIESTESS].msgReceivers[MSG_MISSILE] = priestess_missile;
classStatics[CID_HIGHPRIESTESS].msgReceivers[MSG_RUN] = priestess_run;
classStatics[CID_HIGHPRIESTESS].msgReceivers[MSG_EVADE] = priestess_evade;
classStatics[CID_HIGHPRIESTESS].msgReceivers[MSG_DEATH] = priestess_death;
classStatics[CID_HIGHPRIESTESS].msgReceivers[MSG_PAIN] = priestess_pain;
resInfo.numAnims = NUM_ANIMS;
resInfo.animations = animations;
resInfo.modelIndex = gi.modelindex("models/monsters/highpriestess/tris.fm");
resInfo.numSounds = NUM_SOUNDS;
resInfo.sounds = sounds;
sounds[SND_PAIN1]=gi.soundindex("monsters/highpriestess/pain1.wav");
sounds[SND_PAIN2]=gi.soundindex("monsters/highpriestess/pain2.wav");
sounds[SND_FALL]=gi.soundindex("monsters/highpriestess/fall.wav");
sounds[SND_3BALLATK]=gi.soundindex("monsters/highpriestess/3ballatk.wav");
sounds[SND_BALLHIT]=gi.soundindex("monsters/highpriestess/ballhit.wav");
sounds[SND_WHIRL]=gi.soundindex("weapons/stafftwirl_2.wav");
sounds[SND_BUGS]=gi.soundindex("monsters/highpriestess/bugs.wav");
sounds[SND_BUGBUZZ]=gi.soundindex("monsters/highpriestess/bugbuzz.wav");
sounds[SND_BUGHIT]=gi.soundindex("monsters/highpriestess/bughit.wav");
sounds[SND_ZAP]=gi.soundindex("monsters/highpriestess/zap.wav");
sounds[SND_ZAPHIT]=gi.soundindex("monsters/highpriestess/zaphit.wav");
sounds[SND_HOMINGATK]=gi.soundindex("monsters/highpriestess/homatk.wav");
sounds[SND_HOMINGHIT]=gi.soundindex("monsters/highpriestess/homhit.wav");
sounds[SND_TPORT_IN]=gi.soundindex("monsters/highpriestess/tportin.wav");
sounds[SND_TPORT_OUT]=gi.soundindex("monsters/highpriestess/tpotout.wav");
sounds[SND_SWIPE]=gi.soundindex("weapons/staffswing_2.wav");
sounds[SND_SWIPEHIT]=gi.soundindex("weapons/staffhit_2.wav");
sounds[SND_SWIPEMISS]=gi.soundindex("monsters/seraph/guard/attack_miss.wav");
sounds[SND_SWIPEWALL]=gi.soundindex("weapons/staffhitwall.wav");
classStatics[CID_HIGHPRIESTESS].resInfo = &resInfo;
}
/*QUAKED monster_high_priestess (1 .5 0) (-24 -24 0) (24 24 72)
The High Priestess (what more do you need to know?!?!)
"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)
*/
void SP_monster_high_priestess (edict_t *self)
{
if ((deathmatch->value == 1) && !((int)sv_cheats->value & self_spawn))
{
return;
}
if (!walkmonster_start(self)) // Failed initialization
return;
self->msgHandler = DefaultMsgHandler;
self->classID = CID_HIGHPRIESTESS;
if (!self->health)
{
self->health = HP_HEALTH;
}
//Apply to the end result (whether designer set or not)
self->max_health = self->health = MonsterHealth(self->health);
self->mass = HP_MASS;
self->yaw_speed = 24;
self->movetype = PHYSICSTYPE_STEP;
self->solid=SOLID_BBOX;
self->clipmask = MASK_MONSTERSOLID;
self->s.origin[2] += 36;
VectorSet(self->mins, -24, -24, -36);
VectorSet(self->maxs, 24, 24, 36);
self->materialtype = MAT_INSECT;
self->s.modelindex = classStatics[CID_HIGHPRIESTESS].resInfo->modelIndex;
self->s.skinnum = 0;
self->monsterinfo.jump_time = level.time + 15;
self->monsterinfo.otherenemyname = "monster_rat";
if (self->monsterinfo.scale)
{
self->s.scale = self->monsterinfo.scale = MODEL_SCALE;
}
MG_InitMoods( self );
//Setup her reference points
self->PersistantCFX = gi.CreatePersistantEffect(&self->s,
FX_HP_STAFF,
CEF_OWNERS_ORIGIN | CEF_BROADCAST,
vec3_origin,
"bs",
HP_STAFF_INIT,
self->s.number);
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
self->post_think = priestess_postthink;
self->next_post_think = level.time + 0.1;
self->svflags|=SVF_BOSS;
}