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

2069 lines
49 KiB
C

//==============================================================================
//
// m_morcalavin.c
//
// Heretic II
// Copyright 1998 Raven Software
//
//==============================================================================
#include "g_local.h"
#include "m_morcalavin.h"
#include "m_morcalavin_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 "p_main.h"
#include "g_misc.h"
#include "m_stats.h"
#include "g_playstats.h"
#include "p_actions.h"
static int sounds[NUM_SOUNDS];
static ClassResourceInfo_t resInfo;
void create_morcalavin_proj(edict_t *self,edict_t *proj);
void BecomeDebris(edict_t *self);
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,
};
static animmove_t *animations[NUM_ANIMS] =
{
&morcalavin_move_float,
&morcalavin_move_hurtidle,
&morcalavin_move_attack1,
&morcalavin_move_attack2,
&morcalavin_move_attack2b,
&morcalavin_move_attack3,
&morcalavin_move_def1,
&morcalavin_move_def2,
&morcalavin_move_walk,
&morcalavin_move_fly,
&morcalavin_move_getup,
&morcalavin_move_retort,
&morcalavin_move_fall,
&morcalavin_move_glide,
&morcalavin_move_ground_attack,
&morcalavin_move_tracking_attack1,
&morcalavin_move_attack4,
};
void morcalavin_end_retort(edict_t *self)
{
SetAnim(self, ANIM_WALK);
}
void morcalavin_check_lightning2(edict_t *self)
{
vec3_t vel, org, org2;
float dist;
if (!self->owner->enemy)
{
self->nextthink = level.time + 0.1;
return;
}
VectorSubtract(self->s.origin, self->owner->enemy->s.origin, vel);
dist = VectorNormalize(vel);
if (dist < 72)
{
T_Damage(self->owner->enemy, self, self->owner, vel, vec3_origin, vec3_origin, 2, 0, DAMAGE_SPELL, MOD_SHIELD);
VectorNormalize2(self->velocity, vel);
VectorMA(self->s.origin, 600*FRAMETIME, vel, org);
VectorCopy(self->owner->enemy->s.origin, org2);
org2[2] += irand(-16, 32);
gi.CreateEffect(NULL, FX_LIGHTNING, 0,
org, "vbb", org2, (byte) irand(2,4), (byte) 0);
}
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
morcalavin_big_shot
-----------------------------------------------*/
void morcalavin_big_shot( edict_t *self )
{
edict_t *proj;
vec3_t vf, vr;
proj = G_Spawn();
proj->solid = SOLID_BBOX;
VectorSet(proj->mins, -4, -4, -4);
VectorSet(proj->maxs, 4, 4, 4);
proj->movetype = PHYSICSTYPE_FLY;
proj->gravity = 0;
proj->clipmask = MASK_SHOT;
proj->think = morcalavin_check_lightning2;
proj->nextthink = level.time + 0.1;
proj->isBlocked = proj->isBlocking = proj->bounced = morcalavin_proj3_blocked;
proj->owner = self;
self->target_ent = proj;
AngleVectors(self->s.angles, vf, vr, NULL);
VectorMA(self->s.origin, 24, vf, proj->s.origin);
VectorMA(proj->s.origin, 12, vr, proj->s.origin);
proj->s.origin[2] += 32;
VectorSubtract(self->enemy->s.origin, proj->s.origin, vf);
VectorNormalize(vf);
VectorScale(vf, 600, proj->velocity);
proj->s.effects = EF_MARCUS_FLAG1 | EF_CAMERA_NO_CLIP;
gi.linkentity(proj);
//Create the effect
gi.CreateEffect(&proj->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
proj->s.origin,
"bv",
FX_MORK_MISSILE,
proj->s.origin);
gi.sound (self, CHAN_AUTO, sounds[SND_PPCHARGE], 1, ATTN_NORM, 0);
}
/*-----------------------------------------------
morcalavin_proj_track
-----------------------------------------------*/
void morcalavin_proj_track( 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.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);
if (self->delay + 0.05 < 4.0)
self->delay += 0.05;
if (self->ideal_yaw + 10 < 1000)
self->ideal_yaw += 10;
oldvelmult = self->delay;
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;
}
/*-----------------------------------------------
morcalavin_tracking_projectile
-----------------------------------------------*/
void morcalavin_tracking_projectile ( edict_t *self, float pitch, float yaw, float roll)
{
edict_t *proj;
vec3_t vf, vr, vu, ang;
proj = G_Spawn();
proj->monsterinfo.attack_finished = level.time + 10;
proj->owner = self;
proj->svflags |= SVF_ALWAYS_SEND;
proj->movetype = PHYSICSTYPE_FLY;
proj->gravity = 0;
proj->solid = SOLID_BBOX;
proj->classname = "Morcalavin_Tracking_Missile";
proj->dmg = 1.0;
proj->s.scale = 1.0;
proj->clipmask = MASK_SHOT;
proj->nextthink = level.time + 0.1;
proj->delay = 1.0;
proj->bounced = morcalavin_proj1_blocked;
proj->isBlocking = morcalavin_proj1_blocked;
proj->isBlocked = morcalavin_proj1_blocked;
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);
//Determine the starting velocity of the ball
VectorCopy(self->s.angles, ang);
ang[PITCH] += pitch;
ang[YAW] += yaw;
ang[ROLL] += roll;
AngleVectors(ang, vf, vr, vu);
VectorMA(self->s.origin, 24, vf, proj->s.origin);
VectorMA(proj->s.origin, -16, vr, proj->s.origin);
VectorMA(proj->s.origin, 52, vu, proj->s.origin);
VectorScale(vu, 16, proj->velocity);
proj->ideal_yaw = 300;
proj->think = morcalavin_proj_track;
if (skill->value < 1)
proj->nextthink = level.time + 2;
else
proj->nextthink = level.time + flrand(1.5, 3.0);
gi.sound (self, CHAN_AUTO, sounds[SND_HOMING], 1, ATTN_NORM, 0);
gi.CreateEffect(&proj->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
NULL,
"bv",
FX_MORK_TRACKING_MISSILE,
proj->velocity);
}
/*-----------------------------------------------
morcalavin_proj2_blocked
-----------------------------------------------*/
void morcalavin_proj2_blocked( edict_t *self, trace_t *trace )
{
edict_t *proj;
vec3_t hitDir;
byte exp;
if (trace->ent == self->owner)
return;
//Reflection stuff
if(EntReflecting(trace->ent, true, true))
{
proj = G_Spawn();
create_morcalavin_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);
exp = HPMISSILE1_EXPLODE;
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;
}
if ( trace->ent->takedamage )
{
VectorCopy( self->velocity, hitDir );
VectorNormalize( hitDir );
T_Damage( trace->ent, self, self->owner, hitDir, self->s.origin, trace->plane.normal, 40, 0, DAMAGE_SPELL | DAMAGE_NO_KNOCKBACK,MOD_DIED );
}
gi.CreateEffect(NULL,
FX_M_EFFECTS,
0,
self->s.origin,
"bv",
FX_MORK_MISSILE_HIT,
trace->plane.normal);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
morcalavin_proj3_blocked
-----------------------------------------------*/
void morcalavin_proj3_blocked( edict_t *self, trace_t *trace )
{
edict_t *proj;
vec3_t hitDir;
byte exp;
if (trace->ent == self->owner)
return;
//Reflection stuff
if(EntReflecting(trace->ent, true, true))
{
proj = G_Spawn();
create_morcalavin_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);
exp = HPMISSILE1_EXPLODE;
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;
}
if ( trace->ent->takedamage )
{
VectorCopy( self->velocity, hitDir );
VectorNormalize( hitDir );
T_Damage( trace->ent, self, self->owner, hitDir, self->s.origin, trace->plane.normal, irand(3,5), 0, DAMAGE_SPELL | DAMAGE_NO_KNOCKBACK,MOD_DIED );
}
gi.CreateEffect(NULL,
FX_M_EFFECTS,
0,
self->s.origin,
"bv",
FX_MORK_MISSILE_HIT,
trace->plane.normal);
self->think = G_FreeEdict;
self->nextthink = level.time + 0.1;
}
void morcalavin_check_lightning(edict_t *self)
{
vec3_t vel, org, org2;
float dist;
if (!self->owner || !self->owner->enemy)
{
self->nextthink = level.time + 0.1;
return;
}
VectorSubtract(self->s.origin, self->owner->enemy->s.origin, vel);
dist = VectorNormalize(vel);
if (dist < 150)
{
T_Damage(self->owner->enemy, self, self->owner, vel, vec3_origin, vec3_origin, irand(2,4), 0, DAMAGE_SPELL, MOD_SHIELD);
VectorNormalize2(self->velocity, vel);
VectorMA(self->s.origin, 400*FRAMETIME, vel, org);
VectorCopy(self->owner->enemy->s.origin, org2);
org2[2] += irand(-16, 32);
gi.CreateEffect(NULL, FX_LIGHTNING, 0,
org, "vbb", org2, (byte) irand(2,4), (byte) 0);
if (skill->value < 1)
{
VectorScale(vel, 1.0, vel);
VectorNormalize(self->velocity);
VectorAdd(self->velocity, vel, self->velocity);
VectorScale(self->velocity, 400, self->velocity);
}
}
self->nextthink = level.time + 0.1;
}
void morcalavin_missile_update(edict_t *self)
{
vec3_t vf, vr, endpos;
AngleVectors(self->owner->s.angles, vf, vr, NULL);
switch( self->owner->s.frame )
{
case FRAME_atakc3:
VectorMA(self->owner->s.origin, 16, vf, self->s.origin);
VectorMA(self->s.origin, 10, vr, self->s.origin);
self->s.origin[2] += 24;
break;
case FRAME_atakc4:
VectorMA(self->owner->s.origin, 16, vf, self->s.origin);
VectorMA(self->s.origin, 12, vr, self->s.origin);
self->s.origin[2] += 26;
break;
case FRAME_atakc5:
VectorMA(self->owner->s.origin, 16, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 28;
break;
case FRAME_atakc6:
VectorMA(self->owner->s.origin, 15, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 32;
break;
case FRAME_atakc7:
VectorMA(self->owner->s.origin, 15, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 36;
break;
case FRAME_atakc8:
VectorMA(self->owner->s.origin, 14, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 37;
break;
case FRAME_atakc9:
VectorMA(self->owner->s.origin, 14, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 38;
break;
case FRAME_atakc10:
VectorMA(self->owner->s.origin, 14, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 39;
break;
case FRAME_atakc11:
VectorMA(self->owner->s.origin, 14, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 40;
break;
case FRAME_atakc12:
VectorMA(self->owner->s.origin, 14, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 41;
break;
case FRAME_atakc13:
VectorMA(self->owner->s.origin, 14, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 42;
break;
case FRAME_atakc14:
VectorMA(self->owner->s.origin, 14, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 42;
break;
case FRAME_atakc15:
VectorMA(self->owner->s.origin, 16, vf, self->s.origin);
VectorMA(self->s.origin, 14, vr, self->s.origin);
self->s.origin[2] += 42;
VectorSet(endpos, self->owner->enemy->s.origin[0], self->owner->enemy->s.origin[1], self->owner->enemy->s.origin[2] + self->owner->enemy->viewheight);
VectorSubtract(endpos, self->s.origin, vf);
VectorNormalize(vf);
VectorScale(vf, 400, self->velocity);
self->think = morcalavin_check_lightning;
self->nextthink = level.time + 0.1;
return;
break;
}
gi.linkentity(self);
self->nextthink = level.time + 0.1;
}
void morcalavin_start_missile(edict_t *self)
{
edict_t *proj;
vec3_t vf, vr;
proj = G_Spawn();
proj->solid = SOLID_BBOX;
VectorSet(proj->mins, -4, -4, -4);
VectorSet(proj->maxs, 4, 4, 4);
proj->movetype = PHYSICSTYPE_FLY;
proj->gravity = 0;
proj->clipmask = MASK_SHOT;
proj->think = morcalavin_missile_update;
proj->nextthink = level.time + 0.1;
proj->isBlocked = proj->isBlocking = proj->bounced = morcalavin_proj2_blocked;
proj->owner = self;
self->target_ent = proj;
AngleVectors(self->s.angles, vf, vr, NULL);
VectorMA(self->s.origin, 16, vf, proj->s.origin);
VectorMA(proj->s.origin, 10, vr, proj->s.origin);
proj->s.origin[2] += 24;
proj->s.effects = EF_MARCUS_FLAG1 | EF_CAMERA_NO_CLIP;
gi.linkentity(proj);
//Create the effect
gi.CreateEffect(&proj->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
proj->s.origin,
"bv",
FX_MORK_MISSILE,
proj->s.origin);
gi.sound (self, CHAN_AUTO, sounds[SND_PPCHARGE], 1, ATTN_NORM, 0);
}
void morcalavin_release_missile(edict_t *self)
{
gi.sound (self, CHAN_AUTO, sounds[SND_PPFIRE], 1, ATTN_NORM, 0);
}
/*-----------------------------------------------
morcalavin_proj1_blocked
-----------------------------------------------*/
void morcalavin_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, "Morcalavin_Missile"))
return;
//Reflection stuff
if(EntReflecting(trace->ent, true, true))
{
proj = G_Spawn();
create_morcalavin_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);
exp = HPMISSILE1_EXPLODE;
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
exp = HPMISSILE1_EXPLODE;
damage = irand(4, 8);
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;
}
/*-----------------------------------------------
create_morcalavin_proj
-----------------------------------------------*/
// create the guts of morcalavin's projectile
void create_morcalavin_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 = "Morcalavin_Missile";
proj->dmg = 1.0;
proj->s.scale = 1.0;
proj->clipmask = MASK_SHOT;
proj->nextthink = level.time + 0.1;
proj->isBlocked = proj->isBlocking = proj->bounced = morcalavin_proj1_blocked;
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);
}
/*-----------------------------------------------
morcalavin_taunt_shot
-----------------------------------------------*/
void morcalavin_taunt_shot(edict_t *self)
{
//Bang
edict_t *proj;
vec3_t vf, vr, predPos;
vec3_t ang, vel, startOfs, angles;
if (!self->enemy)
return;
//Only predict once for all the missiles
M_PredictTargetPosition ( self->enemy, self->enemy->velocity, 5, predPos );
AngleVectors(self->s.angles, vf, vr, NULL);
VectorMA(self->s.origin, -8, vf, startOfs);
VectorMA(startOfs, -16, vr, startOfs);
startOfs[2] += 8;
VectorSubtract(predPos, startOfs, vf);
VectorNormalize(vf);
vectoangles( vf, angles );
// Spawn the projectile
proj = G_Spawn();
create_morcalavin_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(700,800) + (skill->value * 100), proj->velocity);
vectoangles(proj->velocity, proj->s.angles);
gi.sound (self, CHAN_AUTO, sounds[SND_HOMING], 1, ATTN_NORM, 0);
gi.CreateEffect(&proj->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
NULL,
"bv",
FX_MORK_TRACKING_MISSILE,
proj->s.origin);
gi.linkentity(proj);
}
void morcalavin_phase_out (edict_t *self)
{
int interval = 40;
if(self->s.color.a > interval)
{
self->s.color.a -= irand(interval/2, interval);
self->pre_think = morcalavin_phase_out;
self->next_pre_think = level.time + 0.05;
}
else
{
self->s.color.a = 0;
self->pre_think = NULL;
self->next_pre_think = -1;
}
}
void morcalavin_phase_in (edict_t *self)
{
int interval = 12;
if(self->s.color.a < 255 - interval)
{
self->s.color.a += irand(interval/2, interval);
self->pre_think = morcalavin_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->monsterinfo.lefty >= 6)
{
self->pre_think = NULL;
self->next_pre_think = -1;
}
else
morcalavin_init_phase_out(self);
}
}
void morcalavin_init_phase_out (edict_t *self)
{
//Become tangible once more
self->solid = SOLID_NOT;
self->pre_think = morcalavin_phase_out;
self->next_pre_think = level.time + FRAMETIME;
self->svflags |= SVF_NO_AUTOTARGET;
}
void morcalavin_init_phase_in (edict_t *self)
{
self->pre_think = morcalavin_phase_in;
self->next_pre_think = level.time + FRAMETIME;
}
/*
morcalavin Shield Functions
*/
void mork_laugh (edict_t *self)
{
gi.sound(self, CHAN_VOICE, sounds[SND_LAUGH], 1, ATTN_NONE, 0);
self->post_think = NULL;
self->next_post_think = -1;
}
void mork_check_killed_enemy(edict_t *attacker)
{
if(attacker->enemy && attacker->enemy->health<=0)
{
attacker->post_think = mork_laugh;
attacker->next_post_think = level.time + flrand(1.3, 2.3);
}
}
void projectile_veer(edict_t *self, float amount)
{
//useful code for making projectiles wander randomly to a specified degree
vec3_t veerdir;
float speed;
speed = VectorLength(self->velocity);
VectorSet(veerdir,
flrand(-amount, amount),
flrand(-amount, amount),
flrand(-amount, amount));
VectorAdd(veerdir, self->velocity, self->velocity);
VectorNormalize(self->velocity);
Vec3ScaleAssign(speed, self->velocity);
//check to see if this is needed
// self.angles=vectoangles(self.velocity);
}
void projectile_homethink (edict_t *self)
{
vec3_t olddir, newdir, huntdir;
float oldvelmult , newveldiv, speed_mod;
float turnspeed;
if(self->delay)
turnspeed = self->delay;
else
turnspeed = 1.3;
VectorCopy(self->velocity, olddir);
VectorNormalize(olddir);
VectorSubtract(self->enemy->s.origin, self->s.origin, huntdir);
VectorNormalize(huntdir);
oldvelmult = turnspeed;
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);
if(self->random)
projectile_veer(self, self->random);
}
/*
morcalavin Projectile Functions
*/
/*-----------------------------------------------
morcalavin_proj1_think
-----------------------------------------------*/
void morcalavin_proj1_think( edict_t *self )
{
if(clear_visible(self, self->enemy))
projectile_homethink(self);
self->nextthink = level.time + 0.1;
}
/*-----------------------------------------------
morcalavin_proj1_blocked
-----------------------------------------------*/
void beam_blocked( edict_t *self, trace_t *trace )
{
// edict_t *proj;
/*
if(EntReflecting(trace->ent, true, true) && self->reflect_debounce_time)
{
proj = G_Spawn();
create_morcalavin_proj(self,proj);
proj->isBlocked = beam_blocked;
proj->classname = "M_ref_HMissile";
proj->owner = self->owner;
proj->ideal_yaw = self->ideal_yaw;
proj->classID = self->classID;
Create_rand_relect_vect(self->velocity, proj->velocity);
Vec3ScaleAssign(proj->ideal_yaw,proj->velocity);
vectoangles(proj->velocity, proj->s.angles);
gi.CreateEffect(&proj->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
NULL,
"bv",
proj->classID,
proj->velocity);
proj->reflect_debounce_time = self->reflect_debounce_time -1;
gi.linkentity(proj);
G_SetToFree(self);
return;
}*/
if (trace->ent->takedamage )
{
vec3_t hitDir;
float damage = flrand(MORK_DMG_PROJ1_MIN, MORK_DMG_PROJ1_MAX);
if(self->dmg)
damage += self->dmg;
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.sound(self, CHAN_WEAPON, gi.soundindex("monsters/seraph/guard/spellhit.wav"), 1, ATTN_NORM, 0);
gi.CreateEffect(&self->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
self->s.origin,
"bv",
FX_M_MISC_EXPLODE,
vec3_origin);
G_SetToFree(self);
}
void beam_think (edict_t *self)
{
self->think = NULL;
self->nextthink = -1;
}
void morcalavin_beam( edict_t *self)
{
edict_t *proj;
vec3_t Forward, vf, vr, endpos;
// Spawn the projectile
proj = G_Spawn();
create_morcalavin_proj(self,proj);
proj->reflect_debounce_time = MAX_REFLECT;
proj->classname = "M_Beam";
proj->isBlocked = beam_blocked;
proj->owner = self;
AngleVectors(self->s.angles, vf, vr, NULL);
VectorMA(proj->s.origin, 48, vf, proj->s.origin);
VectorMA(proj->s.origin, -20, vr, proj->s.origin);
proj->s.origin[2] += 16;
VectorSet(endpos, self->enemy->s.origin[0], self->enemy->s.origin[1], self->enemy->s.origin[2] + self->enemy->viewheight);
VectorSubtract(endpos, proj->s.origin, Forward);
VectorNormalize(Forward);
VectorScale(Forward, 1250, proj->velocity);
vectoangles(proj->velocity, proj->s.angles);
proj->dmg = flrand(MORK_DMG_BEAM_MIN, MORK_DMG_BEAM_MAX);
proj->think=beam_think;
proj->nextthink = level.time + 1;
gi.sound(self, CHAN_WEAPON, gi.soundindex("monsters/seraph/guard/spell.wav"), 1, ATTN_NORM, 0);
//TODO: Spawn a muzzle flash
gi.CreateEffect(&proj->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
vec3_origin,
"bv",
FX_M_BEAM,
proj->s.angles);
gi.linkentity(proj);
}
void morcalavin_beam2( edict_t *self)
{
edict_t *proj;
vec3_t Forward, vf, vr;
// Spawn the projectile
proj = G_Spawn();
create_morcalavin_proj(self,proj);
proj->reflect_debounce_time = MAX_REFLECT;
proj->classname = "M_Beam";
proj->isBlocked = beam_blocked;
proj->owner = self;
AngleVectors(self->s.angles, vf, vr, NULL);
VectorMA(proj->s.origin, 48, vf, proj->s.origin);
VectorMA(proj->s.origin, -20, vr, proj->s.origin);
proj->s.origin[2] += 16;
VectorSubtract(self->enemy->s.origin, proj->s.origin, Forward);
VectorNormalize(Forward);
VectorScale(Forward, 1250, proj->velocity);
vectoangles(proj->velocity, proj->s.angles);
proj->dmg = flrand(MORK_DMG_BEAM_MIN, MORK_DMG_BEAM_MAX);
proj->think=beam_think;
proj->nextthink = level.time + 1;
gi.sound(self, CHAN_WEAPON, gi.soundindex("monsters/seraph/guard/spell.wav"), 1, ATTN_NORM, 0);
//TODO: Spawn a muzzle flash
gi.CreateEffect(&proj->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
vec3_origin,
"bv",
FX_M_BEAM,
proj->s.angles);
gi.linkentity(proj);
}
/*-----------------------------------------------
morcalavin_ground_attack
-----------------------------------------------*/
void morcalavin_ground_attack( edict_t *self )
{
gi.CreateEffect(NULL,
FX_M_EFFECTS,
0,
self->s.origin,
"bv",
FX_GROUND_ATTACK,
self->enemy->s.origin);
}
/*-----------------------------------------------
morcalavin_quake_pause
-----------------------------------------------*/
void morcalavin_quake_pause( edict_t *self )
{
if (self->monsterinfo.flee_finished)
{
self->monsterinfo.flee_finished = false;
SetAnim(self, ANIM_GROUND_ATTACK);
}
}
/*-----------------------------------------------
morcalavin_quake
-----------------------------------------------*/
void morcalavin_quake(edict_t *self, float pitch_ofs, float yaw_ofs, float roll_ofs)
{
vec3_t org, vf, vr;
//Create the effect
AngleVectors(self->s.angles, vf, vr, NULL);
VectorMA(self->s.origin, 44, vf, org);
VectorMA(org, -14, vr, org);
org[2] += self->mins[2];
gi.CreateEffect( NULL,
FX_M_EFFECTS,
0,
self->s.origin,
"bv",
FX_QUAKE_RING,
org);
//Check to see if the player is on the ground, and if he is, then knock him down
if (self->enemy && self->enemy->groundentity && self->enemy->client)
{
//Knock the player down
P_KnockDownPlayer(&self->enemy->client->playerinfo);
//Denote we've done so to follow it with an attack
self->monsterinfo.flee_finished = true;
}
}
/*
Morcalavin Helper Functions
*/
/*-----------------------------------------------
morcalavin_move
-----------------------------------------------*/
void morcalavin_move( edict_t *self, float vf, float vr, float vu )
{
}
void morcalavin_rush_sound (edict_t *self)
{
vec3_t forward;
gi.sound(self, CHAN_BODY, sounds[SND_RUSH], 1, ATTN_NORM, 0);
AngleVectors(self->s.angles, forward, NULL, NULL);
VectorScale(forward, 250, self->velocity);
self->velocity[2] = 150;
}
/*-----------------------------------------------
morcalavin_pause
-----------------------------------------------*/
void morcalavin_pause( edict_t *self )
{
if (self->monsterinfo.lefty < 6 && self->health > 0)
{
SetAnim(self, ANIM_FLOAT);
return;
}
self->takedamage = DAMAGE_YES;
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:
case AI_MOOD_NAVIGATE:
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
break;
case AI_MOOD_DELAY:
case AI_MOOD_STAND:
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
break;
case AI_MOOD_WANDER:
SetAnim(self, ANIM_WALK);
break;
default :
#ifdef _DEVEL
gi.dprintf("mork: Unusable mood %d!\n", self->ai_mood);
#endif
break;
}
}
/*-----------------------------------------------
morcalavin_idle
-----------------------------------------------*/
void morcalavin_idle(edict_t *self)
{
}
/*
morcalavin Message Functions
*/
/*-----------------------------------------------
morcalavin_death
-----------------------------------------------*/
void morcalavin_death( edict_t *self, G_Message_t *msg )
{
self->monsterinfo.stepState++;
}
void morcalavin_retort( edict_t *self)
{
self->msgHandler = DefaultMsgHandler;
SetAnim(self, ANIM_RETORT);
}
void morcalavin_getup( edict_t *self)
{
if (self->monsterinfo.lefty==7)
{
if (self->monsterinfo.attack_finished > 0 && self->monsterinfo.attack_finished < level.time)
{
self->monsterinfo.attack_finished = -1;
gi.sound(self, CHAN_VOICE, sounds[SND_REVIVE], 1, ATTN_NORM, 0);
SetAnim(self, ANIM_GETUP);
}
}
}
void morcalavin_hurtidle( edict_t *self)
{
SetAnim(self, ANIM_HURTIDLE);
}
/*-----------------------------------------------
morcalavin_evade
-----------------------------------------------*/
void morcalavin_evade( edict_t *self, G_Message_t *msg )
{
//FIXME: Make him do something smart here...
}
/*-----------------------------------------------
morcalavin_stand
-----------------------------------------------*/
void morcalavin_stand(edict_t *self, G_Message_t *msg)
{
if (self->health <= 0)
return;
//SetAnim(self, ANIM_ATTACK1);
SetAnim(self, ANIM_FLOAT);
}
/*-----------------------------------------------
morcalavin_run
-----------------------------------------------*/
void mork_ai_hover (edict_t *self, float dist)
{
vec3_t bottom;
trace_t trace;
float desired_vel;
if (self->health <= 0)
return;
if(self->enemy)
ai_charge(self, 0);
else
ai_stand(self, 0);
if(dist)
{
VectorCopy(self->s.origin, bottom);
bottom[2] -= dist;
gi.trace(self->s.origin, self->mins, self->maxs, bottom, self, MASK_SOLID, &trace);
if(trace.fraction<1.0)
{
desired_vel = (1 - trace.fraction) * dist;
if(self->velocity[2] < desired_vel)
self->velocity[2] = desired_vel;
return;
}
}
}
void mork_ai_run (edict_t *self, float dist)
{
vec3_t forward;
if (self->health <= 0)
return;
if(self->curAnimID!=ANIM_FLY)
{
ai_run(self, dist);
if(!self->groundentity)
{
if(self->curAnimID == ANIM_WALK)
{
AngleVectors(self->s.angles, forward, NULL, NULL);
VectorScale(forward, 250, self->velocity);
self->velocity[2] = 150;
}
SetAnim(self, ANIM_GLIDE);
}
else
SetAnim(self, ANIM_WALK);
}
else
{
/*
gi.CreateEffect(&self->s,
FX_M_EFFECTS,
0,
vec3_origin,
"bv",
FX_M_MOBLUR,
self->s.angles);
*/
ai_charge(self, dist);
}
}
void morcalavin_run(edict_t *self, G_Message_t *msg)
{//if can't move, go into a float for a bit
if (self->health <= 0)
return;
if(!self->enemy)
{
SetAnim(self, ANIM_FLOAT);
return;
}
if(self->enemy->health<=0)
{
mork_check_killed_enemy(self->enemy);
SetAnim(self, ANIM_FLOAT);
return;
}
if (self->monsterinfo.lefty == 0)
{
morcalavin_init_phase_out(self);
SetAnim(self, ANIM_FLOAT);
gi.sound(self, CHAN_VOICE, sounds[SND_REVIVE], 1, ATTN_NORM, 0);
self->solid = SOLID_NOT;
self->monsterinfo.sound_start = level.time + 2.5;
self->monsterinfo.lefty++;
return;
}
if(!self->groundentity)
SetAnim(self, ANIM_GLIDE);
else
SetAnim(self, ANIM_WALK);
}
void morcalavin_rush(edict_t *self, G_Message_t *msg)
{
// self->gravity = 0.0f;
SetAnim(self, ANIM_FLY);
}
enum
{
MORK_ATTACK_FADE,
MORK_ATTACK_TRACKING,
MORK_ATTACK_SPHERE,
MORK_ATTACK_BEAM,
MORK_ATTACK_5SPHERE
}; morcalavin_attackID_t;
void morcalavin_missile( edict_t *self, G_Message_t *msg)
{
int chance = irand(0, 100);
if (chance < 5 && self->wait != MORK_ATTACK_FADE)
{
morcalavin_attack_fade_out(self);
self->wait = MORK_ATTACK_FADE;
}
else if (chance < 25 && self->wait != MORK_ATTACK_TRACKING)
{
SetAnim(self, ANIM_TRACKING1);
self->wait = MORK_ATTACK_TRACKING;
}
else if (chance < 50 && self->wait != MORK_ATTACK_SPHERE)
{
SetAnim(self, ANIM_ATTACK2B);
self->wait = MORK_ATTACK_SPHERE;
}
else if (chance < 75 && self->wait != MORK_ATTACK_BEAM)
{
SetAnim(self, ANIM_ATTACK3);
self->wait = MORK_ATTACK_BEAM;
}
else if (self->monsterinfo.stepState > 1 && self->wait != MORK_ATTACK_5SPHERE)
{
SetAnim(self, ANIM_ATTACK4);
self->wait = MORK_ATTACK_5SPHERE;
}
}
void morcalavin_melee( edict_t *self, G_Message_t *msg)
{
SetAnim(self, ANIM_ATTACK1);
}
/*
morcalavin Spawn Functions
*/
void morcalavinStaticsInit()
{
classStatics[CID_MORK].msgReceivers[MSG_STAND] = morcalavin_stand;
classStatics[CID_MORK].msgReceivers[MSG_MELEE] = morcalavin_melee;
classStatics[CID_MORK].msgReceivers[MSG_MISSILE] = morcalavin_missile;
classStatics[CID_MORK].msgReceivers[MSG_RUN] = morcalavin_run;
classStatics[CID_MORK].msgReceivers[MSG_EVADE] = morcalavin_evade;
classStatics[CID_MORK].msgReceivers[MSG_DEATH] = morcalavin_death;
resInfo.numAnims = NUM_ANIMS;
resInfo.animations = animations;
resInfo.modelIndex = gi.modelindex("models/monsters/morcalavin/tris.fm");
resInfo.numSounds = NUM_SOUNDS;
resInfo.sounds = sounds;
//quake attack
sounds[SND_QUAKE]=gi.soundindex("monsters/mork/quake.wav");
//straightt-fire beam
sounds[SND_BEAM]=gi.soundindex("monsters/mork/beam.wav");
sounds[SND_BEAMHIT]=gi.soundindex("monsters/mork/beamhit.wav");
//homing balls
sounds[SND_HOMING]=gi.soundindex("monsters/mork/homing.wav");
sounds[SND_HOMEHIT]=gi.soundindex("monsters/mork/homehit.wav");
//power Puff
sounds[SND_PPCHARGE]=gi.soundindex("monsters/mork/ppcharge.wav");
sounds[SND_PPFIRE]=gi.soundindex("monsters/mork/ppfire.wav");
sounds[SND_PPEXPLODE]=gi.soundindex("monsters/mork/ppexplode.wav");
//Lightning from eyes
sounds[SND_LIGHTNING]=gi.soundindex("monsters/mork/lightning.wav");
sounds[SND_LGHTNGHIT]=gi.soundindex("monsters/mork/lghtnghit.wav");
//Shove
sounds[SND_FORCEWALL]=gi.soundindex("monsters/mork/forcewall.wav");
//Shield
sounds[SND_MAKESHIELD]=gi.soundindex("monsters/mork/makeshield.wav");
sounds[SND_SHIELDHIT]=gi.soundindex("monsters/mork/shieldhit.wav");
sounds[SND_SHIELDPULSE]=gi.soundindex("monsters/mork/shieldpulse.wav");
sounds[SND_SHIELDGONE]=gi.soundindex("monsters/mork/shieldgone.wav");
sounds[SND_SHIELDBREAK]=gi.soundindex("monsters/mork/shieldbreak.wav");
//Fly forward
sounds[SND_RUSH]=gi.soundindex("monsters/mork/rush.wav");
//hurt and get up
sounds[SND_FALL]=gi.soundindex("monsters/mork/fall.wav");
sounds[SND_REVIVE]=gi.soundindex("monsters/mork/revive.wav");
//strafing beams attack
sounds[SND_STRAFEON]=gi.soundindex("monsters/mork/strafeon.wav");
sounds[SND_STRFSWNG]=gi.soundindex("monsters/mork/strfswng.wav");
sounds[SND_STRAFEOFF]=gi.soundindex("monsters/mork/strafeoff.wav");
//hurt/kill player laugh
sounds[SND_LAUGH]=gi.soundindex("monsters/mork/laugh.wav");
//Taunts
sounds[TAUNT_LAUGH1] =gi.soundindex("monsters/mork/laugh1.wav");
sounds[TAUNT_LAUGH2] =gi.soundindex("monsters/mork/laugh2.wav");
sounds[TAUNT_LAUGH3] =gi.soundindex("monsters/mork/laugh3.wav");
sounds[TAUNT_LAUGH4] =gi.soundindex("monsters/mork/laugh4.wav");
sounds[TAUNT_BELLY1] =gi.soundindex("monsters/mork/belly.wav");
sounds[TAUNT_BELLY2] =gi.soundindex("monsters/mork/belly2.wav");
sounds[TAUNT_BELLY3] =gi.soundindex("monsters/mork/digest.wav");
classStatics[CID_MORK].resInfo = &resInfo;
}
void morcalavin_attack_fade_out(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sounds[SND_REVIVE], 1, ATTN_NORM, 0);
self->monsterinfo.sound_start = level.time + 2.0;
morcalavin_init_phase_out(self);
SetAnim(self, ANIM_FLOAT);
self->monsterinfo.lefty = 8;
}
void morcalavin_fade_out(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sounds[SND_REVIVE], 1, ATTN_NORM, 0);
self->monsterinfo.sound_start = level.time + 2.0;
morcalavin_init_phase_out(self);
SetAnim(self, ANIM_FLOAT);
}
qboolean morcalavin_choose_teleport_destination(edict_t *self)
{
vec3_t teleport_angles, forward, endpos, startpos;
trace_t trace;
int num_tries, i;
edict_t *noblockent;
float tracedist;
//Instead of chance, do around self if evade, around other if ambush
if(!self->enemy)
{
//Phase in and become tangible again
morcalavin_init_phase_in(self);
self->takedamage = DAMAGE_YES;
self->monsterinfo.lefty = 10;
return false;
}
num_tries = 10;
for(i = 0; i < num_tries; i++)
{
VectorSet(teleport_angles, 0, anglemod(flrand(0, 360)), 0);
AngleVectors(teleport_angles, forward, NULL, NULL);
VectorCopy(self->enemy->s.origin, startpos);
startpos[2]+=self->enemy->mins[2];
startpos[2]-=self->mins[2];
tracedist = irand(self->min_missile_range, self->missile_range);
VectorMA(startpos, -tracedist, forward, endpos);
noblockent = self->enemy;
gi.trace(startpos, self->mins, self->maxs, endpos, noblockent, MASK_MONSTERSOLID, &trace);
if(trace.fraction*tracedist < 100)//min origin lerp dist
continue;
if(trace.allsolid || trace.startsolid)
continue;
if(vhlen(trace.endpos, self->enemy->s.origin)>=128)
{
VectorCopy(trace.endpos, startpos);
VectorCopy(trace.endpos, endpos);
endpos[2] -=64;
gi.trace(startpos, self->mins, self->maxs, endpos, noblockent, MASK_MONSTERSOLID,&trace);
if(trace.fraction<1.0 && !trace.allsolid && !trace.startsolid)//the last two should be false if trace.fraction is < 1.0 but doesn't hurt to check
{
VectorCopy(trace.endpos, self->s.origin);
gi.linkentity(self);
return true;
}
}
}
return false;
}
//Teleport in and attack the player quickly, before fading out again
void morcalavin_teleport_attack(edict_t *self)
{
int chance;
//Find a valid point away from the player
morcalavin_choose_teleport_destination(self);
//Start the animation for the attack
if (self->monsterinfo.lefty == 8)
SetAnim(self, ANIM_ATTACK4);
else
SetAnim(self, ANIM_ATTACK2);
//Play the teleport in sound fx
gi.sound(self, CHAN_AUTO, sounds[SND_MAKESHIELD], 1, ATTN_NORM, 0);
//Start phasing back in
morcalavin_init_phase_in(self);
//Become tangible once more
self->solid = SOLID_BBOX;
if (self->monsterinfo.stepState)
{
chance = irand(0,6);
switch (chance)
{
case 0:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH2], 1, ATTN_NONE, 0);
break;
case 1:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH3], 1, ATTN_NONE, 0);
break;
case 2:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH4], 1, ATTN_NONE, 0);
break;
default:
break;
}
}
}
void morcalavin_postthink(edict_t *self)
{
int chance;
if (!self->monsterinfo.lefty)
MG_CheckEvade(self);
if (self->enemy && self->monsterinfo.stepState)
{
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);
mork_check_killed_enemy(self->enemy);
}
}
//Check for a teleport razzing
if (self->monsterinfo.jump_time > 0 && self->monsterinfo.jump_time < level.time)
{
if (self->monsterinfo.stepState)
{
morcalavin_init_phase_in(self);
self->monsterinfo.jump_time = -1;
self->monsterinfo.sound_start = -1;
morcalavin_teleport_attack(self);
return;
}
else
{
morcalavin_teleport_attack(self);
self->monsterinfo.jump_time = -1;
return;
}
}
//Check for a pending taunt
if (self->monsterinfo.sound_start > 0 && self->monsterinfo.sound_start < level.time)
{
switch (self->monsterinfo.lefty)
{
case 1:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH1], 1, ATTN_NONE, 0);
self->monsterinfo.sound_start = -1;
self->monsterinfo.jump_time = level.time + 1.0;
self->monsterinfo.lefty++;
break;
case 2:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_BELLY1], 1, ATTN_NONE, 0);
self->monsterinfo.sound_start = -1;
self->monsterinfo.jump_time = level.time + 8.0;
self->monsterinfo.lefty++;
break;
case 3:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_BELLY2], 1, ATTN_NONE, 0);
self->monsterinfo.sound_start = -1;
self->monsterinfo.jump_time = level.time + 5.0;
self->monsterinfo.lefty++;
break;
case 4:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_BELLY3], 1, ATTN_NONE, 0);
self->monsterinfo.sound_start = -1;
self->monsterinfo.jump_time = level.time + 7.0;
self->monsterinfo.lefty++;
break;
case 5:
chance = irand(0,6);
switch (chance)
{
case 0:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH2], 1, ATTN_NONE, 0);
break;
case 1:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH3], 1, ATTN_NONE, 0);
break;
case 2:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH4], 1, ATTN_NONE, 0);
break;
default:
break;
}
self->monsterinfo.sound_start = -1;
self->monsterinfo.jump_time = level.time + 1.0;
break;
case 6:
self->monsterinfo.jump_time = -1;
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH1], 1, ATTN_NONE, 0);
if (!self->targetEnt)
{
#ifdef _DEVEL
if (level.time < 5.0) // Otherwise it will print forever after Morcalavin is dead.
gi.dprintf("GDE Fault: YOU NEED TO PUT A BARRIER IN THIS ROOM FOR THE GAME TO WORK!!!n");
#endif
return;
}
self->svflags &= ~SVF_NO_AUTOTARGET;
if (self->delay)
{
self->monsterinfo.sound_start = self->monsterinfo.attack_finished = level.time + ( self->delay );
self->monsterinfo.sound_start += 1.5;
self->targetEnt->monsterinfo.attack_finished = self->monsterinfo.attack_finished;
self->delay *= 2;
}
else
{
self->monsterinfo.sound_start = level.time + 3.5;
self->monsterinfo.attack_finished = level.time + 2.0;
self->delay = 2;
}
self->monsterinfo.lefty++;
break;
case 7:
self->monsterinfo.sound_start = -1;
gi.sound(self, CHAN_AUTO, sounds[SND_LAUGH], 1, ATTN_NONE, 0);
self->monsterinfo.jump_time = -1;
break;
case 8:
/*
chance = irand(0,2);
switch (chance)
{
case 0:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH2], 1, ATTN_NONE, 0);
break;
case 1:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH3], 1, ATTN_NONE, 0);
break;
case 2:
gi.sound(self, CHAN_AUTO, sounds[TAUNT_LAUGH4], 1, ATTN_NONE, 0);
break;
default:
break;
}*/
self->monsterinfo.sound_start = -1;
self->monsterinfo.jump_time = level.time + 1.0;
break;
}
}
self->next_post_think = level.time + 0.05;
}
int morcalavin_resist_death (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
vec3_t vf, vr, temp;
self->msgHandler = DeadMsgHandler;
gi.sound(self, CHAN_VOICE, sounds[SND_FALL], 1, ATTN_NORM, 0);
SetAnim(self, ANIM_FALL);
self->s.color.a = 0xFFFFFFFF;
self->pre_think = NULL;
self->next_pre_think = -1;
self->takedamage = DAMAGE_NO;
self->dmg = 1;
self->health = self->max_health = MonsterHealth(MORK_HEALTH);
self->monsterinfo.sound_start = level.time + 2.5;
self->solid = SOLID_BBOX;
self->monsterinfo.stepState++;
self->monsterinfo.lefty = 6;
//Check to release a charging weapon
if (self->target_ent)
{
AngleVectors(self->s.angles, vf, vr, NULL);
VectorMA(self->s.origin, 10, vf, self->target_ent->s.origin);
VectorMA(self->target_ent->s.origin, 14, vr, self->target_ent->s.origin);
self->target_ent->s.origin[2] += 42;
VectorScale(vf, 400, self->target_ent->velocity);
self->target_ent->think = morcalavin_check_lightning;
self->target_ent->nextthink = level.time + 0.1;
}
//TODO: Create an effect around him
VectorClear(temp);
temp[0] = self->delay;
/*
gi.CreateEffect( &self->s,
FX_M_EFFECTS,
CEF_OWNERS_ORIGIN,
self->s.origin,
"bv",
FX_MORK_RECHARGE,
temp);
*/
//TODO: Create a barrier around him so the player cannot get close to him
return false;
}
/*QUAKED monster_morcalavin(1 .5 0) (-24 -24 -50) (24 24 40)
Morky
"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_morcalavin (edict_t *self)
{
if (!walkmonster_start(self)) // Failed initialization
return;
self->msgHandler = DefaultMsgHandler;
self->classID = CID_MORK;
self->classname = "monster_morcalavin";
if (!self->health)
self->health = MORK_HEALTH1;
//Apply to the end result (whether designer set or not)
self->max_health = self->health = MonsterHealth(self->health);
self->mass = MORK_MASS;
self->yaw_speed = 24;
self->movetype = PHYSICSTYPE_STEP;
self->solid=SOLID_BBOX;
//This is the number of times he's died (used to calculate window of opportunity for the player)
self->delay = 0.0;
self->s.origin[2] += 50;
VectorSet(self->mins, -24, -24, -48);
VectorSet(self->maxs, 24, 24, 40);
self->viewheight = 36;
self->materialtype = MAT_FLESH;
self->s.modelindex = classStatics[CID_MORK].resInfo->modelIndex;
self->s.skinnum=0;
self->monsterinfo.otherenemyname = "player";
self->post_think = morcalavin_postthink;
self->next_post_think = level.time + 0.1;
if (self->monsterinfo.scale)
{
self->s.scale = self->monsterinfo.scale = MODEL_SCALE;
}
MG_InitMoods( self );
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
self->s.color.c = 0xFFFFFFFF;
self->s.renderfx |= RF_GLOW;
self->monsterinfo.stepState = 0;
self->svflags |= SVF_BOSS | SVF_FLOAT;
self->count = self->s.modelindex; // For Cinematic purposes
self->gravity = MORK_GRAV;
self->die = morcalavin_resist_death;
gi.linkentity(self);
}
/*QUAKED obj_morcalavin_barrier (1 .5 0) ? ?
The magical barrier that prevents the player from entering the tome area and defeating
Morcalavin
*/
void morcalavin_barrier_think(edict_t *self)
{
edict_t *owner = NULL;
//If we haven't found an owner yet, find one
if (!self->owner)
{
owner = G_Find(NULL, FOFS(classname), "monster_morcalavin");
if (!owner)
{
// gi.dprintf("Unable to bind barrier to Morcalavin!\n");
}
else
{
self->owner = owner;
owner->targetEnt = self;
}
}
if (self->monsterinfo.attack_finished > level.time)
{
self->count = false;
self->svflags |= SVF_NOCLIENT;
}
else
{
self->count = true;
self->svflags &= ~SVF_NOCLIENT;
}
self->nextthink = level.time + 0.1;
}
void morcalavin_barrier_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t vel;
if (!strcmp(other->classname, "player"))
{
if (self->count)
{
VectorSubtract(self->s.origin, other->s.origin, vel);
VectorNormalize(vel);
VectorScale(vel, -1, vel);
VectorScale(vel, 512, other->velocity);
other->velocity[2] = 128;
other->client->playerinfo.flags |= PLAYER_FLAG_USE_ENT_POS;
//NOTENOTE: We should always have an owner.. but this is for safety
if (self->owner)
T_Damage(other, self, self->owner, vel, other->s.origin, vel, irand(5, 10), 250, DAMAGE_AVOID_ARMOR, MOD_DIED);
else
T_Damage(other, self, self, vel, other->s.origin, vel, irand(5, 10), 250, DAMAGE_AVOID_ARMOR, MOD_DIED);
if (self->delay < level.time)
{
self->delay = level.time + 0.5;
gi.CreateEffect( NULL,
FX_WEAPON_STAFF_STRIKE,
0,
other->s.origin,
"db",
vel,
2);
}
}
}
}
void morcalavin_barrier_use (edict_t *self, edict_t *other, edict_t *activator)
{
//Become visible again
self->svflags &= ~SVF_NOCLIENT;
//Never do this again
self->use = NULL;
//Start blocking
self->think = morcalavin_barrier_think;
self->nextthink = level.time + 0.1;
}
void SP_obj_morcalavin_barrier (edict_t *self)
{
gi.setmodel(self, self->model);
self->solid = SOLID_TRIGGER;
self->movetype = PHYSICSTYPE_NONE;
self->touch = morcalavin_barrier_touch;
self->use = morcalavin_barrier_use;
self->s.color.c = 0xFFFFFFFF;
self->count = 1;
self->health = true;
gi.linkentity(self);
//Be invisible until used
self->svflags |= SVF_NOCLIENT;
}