heretic2-sdk/Toolkit/Programming/GameCode/game/m_elflord.c

853 lines
20 KiB
C
Raw Normal View History

1998-11-24 00:00:00 +00:00
//==============================================================================
//
// m_elflord.c
//
// Heretic II
// Copyright 1998 Raven Software
//
//==============================================================================
#include "g_local.h"
#include "Utilities.h"
#include "g_DefaultMessageHandler.h"
#include "g_monster.h"
#include "fx.h"
#include "random.h"
#include "buoy.h"
#include "vector.h"
#include "g_playstats.h"
#include "m_elflord.h"
#include "m_elflord_anims.h"
#include "g_monster.h"
#include "m_stats.h"
#include "g_HitLocation.h"
/*QUAKED monster_elflord (1 .5 0) (-16 -16 -0) (16 16 32)
*/
//Mirrored on client side
enum
{
CW_STAR,
CW_STAR_HIT,
CW_BEAM,
CW_METEOR,
} cwatcher_effect_id_t;
void SpellCastSphereOfAnnihilation(edict_t *Caster,vec3_t StartPos,vec3_t AimAngles,vec3_t AimDir,
float Value,qboolean *ReleaseFlagsPtr);
/*----------------------------------------------------------------------
Elf Lord Base Info
-----------------------------------------------------------------------*/
static animmove_t *animations[] =
{
&elflord_move_idle,
&elflord_move_run,
&elflord_move_charge,
&elflord_move_charge_trans,
&elflord_move_floatback,
&elflord_move_dodgeright,
&elflord_move_dodgeleft,
&elflord_move_soa_begin,
&elflord_move_soa_loop,
&elflord_move_soa_end,
&elflord_move_ls,
&elflord_move_pain,
&elflord_move_death_btrans,
&elflord_move_death_loop,
&elflord_move_shield,
&elflord_move_attack,
&elflord_move_move,
&elflord_move_wait,
&elflord_move_come_to_life,
};
static int sounds[NUM_SOUNDS];
static ClassResourceInfo_t resInfo;
/*-----------------------------------------------
elflord_projectile_blocked
-----------------------------------------------*/
void elflord_projectile_blocked( edict_t *self, trace_t *trace )
{
vec3_t vel;
if ( (!stricmp(trace->ent->classname, "elflord_projectile")) || (trace->ent == self->owner) )
return;
VectorNormalize2(self->velocity, vel);
if (trace->ent && trace->ent->takedamage)
{
T_Damage(trace->ent, self->owner, self->owner, vel, trace->endpos, trace->plane.normal, irand(ELFLORD_STAR_MIN_DAMAGE, ELFLORD_STAR_MAX_DAMAGE), 0, DAMAGE_NORMAL, MOD_DIED);
}
//Create the star explosion
gi.CreateEffect( NULL,
FX_CWATCHER,
0,
trace->endpos,
"bv",
CW_STAR_HIT,
trace->plane.normal);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
elford_Attack
-----------------------------------------------*/
void elford_Attack( edict_t *self )
{
edict_t *projectile;
vec3_t aim, vf, vr, ang, org;
float len, yaw_ofs = -20;
int i;
if (!M_ValidTarget(self, self->enemy))
return;
for(i=0;i<3;i++)
{
projectile = G_Spawn();
projectile->classname = "elflord_projectile";
projectile->solid = SOLID_BBOX;
projectile->movetype = PHYSICSTYPE_FLY;
projectile->clipmask = MASK_SHOT;
AngleVectors(self->s.angles, vf, vr, NULL);
VectorCopy(self->s.origin, projectile->s.origin);
VectorMA(projectile->s.origin, 48, vf, projectile->s.origin);
VectorMA(projectile->s.origin, 16, vr, projectile->s.origin);
projectile->s.origin[2] += 8;
VectorSet(projectile->mins, -2, -2, -2);
VectorSet(projectile->maxs, 2, 2, 2);
projectile->owner = self;
projectile->svflags |= SVF_ALWAYS_SEND;
VectorCopy(self->enemy->s.origin, org);
M_PredictTargetPosition(self->enemy, self->enemy->velocity, skill->value * 2, org);
org[2] += self->enemy->viewheight;
VectorSubtract(org, projectile->s.origin, aim);
len = VectorNormalize(aim);
vectoangles(aim, ang);
ang[YAW] += yaw_ofs;
ang[PITCH] *= -1;
yaw_ofs += 20;
AngleVectors(ang, aim, NULL, NULL);
VectorScale(aim, (600 + (skill->value * 100)), projectile->velocity);
projectile->gravity = 0;
gi.linkentity(projectile);
gi.CreateEffect( &projectile->s,
FX_CWATCHER,
CEF_OWNERS_ORIGIN,
projectile->s.origin,
"bv",
CW_STAR,
self->s.origin);
projectile->isBlocking = projectile->bounced = projectile->isBlocked = elflord_projectile_blocked;
}
gi.sound(self, CHAN_WEAPON, sounds[SND_PROJ1], 1, ATTN_NORM, 0);
}
/*-----------------------------------------------
elflord_StartBeam
-----------------------------------------------*/
void elflord_StartBeam(edict_t *self)
{
edict_t *beam;
trace_t trace;
vec3_t endpos, ang, vr;
vec3_t mins = {-2, -2, -2};
vec3_t maxs = { 2, 2, 2};
if (!M_ValidTarget(self, self->enemy))
return;
beam = G_Spawn();
VectorCopy(self->s.angles, ang);
ang[PITCH] *= -1;
AngleVectors(ang, self->pos1, vr, NULL);
VectorMA(self->s.origin, 32, self->pos1, self->pos2);
self->pos2[2] -= 32;
beam->classname = "elflord_Beam";
beam->solid = SOLID_NOT;
beam->movetype = PHYSICSTYPE_NONE;
beam->owner = self;
beam->svflags |= SVF_ALWAYS_SEND;
VectorMA(self->s.origin, 640, self->pos1, endpos);
gi.trace(self->s.origin, mins, maxs, endpos, self, MASK_SHOT, &trace);
VectorCopy(trace.endpos, beam->s.origin);
beam->pain_debounce_time = level.time + 5;
gi.linkentity(beam);
gi.CreateEffect( &beam->s,
FX_CWATCHER,
CEF_OWNERS_ORIGIN,
beam->s.origin,
"bv",
CW_BEAM,
self->pos2);
gi.sound(self, CHAN_VOICE, sounds[SND_BEAM], 0.5, ATTN_NONE, 0);
self->targetEnt = beam;
}
/*-----------------------------------------------
elflord_EndBeam
-----------------------------------------------*/
void elflord_EndBeam(edict_t *self)
{
self->targetEnt->think = G_FreeEdict;
self->targetEnt->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
elflord_decell
-----------------------------------------------*/
void elflord_decell(edict_t *self, float value)
{
if (self->velocity[0] != 0.0 || self->velocity[1] != 0.0 || self->velocity[2] != 0.0)
{
self->velocity[0] *= value;
self->velocity[1] *= value;
self->velocity[2] *= value;
if (abs(self->velocity[0]) < 1.0)
self->velocity[0] = 0.0;
if (abs(self->velocity[1]) < 1.0)
self->velocity[1] = 0.0;
if (abs(self->velocity[2]) < 1.0)
self->velocity[2] = 0.0;
}
}
/*-----------------------------------------------
elflord_ai_stand
-----------------------------------------------*/
void elflord_ai_stand (edict_t *self, float dist)
{
ai_stand(self, 0);
if (M_ValidTarget(self, self->enemy))
{
return;
}
}
/**************************************/
/*-----------------------------------------------
elflord_finish_death
-----------------------------------------------*/
void elflord_finish_death(edict_t *self)
{
SetAnim(self, ANIM_DIE_LOOP);
}
/*-----------------------------------------------
elfLordGoCharge
-----------------------------------------------*/
void elfLordGoCharge(edict_t *self)
{
SetAnim(self, ANIM_CHARGE);
}
/*-----------------------------------------------
elflord_soa_loop
-----------------------------------------------*/
void elflord_soa_loop(edict_t *self)
{
SetAnim(self, ANIM_ATTACK_SOA_LOOP);
}
/*-----------------------------------------------
elflord_soa_end
-----------------------------------------------*/
void elflord_soa_end(edict_t *self)
{
self->show_hostile = false;
gi.sound(self, CHAN_WEAPON, sounds[SND_SAFIRE], 1, ATTN_NORM, 0);
SetAnim(self, ANIM_ATTACK_SOA_END);
}
/*-----------------------------------------------
elflord_stand
-----------------------------------------------*/
void elflord_stand(edict_t *self, G_Message_t *msg)
{
SetAnim(self, ANIM_HOVER);
}
/*-----------------------------------------------
elflord_flymove
-----------------------------------------------*/
void elflord_flymove (edict_t *self, float dist)
{
vec3_t forward;
if (!M_ValidTarget(self, self->enemy))
return;
VectorSubtract(self->enemy->s.origin, self->s.origin, forward);
self->ideal_yaw = vectoyaw(forward);
M_ChangeYaw(self);
AngleVectors(self->s.angles, forward, NULL, NULL);
VectorMA(self->velocity, dist, forward, self->velocity);
self->velocity[2] = self->enemy->s.origin[2] + 100 - self->absmin[2];
if(!elfLordCheckAttack(self))
MG_CheckEvade(self);
}
/*-----------------------------------------------
elflordRandomRushSound
-----------------------------------------------*/
void elflordRandomRushSound(edict_t *self)
{
}
/*-----------------------------------------------
elflord_run
-----------------------------------------------*/
void elflord_run(edict_t *self, G_Message_t *msg)
{
SetAnim(self, ANIM_FLOAT_FORWARD);
}
/*-----------------------------------------------
elflord_soa_start
-----------------------------------------------*/
void elflord_soa_start(edict_t *self, G_Message_t *msg)
{
vec3_t forward, startpos;
if (!M_ValidTarget(self, self->enemy))
return;
gi.sound(self, CHAN_VOICE, sounds[SND_SACHARGE], 1, ATTN_NORM, 0);
self->show_hostile = true;
AngleVectors(self->s.angles, forward, NULL, NULL);
VectorCopy(self->s.origin, startpos);
SpellCastSphereOfAnnihilation(self,
startpos,
self->s.angles, //v_angle,
forward,
0.0,
&self->show_hostile);
SetAnim(self, ANIM_ATTACK_SOA_BTRANS);
}
/*-----------------------------------------------
elflord_soa_charge
-----------------------------------------------*/
void elflord_soa_charge(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sounds[SND_SACHARGE], 1, ATTN_NORM, 0);
}
/*-----------------------------------------------
elflord_soa_go
-----------------------------------------------*/
void elflord_soa_go(edict_t *self)
{
vec3_t forward;
gi.sound(self, CHAN_VOICE, sounds[SND_SAFIRE], 1, ATTN_NORM, 0);
self->show_hostile = false;
AngleVectors(self->s.angles, forward, NULL, NULL);
SpellCastSphereOfAnnihilation(self,
self->s.origin,
self->s.angles, //v_angle,
forward,
0.0,
&self->show_hostile);
}
/*-----------------------------------------------
elflord_death_start
-----------------------------------------------*/
void elflord_death_start(edict_t *self, G_Message_t *msg)
{
//Turn off a beam if it's on
if (self->targetEnt)
G_FreeEdict(self->targetEnt);
self->health = 0;
self->max_health = 0;
M_ShowLifeMeter( self, 0, 0);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
elflord_pain
-----------------------------------------------*/
void elflord_pain (edict_t *self, G_Message_t *msg)
{
if (irand(0,9))
return;
if(!irand(0, 1))
gi.sound(self, CHAN_VOICE, sounds[SND_PAIN1], 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sounds[SND_PAIN2], 1, ATTN_NORM, 0);
}
/*-----------------------------------------------
elflordSound
-----------------------------------------------*/
void elflordSound(edict_t *self, float channel, float sndindex, float atten)
{
gi.sound(self, channel, sounds[(int)(sndindex)], 1, atten, 0);
}
/*-----------------------------------------------
elflord_FindMoveTarget
-----------------------------------------------*/
void elflord_FindMoveTarget (edict_t *self)
{
edict_t *movetarg = NULL, *lastvalid = NULL;
vec3_t vel, target;
float len;
while((movetarg = oldfindradius(movetarg, self->s.origin, 640)) != NULL)
{
//Must be a path_corner
if (strcmp(movetarg->classname, "path_corner"))
continue;
//Must be a specified path_corner too
if (strcmp(movetarg->targetname, "elflord"))
continue;
if (vhlen(movetarg->s.origin, self->s.origin) < 64)
continue;
lastvalid = movetarg;
if (irand(0,1))
continue;
//TODO: Determine a velocity to get us here
VectorCopy(movetarg->s.origin, target);
target[2] = self->s.origin[2];
VectorSubtract(target, self->s.origin, vel);
len = VectorNormalize(vel);
len = ( ((len / 10) / FRAMETIME) * 2 );
VectorScale(vel, len, self->velocity);
return;
}
//We randomly skipped the last possible spot, so just use that
if (lastvalid)
{
VectorCopy(lastvalid->s.origin, target);
target[2] = self->s.origin[2];
VectorSubtract(target, self->s.origin, vel);
len = VectorNormalize(vel);
len = ( ((len / 10) / FRAMETIME) * 2 );
VectorScale(vel, len, self->velocity);
}
}
/*-----------------------------------------------
elflord_track
-----------------------------------------------*/
void elflord_track(edict_t *self)
{
trace_t trace;
vec3_t dir, newdir, endpos;
vec3_t mins = {-2, -2, -2};
vec3_t maxs = { 2, 2, 2};
if (!M_ValidTarget(self, self->enemy))
{
//Remove the beam
self->targetEnt->think = G_FreeEdict;
self->targetEnt->nextthink = level.time + 0.1;
//Don't finish what we were doing
SetAnim(self, ANIM_HOVER);
return;
}
VectorSubtract(self->enemy->s.origin, self->pos2, dir);
VectorNormalize(dir);
VectorScale(self->pos1, 3 - (skill->value * 0.5), newdir);
VectorAdd(newdir, dir, newdir);
VectorScale(newdir, 1 / ((3 - (skill->value * 0.5)) + 1), newdir);
VectorNormalize(newdir);
VectorMA(self->s.origin, 640, newdir, endpos);
gi.trace(self->s.origin, mins, maxs, endpos, self, MASK_SHOT, &trace);
if (trace.ent && trace.ent->takedamage)
{
T_Damage(trace.ent, self, self, newdir, trace.endpos, trace.plane.normal, irand(ELFLORD_BEAM_MIN_DAMAGE, ELFLORD_BEAM_MAX_DAMAGE), 0, DAMAGE_NORMAL, MOD_DIED);
}
VectorCopy(trace.endpos, self->targetEnt->s.origin);
vectoangles(newdir, self->s.angles);
ai_charge2(self, 0);
VectorCopy(newdir, self->pos1);
}
/*-----------------------------------------------
elflord_FixAngles
-----------------------------------------------*/
void elflord_FixAngles(edict_t *self)
{
self->s.angles[PITCH] = 0;
}
/*-----------------------------------------------
elflord_MoveToFinalPosition
-----------------------------------------------*/
void elflord_MoveToFinalPosition( edict_t *self )
{
edict_t *movetarg = NULL;
vec3_t vel, target;
float len;
while((movetarg = oldfindradius(movetarg, self->s.origin, 640)) != NULL)
{
//Must be a path_corner
if (strcmp(movetarg->classname, "path_corner"))
continue;
//Must be a specified path_corner too
if (strcmp(movetarg->targetname, "elflord_final"))
continue;
VectorCopy(movetarg->s.origin, target);
target[2] = self->s.origin[2];
VectorSubtract(target, self->s.origin, vel);
len = VectorNormalize(vel);
len = ( ((len / 10) / FRAMETIME) * 2 );
VectorScale(vel, len, self->velocity);
return;
}
}
/*-----------------------------------------------
elfLordCheckAttack
-----------------------------------------------*/
qboolean elfLordCheckAttack (edict_t *self)
{
int chance,
p_chance = 0,
soa_chance = 0,
beam_chance = 0,
move_chance = 0;
if (!M_ValidTarget(self, self->enemy))
{
SetAnim(self, ANIM_HOVER);
return false;
}
elflord_decell(self, 0.8);
if (self->count < self->max_health)
{
VectorClear(self->velocity);
SetAnim(self, ANIM_COME_TO_LIFE);
return false;
}
if (self->health < self->max_health / 3)
{//Last stage
if (!self->dmg)
{
elflord_MoveToFinalPosition(self);
SetAnim(self, ANIM_MOVE);
self->dmg = 1;
return false;
}
if (coop->value)
{
p_chance = 50;
soa_chance = 50;
beam_chance = 0;
}
else
{
p_chance = 5;
soa_chance = 5;
beam_chance = 90;
}
}
else if (self->health < self->max_health / 1.5)
{//Second stage
p_chance = 25;
soa_chance = 75;
beam_chance = 0;
}
else
{//First stage
p_chance = 90;
soa_chance = 0;
beam_chance = 0;
}
chance = irand(0,100);
if(irand(0,100) < p_chance)
{
SetAnim(self, ANIM_ATTACK);
return false;
}
else if(irand(0,100) < beam_chance)
{
SetAnim(self, ANIM_ATTACK_LS);
return false;
}
else if(irand(0,100) < soa_chance)
{
SetAnim(self, ANIM_ATTACK_SOA_BTRANS);
return false;
}
if (!self->dmg)
{
elflord_FindMoveTarget(self);
SetAnim(self, ANIM_MOVE);
return false;
}
return false;
}
/*-----------------------------------------------
elfLordPause
-----------------------------------------------*/
void elfLordPause(edict_t *self)
{
elfLordCheckAttack(self);
}
/*-----------------------------------------------
elfLordWakeUp
-----------------------------------------------*/
void elfLordWakeUp (edict_t *self, G_Message_t *msg)
{
SetAnim(self, ANIM_COME_TO_LIFE);
}
/*-----------------------------------------------
elflord_face
-----------------------------------------------*/
void elflord_face(edict_t *self)
{
if (!M_ValidTarget(self, self->enemy))
return;
ai_charge2(self, 0);
}
static ClassResourceInfo_t resInfo;
/*-----------------------------------------------
elflord_SlideMeter
-----------------------------------------------*/
void elflord_SlideMeter( edict_t *self )
{
self->velocity[2] = 32;
if (self->count < self->max_health)
{
M_ShowLifeMeter( self, self->count, self->count);
self->count += self->max_health / 20;
}
}
/*-----------------------------------------------
elflord_PreThink
-----------------------------------------------*/
void elflord_PreThink( edict_t *self )
{
if (self->enemy && self->count >= self->max_health)
{
M_ShowLifeMeter( self, self->health, self->max_health);
}
self->next_pre_think = level.time + 0.1;
}
/*-----------------------------------------------
ElflordStaticsInit
-----------------------------------------------*/
void ElflordStaticsInit()
{
classStatics[CID_ELFLORD].msgReceivers[MSG_STAND] = elflord_stand;
classStatics[CID_ELFLORD].msgReceivers[MSG_RUN] = elflord_run;
classStatics[CID_ELFLORD].msgReceivers[MSG_FLY] = elflord_run;
classStatics[CID_ELFLORD].msgReceivers[MSG_DEATH] = elflord_death_start;
classStatics[CID_ELFLORD].msgReceivers[MSG_MISSILE] = elflord_soa_start;
classStatics[CID_ELFLORD].msgReceivers[MSG_PAIN] = elflord_pain;
classStatics[CID_ELFLORD].msgReceivers[MSG_SIGHT] = elfLordWakeUp;
resInfo.numAnims = NUM_ANIMS;
resInfo.animations = animations;
resInfo.modelIndex = gi.modelindex("models/monsters/elflord/tris.fm");
sounds[SND_PAIN1] = gi.soundindex ("monsters/elflord/pain1.wav");
sounds[SND_PAIN2] = gi.soundindex ("monsters/elflord/pain2.wav");
sounds[SND_DIE] = gi.soundindex ("monsters/elflord/death1.wav");
//use sphere sounds
sounds[SND_SACHARGE] = gi.soundindex ("weapons/SphereGrow.wav");
sounds[SND_SAFIRE] = gi.soundindex ("weapons/SphereFire.wav");
sounds[SND_SAHIT] = gi.soundindex ("weapons/SphereImpact.wav");
sounds[SND_PROJ1] = gi.soundindex ("monsters/elflord/shoot.wav");
sounds[SND_BEAM] = gi.soundindex ("monsters/elflord/beam.wav");
resInfo.numSounds = NUM_SOUNDS;
resInfo.sounds = sounds;
classStatics[CID_ELFLORD].resInfo = &resInfo;
}
/*QUAKED SP_monster_elflord (0.5 0.5 1) (-24 -24 -64) (24 24 16)
Celestial Watcher
"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_elflord (edict_t *self)
{
// Generic Monster Initialization
if (!flymonster_start(self))
return; // Failed initialization
self->msgHandler = DefaultMsgHandler;
self->classID = CID_ELFLORD;
if (!self->health)
self->health = ELFLORD_HEALTH;
self->max_health = self->health = MonsterHealth(self->health);
self->mass = ELFLORD_MASS;
self->yaw_speed = 20;
self->movetype=PHYSICSTYPE_STEP;
self->flags |= FL_FLY;
self->gravity = 0.0;
self->clipmask= MASK_MONSTERSOLID;
self->svflags |= SVF_ALWAYS_SEND|SVF_BOSS|SVF_TAKE_NO_IMPACT_DMG;
self->materialtype = MAT_FLESH;
self->solid=SOLID_BBOX;
VectorSet(self->mins, -24, -24, -64);
VectorSet(self->maxs, 24, 24, 16);
VectorClear(self->velocity);
self->s.modelindex = classStatics[CID_ELFLORD].resInfo->modelIndex;
self->dmg = 0;
self->pre_think = elflord_PreThink;
self->s.skinnum = 0;
self->monsterinfo.scale = 2.0;
self->count = 1;
self->monsterinfo.otherenemyname = "player";
self->s.scale = 2.0;
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
self->next_pre_think = level.time + 0.1;
self->s.renderfx |= RF_GLOW;
gi.linkentity(self);
}