2405 lines
60 KiB
C
2405 lines
60 KiB
C
/*
|
|
==============================================================================
|
|
|
|
// m_gorgon.c
|
|
//
|
|
// Heretic II
|
|
// Copyright 1998 Raven Software
|
|
|
|
|
|
GORGON
|
|
|
|
AI :
|
|
|
|
STANDING 1 : Looking straight ahead
|
|
STANDING 2 : Looking to left
|
|
STANDING 3 : Looking to the right
|
|
|
|
WALK : a normal straight line
|
|
WALK RIGHT : if turning to the right while walking
|
|
WALK LEFT : if turning to the left while walking
|
|
|
|
RUN1 : Running
|
|
RUN2 : Turning left while running
|
|
RUN3 : Turning right while running
|
|
|
|
MELEE1 : Attack Left
|
|
MELEE2 : Attack Right
|
|
MELEE3 : Attack Up
|
|
MELEE4 : Attack Pullback
|
|
MELEE5 : Running attack
|
|
MELEE6 : Hop left
|
|
MELEE7 : Hop right
|
|
MELEE8 : Hop forward
|
|
MELEE9 : Hop backward
|
|
|
|
PAIN1 : step back and bow head down
|
|
PAIN2 : bow head to the left
|
|
PAIN3 : bow head to the right
|
|
|
|
DIE1 : take a few steps backwards and keel over
|
|
DIE2 : fly backwards and slide to a stop
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#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_misc.h"
|
|
#include "m_gorgon.h"
|
|
#include "m_gorgon_anim.h"
|
|
#include "m_stats.h"
|
|
#include "p_anim_branch.h"
|
|
#include "p_anims.h"
|
|
#include "g_HitLocation.h"
|
|
#include "p_actions.h"
|
|
|
|
|
|
// *************************************
|
|
// Definitions
|
|
// *************************************
|
|
|
|
void BecomeDebris(edict_t *self);
|
|
qboolean ai_have_enemy (edict_t *self);
|
|
qboolean clear_visible (edict_t *self, edict_t *other);
|
|
qboolean EqualAngle(float angle1, float angle2, float leniency);
|
|
qboolean ok_to_wake (edict_t *monster, qboolean gorgon_roar, qboolean ignore_ambush);
|
|
float MG_ChangeWhichYaw (edict_t *self, qboolean ideal_yaw);
|
|
qboolean gorgon_check_jump (edict_t *self);
|
|
|
|
#define TRYSTEP_OK 0
|
|
#define TRYSTEP_ALLSOLID 1
|
|
#define TRYSTEP_STARTSOLID 2
|
|
#define TRYSTEP_OFFEDGE 3
|
|
#define TRYSTEP_NOSUPPORT 4
|
|
#define TRYSTEP_INWATER 5
|
|
#define GORGON_STD_MELEE_RNG 48
|
|
#define GORGON_STD_MAXHOP_RNG 200
|
|
|
|
static animmove_t *animations[NUM_ANIMS] =
|
|
{
|
|
&gorgon_move_stand1,
|
|
&gorgon_move_stand2,
|
|
&gorgon_move_stand3,
|
|
&gorgon_move_stand4,
|
|
&gorgon_move_walk,
|
|
&gorgon_move_walk2,
|
|
&gorgon_move_walk3,
|
|
&gorgon_move_melee1,
|
|
&gorgon_move_melee2,
|
|
&gorgon_move_melee3,
|
|
&gorgon_move_melee4,
|
|
&gorgon_move_melee5,
|
|
&gorgon_move_melee6,
|
|
&gorgon_move_melee7,
|
|
&gorgon_move_melee8,
|
|
&gorgon_move_melee9,
|
|
&gorgon_move_melee10,
|
|
&gorgon_move_fjump,
|
|
&gorgon_move_run1,
|
|
&gorgon_move_run2,
|
|
&gorgon_move_run3,
|
|
&gorgon_move_pain1,
|
|
&gorgon_move_pain2,
|
|
&gorgon_move_pain3,
|
|
&gorgon_move_die1,
|
|
&gorgon_move_die2,
|
|
&gorgon_move_snatch,
|
|
&gorgon_move_catch,
|
|
&gorgon_move_miss,
|
|
&gorgon_move_readycatch,
|
|
&gorgon_move_snatchhi,
|
|
&gorgon_move_snatchlow,
|
|
&gorgon_move_slip,
|
|
&gorgon_move_slip_pain,
|
|
&gorgon_move_delay,
|
|
&gorgon_move_roar,
|
|
&gorgon_move_roar2,
|
|
&gorgon_move_land2,
|
|
&gorgon_move_land,
|
|
&gorgon_move_inair,
|
|
&gorgon_move_to_swim,
|
|
&gorgon_move_swim,
|
|
&gorgon_move_swim_bite_a,
|
|
&gorgon_move_swim_bite_a,
|
|
&gorgon_move_outwater,
|
|
&gorgon_move_eat_down,
|
|
&gorgon_move_eat_up,
|
|
&gorgon_move_eat_loop,
|
|
&gorgon_move_eat_tear,
|
|
&gorgon_move_eat_pullback,
|
|
&gorgon_move_look_around,
|
|
&gorgon_move_eat_left,
|
|
&gorgon_move_eat_right,
|
|
&gorgon_move_eat_snap,
|
|
&gorgon_move_eat_react
|
|
};
|
|
|
|
static int sounds[NUM_SOUNDS];
|
|
|
|
static ClassResourceInfo_t resInfo;
|
|
|
|
void gorgon_watch(edict_t *self, G_Message_t *msg);
|
|
void gorgon_death_pain(edict_t *self, G_Message_t *msg);
|
|
|
|
void gorgon_blocked (edict_t *self, trace_t *trace)
|
|
{
|
|
vec3_t dir;
|
|
|
|
if(self->velocity[2]>=0)
|
|
return;
|
|
|
|
if(trace->ent->s.origin[2] + trace->ent->maxs[2] > self->s.origin[2] + self->mins[2])
|
|
return;
|
|
|
|
if(!trace->ent->takedamage)
|
|
return;
|
|
|
|
VectorCopy(self->velocity,dir);
|
|
VectorNormalize(dir);
|
|
T_Damage (trace->ent, self, self, self->velocity, trace->ent->s.origin, dir, flrand(5, 15), 50, DAMAGE_EXTRA_KNOCKBACK,MOD_DIED);
|
|
if(trace->ent->health>0)
|
|
{
|
|
if(trace->ent->client)
|
|
{
|
|
if(!irand(0, 2))
|
|
{
|
|
if(trace->ent->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN)
|
|
{
|
|
if(infront(trace->ent, self))
|
|
{
|
|
P_KnockDownPlayer(&trace->ent->client->playerinfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void gorgon_roar_sound (edict_t *self)
|
|
{
|
|
int chance;
|
|
|
|
chance = irand(0, 100);
|
|
if (chance < 20)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_PAIN1], 1, ATTN_NORM, 0);
|
|
else if (chance < 40)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_PAIN2], 1, ATTN_NORM, 0);
|
|
else if (chance < 60)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_DIE], 1, ATTN_NORM, 0);
|
|
else
|
|
gorgon_growl(self);
|
|
}
|
|
|
|
|
|
void gorgon_roar_response_go (edict_t *self)
|
|
{
|
|
self->pre_think = NULL;
|
|
self->next_pre_think = -1;
|
|
|
|
if(self->ai_mood == AI_MOOD_EAT)
|
|
self->ai_mood = AI_MOOD_PURSUE;
|
|
|
|
SetAnim(self, ANIM_ROAR2);
|
|
|
|
self->nextthink = level.time + 0.1;
|
|
}
|
|
|
|
void gorgon_roar_response (edict_t *self, G_Message_t *msg)
|
|
{//respond to call
|
|
if(!irand(0, 3))
|
|
return;//25% don't roar back
|
|
|
|
self->pre_think = gorgon_roar_response_go;
|
|
self->next_pre_think = level.time + flrand (0.5, 2);
|
|
self->nextthink = level.time + 10;
|
|
}
|
|
|
|
void gorgonRoar (edict_t *self)
|
|
{//finds gorgons in immediate vicinity and wakes them up
|
|
edict_t *found = NULL;
|
|
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
while(found = findradius(found, self->s.origin, GORGON_ALERT_DIST))
|
|
{
|
|
if(found->health>0)
|
|
{
|
|
if(!found->enemy)
|
|
{
|
|
if(found->svflags & SVF_MONSTER)
|
|
{
|
|
if(found->classID == CID_GORGON)
|
|
{
|
|
if(!found->monsterinfo.roared)
|
|
{
|
|
if(gi.inPHS(self->s.origin, found->s.origin))
|
|
{//make sure they can hear me
|
|
if(ok_to_wake(found, true, true))
|
|
{
|
|
found->monsterinfo.roared = true;
|
|
found->enemy = self->enemy;
|
|
FoundTarget(found, false);
|
|
QPostMessage(found, MSG_VOICE_POLL, PRI_DIRECTIVE, "");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//Not just an alert monsters since we want to wake up gorgons only
|
|
// AlertMonsters(self, self->enemy, 5, 1);
|
|
}
|
|
|
|
qboolean gorgonFindAsleepGorgons (edict_t *self)
|
|
{//sees if there are any gorgons in range that aren't awake
|
|
edict_t *found = NULL;
|
|
|
|
while(found = findradius(found, self->s.origin, GORGON_ALERT_DIST))
|
|
{
|
|
if(found!=self)
|
|
{
|
|
if(found->health>0)
|
|
{
|
|
if(!found->enemy)
|
|
{
|
|
if(found->svflags & SVF_MONSTER)
|
|
{
|
|
if(found->classID == CID_GORGON)
|
|
{
|
|
if(!found->monsterinfo.roared)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
//----------------------------------------------------------------------
|
|
// Gorgon Eat - decide which eating animations to use
|
|
//----------------------------------------------------------------------
|
|
void gorgon_eat(edict_t *self, G_Message_t *msg)
|
|
{
|
|
int chance;
|
|
|
|
chance = irand(0, 100);
|
|
if (chance < 90) // Eating
|
|
{
|
|
if (chance < 80)
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
else
|
|
SetAnim(self, ANIM_EAT_PULLBACK);
|
|
}
|
|
else
|
|
{
|
|
SetAnim(self, ANIM_EAT_TEAR);
|
|
}
|
|
|
|
self->monsterinfo.misc_debounce_time = level.time + 5;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
Gorgon Watch -decide which standing animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void gorgon_watch(edict_t *self, G_Message_t *msg)
|
|
{
|
|
int chance;
|
|
|
|
chance = irand(0, 100);
|
|
if (chance < 10)
|
|
SetAnim(self, ANIM_STAND2);
|
|
else if (chance < 20)
|
|
SetAnim(self, ANIM_STAND3);
|
|
else
|
|
SetAnim(self, ANIM_STAND1);
|
|
}
|
|
|
|
qboolean gorgon_check_attack(edict_t *self)
|
|
{
|
|
vec3_t v, vr, vf;
|
|
float dot, dot2, len;
|
|
int chance;
|
|
|
|
if(!M_ValidTarget(self, self->enemy))
|
|
return false;
|
|
|
|
if (!clear_visible(self, self->enemy))
|
|
return false;
|
|
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, v);
|
|
len = VectorNormalize(v);
|
|
|
|
if (len < 200)
|
|
{
|
|
self->show_hostile = level.time + 1; // wake up other monsters
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
|
|
return true;
|
|
}
|
|
else if (len < 400)
|
|
{
|
|
if (!irand(0, 3))
|
|
{
|
|
gorgon_growl(self);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!irand(0, 3))
|
|
{
|
|
AngleVectors(self->s.angles, vf, vr, NULL);
|
|
dot = DotProduct(vf, v);
|
|
dot2 = DotProduct(vr, v);
|
|
|
|
if (dot > -0.85 && dot < -0.25) //Behind and aesthetically correct
|
|
{
|
|
gorgon_growl(self);
|
|
|
|
if (dot2 > 0) //to the right
|
|
SetAnim(self, ANIM_STAND3);
|
|
else //to the left
|
|
SetAnim(self, ANIM_STAND2);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (self->monsterinfo.aiflags & AI_EATING)
|
|
{
|
|
chance = irand(0, 100);
|
|
if (chance < 10)
|
|
SetAnim(self, ANIM_EAT_TEAR);
|
|
else if (chance < 20)
|
|
SetAnim(self, ANIM_EAT_PULLBACK);
|
|
else
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
/*----------------------------------------------------------------------
|
|
Gorgon Stand -decide which standing animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void gorgon_stand(edict_t *self, G_Message_t *msg)
|
|
{
|
|
int chance;
|
|
|
|
if (self->ai_mood == AI_MOOD_DELAY)
|
|
{
|
|
SetAnim(self, ANIM_DELAY);
|
|
return;
|
|
}
|
|
|
|
if(gorgon_check_attack(self))
|
|
return;
|
|
|
|
if (self->monsterinfo.aiflags & AI_EATING)
|
|
{
|
|
chance = irand(0, 100);
|
|
if (chance < 10)
|
|
SetAnim(self, ANIM_EAT_TEAR);
|
|
else if (chance < 20)
|
|
SetAnim(self, ANIM_EAT_PULLBACK);
|
|
else
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
}
|
|
else
|
|
{
|
|
chance = irand(0, 100);
|
|
if (chance < 10)
|
|
SetAnim(self, ANIM_STAND2);
|
|
else if (chance < 20)
|
|
SetAnim(self, ANIM_STAND3);
|
|
else
|
|
SetAnim(self, ANIM_STAND1);
|
|
}
|
|
|
|
self->monsterinfo.misc_debounce_time = level.time + 5;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
Gorgon Walk -decide which walk animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void gorgon_walk(edict_t *self, G_Message_t *msg)
|
|
{
|
|
vec3_t v;
|
|
float len;
|
|
float delta;
|
|
vec3_t targ_org;
|
|
|
|
if(!MG_GetTargOrg(self, targ_org))
|
|
return;
|
|
|
|
|
|
if (!self->enemy)//?goal?
|
|
{
|
|
SetAnim(self, ANIM_WALK1);
|
|
return;
|
|
}
|
|
|
|
if (self->spawnflags & MSF_COWARD)
|
|
{
|
|
VectorSubtract (self->s.origin, targ_org, v);
|
|
len = VectorLength (v);
|
|
self->ideal_yaw = vectoyaw(v);
|
|
M_ChangeYaw(self);
|
|
|
|
if (len < 200)
|
|
{
|
|
self->monsterinfo.aiflags |= AI_FLEE;
|
|
self->monsterinfo.flee_finished = level.time + flrand(4.0, 7.0);
|
|
SetAnim(self, ANIM_RUN1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(clear_visible(self, self->enemy) && infront(self, self->enemy))
|
|
{
|
|
VectorSubtract (self->s.origin, targ_org, v);
|
|
len = VectorLength (v);
|
|
// targ_org is within range and far enough above or below to warrant a jump
|
|
if ((len > 40) && (len < 600) && ((self->s.origin[2] < targ_org[2] - 18) ||
|
|
(self->s.origin[2] > targ_org[2] + 18)))
|
|
{
|
|
if(gorgon_check_jump(self))
|
|
{
|
|
SetAnim(self, ANIM_FJUMP);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
|
|
if (delta > 25 && delta <= 180)
|
|
{
|
|
SetAnim(self, ANIM_WALK3);
|
|
}
|
|
else if (delta > 180 && delta < 335)
|
|
{
|
|
SetAnim(self, ANIM_WALK2);
|
|
}
|
|
else
|
|
{
|
|
SetAnim(self, ANIM_WALK1);
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
Gorgon Melee - decide which melee animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void gorgon_melee(edict_t *self, G_Message_t *msg)
|
|
{
|
|
trace_t trace;
|
|
vec3_t v, source, melee_point, forward, up;
|
|
float len, seperation, melee_range, max_hop_range;
|
|
float chance;
|
|
|
|
if(self->ai_mood == AI_MOOD_NAVIGATE)
|
|
return;
|
|
|
|
if(!ai_have_enemy(self))
|
|
return;
|
|
|
|
AngleVectors(self->s.angles,forward, NULL, up);
|
|
VectorMA(self->s.origin, self->maxs[2]*0.5, up, melee_point);
|
|
VectorMA(melee_point, self->maxs[0], forward, melee_point);
|
|
|
|
VectorSubtract (self->enemy->s.origin, melee_point, v);
|
|
len = VectorLength (v);
|
|
|
|
seperation = self->enemy->maxs[0];
|
|
melee_range = self->melee_range;
|
|
|
|
|
|
if (self->monsterinfo.attack_finished > level.time)
|
|
{//too soon to attack again
|
|
// gi.dprintf("Waiting for next attack\n");
|
|
chance = flrand(0, 1);
|
|
if (chance < 0.6)
|
|
SetAnim(self, ANIM_STAND4);
|
|
else if (chance < 0.7)
|
|
SetAnim(self, ANIM_MELEE6); // Hop left
|
|
else if (chance < 0.8)
|
|
SetAnim(self, ANIM_MELEE7); // Hop right
|
|
else
|
|
SetAnim(self, ANIM_MELEE9); // Hop backward
|
|
|
|
return;
|
|
}
|
|
|
|
//ok to attack
|
|
if(len - seperation < melee_range)
|
|
{//melee
|
|
// gi.dprintf("Biting: ");
|
|
chance = flrand(0, 1);
|
|
|
|
if(!stricmp(self->enemy->classname,"monster_rat"))
|
|
{
|
|
if(self->enemy->s.origin[2] > self->s.origin[2])
|
|
{
|
|
// gi.dprintf(" snatch high\n");
|
|
SetAnim(self,ANIM_SNATCHHI);
|
|
}
|
|
else
|
|
{
|
|
// gi.dprintf(" snatch low\n");
|
|
SetAnim(self,ANIM_SNATCHLOW);
|
|
}
|
|
}
|
|
else if (chance < 0.25)
|
|
{
|
|
// gi.dprintf(" melee1\n");
|
|
SetAnim(self, ANIM_MELEE1); // Attack left
|
|
}
|
|
else if (chance < 0.5)
|
|
{
|
|
// gi.dprintf(" melee2\n");
|
|
SetAnim(self, ANIM_MELEE2); // Attack right
|
|
}
|
|
else if (chance < 0.75)
|
|
{
|
|
// gi.dprintf(" melee3\n");
|
|
SetAnim(self, ANIM_MELEE3); // Attack Up
|
|
}
|
|
else
|
|
{
|
|
// gi.dprintf(" melee4\n");
|
|
SetAnim(self, ANIM_MELEE4); // Pull back
|
|
}
|
|
|
|
self->monsterinfo.attack_finished = level.time + flrand(0, 3 - skill->value);
|
|
return;
|
|
}
|
|
|
|
//Out of melee range
|
|
if (len < 150)// && enemy_vis)
|
|
{
|
|
SetAnim(self, ANIM_MELEE5);
|
|
return;
|
|
}
|
|
max_hop_range = self->s.scale * GORGON_STD_MAXHOP_RNG;
|
|
if (len < max_hop_range) // Hop forward
|
|
{//cheacks ahead to see if can hop at it
|
|
// gi.dprintf(" too far\n");
|
|
//Setup the trace
|
|
// gi.dprintf("Hopping forward\n");
|
|
VectorCopy(self->s.origin, source);
|
|
VectorMA(source, 64 * self->s.scale, forward, source);
|
|
|
|
gi.trace (self->s.origin, self->mins, self->maxs, source, self, MASK_SHOT,&trace);
|
|
|
|
if (trace.ent == self->enemy || trace.fraction == 1)
|
|
SetAnim(self, ANIM_MELEE8);
|
|
else
|
|
{
|
|
VectorCopy(self->s.origin, source);
|
|
VectorMA(source, 32 * self->s.scale, forward, source);
|
|
|
|
gi.trace (self->s.origin, self->mins, self->maxs, source, self, MASK_SHOT,&trace);
|
|
|
|
if (trace.fraction == 1)
|
|
SetAnim(self, ANIM_MELEE7);
|
|
else
|
|
SetAnim(self, ANIM_MELEE6);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
Gorgon Run -decide which run animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void gorgon_run(edict_t *self, G_Message_t *msg)
|
|
{
|
|
vec3_t v;
|
|
float len;
|
|
float delta;
|
|
qboolean enemy_vis;
|
|
vec3_t targ_org;
|
|
|
|
if(!ai_have_enemy(self))
|
|
return;
|
|
|
|
if(!MG_GetTargOrg(self, targ_org))
|
|
return;
|
|
|
|
if(self->flags & FL_INWATER)
|
|
{
|
|
gorgonGoSwim(self);
|
|
return;
|
|
}
|
|
|
|
VectorSubtract (self->s.origin, targ_org, v);
|
|
len = VectorLength (v);
|
|
if(self->ai_mood == AI_MOOD_PURSUE)
|
|
{
|
|
// gi.dprintf("Running gorgon after player...\n");
|
|
enemy_vis = clear_visible(self, self->enemy);
|
|
}
|
|
else
|
|
enemy_vis = clear_visible_pos(self, self->monsterinfo.nav_goal);
|
|
|
|
if(enemy_vis)
|
|
{//JUMP
|
|
if(self->enemy && irand(0, 4) && self->damage_debounce_time < level.time && !self->monsterinfo.roared)
|
|
{//should we do this the first time we see player?
|
|
if(infront(self, self->enemy))
|
|
{
|
|
if(gorgonFindAsleepGorgons(self))
|
|
{
|
|
self->damage_debounce_time = level.time + 10;
|
|
SetAnim(self, ANIM_ROAR);//threaten, brings other monsters
|
|
return;
|
|
}
|
|
else if (!self->dmg_radius)
|
|
{//make a wakeup roar
|
|
self->dmg_radius = true;
|
|
SetAnim(self, ANIM_ROAR2);
|
|
}
|
|
}
|
|
}
|
|
// Enemy is within range and far enough above or below to warrant a jump
|
|
if(infront_pos(self, targ_org))
|
|
{
|
|
if ((len > 40) && (len < 600) && ((self->s.origin[2] < targ_org[2] - 24) ||
|
|
(self->s.origin[2] > targ_org[2] + 24)))
|
|
{
|
|
if (abs(self->s.origin[2] - targ_org[2] - 24) < 200) // Can't jump more than 200 high
|
|
{
|
|
if (!irand(0, 2))
|
|
{
|
|
if(self->ai_mood == AI_MOOD_PURSUE||!irand(0, 4))
|
|
{//20% chance to jump at a buoy
|
|
if(gorgon_check_jump(self))
|
|
{
|
|
SetAnim(self, ANIM_FJUMP);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
|
|
if (delta > 45 && delta <= 180)
|
|
{
|
|
SetAnim(self, ANIM_RUN3); // Turn right
|
|
}
|
|
else if (delta > 180 && delta < 315)
|
|
{
|
|
SetAnim(self, ANIM_RUN2); // Turn left
|
|
}
|
|
else
|
|
{
|
|
SetAnim(self, ANIM_RUN1); // Run on
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
Gorgon Pain - make the decision between pains 1, 2, or 3 or slip
|
|
-----------------------------------------------------------------------*/
|
|
qboolean gorgonCheckSlipGo (edict_t *self, qboolean frompain);
|
|
void gorgon_pain(edict_t *self, G_Message_t *msg)
|
|
{
|
|
edict_t *tempent;
|
|
int chance, temp, damage;
|
|
qboolean force_pain;
|
|
|
|
ParseMsgParms(msg, "eeiii", &tempent, &tempent, &force_pain, &damage, &temp);
|
|
|
|
if(!force_pain)
|
|
{
|
|
if(!irand(0, 2)||!self->groundentity)
|
|
return;
|
|
|
|
if(self->pain_debounce_time > level.time)
|
|
return;
|
|
}
|
|
|
|
self->pain_debounce_time = level.time + 0.5;
|
|
|
|
chance = irand(0, 100);
|
|
if (chance < 50)
|
|
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);
|
|
|
|
if(!irand(0, 4))
|
|
self->s.skinnum = GORGON_PAIN_SKIN;
|
|
|
|
if(skill->value > 1.0 || !gorgonCheckSlipGo(self, true))
|
|
{
|
|
if (chance < 33)
|
|
SetAnim(self, ANIM_PAIN1);
|
|
else if (chance < 66)
|
|
SetAnim(self, ANIM_PAIN2);
|
|
else
|
|
SetAnim(self, ANIM_PAIN3);
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
Gorgon Die - choose death
|
|
-----------------------------------------------------------------------*/
|
|
void gorgon_death_pain(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if (self->health <= -80)
|
|
{
|
|
gi.sound(self, CHAN_BODY, sounds[SND_GIB], 1, ATTN_NORM, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
BecomeDebris(self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void gorgon_death(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(self->monsterinfo.aiflags&AI_DONT_THINK)
|
|
{
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_DIE], 1, ATTN_NORM, 0);
|
|
if (irand(0,10) < 5) // Big enough death to be thrown back
|
|
SetAnim(self, ANIM_DIE2);
|
|
else
|
|
SetAnim(self, ANIM_DIE1);
|
|
return;
|
|
}
|
|
self->msgHandler = DeadMsgHandler;
|
|
|
|
// check for gib
|
|
if (self->health <= -80)
|
|
{
|
|
gi.sound(self, CHAN_BODY, sounds[SND_GIB], 1, ATTN_NORM, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
BecomeDebris(self);
|
|
return;
|
|
}
|
|
|
|
if (self->deadflag == DEAD_DEAD) // Dead but still being hit
|
|
return;
|
|
|
|
self->pre_think = NULL;
|
|
self->next_pre_think = -1;
|
|
|
|
self->gravity = 1.0f;
|
|
self->svflags |= SVF_TAKE_NO_IMPACT_DMG | SVF_DO_NO_IMPACT_DMG;
|
|
// regular death
|
|
self->s.skinnum = GORGON_PAIN_SKIN;
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_DIE], 1, ATTN_NORM, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
self->takedamage = DAMAGE_YES;
|
|
|
|
if (self->health <= -10) // Big enough death to be thrown back
|
|
SetAnim(self, ANIM_DIE2);
|
|
else
|
|
SetAnim(self, ANIM_DIE1);
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
|
|
ACTION FUNCTIONS FOR THE MONSTER
|
|
|
|
-----------------------------------------------------------------------*/
|
|
|
|
void gorgon_footstep (edict_t *self)
|
|
{
|
|
int chance;
|
|
|
|
chance = irand(0, 100);
|
|
if (chance < 25)
|
|
gi.sound(self, CHAN_BODY, sounds[SND_STEP1], 1, ATTN_NORM, 0);
|
|
else if (chance < 50)
|
|
gi.sound(self, CHAN_BODY, sounds[SND_STEP2], 1, ATTN_NORM, 0);
|
|
else if (chance < 75)
|
|
gi.sound(self, CHAN_BODY, sounds[SND_STEP3], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_BODY, sounds[SND_STEP4], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void gorgon_growl (edict_t *self)
|
|
{
|
|
int chance;
|
|
|
|
chance = irand(0, 100);
|
|
if (chance < 10)
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_GROWL1], 1, ATTN_NORM, 0);
|
|
else if (chance < 20)
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_GROWL2], 1, ATTN_NORM, 0);
|
|
else if (chance < 30)
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_GROWL3], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void gorgon_prethink (edict_t *self);
|
|
qboolean gorgonCheckMood(edict_t *self)
|
|
{
|
|
self->pre_think = gorgon_prethink;
|
|
self->next_pre_think = level.time + 0.1;
|
|
|
|
self->mood_think(self);
|
|
|
|
if(self->ai_mood == AI_MOOD_NORMAL)
|
|
return false;
|
|
|
|
switch (self->ai_mood)
|
|
{
|
|
case AI_MOOD_ATTACK://melee and missile the same
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
case AI_MOOD_PURSUE:
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
case AI_MOOD_WALK:
|
|
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
case AI_MOOD_NAVIGATE:
|
|
if(self->flags & FL_INWATER)
|
|
{
|
|
gorgonGoSwim(self);
|
|
return true;
|
|
}
|
|
if(self->curAnimID == ANIM_RUN1 ||
|
|
self->curAnimID == ANIM_RUN2||
|
|
self->curAnimID == ANIM_RUN3)
|
|
return true;
|
|
else
|
|
SetAnim(self, ANIM_RUN1);
|
|
break;
|
|
case AI_MOOD_STAND:
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
case AI_MOOD_DELAY:
|
|
SetAnim(self, ANIM_DELAY);
|
|
break;
|
|
case AI_MOOD_WANDER:
|
|
case AI_MOOD_FLEE:
|
|
if(self->flags & FL_INWATER)
|
|
{
|
|
gorgonGoSwim(self);
|
|
return true;
|
|
}
|
|
if(self->curAnimID == ANIM_RUN1 ||
|
|
self->curAnimID == ANIM_RUN2||
|
|
self->curAnimID == ANIM_RUN3)
|
|
return true;
|
|
else
|
|
SetAnim(self, ANIM_RUN1);
|
|
break;
|
|
case AI_MOOD_JUMP:
|
|
if(self->jump_chance < irand(0, 100))
|
|
SetAnim(self, ANIM_DELAY);
|
|
else
|
|
SetAnim(self, ANIM_FJUMP);
|
|
break;
|
|
case AI_MOOD_EAT://FIXME: this is not neccessary?
|
|
gorgon_ai_eat(self, 0);
|
|
//QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
default :
|
|
#ifdef _DEVEL
|
|
gi.dprintf("gorgon: Unusable mood %d!\n", self->ai_mood);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void gorgon_check_mood (edict_t *self, G_Message_t *msg)
|
|
{
|
|
ParseMsgParms(msg, "i", &self->ai_mood);
|
|
|
|
gorgonCheckMood(self);
|
|
}
|
|
|
|
void gorgonbite (edict_t *self)
|
|
{
|
|
vec3_t v;
|
|
float damage;
|
|
float melee_range;
|
|
vec3_t temp, forward, up, melee_point, bite_endpos;
|
|
trace_t trace;
|
|
|
|
if(self->ai_mood == AI_MOOD_NAVIGATE)
|
|
return;
|
|
|
|
//fixme: do a checkenemy that checks oldenemy & posts messages
|
|
if(!ai_have_enemy(self))
|
|
return;
|
|
|
|
AngleVectors(self->s.angles,forward, NULL, up);
|
|
VectorMA(self->s.origin, self->maxs[2]*0.5, up, melee_point);
|
|
VectorMA(melee_point, self->maxs[0], forward, melee_point);
|
|
|
|
melee_range = self->s.scale * GORGON_STD_MELEE_RNG * 1.25;//give axtra range
|
|
VectorMA(melee_point, melee_range, forward, bite_endpos);
|
|
|
|
//let's do this the right way
|
|
gi.trace(melee_point, vec3_origin, vec3_origin, bite_endpos, self, MASK_SHOT,&trace);
|
|
if (trace.fraction < 1 && !trace.startsolid && !trace.allsolid && trace.ent->takedamage)// A hit
|
|
{
|
|
if (irand(0, 1))
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEHIT1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEHIT2], 1, ATTN_NORM, 0);
|
|
|
|
VectorCopy (self->enemy->s.origin, temp);
|
|
temp[2] += 5;
|
|
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, v);
|
|
VectorNormalize(v);
|
|
|
|
damage = irand(GORGON_DMG_MIN, GORGON_DMG_MAX) * self->monsterinfo.scale;
|
|
T_Damage (self->enemy, self, self, forward, trace.endpos, v, damage, damage/2, DAMAGE_EXTRA_BLOOD,MOD_DIED);
|
|
}
|
|
else // A misssss
|
|
{
|
|
if (irand(0, 1))
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEMISS1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEMISS2], 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
void gorgon_dead(edict_t *self)
|
|
{
|
|
self->pre_think = NULL;
|
|
self->next_pre_think = -1;
|
|
self->deadState = DEAD_DEAD;
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
M_EndDeath(self);
|
|
}
|
|
|
|
void gorgon_land(edict_t *self)
|
|
{
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_LAND], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void gorgon_eatorder (edict_t *self)
|
|
{
|
|
if(gorgon_check_attack(self))
|
|
return;
|
|
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
#define JUMP_SCALE 1.5
|
|
|
|
void gorgon_hop (edict_t *self)
|
|
{
|
|
vec3_t forward,right;
|
|
vec3_t temp;
|
|
|
|
if (self->s.frame == FRAME_hop8)
|
|
{
|
|
self->movetype = PHYSICSTYPE_STEP;
|
|
self->velocity[2] = -10;
|
|
}
|
|
else
|
|
{
|
|
if (self->monsterinfo.currentmove == &gorgon_move_melee6) // hop left
|
|
{
|
|
VectorCopy (self->s.angles, temp);
|
|
temp[YAW] -= 15;
|
|
AngleVectors (temp, NULL, right, NULL);
|
|
VectorScale (right, -40*JUMP_SCALE, self->velocity);
|
|
}
|
|
else if (self->monsterinfo.currentmove == &gorgon_move_melee7) // hop right
|
|
{
|
|
VectorCopy (self->s.angles, temp);
|
|
temp[YAW] += 15;
|
|
AngleVectors (temp, NULL, right, NULL);
|
|
VectorScale (right, 40*JUMP_SCALE, self->velocity);
|
|
}
|
|
else if (self->monsterinfo.currentmove == &gorgon_move_melee8) // hop forward
|
|
{
|
|
VectorCopy (self->s.angles, temp);
|
|
AngleVectors (temp, forward, NULL, NULL);
|
|
|
|
VectorScale (forward, 50*JUMP_SCALE, self->velocity);
|
|
}
|
|
else if (self->monsterinfo.currentmove == &gorgon_move_melee9) // hop backward
|
|
{
|
|
VectorCopy (self->s.angles, temp);
|
|
AngleVectors (temp, forward, NULL, NULL);
|
|
VectorScale (forward, -50*JUMP_SCALE, self->velocity);
|
|
}
|
|
|
|
//self->movetype = PHYSICSTYPE_TOSS;
|
|
//self->groundentity = self;
|
|
self->velocity[2] += 175;
|
|
}
|
|
}
|
|
|
|
void gorgonApplyJump (edict_t *self)
|
|
{
|
|
if(Vec3IsZero(self->movedir))
|
|
{
|
|
vec3_t forward;
|
|
|
|
// gi.dprintf("Gorgon in FJump with no movedir!!!!\n");
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorMA(self->velocity, flrand(200, 400), forward, self->velocity);
|
|
self->velocity[2] = flrand(100, 200);
|
|
}
|
|
else
|
|
{
|
|
self->jump_time = level.time + 0.5;
|
|
VectorCopy(self->movedir, self->velocity);
|
|
VectorClear(self->movedir);
|
|
}
|
|
}
|
|
|
|
void gorgonJumpOutWater (edict_t *self)
|
|
{
|
|
vec3_t endpos, forward;
|
|
|
|
if(!self->enemy)
|
|
VectorCopy(self->enemy->s.origin, endpos);
|
|
else
|
|
{
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorMA(self->s.origin, 100, forward, endpos);
|
|
}
|
|
|
|
endpos[2] += 30;
|
|
VectorSubtract(endpos, self->s.origin, self->velocity);
|
|
self->velocity[2] += 200;
|
|
}
|
|
|
|
void gorgonForward (edict_t *self, float dist)
|
|
{
|
|
vec3_t forward, fdir;
|
|
float dot;
|
|
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
|
|
VectorCopy(self->velocity, fdir);
|
|
VectorNormalize(fdir);
|
|
|
|
dot = DotProduct(fdir, forward);
|
|
|
|
dist *= (1 - dot);
|
|
|
|
if(dist)
|
|
VectorMA(self->velocity, dist, forward, self->velocity);
|
|
}
|
|
|
|
void gorgonFixPitch (edict_t *self)
|
|
{
|
|
MG_ChangePitch(self, 0, 10);
|
|
}
|
|
|
|
void gorgonZeroPitch (edict_t *self)
|
|
{
|
|
self->s.angles[PITCH] = 0;
|
|
}
|
|
|
|
void gorgonGoSwim (edict_t *self)
|
|
{
|
|
SetAnim(self, ANIM_SWIM);
|
|
}
|
|
|
|
void gorgonCheckInWater (edict_t *self)
|
|
{
|
|
trace_t trace;
|
|
vec3_t endpos;
|
|
|
|
VectorCopy(self->s.origin, endpos);
|
|
endpos[2] -= 32;
|
|
|
|
gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID,&trace);
|
|
if(trace.fraction < 1.0)
|
|
{
|
|
if(trace.contents & CONTENTS_SOLID || trace.contents & CONTENTS_MONSTER)
|
|
SetAnim(self, ANIM_INAIR);
|
|
}
|
|
}
|
|
|
|
void gorgon_check_landed (edict_t *self)
|
|
{
|
|
vec3_t pos;
|
|
float save_yspeed;
|
|
int contents;
|
|
|
|
save_yspeed = self->yaw_speed;
|
|
self->yaw_speed *= 0.33;
|
|
self->best_move_yaw = vectoyaw(self->velocity);
|
|
MG_ChangeWhichYaw(self, false);//turn toward best_move_yaw, 1/3 as fast as if on ground
|
|
self->yaw_speed = save_yspeed;
|
|
|
|
if(self->groundentity)
|
|
{
|
|
if(irand(0, 1))
|
|
SetAnim(self, ANIM_LAND2);
|
|
else
|
|
SetAnim(self, ANIM_LAND);
|
|
}
|
|
else if(self->velocity[2]<0)
|
|
{
|
|
VectorCopy(self->s.origin, pos);
|
|
pos[2] += self->mins[2];
|
|
VectorMA(pos, 0.5, self->velocity, pos);
|
|
contents = gi.pointcontents(pos);
|
|
if(contents&CONTENTS_SOLID)
|
|
{
|
|
if(irand(0, 1))
|
|
SetAnim(self, ANIM_LAND2);
|
|
else
|
|
SetAnim(self, ANIM_LAND);
|
|
}
|
|
else if(contents&CONTENTS_WATER)
|
|
SetAnim(self, ANIM_TO_SWIM);
|
|
}
|
|
}
|
|
|
|
void gorgon_go_inair (edict_t *self)
|
|
{
|
|
SetAnim(self, ANIM_INAIR);
|
|
}
|
|
|
|
qboolean gorgon_check_jump (edict_t *self)
|
|
{
|
|
vec3_t forward, right, up;
|
|
vec3_t landing_spot,arc_spot, test_spot;
|
|
vec3_t angles , v, landing_spot_angles;
|
|
float hold;
|
|
float len;
|
|
trace_t trace;
|
|
|
|
if(self->jump_chance < irand(0, 100))
|
|
return false;
|
|
|
|
if(!MG_GetTargOrg(self, landing_spot))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!infront_pos(self, landing_spot))
|
|
return false;
|
|
|
|
VectorSubtract(self->s.origin, landing_spot, v);
|
|
len = VectorLength(v);
|
|
|
|
if(len > 400)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(self->enemy)
|
|
VectorSet(angles, 0, anglemod(-self->enemy->s.angles[YAW]), 0);
|
|
else
|
|
VectorCopy(self->s.angles, angles);
|
|
|
|
//incorporate scale?
|
|
|
|
// JUMPING
|
|
// Calculate landing spot behind enemy to jump to
|
|
// Caclulate arc spot to jump at which will arc the monster to the landing spot
|
|
// Calculate velocity to make monster jump to hit arc spot
|
|
|
|
// choose landing spot behind enemy
|
|
AngleVectors (angles, forward, right, up);
|
|
|
|
VectorMA (landing_spot, 60, forward, landing_spot);
|
|
|
|
VectorCopy(landing_spot, test_spot);
|
|
test_spot[2] -= 1024;
|
|
|
|
gi.trace(landing_spot, self->mins, self->maxs, test_spot, self, MASK_MONSTERSOLID|MASK_WATER,&trace);
|
|
|
|
if (trace.fraction == 1.0)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!(trace.contents & CONTENTS_SOLID) && !(trace.contents & CONTENTS_WATER))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* if (trace.startsolid || trace.allsolid)
|
|
{
|
|
if(trace.ent != self->enemy)
|
|
return;
|
|
}*/
|
|
|
|
self->jump_time = level.time + 0.5;
|
|
|
|
// calculate arc spot (the top of his jump arc) which will land monster at landing spot
|
|
VectorSubtract (self->s.origin, landing_spot, v);
|
|
landing_spot_angles[PITCH] = 0;
|
|
landing_spot_angles[ROLL] = 0;
|
|
landing_spot_angles[YAW] = vectoyaw(v);
|
|
|
|
AngleVectors (landing_spot_angles, forward, right, up);
|
|
|
|
VectorMA (landing_spot, 20, forward, arc_spot);
|
|
VectorMA (landing_spot, 180, up, arc_spot);
|
|
|
|
AngleVectors (self->s.angles, forward, right, up);
|
|
|
|
// Calculate velocity to make monster jump to hit arc spot
|
|
VectorSubtract (arc_spot,self->s.origin, v); // Face monster to arc spot
|
|
vectoangles(v,angles);
|
|
self->best_move_yaw = angles[YAW];
|
|
|
|
len = VectorLength (v);
|
|
|
|
hold = len / 200;
|
|
|
|
AngleVectors (angles, forward, right, up);
|
|
VectorScale (forward, 300 * hold, self->movedir);
|
|
|
|
self->movedir[2] = 200 * hold;
|
|
|
|
self->monsterinfo.jump_time = level.time + 3;
|
|
return true;
|
|
}
|
|
|
|
void gorgon_jump (edict_t *self)
|
|
{
|
|
vec3_t forward, right, up;
|
|
vec3_t landing_spot,arc_spot, test_spot;
|
|
vec3_t angles , v, landing_spot_angles;
|
|
float hold;
|
|
float len;
|
|
trace_t trace;
|
|
|
|
if(!MG_GetTargOrg(self, landing_spot))
|
|
{
|
|
if(!irand(0,3))
|
|
SetAnim(self, ANIM_ROAR2);
|
|
else
|
|
SetAnim(self, ANIM_RUN1);
|
|
return;
|
|
}
|
|
|
|
VectorSubtract(self->s.origin, landing_spot, v);
|
|
len = VectorLength(v);
|
|
|
|
if(len > 400)
|
|
{
|
|
if(!irand(0,3))
|
|
SetAnim(self, ANIM_ROAR2);
|
|
else
|
|
SetAnim(self, ANIM_RUN1);
|
|
return;
|
|
}
|
|
|
|
if(self->enemy)
|
|
VectorSet(angles, 0, anglemod(-self->enemy->s.angles[YAW]), 0);
|
|
else
|
|
VectorCopy(self->s.angles, angles);
|
|
|
|
//incorporate scale?
|
|
|
|
// JUMPING
|
|
// Calculate landing spot behind enemy to jump to
|
|
// Caclulate arc spot to jump at which will arc the monster to the landing spot
|
|
// Calculate velocity to make monster jump to hit arc spot
|
|
|
|
// choose landing spot behind enemy
|
|
AngleVectors (angles, forward, right, up);
|
|
|
|
VectorMA (landing_spot, 60, forward, landing_spot);
|
|
|
|
VectorCopy(landing_spot, test_spot);
|
|
test_spot[2] -= 1024;
|
|
|
|
gi.trace(landing_spot, self->mins, self->maxs, test_spot, self, MASK_MONSTERSOLID|MASK_WATER,&trace);
|
|
|
|
if (trace.fraction == 1.0)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (!(trace.contents & CONTENTS_SOLID) && !(trace.contents & CONTENTS_WATER))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* if (trace.startsolid || trace.allsolid)
|
|
{
|
|
if(trace.ent != self->enemy)
|
|
return;
|
|
}*/
|
|
|
|
self->jump_time = level.time + 0.5;
|
|
|
|
// calculate arc spot (the top of his jump arc) which will land monster at landing spot
|
|
VectorSubtract (self->s.origin, landing_spot, v);
|
|
landing_spot_angles[PITCH] = 0;
|
|
landing_spot_angles[ROLL] = 0;
|
|
landing_spot_angles[YAW] = vectoyaw(v);
|
|
|
|
AngleVectors (landing_spot_angles, forward, right, up);
|
|
|
|
VectorMA (landing_spot, 20, forward, arc_spot);
|
|
VectorMA (landing_spot, 180, up, arc_spot);
|
|
|
|
AngleVectors (self->s.angles, forward, right, up);
|
|
|
|
// Calculate velocity to make monster jump to hit arc spot
|
|
VectorSubtract (arc_spot,self->s.origin, v); // Face monster to arc spot
|
|
vectoangles(v,angles);
|
|
self->best_move_yaw = angles[YAW];
|
|
|
|
len = VectorLength (v);
|
|
|
|
hold = len / 200;
|
|
|
|
AngleVectors (angles, forward, right, up);
|
|
VectorScale (forward, 300 * hold, self->velocity);
|
|
|
|
self->velocity[2] = 200 * hold;
|
|
|
|
self->monsterinfo.jump_time = level.time + 3;
|
|
}
|
|
|
|
//Gorgon Evasion!
|
|
|
|
void gorgon_evade (edict_t *self, G_Message_t *msg)
|
|
{
|
|
edict_t *projectile;
|
|
HitLocation_t HitLocation;
|
|
int duck_chance, dodgeleft_chance, dodgeright_chance, jump_chance, backflip_chance, frontflip_chance;
|
|
int chance;
|
|
float eta;
|
|
|
|
ParseMsgParms(msg, "eif", &projectile, &HitLocation, &eta);
|
|
|
|
if(eta<0.3)
|
|
return;//needs a .3 seconds to respond, minimum
|
|
|
|
switch(HitLocation)
|
|
{
|
|
case hl_Head:
|
|
duck_chance = 30;
|
|
dodgeleft_chance = 50;
|
|
dodgeright_chance = 50;
|
|
jump_chance = 0;
|
|
backflip_chance = 20;
|
|
frontflip_chance = 20;
|
|
break;
|
|
case hl_TorsoFront://split in half?
|
|
duck_chance = 20;
|
|
dodgeleft_chance = 40;
|
|
dodgeright_chance = 40;
|
|
jump_chance = 0;
|
|
backflip_chance = 80;
|
|
frontflip_chance = 0;
|
|
break;
|
|
case hl_TorsoBack://split in half?
|
|
duck_chance = 20;
|
|
dodgeleft_chance = 40;
|
|
dodgeright_chance = 40;
|
|
jump_chance = 0;
|
|
backflip_chance = 0;
|
|
frontflip_chance = 80;
|
|
break;
|
|
case hl_ArmUpperLeft:
|
|
duck_chance = 10;
|
|
dodgeleft_chance = 0;
|
|
dodgeright_chance = 90;
|
|
jump_chance = 0;
|
|
backflip_chance = 20;
|
|
frontflip_chance = 20;
|
|
break;
|
|
case hl_ArmLowerLeft://left arm
|
|
duck_chance = 0;
|
|
dodgeleft_chance = 0;
|
|
dodgeright_chance = 80;
|
|
jump_chance = 30;
|
|
backflip_chance = 20;
|
|
frontflip_chance = 20;
|
|
break;
|
|
case hl_ArmUpperRight:
|
|
duck_chance = 20;
|
|
dodgeleft_chance = 90;
|
|
dodgeright_chance = 0;
|
|
jump_chance = 0;
|
|
backflip_chance = 20;
|
|
frontflip_chance = 20;
|
|
break;
|
|
case hl_ArmLowerRight://right arm
|
|
duck_chance = 0;
|
|
dodgeleft_chance = 80;
|
|
dodgeright_chance = 0;
|
|
jump_chance = 30;
|
|
backflip_chance = 20;
|
|
frontflip_chance = 20;
|
|
break;
|
|
case hl_LegUpperLeft:
|
|
duck_chance = 0;
|
|
dodgeleft_chance = 0;
|
|
dodgeright_chance = 60;
|
|
jump_chance = 50;
|
|
backflip_chance = 30;
|
|
frontflip_chance = 30;
|
|
break;
|
|
case hl_LegLowerLeft://left leg
|
|
duck_chance = 0;
|
|
dodgeleft_chance = 0;
|
|
dodgeright_chance = 30;
|
|
jump_chance = 90;
|
|
backflip_chance = 60;
|
|
frontflip_chance = 60;
|
|
break;
|
|
case hl_LegUpperRight:
|
|
duck_chance = 0;
|
|
dodgeleft_chance = 60;
|
|
dodgeright_chance = 0;
|
|
jump_chance = 50;
|
|
backflip_chance = 30;
|
|
frontflip_chance = 30;
|
|
break;
|
|
case hl_LegLowerRight://right leg
|
|
duck_chance = 0;
|
|
dodgeleft_chance = 30;
|
|
dodgeright_chance = 0;
|
|
jump_chance = 90;
|
|
backflip_chance = 30;
|
|
frontflip_chance = 30;
|
|
break;
|
|
default:
|
|
duck_chance = 5;
|
|
dodgeleft_chance = 10;
|
|
dodgeright_chance = 10;
|
|
jump_chance = 10;
|
|
backflip_chance = 10;
|
|
frontflip_chance = 10;
|
|
break;
|
|
}
|
|
if(self->jump_chance < 0)
|
|
jump_chance = -1;
|
|
|
|
chance = irand(0, 100);
|
|
if(chance < frontflip_chance)
|
|
{
|
|
SetAnim(self, ANIM_MELEE8);//hop forward
|
|
return;
|
|
}
|
|
|
|
chance = irand(0, 100);
|
|
if(chance < backflip_chance)
|
|
{
|
|
if(self->curAnimID == ANIM_RUN1&&irand(0,10)<8)//running, do the front jump
|
|
{
|
|
SetAnim(self, ANIM_MELEE10);//jump forward
|
|
}
|
|
else
|
|
{
|
|
SetAnim(self, ANIM_MELEE9);//hop forward
|
|
}
|
|
return;
|
|
}
|
|
|
|
chance = irand(0, 100);
|
|
if(chance < dodgeleft_chance)
|
|
{
|
|
SetAnim(self, ANIM_MELEE6);//hop left
|
|
return;
|
|
}
|
|
|
|
chance = irand(0, 100);
|
|
if(chance < dodgeright_chance)
|
|
{
|
|
SetAnim(self, ANIM_MELEE7);//hop left
|
|
return;
|
|
}
|
|
|
|
chance = irand(0, 100);
|
|
if(chance < jump_chance)
|
|
{
|
|
SetAnim(self, ANIM_MELEE10);//jump forward
|
|
return;
|
|
}
|
|
|
|
chance = irand(0, 100);
|
|
if(chance < duck_chance)
|
|
{
|
|
SetAnim(self, ANIM_PAIN1);//jump forward
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================================
|
|
GORGON PICK UP AND GORE SOMETHING
|
|
========================================
|
|
*/
|
|
void gorgon_ready_catch (edict_t *self)
|
|
{
|
|
float enemy_zdist, ok_zdist;
|
|
|
|
if(!ai_have_enemy(self))
|
|
{
|
|
SetAnim(self,ANIM_CATCH);
|
|
return;
|
|
}
|
|
|
|
ok_zdist = 128 * (self->s.scale*0.5/2.5);
|
|
if(ok_zdist<48)
|
|
ok_zdist = 48;
|
|
|
|
enemy_zdist = self->enemy->absmin[2] - self->absmax[2];
|
|
|
|
if(enemy_zdist <= 0 || (enemy_zdist <= ok_zdist && self->enemy->velocity[2] <= -60))
|
|
SetAnim(self,ANIM_CATCH);
|
|
else
|
|
SetAnim(self,ANIM_READY_CATCH);
|
|
}
|
|
|
|
void gorgon_throw_toy(edict_t *self)
|
|
{
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
self->enemy->flags &= ~FL_FLY;
|
|
self->enemy->velocity[0] = self->enemy->velocity[1] = 0;
|
|
self->enemy->velocity[2] = 500;
|
|
if(self->enemy->movetype>NUM_PHYSICSTYPES)
|
|
self->enemy->movetype = PHYSICSTYPE_STEP;
|
|
VectorRandomCopy(vec3_origin,self->enemy->avelocity,300);
|
|
|
|
if(stricmp(self->enemy->classname,"player"))
|
|
QPostMessage(self->enemy, MSG_DEATH, PRI_DIRECTIVE, NULL);
|
|
|
|
//FIXME: What if I miss? Set the monster's touch to
|
|
// something that restores it's angles and normal thinking (AI_FLEE)
|
|
/* if(!stricmp(self->enemy->classname,"player"))
|
|
{
|
|
PlayerAnimSetUpperSeq(self->enemy, 92);
|
|
PlayerAnimSetLowerSeq(&self->enemy->client->playerinfo, 92);
|
|
}*/
|
|
}
|
|
|
|
void gorgon_toy_ofs(edict_t *self, float ofsf, float ofsr, float ofsu)
|
|
{
|
|
vec3_t enemy_ofs, forward, right, up, blooddir, enemy_face;
|
|
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
//adjust for scale
|
|
ofsf *= self->s.scale*0.5/2.5;
|
|
ofsr *= self->s.scale*0.5/2.5;
|
|
ofsu += self->mins[2];//because origin moved up since those were calced
|
|
ofsu *= self->s.scale*0.25/2.5;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
VectorMA(self->s.origin, ofsf, forward, enemy_ofs);
|
|
VectorMA(enemy_ofs, ofsr, right, enemy_ofs);
|
|
VectorMA(enemy_ofs, ofsu, up, self->enemy->s.origin);
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, blooddir);
|
|
|
|
VectorScale(blooddir, -1, enemy_face);
|
|
enemy_face[2]/=10;
|
|
vectoangles(enemy_face, self->enemy->s.angles);
|
|
|
|
switch(self->enemy->count)
|
|
{
|
|
case 1:
|
|
self->enemy->s.angles[PITCH]=anglemod(self->enemy->s.angles[PITCH]+90);//can't do roll?
|
|
break;
|
|
case 2:
|
|
self->enemy->s.angles[PITCH]=anglemod(self->enemy->s.angles[PITCH]-90);//can't do roll?
|
|
break;
|
|
case 3:
|
|
self->enemy->s.angles[ROLL]=anglemod(self->enemy->s.angles[ROLL]+90);//can't do roll?
|
|
break;
|
|
case 4:
|
|
self->enemy->s.angles[ROLL]=anglemod(self->enemy->s.angles[ROLL]-90);//can't do roll?
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
VectorClear(self->enemy->velocity);
|
|
VectorClear(self->enemy->avelocity);
|
|
|
|
if(flrand(0,1)<0.5)
|
|
{
|
|
if(self->enemy->materialtype == MAT_INSECT)
|
|
gi.CreateEffect(&self->enemy->s, FX_BLOOD, CEF_FLAG8, self->enemy->s.origin, "ub", blooddir, 200);
|
|
else
|
|
gi.CreateEffect(&self->enemy->s, FX_BLOOD, 0, self->enemy->s.origin, "ub", blooddir, 200);
|
|
}
|
|
}
|
|
|
|
void gorgon_check_snatch(edict_t *self, float ofsf, float ofsr, float ofsu)
|
|
{
|
|
float enemy_dist, ok_zdist;
|
|
vec3_t forward, right, up, startpos, endpos;
|
|
trace_t trace;
|
|
|
|
//adjust for scale
|
|
ok_zdist = 64 * (self->s.scale*0.5/2.5);
|
|
if(ok_zdist<24)
|
|
ok_zdist = 24;
|
|
|
|
AngleVectors(self->s.angles,forward,right,up);
|
|
VectorMA(self->s.origin, self->maxs[0], forward, startpos);
|
|
VectorMA(startpos, self->maxs[2]*0.5, up, startpos);
|
|
VectorCopy(startpos, endpos);
|
|
VectorMA(endpos, ofsf, forward, endpos);
|
|
VectorMA(endpos, ofsr, right, endpos);
|
|
VectorMA(endpos, ofsu, up, endpos);
|
|
gi.trace(startpos,vec3_origin,vec3_origin,endpos, self, MASK_SHOT,&trace);
|
|
VectorCopy(trace.endpos,endpos);
|
|
|
|
VectorSubtract(self->enemy->s.origin, endpos, endpos);
|
|
enemy_dist = VectorLength(endpos);
|
|
if(enemy_dist>ok_zdist)
|
|
{
|
|
// gi.dprintf("Snatch missed (%4.2f)\n", enemy_dist);
|
|
self->msgHandler = DefaultMsgHandler;
|
|
if(!stricmp(self->enemy->classname,"player"))
|
|
if(self->oldenemy)
|
|
if(self->oldenemy->health>0)
|
|
{
|
|
self->oldenemy = NULL;
|
|
self->enemy = self->oldenemy;
|
|
}
|
|
|
|
if(self->curAnimID == ANIM_SNATCHLOW)
|
|
SetAnim(self,ANIM_MISS);
|
|
else
|
|
SetAnim(self,ANIM_MELEE2);//?
|
|
return;
|
|
}
|
|
// gi.dprintf("SNAGGED!\n");
|
|
//FIXME: if health is low, just chomp it now
|
|
self->enemy->flags |= FL_FLY;
|
|
if(self->enemy->movetype>NUM_PHYSICSTYPES)
|
|
self->enemy->movetype = PHYSICSTYPE_FLY;
|
|
|
|
if(stricmp(self->enemy->classname,"player"))
|
|
{
|
|
self->enemy->monsterinfo.aiflags |= AI_DONT_THINK;
|
|
self->enemy->count = irand(1,5);
|
|
}
|
|
else
|
|
self->enemy->nextthink = level.time + 10;//stuck for 10 seconds.
|
|
|
|
VectorClear(self->enemy->velocity);
|
|
VectorClear(self->enemy->avelocity);
|
|
}
|
|
|
|
void gorgon_gore_toy(edict_t *self, float jumpht)
|
|
{
|
|
float enemy_zdist, ok_zdist;
|
|
byte num_chunks;
|
|
vec3_t dir, forward;
|
|
|
|
if(jumpht!=-1)
|
|
{//not getting here
|
|
self->velocity[2] += jumpht;
|
|
if(self->groundentity)
|
|
{
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorMA(self->velocity, -100, forward, self->velocity);
|
|
}
|
|
}
|
|
else
|
|
self->count = 0;
|
|
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
if(self->enemy->health<0)
|
|
return;
|
|
|
|
if(self->count)
|
|
return;
|
|
|
|
ok_zdist = 56 * (self->s.scale*0.5/2.5);
|
|
if(ok_zdist<36)
|
|
ok_zdist = 36;
|
|
enemy_zdist = self->enemy->s.origin[2] - self->s.origin[2];
|
|
if(enemy_zdist <= self->maxs[2] + ok_zdist || jumpht == -1)
|
|
{
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEMISS2], 1, ATTN_NORM, 0);
|
|
if(jumpht!=-1)
|
|
self->count = 1;
|
|
VectorCopy(self->velocity,dir);
|
|
VectorNormalize(dir);
|
|
if(self->enemy->materialtype != MAT_FLESH)//foo
|
|
self->enemy->materialtype = MAT_FLESH;
|
|
num_chunks = (byte)(self->enemy->health/4);
|
|
if(num_chunks>15)
|
|
num_chunks = 15;
|
|
SprayDebris(self->enemy, self->enemy->s.origin, num_chunks, self->enemy->health*4);//self->enemy is thingtype wood?!
|
|
|
|
if(stricmp(self->enemy->classname,"player"))
|
|
{
|
|
gi.sound(self->enemy, CHAN_BODY, sounds[SND_GIB], 1, ATTN_NORM, 0);
|
|
BecomeDebris(self->enemy);
|
|
}
|
|
else
|
|
{
|
|
self->enemy->nextthink = level.time;
|
|
T_Damage (self->enemy, self, self, self->velocity, self->enemy->s.origin, dir, 2000, 300, 0,MOD_DIED);
|
|
}
|
|
}
|
|
}
|
|
|
|
void gorgon_miss_sound (edict_t *self)
|
|
{
|
|
if (irand(0, 1))
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEMISS1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEMISS2], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void gorgon_anger_sound (edict_t *self)
|
|
{
|
|
byte chance;
|
|
vec3_t spot;
|
|
|
|
chance = irand(0,100);
|
|
if (chance < 10)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_PAIN1], 1, ATTN_NORM, 0);
|
|
else if (chance < 20)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_PAIN2], 1, ATTN_NORM, 0);
|
|
else if (chance < 30)
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEHIT1], 1, ATTN_NORM, 0);
|
|
else if (chance < 40)
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_MELEEHIT2], 1, ATTN_NORM, 0);
|
|
else if (chance < 50)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_DIE], 1, ATTN_NORM, 0);
|
|
else if (chance < 60)
|
|
gorgon_growl(self);
|
|
|
|
if(self->enemy)
|
|
{
|
|
chance = (byte)irand(1,3);
|
|
VectorCopy(self->enemy->s.origin, spot);
|
|
|
|
QPostMessage(self->enemy,MSG_DISMEMBER,PRI_DIRECTIVE,"ii", self->enemy->health*0.5, irand(1,13));//do I need last three if not sending them?
|
|
QPostMessage(self->enemy,MSG_PAIN,PRI_DIRECTIVE,"ii", self->enemy, self);//do I need last three if not sending them?
|
|
}
|
|
}
|
|
|
|
void gorgon_go_snatch (edict_t *self)
|
|
{
|
|
SetAnim(self,ANIM_SNATCH);
|
|
}
|
|
|
|
void gorgon_done_gore (edict_t *self)
|
|
{
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->count = 0;
|
|
if(self->oldenemy)
|
|
{
|
|
if(self->oldenemy->health>0)
|
|
{
|
|
self->enemy = self->oldenemy;
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
return;
|
|
}
|
|
}
|
|
SetAnim(self,ANIM_EAT_LOOP);
|
|
}
|
|
|
|
/* ========================================
|
|
Trip and fall if making too tight a turn, wont work until origin is in center
|
|
========================================== */
|
|
|
|
void gorgonRoll (edict_t *self, float rollangle)
|
|
{//FIXME: cannot interrupt!!!
|
|
self->s.angles[ROLL] = anglemod(rollangle);
|
|
}
|
|
|
|
void gorgonLerpOff (edict_t *self)
|
|
{
|
|
self->s.renderfx &= ~RF_FRAMELERP;
|
|
}
|
|
|
|
void gorgonLerpOn (edict_t *self)
|
|
{
|
|
self->s.renderfx |= RF_FRAMELERP;
|
|
}
|
|
|
|
qboolean gorgonCheckSlipGo (edict_t *self, qboolean frompain)
|
|
{
|
|
vec3_t v, right;
|
|
|
|
if(!self->enemy)
|
|
return false;
|
|
|
|
VectorSubtract (self->enemy->s.origin, self->s.origin, v);
|
|
VectorNormalize(v);
|
|
AngleVectors(self->s.angles, NULL, right, NULL);
|
|
if(DotProduct(right, v) > 0.3 && irand(0, 1))
|
|
{//fall down, go boom
|
|
if(frompain)
|
|
{
|
|
SetAnim(self, ANIM_SLIP_PAIN);
|
|
return true;
|
|
}
|
|
else if(self->monsterinfo.misc_debounce_time < level.time && !irand(0, 4))
|
|
{
|
|
self->monsterinfo.misc_debounce_time = level.time + 7;
|
|
SetAnim(self, ANIM_SLIP);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void gorgonCheckSlip (edict_t *self)
|
|
{
|
|
if(!(self->spawnflags & MSF_GORGON_SPEEDY) && self->s.scale > 0.75)
|
|
{
|
|
gorgonCheckMood(self);
|
|
return;
|
|
}
|
|
|
|
if(!gorgonCheckSlipGo (self, false))
|
|
gorgonCheckMood(self);
|
|
}
|
|
|
|
void gorgonSlide (edict_t *self, float force)
|
|
{
|
|
vec3_t right;
|
|
|
|
if(!self->groundentity)
|
|
return;//already in air
|
|
|
|
if(force == 0)
|
|
{
|
|
self->friction = 1.0;
|
|
return;
|
|
}
|
|
|
|
AngleVectors(self->s.angles, NULL, right, NULL);
|
|
VectorMA(self->velocity, force, right, self->velocity);
|
|
self->velocity[2] = 50;
|
|
self->friction = 0.2;
|
|
}
|
|
|
|
void gorgonChooseDeath (edict_t *self)
|
|
{
|
|
if(irand(0, 1))
|
|
gorgon_death2twitch(self);
|
|
}
|
|
|
|
void gorgon_ai_swim (edict_t *self, float dist)
|
|
{
|
|
vec3_t dir = {0, 0, 0};
|
|
vec3_t vec, angles;
|
|
|
|
gorgon_prethink(self);
|
|
self->pre_think = gorgon_prethink;
|
|
self->next_pre_think = level.time + 0.1;
|
|
|
|
self->mood_think(self);
|
|
// MG_Pathfind(self, false);
|
|
|
|
MG_SetNormalizeVelToGoal(self, dir);
|
|
|
|
if(Vec3IsZero(dir))
|
|
{
|
|
// gi.dprintf("swimming gorgon couldn't find a target\n");
|
|
Vec3ScaleAssign(0.8, self->velocity);
|
|
return;
|
|
}
|
|
|
|
self->ideal_yaw = vectoyaw(dir);
|
|
MG_ChangeYaw(self);
|
|
|
|
if(dist == -1)
|
|
Vec3ScaleAssign(150, dir);
|
|
else
|
|
Vec3ScaleAssign(dist * 3, dir);
|
|
|
|
VectorAdd(self->velocity, dir, self->velocity);
|
|
VectorNormalize(self->velocity);
|
|
Vec3ScaleAssign(200, self->velocity);
|
|
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
|
|
vectoangles(vec, angles);
|
|
MG_ChangePitch(self, angles[PITCH], 10);
|
|
|
|
//MG_ChangePitchForZVel(self, 10, dist * 3, 60);
|
|
|
|
if(dist != -1)
|
|
{//-1 = charge
|
|
if(self->monsterinfo.attack_finished < level.time)
|
|
{
|
|
if(M_ValidTarget(self, self->enemy))
|
|
{
|
|
if(clear_visible(self, self->enemy))
|
|
{
|
|
if(infront(self, self->enemy))
|
|
{
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
|
|
dist = VectorLength(dir);
|
|
|
|
if(dist < self->melee_range + VectorLength(self->velocity) * 0.1)
|
|
{
|
|
if(irand(0, 1))
|
|
SetAnim(self, ANIM_SWIM_BITE_A);
|
|
else
|
|
SetAnim(self, ANIM_SWIM_BITE_B);
|
|
self->monsterinfo.attack_finished = level.time + flrand(0, 3 - skill->value);
|
|
}
|
|
else if(self->monsterinfo.jump_time < level.time)
|
|
{
|
|
if(!(self->enemy->flags & FL_INWATER))
|
|
{
|
|
if(dist < GORGON_STD_MAXHOP_RNG * 2)
|
|
SetAnim(self, ANIM_OUT_WATER);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void gorgon_prethink (edict_t *self)
|
|
{//also make wake on surface of water?
|
|
if(self->flags & FL_INWATER)
|
|
{
|
|
self->gravity = 0.0f;
|
|
self->svflags |= SVF_TAKE_NO_IMPACT_DMG | SVF_DO_NO_IMPACT_DMG;
|
|
|
|
if(!self->wait)
|
|
{
|
|
gi.CreateEffect(NULL, FX_WATER_ENTRYSPLASH, CEF_FLAG7,
|
|
self->s.origin, "bd", 128|96, vec3_up);
|
|
self->wait = true;
|
|
}
|
|
|
|
if(self->curAnimID == ANIM_INAIR)
|
|
SetAnim(self, ANIM_TO_SWIM);
|
|
}
|
|
else
|
|
{
|
|
gi.RemoveEffects (&self->s, FX_M_EFFECTS);
|
|
self->gravity = 1.0f;
|
|
self->svflags &= ~SVF_TAKE_NO_IMPACT_DMG;
|
|
if(self->s.scale>0.5)
|
|
self->svflags &= ~SVF_DO_NO_IMPACT_DMG;
|
|
|
|
if(self->wait)
|
|
{
|
|
gi.CreateEffect(NULL, FX_WATER_ENTRYSPLASH, 0,
|
|
self->s.origin, "bd", 128|96, vec3_up);
|
|
self->wait = false;
|
|
}
|
|
|
|
if(self->curAnimID == ANIM_SWIM ||
|
|
self->curAnimID == ANIM_SWIM_BITE_A ||
|
|
self->curAnimID == ANIM_SWIM_BITE_B)
|
|
SetAnim(self, ANIM_RUN1);
|
|
|
|
gorgonFixPitch(self);
|
|
}
|
|
self->next_pre_think = level.time + 0.1;
|
|
}
|
|
|
|
void gorgon_ai_eat(edict_t *self, float crap)
|
|
{//fixme: crap will be a yaw mod for view_ofs looking around
|
|
vec3_t forward, right, v;
|
|
float edist, fdot, rdot;
|
|
|
|
if(self->enemy)
|
|
{
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, v);
|
|
edist = VectorNormalize(v);
|
|
if(edist<self->wakeup_distance)
|
|
{
|
|
if(visible(self, self->enemy))
|
|
{
|
|
self->spawnflags &= ~MSF_EATING;
|
|
self->monsterinfo.aiflags &= ~AI_EATING;
|
|
FoundTarget(self, true);
|
|
return;
|
|
}
|
|
}
|
|
else if(self->curAnimID == ANIM_EAT_LOOP && !irand(0, 5))
|
|
{
|
|
if(visible(self, self->enemy))
|
|
{
|
|
AngleVectors(self->s.angles, forward, right, NULL);
|
|
fdot = DotProduct(v, forward);
|
|
rdot = DotProduct(v, right);
|
|
if(fdot < 0)
|
|
{
|
|
if(rdot > 0.3)
|
|
SetAnim(self, ANIM_EAT_RIGHT);
|
|
if(rdot < -0.3)
|
|
SetAnim(self, ANIM_EAT_LEFT);
|
|
else
|
|
SetAnim(self, ANIM_EAT_UP);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
FindTarget(self);
|
|
|
|
|
|
if(crap != -1)
|
|
return;
|
|
|
|
switch(self->curAnimID)
|
|
{
|
|
case ANIM_EAT_DOWN:
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
break;
|
|
|
|
case ANIM_EAT_UP:
|
|
SetAnim(self, ANIM_LOOK_AROUND);
|
|
break;
|
|
|
|
case ANIM_EAT_LOOP:
|
|
if(irand(0, 1))
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
else if(irand(0, 1))
|
|
SetAnim(self, ANIM_EAT_PULLBACK);
|
|
else if(irand(0, 1))
|
|
SetAnim(self, ANIM_EAT_TEAR);
|
|
else if(irand(0, 1))//fixme- check gorgon to right
|
|
SetAnim(self, ANIM_EAT_SNAP);
|
|
else if(irand(0, 1))//fixme- check if gorgon to left snapped
|
|
SetAnim(self, ANIM_EAT_REACT);
|
|
else if(irand(0, 1))//fixme- check enemy
|
|
SetAnim(self, ANIM_EAT_LEFT);
|
|
else//fixme- check enemy
|
|
SetAnim(self, ANIM_EAT_RIGHT);
|
|
break;
|
|
|
|
case ANIM_EAT_TEAR:
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
break;
|
|
|
|
case ANIM_EAT_PULLBACK:
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
break;
|
|
|
|
case ANIM_LOOK_AROUND:
|
|
SetAnim(self, ANIM_EAT_DOWN);
|
|
break;
|
|
|
|
case ANIM_EAT_LEFT:
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
break;
|
|
|
|
case ANIM_EAT_RIGHT:
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
break;
|
|
|
|
case ANIM_EAT_SNAP:
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
break;
|
|
|
|
case ANIM_EAT_REACT:
|
|
SetAnim(self, ANIM_EAT_LOOP);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void gorgon_jump_msg (edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(self->jump_chance < irand(0, 100))
|
|
return;
|
|
SetAnim(self, ANIM_FJUMP);
|
|
}
|
|
|
|
void GorgonStaticsInit()
|
|
{
|
|
classStatics[CID_GORGON].msgReceivers[MSG_STAND] = gorgon_stand;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_WALK] = gorgon_walk;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_RUN] = gorgon_run;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_EAT] = gorgon_eat;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_MELEE] = gorgon_melee;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_MISSILE] = gorgon_melee;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_WATCH] = gorgon_walk;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_PAIN] = gorgon_pain;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_DEATH] = gorgon_death;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_JUMP]=gorgon_jump_msg;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_DEATH_PAIN] = gorgon_death_pain;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_CHECK_MOOD] = gorgon_check_mood;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_VOICE_POLL] = gorgon_roar_response;
|
|
classStatics[CID_GORGON].msgReceivers[MSG_EVADE] = gorgon_evade;
|
|
|
|
resInfo.numAnims = NUM_ANIMS;
|
|
resInfo.animations = animations;
|
|
resInfo.modelIndex = gi.modelindex("models/monsters/gorgon/tris.fm");
|
|
|
|
sounds[SND_PAIN1]= gi.soundindex ("monsters/gorgon/pain1.wav");
|
|
sounds[SND_PAIN2]= gi.soundindex ("monsters/gorgon/pain2.wav");
|
|
sounds[SND_DIE]= gi.soundindex ("monsters/gorgon/death1.wav");
|
|
sounds[SND_GURGLE]= gi.soundindex ("monsters/gorgon/gurgle.wav"); //what is this for?
|
|
sounds[SND_GIB]= gi.soundindex ("monsters/gorgon/gib.wav");
|
|
|
|
sounds[SND_MELEEHIT1]= gi.soundindex ("monsters/gorgon/meleehit1.wav");
|
|
sounds[SND_MELEEHIT2] = gi.soundindex ("monsters/gorgon/meleehit2.wav");
|
|
sounds[SND_MELEEMISS1] = gi.soundindex ("monsters/gorgon/meleemiss1.wav");
|
|
sounds[SND_MELEEMISS2] = gi.soundindex ("monsters/gorgon/meleemiss2.wav");
|
|
|
|
sounds[SND_STEP1] = gi.soundindex ("monsters/gorgon/footstep1.wav");
|
|
sounds[SND_STEP2] = gi.soundindex ("monsters/gorgon/footstep2.wav");
|
|
sounds[SND_STEP3] = gi.soundindex ("monsters/gorgon/footstep3.wav");
|
|
sounds[SND_STEP4] = gi.soundindex ("monsters/gorgon/footstep4.wav");
|
|
|
|
sounds[SND_GROWL1] = gi.soundindex ("monsters/gorgon/growl1.wav");
|
|
sounds[SND_GROWL2] = gi.soundindex ("monsters/gorgon/growl2.wav");
|
|
sounds[SND_GROWL3] = gi.soundindex ("monsters/gorgon/growl3.wav");
|
|
|
|
sounds[SND_LAND] = gi.soundindex ("monsters/gorgon/land.wav");
|
|
|
|
resInfo.numSounds = NUM_SOUNDS;
|
|
resInfo.sounds = sounds;
|
|
|
|
classStatics[CID_GORGON].resInfo = &resInfo;
|
|
}
|
|
|
|
/*QUAKED monster_gorgon_leader (1 .5 0) (-16 -16 -0) (16 16 32) AMBUSH ASLEEP EATING 8 16 32 64 128
|
|
|
|
The gorgon leader
|
|
|
|
*/
|
|
void SP_monster_gorgon_leader (edict_t *self)
|
|
{
|
|
float scale;
|
|
|
|
G_SetToFree(self);
|
|
return;
|
|
// Generic Monster Initialization
|
|
if (!monster_start(self))
|
|
return; // Failed initialization
|
|
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->classID = CID_GORGON;
|
|
self->think = walkmonster_start_go;
|
|
self->monsterinfo.aiflags |= AI_BRUTAL|AI_AGRESSIVE|AI_SHOVE;
|
|
|
|
if (!self->health)
|
|
self->health = GORGON_LEADER_HEALTH;
|
|
|
|
self->max_health = self->health = MonsterHealth(self->health);
|
|
|
|
self->mass = GORGON_LEADER_MASS;
|
|
self->yaw_speed = 10;
|
|
self->isBlocked = gorgon_blocked;
|
|
|
|
self->movetype=PHYSICSTYPE_STEP;
|
|
VectorClear(self->knockbackvel);
|
|
|
|
self->solid=SOLID_BBOX;
|
|
self->materialtype = MAT_FLESH;
|
|
|
|
VectorSet(self->mins,-42,-42,0);//-48,-48,0
|
|
VectorSet(self->maxs,42,42,56);//48,48,64
|
|
self->viewheight = self->maxs[2]*0.8;
|
|
|
|
self->s.modelindex = classStatics[CID_GORGON].resInfo->modelIndex;
|
|
|
|
//Big guy can be stood on top of perhaps?
|
|
//self->touch = M_Touch;
|
|
|
|
self->s.skinnum = GORGON_SKIN;
|
|
|
|
scale = 2;//flrand(0.9, 1.4);
|
|
|
|
if (!self->s.scale)
|
|
{
|
|
self->monsterinfo.scale = self->s.scale = scale;
|
|
}
|
|
|
|
self->monsterinfo.aiflags |= AI_NIGHTVISION;
|
|
|
|
VectorScale(self->mins, scale, self->mins);
|
|
VectorScale(self->maxs, scale, self->maxs);
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
MG_InitMoods(self);
|
|
self->svflags |= SVF_WAIT_NOTSOLID;
|
|
}
|
|
|
|
|
|
/*QUAKED monster_gorgon (1 .5 0) (-16 -16 0) (16 16 32) AMBUSH ASLEEP EATING SPEEDY 16 32 64 128 WANDER MELEE_LEAD STALK COWARD EXTRA1 EXTRA2 EXTRA3 EXTRA4
|
|
|
|
The gorgon
|
|
|
|
AMBUSH - Will not be woken up by other monsters or shots from player
|
|
|
|
ASLEEP - will not appear until triggered
|
|
|
|
EATING - Chomp chomp... chewie chomp (wakeup_distance will default to 300)
|
|
|
|
SPEEDY - generally faster gorgon
|
|
|
|
WANDER - Monster will wander around aimlessly (but follows buoys)
|
|
|
|
MELEE_LEAD - Monster will tryto cut you off when you're running and fighting him, works well if there are a few monsters in a group, half doing this, half not
|
|
|
|
STALK - Monster will only approach and attack from behind- if you're facing the monster it will just stand there. Once the monster takes pain, however, it will stop this behaviour and attack normally
|
|
|
|
COWARD - Monster starts off in flee mode- runs away from you when woken up
|
|
|
|
"homebuoy" - monsters will head to this buoy if they don't have an enemy ("homebuoy" should be targetname of the buoy you want them to go to)
|
|
|
|
"wakeup_target" - monsters will fire this target the first time it wakes up (only once)
|
|
|
|
"pain_target" - monsters will fire this target the first time it gets hurt (only once)
|
|
|
|
mintel - monster intelligence- this basically tells a monster how many buoys away an enemy has to be for it to give up.
|
|
|
|
melee_range - How close the player has to be, maximum, for the monster to go into melee. If this is zero, the monster will never melee. If it is negative, the monster will try to keep this distance from the player. If the monster has a backup, he'll use it if too clode, otherwise, a negative value here means the monster will just stop running at the player at this distance.
|
|
Examples:
|
|
melee_range = 60 - monster will start swinging it player is closer than 60
|
|
melee_range = 0 - monster will never do a mele attack
|
|
melee_range = -100 - monster will never do a melee attack and will back away (if it has that ability) when player gets too close
|
|
|
|
missile_range - Maximum distance the player can be from the monster to be allowed to use it's ranged attack.
|
|
|
|
min_missile_range - Minimum distance the player can be from the monster to be allowed to use it's ranged attack.
|
|
|
|
bypass_missile_chance - Chance that a monster will NOT fire it's ranged attack, even when it has a clear shot. This, in effect, will make the monster come in more often than hang back and fire. A percentage (0 = always fire/never close in, 100 = never fire/always close in).- must be whole number
|
|
|
|
jump_chance - every time the monster has the opportunity to jump, what is the chance (out of 100) that he will... (100 = jump every time)- must be whole number
|
|
|
|
wakeup_distance - How far (max) the player can be away from the monster before it wakes up. This just means that if the monster can see the player, at what distance should the monster actually notice him and go for him.
|
|
|
|
DEFAULTS:
|
|
mintel = 20
|
|
melee_range = 48
|
|
missile_range = 0
|
|
min_missile_range = 0
|
|
bypass_missile_chance = 0
|
|
jump_chance = 80
|
|
wakeup_distance = 1024
|
|
|
|
NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1
|
|
*/
|
|
void SP_monster_gorgon (edict_t *self)
|
|
{
|
|
//fix some spawnflags
|
|
if (self->spawnflags & MSF_GORGON_COWARD)
|
|
{
|
|
self->spawnflags &= ~MSF_GORGON_COWARD;
|
|
self->spawnflags |= MSF_COWARD;
|
|
}
|
|
|
|
// Generic Monster Initialization
|
|
if (!walkmonster_start(self)) // Failed initialization
|
|
return;
|
|
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->classID = CID_GORGON;
|
|
self->materialtype = MAT_FLESH;
|
|
self->touch = M_Touch;
|
|
|
|
self->mass = GORGON_MASS;
|
|
if(self->spawnflags & MSF_GORGON_SPEEDY)
|
|
self->yaw_speed = 30;
|
|
else
|
|
self->yaw_speed = 15;
|
|
self->dmg = 0;//used for slight turn during run
|
|
|
|
self->movetype=PHYSICSTYPE_STEP;
|
|
VectorClear(self->knockbackvel);
|
|
self->solid=SOLID_BBOX;
|
|
|
|
if(irand(0, 1))
|
|
self->ai_mood_flags |= AI_MOOD_FLAG_PREDICT;
|
|
|
|
VectorCopy(STDMinsForClass[self->classID], self->mins);
|
|
VectorCopy(STDMaxsForClass[self->classID], self->maxs);
|
|
|
|
self->s.modelindex = classStatics[CID_GORGON].resInfo->modelIndex;
|
|
|
|
self->s.skinnum = GORGON_SKIN;
|
|
|
|
self->monsterinfo.otherenemyname = "monster_rat";
|
|
|
|
|
|
if (self->spawnflags & MSF_COWARD)
|
|
{
|
|
if(!self->health)
|
|
self->health = GORGON_HEALTH/2;
|
|
|
|
self->monsterinfo.aiflags |= AI_COWARD;
|
|
self->monsterinfo.scale = self->s.scale = 0.5;
|
|
}
|
|
else
|
|
{
|
|
if (!self->health)
|
|
self->health = GORGON_HEALTH;
|
|
|
|
if (!self->s.scale)
|
|
self->s.scale = flrand(GORGON_SCALE_MIN, GORGON_SCALE_MAX);
|
|
self->monsterinfo.scale = self->s.scale;
|
|
}
|
|
|
|
self->max_health = self->health = MonsterHealth(self->health);
|
|
|
|
if (self->spawnflags & MSF_EATING)
|
|
{
|
|
self->monsterinfo.aiflags |= AI_EATING;
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
if(!self->wakeup_distance)
|
|
self->wakeup_distance = 300;
|
|
}
|
|
else
|
|
{
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
MG_InitMoods(self);
|
|
|
|
self->svflags |= SVF_WAIT_NOTSOLID;
|
|
self->flags |= FL_AMPHIBIAN;
|
|
self->monsterinfo.aiflags |= AI_SWIM_OK;
|
|
|
|
self->monsterinfo.roared = false;
|
|
|
|
if(!irand(0, 2))//33% chance of not making a wakeup roar
|
|
self->dmg_radius = true;
|
|
else
|
|
self->dmg_radius = false;
|
|
|
|
self->pre_think = gorgon_prethink;
|
|
self->next_pre_think = level.time + 0.1;
|
|
}
|