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

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;
}