2804 lines
74 KiB
C
2804 lines
74 KiB
C
/*
|
|
==============================================================================
|
|
|
|
// m_tbeast.c
|
|
//
|
|
// Heretic II
|
|
// Copyright 1998 Raven Software
|
|
|
|
|
|
TBEAST
|
|
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#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 "p_actions.h"
|
|
#include "p_anims.h"
|
|
#include "p_anim_branch.h"
|
|
#include "q_Physics.h"
|
|
#include "g_Physics.h"
|
|
#include "g_misc.h"
|
|
#include "m_beast.h"
|
|
#include "m_beast_anim.h"
|
|
#include "m_stats.h"
|
|
|
|
|
|
// *************************************
|
|
// Definitions
|
|
// *************************************
|
|
|
|
void BecomeDebris(edict_t *self);
|
|
qboolean clear_visible (edict_t *self, edict_t *other);
|
|
qboolean MG_MoveToGoal (edict_t *self, float dist);
|
|
trace_t MG_WalkMove (edict_t *self, float yaw, float dist, qboolean *succeeded);
|
|
int tbeast_inwalkframes(edict_t *self);
|
|
|
|
static vec3_t GetLeftFootOffsetForFrameIndex[18] =
|
|
{// x y z
|
|
160.00F, -64.00F, 0.00F, // 1
|
|
144.00F, -60.00F, 0.00F, // 2
|
|
96.00F, -56.00F, 0.00F, // 3
|
|
64.00F, -52.00F, 0.00F, // 4
|
|
16.00F, -48.00F, -2.00F, // 5
|
|
-48.00F, -44.00F, -4.00F, // 6
|
|
-80.00F, -40.00F, -2.00F, // 7
|
|
-112.00F, -40.00F, 0.00F, // 8
|
|
-160.00F, -40.00F, -4.00F, // 9
|
|
-192.00F, -40.00F, 0.00F, // 10
|
|
-120.00F, -42.00F, 2.00F, // 11
|
|
-48.00F, -44.00F, 6.00F, // 12
|
|
0.00F, -44.00F, 14.00F, // 13
|
|
24.00F, -42.00F, 10.00F, // 14
|
|
28.00F, -48.00F, 16.00F, // 15
|
|
34.00F, -48.00F, 14.00F, // 16
|
|
110.00F, -58.00F, 0.00F, // 17
|
|
144.00F, -72.00F, 8.00F, // 18
|
|
};
|
|
|
|
static vec3_t GetRightFootOffsetForFrameIndex[18] =
|
|
{// x y z
|
|
-160.00F, 32.00F, 0.00F, // 1
|
|
-144.00F, 32.00F, 12.00F, // 2
|
|
-96.00F, 28.00F, 14.00F, // 3
|
|
-64.00F, 28.00F, 18.00F, // 4
|
|
-16.00F, 28.00F, 22.00F, // 5
|
|
32.00F, 32.00F, 28.00F, // 6
|
|
64.00F, 38.00F, 28.00F, // 7
|
|
104.00F, 40.00F, 24.00F, // 8
|
|
160.00F, 48.00F, -4.00F, // 9
|
|
128.00F, 52.00F, -8.00F, // 10
|
|
112.00F, 54.00F, 0.00F, // 11
|
|
108.00F, 52.00F, 0.00F, // 12
|
|
72.00F, 48.00F, -2.00F, // 13
|
|
32.00F, 40.00F, 0.00F, // 14
|
|
-4.00F, 36.00F, 0.00F, // 15
|
|
-24.00F, 34.00F, 0.00F, // 16
|
|
-80.00F, 32.00F, 2.00F, // 17
|
|
-128.00F, 32.00F, -2.00F, // 18
|
|
};
|
|
|
|
static animmove_t *animations[NUM_ANIMS] =
|
|
{
|
|
&tbeast_move_biteup,
|
|
&tbeast_move_bitelow,
|
|
&tbeast_move_biteup2,
|
|
&tbeast_move_eating_twitch,
|
|
&tbeast_move_eating,
|
|
&tbeast_move_eatdown,
|
|
&tbeast_move_walk,
|
|
&tbeast_move_walkleft,
|
|
&tbeast_move_walkrt,
|
|
&tbeast_move_jump,
|
|
&tbeast_move_forced_jump,
|
|
&tbeast_move_inair,
|
|
&tbeast_move_land,
|
|
&tbeast_move_ginair,
|
|
&tbeast_move_gland,
|
|
&tbeast_move_stand,
|
|
&tbeast_move_delay,
|
|
&tbeast_move_die,
|
|
&tbeast_move_die_norm,
|
|
&tbeast_move_charge,
|
|
&tbeast_move_roar,
|
|
&tbeast_move_walkatk,
|
|
&tbeast_move_stun,
|
|
&tbeast_move_snatch,
|
|
&tbeast_move_ready_catch,
|
|
&tbeast_move_catch,
|
|
&tbeast_move_biteup_sfin,
|
|
&tbeast_move_bitelow_sfin,
|
|
&tbeast_move_biteup2_sfin,
|
|
&tbeast_move_quick_charge
|
|
};
|
|
|
|
static int sounds[NUM_SOUNDS];
|
|
|
|
static ClassResourceInfo_t resInfo;
|
|
|
|
void tbeast_watch(edict_t *self, G_Message_t *msg);
|
|
|
|
|
|
qboolean visible_to_client (edict_t *self)
|
|
{
|
|
edict_t *ent = NULL;
|
|
int i;
|
|
|
|
for(i = 0; i <= game.maxclients; i++)
|
|
{
|
|
ent = &g_edicts[i];
|
|
|
|
if(ent->client)
|
|
{
|
|
edict_t *temp;
|
|
|
|
temp = G_Spawn();
|
|
|
|
VectorSet(temp->s.origin,
|
|
ent->client->playerinfo.pcmd.camera_vieworigin[0] * 0.125,
|
|
ent->client->playerinfo.pcmd.camera_vieworigin[1] * 0.125,
|
|
ent->client->playerinfo.pcmd.camera_vieworigin[2] * 0.125);
|
|
|
|
VectorSet(temp->s.angles,
|
|
SHORT2ANGLE(ent->client->playerinfo.pcmd.camera_viewangles[0]),
|
|
SHORT2ANGLE(ent->client->playerinfo.pcmd.camera_viewangles[1]),
|
|
SHORT2ANGLE(ent->client->playerinfo.pcmd.camera_viewangles[2]));
|
|
|
|
if(infront_pos(temp, self->s.origin) && gi.inPVS(temp->s.origin, self->s.origin))
|
|
{
|
|
G_FreeEdict(temp);
|
|
return true;
|
|
}
|
|
|
|
G_FreeEdict(temp);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qboolean shoulder_room_ahead (edict_t *self)
|
|
{
|
|
vec3_t mins, maxs, endpos, angles, forward;
|
|
trace_t trace;
|
|
|
|
VectorSet(angles, 0, self->s.angles[YAW], 0);
|
|
AngleVectors(angles, forward, NULL, NULL);
|
|
VectorMA(self->s.origin, 128, forward, endpos);
|
|
VectorCopy(self->mins, mins);
|
|
VectorCopy(self->maxs, maxs);
|
|
mins[2] = 0;
|
|
maxs[2] = 1;
|
|
|
|
gi.trace(self->s.origin, mins, maxs, endpos, self, MASK_SOLID, &trace);
|
|
|
|
if(trace.allsolid || trace.startsolid || trace.fraction < 1.0)
|
|
{
|
|
// gi.dprintf("No shoulder room ahead for beast to charge!\n");
|
|
return (false);
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
void tbeast_blocked (edict_t *self, trace_t *trace)
|
|
{//fake_touch does all the actual damage, this is just a check for the charge stuff
|
|
vec3_t dir, start, forward, end, mins, maxs;
|
|
float speed;
|
|
trace_t tr;
|
|
qboolean playsound = true;
|
|
qboolean stop = false;
|
|
edict_t *pillar = NULL;
|
|
|
|
if(self->curAnimID==ANIM_CHARGE || (self->curAnimID==ANIM_QUICK_CHARGE && self->s.frame >= FRAME_charge1 && self->s.frame <= FRAME_charge10))
|
|
{
|
|
if(trace->ent == world)
|
|
{
|
|
if(&trace->plane)
|
|
{
|
|
if(!Vec3IsZero(trace->plane.normal))
|
|
{
|
|
if(trace->plane.normal[2]>0.7)
|
|
{//it's just a slope
|
|
playsound = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(playsound)
|
|
{
|
|
gi.sound(self, CHAN_ITEM, sounds[SND_SLAM], 1, ATTN_NORM, 0);
|
|
if(trace->ent)
|
|
{
|
|
if(!movable(trace->ent)&&!trace->ent->takedamage && trace->plane.normal[2] < 0.5)
|
|
stop = true;
|
|
}
|
|
}
|
|
|
|
if(trace->ent && trace->ent->targetname && !stricmp(trace->ent->targetname, "pillar"))
|
|
pillar = trace->ent;
|
|
else
|
|
{
|
|
VectorCopy(self->s.origin, start);
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
start[2] = self->absmin[2] + self->size[2] * 0.8 + TB_UP_OFFSET;
|
|
VectorMA(start, self->maxs[0] * 0.8 + TB_FWD_OFFSET, forward, start);
|
|
VectorMA(start, 150, forward, end);
|
|
|
|
VectorSet(mins, -24, -24, -1);
|
|
VectorSet(maxs, 24, 24, 1);
|
|
gi.trace(start, mins, maxs, end, self, MASK_SOLID,&tr);
|
|
if(tr.fraction<1.0 && tr.ent && tr.ent->targetname)
|
|
{
|
|
if(!stricmp(tr.ent->targetname, "pillar"))
|
|
pillar = tr.ent;
|
|
}
|
|
}
|
|
|
|
if(pillar)
|
|
{//FIXME: In higher skills, less chance of breaking it? Or debounce time?
|
|
// gi.dprintf("Hit a Pillar!\n");
|
|
|
|
if(visible_to_client(self))
|
|
{
|
|
self->red_rain_count++;
|
|
if(self->red_rain_count >= 2)//got both pillars, now die
|
|
{
|
|
//self->clipmask = 0;
|
|
self->solid = SOLID_NOT;
|
|
self->takedamage = DAMAGE_NO;
|
|
}
|
|
G_UseTargets (pillar, self);
|
|
pillar->targetname = "";//so we don't hit them again
|
|
pillar->target = "stop";//so it doesn't fire the target when it's broken later
|
|
|
|
self->monsterinfo.attack_finished = level.time + 3;
|
|
}
|
|
|
|
stop = true;
|
|
}
|
|
|
|
if(stop)
|
|
{
|
|
gi.CreateEffect(&self->s,
|
|
FX_QUAKE,
|
|
0,
|
|
vec3_origin,
|
|
"bbb",
|
|
4,
|
|
3,
|
|
7);
|
|
|
|
VectorCopy(self->velocity, dir);
|
|
speed = VectorNormalize(dir);
|
|
|
|
self->velocity[0] = self->velocity[1] = 0;
|
|
self->sounds++;
|
|
if(self->sounds!=2 && irand(0, 1))
|
|
SetAnim(self, ANIM_STUN);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tbeast_charge (edict_t *self, float force)
|
|
{
|
|
vec3_t forward, enemy_dir;
|
|
float save_v2;
|
|
qboolean succeeded = false;
|
|
|
|
if(!M_ValidTarget(self, self->enemy))
|
|
{
|
|
SetAnim(self, ANIM_WALK);
|
|
return;
|
|
}
|
|
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, enemy_dir);
|
|
VectorNormalize(enemy_dir);
|
|
|
|
if(DotProduct(forward, enemy_dir) < 0.75)//enemy not generally ahead
|
|
ai_charge(self, 0);
|
|
|
|
MG_WalkMove (self, self->s.angles[YAW], force, &succeeded);
|
|
|
|
if(self->groundentity)
|
|
{
|
|
save_v2 = self->velocity[2];
|
|
VectorScale(forward, force * 10 * 2, self->velocity);
|
|
self->velocity[2] = save_v2;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// TBeast Eat - decide which eating animations to use
|
|
//----------------------------------------------------------------------
|
|
void tbeast_eat(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(FindTarget(self))
|
|
return;
|
|
|
|
if(!irand(0,1))
|
|
SetAnim(self, ANIM_EATING_TWITCH);
|
|
else
|
|
SetAnim(self, ANIM_EATING);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
TBeast Watch -decide which standing animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void tbeast_watch(edict_t *self, G_Message_t *msg)
|
|
{
|
|
SetAnim(self, ANIM_STAND);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
TBeast Stand -decide which standing animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void tbeast_stand(edict_t *self, G_Message_t *msg)
|
|
{
|
|
vec3_t v;
|
|
float len;
|
|
|
|
if (self->ai_mood == AI_MOOD_DELAY)
|
|
{
|
|
SetAnim(self, ANIM_DELAY);
|
|
return;
|
|
}
|
|
|
|
if (self->enemy)
|
|
{
|
|
if (clear_visible(self, self->enemy))
|
|
return;
|
|
|
|
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;
|
|
}
|
|
else if (len < 400)
|
|
{
|
|
if (!irand(0, 3))
|
|
{
|
|
tbeast_growl(self);
|
|
}
|
|
}
|
|
|
|
if (!irand(0, 3))
|
|
{
|
|
tbeast_growl(self);
|
|
SetAnim(self, ANIM_STAND);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (self->monsterinfo.aiflags & AI_EATING)
|
|
SetAnim(self, ANIM_EATING);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
SetAnim(self, ANIM_STAND);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
TBeast Walk -decide which walk animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void tbeast_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_WALK);
|
|
return;
|
|
}
|
|
|
|
/* if(clear_visible(self, self->enemy) && ahead(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)))
|
|
{
|
|
gi.dprintf("Jump from walk at enemy\n");
|
|
SetAnim(self,ANIM_JUMP);
|
|
return;
|
|
}
|
|
}*/
|
|
|
|
delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
|
|
if (delta > 25 && delta <= 180)
|
|
{
|
|
SetAnim(self, ANIM_WALKRT);
|
|
}
|
|
else if (delta > 180 && delta < 335)
|
|
{
|
|
SetAnim(self, ANIM_WALKLEFT);
|
|
}
|
|
else
|
|
{
|
|
SetAnim(self, ANIM_WALK);
|
|
}
|
|
}
|
|
|
|
void tbeast_init_charge (edict_t *self)
|
|
{
|
|
// gi.dprintf("Beast CHARGE!\n");
|
|
if(!ahead(self, self->enemy) || !irand(0,3))
|
|
SetAnim(self, ANIM_QUICK_CHARGE);
|
|
else
|
|
SetAnim(self, ANIM_CHARGE);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
TBeast Melee - decide which melee animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void tbeast_melee(edict_t *self, G_Message_t *msg)
|
|
{
|
|
vec3_t v, melee_point, forward, up;
|
|
float len, seperation;
|
|
float chance;
|
|
|
|
if(!M_ValidTarget(self, self->enemy))
|
|
{
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
return;
|
|
}
|
|
|
|
AngleVectors(self->s.angles,forward, NULL, up);
|
|
VectorMA(self->s.origin, self->maxs[2]*0.5 + TB_UP_OFFSET, up, melee_point);
|
|
VectorMA(melee_point, 150 + TB_FWD_OFFSET, forward, melee_point);
|
|
|
|
VectorSubtract (self->enemy->s.origin, melee_point, v);
|
|
len = VectorLength (v);
|
|
|
|
seperation = self->enemy->maxs[0];
|
|
|
|
if ((len > 250 || !skill->value) && self->monsterinfo.attack_finished > level.time)
|
|
{//too soon to attack again
|
|
if (flrand(0, 1) < 0.9 && !skill->value)
|
|
SetAnim(self, ANIM_STAND);
|
|
else
|
|
SetAnim(self, ANIM_ROAR);
|
|
|
|
return;
|
|
}
|
|
|
|
//ok to attack
|
|
if(len - seperation < 100)
|
|
{//melee
|
|
// gi.dprintf("Biting: ");
|
|
chance = flrand(0, 1);
|
|
|
|
if(self->enemy->absmin[2] > melee_point[2] + 128)
|
|
{
|
|
// gi.dprintf(" snatch extra high\n");
|
|
SetAnim(self,ANIM_BITEUP2);
|
|
}
|
|
else if(self->enemy->absmin[2] > melee_point[2])
|
|
{
|
|
// gi.dprintf(" snatch high\n");
|
|
SetAnim(self,ANIM_BITEUP);
|
|
}
|
|
else
|
|
{
|
|
// gi.dprintf(" snatch low\n");
|
|
SetAnim(self,ANIM_BITELOW);
|
|
}
|
|
|
|
self->monsterinfo.attack_finished = level.time + flrand(2, 3);
|
|
return;
|
|
}
|
|
else if(len - seperation < self->melee_range && !irand(0,2))
|
|
{
|
|
// gi.dprintf("walk attack\n");
|
|
SetAnim(self,ANIM_WALKATK);
|
|
}
|
|
|
|
if (self->enemy->classID != CID_TCHECKRIK && ((irand(0, 1) && infront(self, self->enemy)) || ahead(self, self->enemy)) && shoulder_room_ahead(self))
|
|
{
|
|
tbeast_init_charge(self);
|
|
}
|
|
else
|
|
SetAnim(self, ANIM_WALK);
|
|
}
|
|
|
|
void tbeast_start_charge(edict_t *self, G_Message_t *msg)
|
|
{
|
|
MG_ChangeYaw(self);
|
|
if(self->enemy->classID != CID_TCHECKRIK && ((irand(0, 1) && infront(self, self->enemy)) || ahead(self, self->enemy)) && shoulder_room_ahead(self))
|
|
{
|
|
tbeast_init_charge(self);
|
|
}
|
|
else
|
|
SetAnim(self, ANIM_WALK);
|
|
}
|
|
|
|
#define TBEAST_SBAR_SIZE 3500
|
|
|
|
/*----------------------------------------------------------------------
|
|
TBeast Run -decide which run animations to use
|
|
-----------------------------------------------------------------------*/
|
|
void tbeast_run(edict_t *self, G_Message_t *msg)
|
|
{
|
|
vec3_t v;
|
|
float len;
|
|
float delta;
|
|
qboolean enemy_vis;
|
|
vec3_t targ_org;
|
|
|
|
if(!M_ValidTarget(self, self->enemy))
|
|
return;
|
|
|
|
if(!MG_GetTargOrg(self, targ_org))
|
|
return;
|
|
|
|
if(!self->dmg)
|
|
{
|
|
self->dmg = true;
|
|
SetAnim(self, ANIM_CHARGE);
|
|
return;
|
|
}
|
|
|
|
VectorSubtract (self->s.origin, targ_org, v);
|
|
len = VectorLength (v);
|
|
|
|
enemy_vis = clear_visible(self, self->enemy);
|
|
/* if(enemy_vis && ahead(self, self->enemy))
|
|
{
|
|
// Enemy is within range and far enough above or below to warrant a jump
|
|
if ((len > 40) && (len < 600) && ((self->s.origin[2] < self->enemy->s.origin[2] - 40) ||
|
|
(self->s.origin[2] > self->enemy->s.origin[2] + 40)))
|
|
{
|
|
if (abs(self->s.origin[2] - self->enemy->s.origin[2] - 40) < 200) // Can't jump more than 200 high
|
|
{
|
|
if (!irand(0, 2))
|
|
{
|
|
gi.dprintf("Jump from run at enemy\n");
|
|
SetAnim(self, ANIM_JUMP);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
if (self->enemy->classID != CID_TCHECKRIK && enemy_vis && ((irand(0, 1) && infront(self, self->enemy)) || ahead(self, self->enemy)) && shoulder_room_ahead(self))
|
|
{
|
|
tbeast_init_charge(self);
|
|
}
|
|
else// if ((len < 200) && (self->monsterinfo.currframeindex == 0))
|
|
{
|
|
delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
|
|
if (delta > 45 && delta <= 180)
|
|
{
|
|
SetAnim(self, ANIM_WALKRT); // Turn right
|
|
}
|
|
else if (delta > 180 && delta < 315)
|
|
{
|
|
SetAnim(self, ANIM_WALKLEFT); // Turn left
|
|
}
|
|
else
|
|
{
|
|
SetAnim(self, ANIM_WALK); // Run on
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
TBeast Pain - make the decision between pains 1, 2, or 3
|
|
-----------------------------------------------------------------------*/
|
|
|
|
int tbeast_foot_damaged(edict_t *self, edict_t *attacker, float knockback, int take)
|
|
{
|
|
QPostMessage(self->owner, MSG_PAIN, PRI_DIRECTIVE, "eeiii", self->owner, attacker, knockback, take, 0);
|
|
return false;
|
|
}
|
|
|
|
void tbeast_pain(edict_t *self, G_Message_t *msg)
|
|
{
|
|
edict_t *tempent;
|
|
int temp, damage;
|
|
qboolean force_pain;
|
|
|
|
if(self->health < 1000)
|
|
return;
|
|
|
|
if(!self->groundentity)
|
|
return;
|
|
|
|
if(self->pain_debounce_time > level.time)
|
|
return;
|
|
|
|
ParseMsgParms(msg, "eeiii", &tempent, &tempent, &force_pain, &damage, &temp);
|
|
|
|
if(damage < irand(100, 200))
|
|
return;
|
|
|
|
self->pain_debounce_time = level.time + 10;
|
|
|
|
if (!irand(0,1))
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_PAIN1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_PAIN2], 1, ATTN_NORM, 0);
|
|
|
|
SetAnim(self, ANIM_STUN);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
TBeast Die - choose death
|
|
-----------------------------------------------------------------------*/
|
|
void tbeast_death(edict_t *self, G_Message_t *msg)
|
|
{
|
|
self->msgHandler = DeadMsgHandler;
|
|
|
|
if (self->deadflag == DEAD_DEAD) // Dead but still being hit
|
|
return;
|
|
|
|
M_ShowLifeMeter(self, 0, 0);
|
|
// regular death
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_DIE], 1, ATTN_NONE, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
self->takedamage = DAMAGE_YES;
|
|
|
|
SetAnim(self, ANIM_DIE_NORM);
|
|
}
|
|
|
|
void pitch_roll_for_slope (edict_t *forwhom, vec3_t *slope);
|
|
void tbeast_go_die (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
M_ShowLifeMeter(self, 0, 0);
|
|
|
|
self->msgHandler = DeadMsgHandler;
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_DIE], 1, ATTN_NONE, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
self->takedamage = DAMAGE_NO;
|
|
|
|
self->solid = SOLID_NOT;
|
|
self->movetype = PHYSICSTYPE_NONE;
|
|
self->clipmask = 0;
|
|
VectorClear(self->mins);
|
|
VectorClear(self->maxs);
|
|
|
|
// self->s.origin[2] += -8 - self->mins[2];
|
|
// self->mins[2] = -8;
|
|
// self->maxs[2] = self->mins[2] + 24;
|
|
// self->svflags |= SVF_DEADMONSTER;
|
|
|
|
self->health = 0;
|
|
self->post_think = NULL;
|
|
self->next_post_think = -1;
|
|
self->touch = NULL;
|
|
SetAnim(self, ANIM_DIE);
|
|
pitch_roll_for_slope(self, NULL);
|
|
G_UseTargets(self, activator);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------
|
|
|
|
ACTION FUNCTIONS FOR THE MONSTER
|
|
|
|
-----------------------------------------------------------------------*/
|
|
|
|
void tbeast_standorder (edict_t *self)
|
|
{
|
|
if(tbeastCheckMood(self))
|
|
return;
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
void tbeast_walkorder (edict_t *self)
|
|
{
|
|
if(tbeastCheckMood(self))
|
|
return;
|
|
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
void tbeast_footstep (edict_t *self)
|
|
{
|
|
vec3_t forward, right, pos, up, lfootoffset, rfootoffset, bottom;
|
|
int leg_check_index;
|
|
trace_t trace;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
|
|
leg_check_index = tbeast_inwalkframes(self);
|
|
|
|
if(leg_check_index > -1)
|
|
{//set up leg checks - only if in these frames
|
|
//left leg
|
|
if(leg_check_index < 6 || leg_check_index > 14)
|
|
{
|
|
VectorCopy(GetLeftFootOffsetForFrameIndex[leg_check_index], lfootoffset);
|
|
VectorMA(self->s.origin, lfootoffset[0] + TB_FWD_OFFSET, forward, pos);
|
|
VectorMA(pos, lfootoffset[1] + TB_RT_OFFSET, right, pos);
|
|
VectorMA(pos, lfootoffset[2] + TB_UP_OFFSET, up, pos);
|
|
}
|
|
else
|
|
{//right leg
|
|
VectorCopy(GetRightFootOffsetForFrameIndex[leg_check_index], rfootoffset);
|
|
VectorMA(self->s.origin, rfootoffset[0] + TB_FWD_OFFSET, forward, pos);
|
|
VectorMA(pos, rfootoffset[1] + TB_RT_OFFSET, right, pos);
|
|
VectorMA(pos, rfootoffset[2] + TB_UP_OFFSET, up, pos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(self->s.origin, pos);
|
|
VectorMA(pos, self->maxs[0], forward, pos);
|
|
pos[2] += self->mins[2];
|
|
|
|
if(self->monsterinfo.currframeindex == FRAME_walk11 ||
|
|
self->monsterinfo.currframeindex == FRAME_wlkrt11 ||
|
|
self->monsterinfo.currframeindex == FRAME_wlklft11)
|
|
{
|
|
VectorMA(pos, -32, right, pos);
|
|
}
|
|
else
|
|
{
|
|
VectorMA(pos, 32, right, pos);
|
|
}
|
|
}
|
|
|
|
VectorCopy(pos, bottom);
|
|
bottom[2]-=128;
|
|
gi.trace(pos, vec3_origin, vec3_origin, bottom, self, MASK_SOLID, &trace);
|
|
if(trace.fraction < 1.0)
|
|
VectorCopy(trace.endpos, pos);
|
|
|
|
pos[2] += 10;
|
|
gi.CreateEffect(NULL,
|
|
FX_TB_EFFECTS,
|
|
0,
|
|
pos,
|
|
"bv",
|
|
FX_TB_PUFF,
|
|
vec3_origin);
|
|
|
|
VectorSet(up, flrand(-20, 20), flrand(-20, 20), flrand(20, 100));
|
|
gi.CreateEffect(NULL, FX_OGLE_HITPUFF, 0, pos, "v", up);
|
|
|
|
gi.CreateEffect(&self->s,
|
|
FX_QUAKE,
|
|
0,
|
|
vec3_origin,
|
|
"bbb",
|
|
3,
|
|
1,
|
|
2);
|
|
|
|
if (!irand(0,1))
|
|
gi.sound(self, CHAN_BODY, sounds[SND_STEP1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_BODY, sounds[SND_STEP2], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void tbeast_growl (edict_t *self)
|
|
{
|
|
int chance;
|
|
|
|
chance = irand(0, 200);
|
|
if (chance < 10)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_GROWL1], 1, ATTN_NORM, 0);
|
|
else if (chance < 20)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_GROWL2], 1, ATTN_NORM, 0);
|
|
else if (chance < 30)
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_GROWL3], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void tbeast_snort (edict_t *self)
|
|
{
|
|
int chance;
|
|
vec3_t forward, right, spot, spot2, vec;
|
|
|
|
chance = irand(0, 20);
|
|
if (chance < 2)
|
|
{
|
|
if(!irand(0,1))
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_SNORT1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_SNORT2], 1, ATTN_NORM, 0);
|
|
//make snort effect from nose
|
|
AngleVectors(self->s.angles, forward, right, NULL);
|
|
|
|
VectorCopy(self->s.origin, spot);
|
|
spot[2] += 36;
|
|
|
|
VectorMA(spot, 100, forward, spot2);
|
|
|
|
VectorMA(spot2, 64, right, spot2);//more than we want to get a nice vec
|
|
VectorSubtract(spot2, spot, vec);
|
|
VectorNormalize(vec);
|
|
VectorMA(spot2, -56, right, spot2);//back to + 8
|
|
|
|
gi.CreateEffect(NULL, FX_FLAMETHROWER, CEF_FLAG6|CEF_FLAG7, spot2, "df", vec, 100.0f);
|
|
|
|
|
|
Vec3ScaleAssign(-1, right);
|
|
|
|
VectorMA(spot2, 72, right, spot2);//more than we want to get a nice vec
|
|
VectorSubtract(spot2, spot, vec);
|
|
VectorNormalize(vec);
|
|
VectorMA(spot2, -56, right, spot2);//back to +16
|
|
|
|
gi.CreateEffect(NULL, FX_FLAMETHROWER, CEF_FLAG6|CEF_FLAG7, spot2, "df", vec, 100.0f);
|
|
}
|
|
}
|
|
|
|
qboolean tbeastCheckMood(edict_t *self)
|
|
{
|
|
self->mood_think(self);
|
|
|
|
if(self->ai_mood == AI_MOOD_NORMAL)
|
|
return false;
|
|
|
|
switch (self->ai_mood)
|
|
{
|
|
case AI_MOOD_ATTACK:
|
|
if(self->ai_mood_flags&AI_MOOD_FLAG_MISSILE)
|
|
QPostMessage(self, MSG_MISSILE, PRI_DIRECTIVE, NULL);
|
|
else
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
case AI_MOOD_PURSUE:
|
|
self->wait = 0;
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
case AI_MOOD_NAVIGATE:
|
|
SetAnim(self, ANIM_WALK);
|
|
break;
|
|
|
|
case AI_MOOD_STAND:
|
|
if (self->monsterinfo.aiflags & AI_EATING)
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
else
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
case AI_MOOD_DELAY:
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
case AI_MOOD_EAT:
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
default :
|
|
#ifdef _DEVEL
|
|
gi.dprintf("beast: Unusable mood %d!\n", self->ai_mood);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
TBeast Pause - decide what to do after attacking
|
|
-----------------------------------------------------------------------*/
|
|
void tbeast_pause (edict_t *self)
|
|
{
|
|
vec3_t v;
|
|
float len;
|
|
|
|
if(self->enemy && self->enemy->classID != CID_TCHECKRIK && self->curAnimID == ANIM_STUN && self->pain_debounce_time > level.time + 7 && ahead(self, self->enemy))
|
|
{
|
|
tbeast_init_charge(self);
|
|
return;
|
|
}
|
|
|
|
if(tbeastCheckMood(self))
|
|
return;
|
|
|
|
if(!M_ValidTarget(self, self->enemy))
|
|
return;
|
|
|
|
if(clear_visible(self, self->enemy))
|
|
{
|
|
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
|
len = VectorLength (v);
|
|
}
|
|
else
|
|
len = 999999;
|
|
|
|
if (len > 120) // Far enough to run after
|
|
{
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
}
|
|
else // Close enough to Attack or Hop
|
|
{
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
void tbeast_runorder (edict_t *self)
|
|
{
|
|
if(tbeastCheckMood(self))
|
|
return;
|
|
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
void tbeastbite (edict_t *self, float ofsf, float ofsr, float ofsu)
|
|
{
|
|
vec3_t v;
|
|
float damage;
|
|
float melee_range;
|
|
vec3_t temp, forward, right, 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(!M_ValidTarget(self, self->enemy))
|
|
return;
|
|
|
|
AngleVectors(self->s.angles,forward, right, up);
|
|
VectorMA(self->s.origin, ofsu + TB_UP_OFFSET, up, melee_point);
|
|
VectorMA(melee_point, ofsf + TB_FWD_OFFSET, forward, melee_point);
|
|
VectorMA(melee_point, ofsr, right, melee_point);
|
|
|
|
melee_range = TBEAST_STD_MELEE_RNG;//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
|
|
{
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_CHOMP], 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(TB_DMG_BITE_MIN, TB_DMG_BITE_MAX);
|
|
T_Damage (self->enemy, self, self, forward, trace.endpos, v, damage, damage/2, DAMAGE_DISMEMBER,MOD_DIED);
|
|
}
|
|
else // A misssss
|
|
{
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_SNATCH], 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
void tbeast_dead(edict_t *self)
|
|
{
|
|
self->movetype = PHYSICSTYPE_NONE;
|
|
self->deadState = DEAD_DEAD;
|
|
self->think = NULL;
|
|
self->nextthink = 0;
|
|
level.fighting_beast = false;
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
void tbeast_land(edict_t *self)
|
|
{
|
|
vec3_t up, pos;
|
|
edict_t *found = NULL;
|
|
|
|
// self->gravity = 1.0;
|
|
|
|
gi.CreateEffect(&self->s,
|
|
FX_QUAKE,
|
|
0,
|
|
vec3_origin,
|
|
"bbb",
|
|
7,
|
|
7,
|
|
7);
|
|
|
|
|
|
VectorSet(up, flrand(-50,50), flrand(-50,50), flrand(50,300));
|
|
|
|
VectorCopy(self->s.origin, pos);
|
|
pos[0] += flrand(-50,50);
|
|
pos[1] += flrand(-50,50);
|
|
pos[2] += self->mins[2];
|
|
gi.CreateEffect(NULL, FX_OGLE_HITPUFF, 0, pos, "v", up);
|
|
VectorCopy(self->s.origin, pos);
|
|
pos[0] += flrand(-50,50);
|
|
pos[1] += flrand(-50,50);
|
|
pos[2] += self->mins[2];
|
|
gi.CreateEffect(NULL, FX_OGLE_HITPUFF, 0, pos, "v", up);
|
|
VectorCopy(self->s.origin, pos);
|
|
pos[0] += flrand(-50,50);
|
|
pos[1] += flrand(-50,50);
|
|
pos[2] += self->mins[2];
|
|
gi.CreateEffect(NULL, FX_OGLE_HITPUFF, 0, pos, "v", up);
|
|
VectorCopy(self->s.origin, pos);
|
|
pos[0] += flrand(-50,50);
|
|
pos[1] += flrand(-50,50);
|
|
pos[2] += self->mins[2];
|
|
gi.CreateEffect(NULL, FX_OGLE_HITPUFF, 0, pos, "v", up);
|
|
|
|
gi.sound(self, CHAN_ITEM, sounds[SND_LAND], 1, ATTN_NORM, 0);
|
|
|
|
while(found = findradius(found, self->s.origin, 512))
|
|
{
|
|
if(found->client)
|
|
{
|
|
if(found->health > 0 && found->groundentity)
|
|
{
|
|
if(found->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN)
|
|
{
|
|
P_KnockDownPlayer(&found->client->playerinfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void tbeast_roar_knockdown(edict_t *self)
|
|
{
|
|
edict_t *found = NULL;
|
|
|
|
if(irand(0, 2))
|
|
return;
|
|
|
|
while(found = findradius(found, self->s.origin, 512))
|
|
{
|
|
if(found->client && ahead(self, found))
|
|
{
|
|
if(found->health > 0 && found->groundentity)
|
|
{
|
|
if(found->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN)
|
|
{
|
|
P_KnockDownPlayer(&found->client->playerinfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void tbeast_roar(edict_t *self)
|
|
{
|
|
/* vec3_t forward, endpos;
|
|
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorMA(self->s.origin, 128, forward, endpos);
|
|
gi.CreateEffect( NULL, FX_FLAMETHROWER, 0, endpos, "df", forward, 200);*/
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_ROAR2], 1, ATTN_NONE, 0);
|
|
}
|
|
|
|
void tbeast_roar_short(edict_t *self)
|
|
{
|
|
if(!self->delay)
|
|
{
|
|
self->monsterinfo.currframeindex = 25;
|
|
self->monsterinfo.nextframeindex = 26;
|
|
self->s.frame = FRAME_charge1;
|
|
self->delay = true;
|
|
}
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_ROAR], 1, ATTN_NONE, 0);
|
|
}
|
|
|
|
void tbeast_eatorder (edict_t *self)
|
|
{
|
|
if(tbeastCheckMood(self))
|
|
return;
|
|
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
}
|
|
|
|
void tbeast_apply_jump (edict_t *self)
|
|
{
|
|
// gi.dprintf("Jump from TB_CheckJump\n");
|
|
// self->gravity = TB_JUMP_GRAV;
|
|
VectorCopy(self->movedir, self->velocity);
|
|
VectorNormalize(self->movedir);
|
|
}
|
|
|
|
|
|
qboolean CheckMoveFoot (edict_t *self, edict_t *foot, vec3_t dest)
|
|
{
|
|
vec3_t dir;
|
|
trace_t trace;
|
|
|
|
VectorSubtract(dest, foot->s.origin, dir);
|
|
VectorNormalize(dir);
|
|
|
|
gi.trace(foot->s.origin, foot->mins, foot->maxs, dest, foot, MASK_MONSTERSOLID,&trace);
|
|
|
|
if(trace.ent)
|
|
{
|
|
if(trace.ent->takedamage)
|
|
{
|
|
T_Damage(trace.ent, self, self, dir, trace.endpos, dir, 1000, 250, DAMAGE_DISMEMBER,MOD_DIED);
|
|
VectorCopy(foot->s.origin, foot->last_org);
|
|
VectorCopy(dest, foot->s.origin);
|
|
// VectorCopy(trace.endpos, foot->s.origin);
|
|
return true;
|
|
}
|
|
// else
|
|
// return false;
|
|
}
|
|
|
|
// if(trace.allsolid||trace.startsolid)
|
|
// return false;
|
|
|
|
// if(trace.fraction<0.9)
|
|
// return false;
|
|
|
|
VectorCopy(foot->s.origin, foot->last_org);
|
|
VectorCopy(dest, foot->s.origin);
|
|
// VectorCopy(trace.endpos, foot->s.origin);
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean TB_CheckBottom (edict_t *self)
|
|
{
|
|
vec3_t end;
|
|
trace_t trace;
|
|
vec3_t other_top, down, up;
|
|
|
|
VectorCopy(self->s.origin, end);
|
|
end[2] -= 1;
|
|
gi.trace(self->s.origin, self->mins, self->maxs, end, self, MASK_ALL,&trace);
|
|
|
|
if(trace.ent && stricmp(trace.ent->classname, "worldspawn"))
|
|
{
|
|
if(trace.ent->takedamage)
|
|
{
|
|
VectorCopy(trace.ent->s.origin, other_top);
|
|
other_top[2] += trace.ent->maxs[2];
|
|
VectorSubtract(trace.ent->s.origin, self->s.origin, down);
|
|
VectorScale(down, -1, up);
|
|
// gi.dprintf("CheckBottom damaging %s\n", trace.ent->classname);
|
|
// T_Damage(trace.ent, self, self, down, other_top, up, 1000, 0, DAMAGE_DISMEMBER);
|
|
}
|
|
}
|
|
|
|
if(trace.fraction < 1.0 || trace.startsolid || trace.allsolid)
|
|
{
|
|
/*if(&trace.plane)
|
|
{
|
|
if(!Vec3IsZero(trace.plane.normal))
|
|
{
|
|
if(trace.plane.normal[2]>=0.5&&trace.plane.normal[2]<=1)//not a slope can go up
|
|
{//raise him up if on flat ground, lower is on slope - to keep feet on ground;
|
|
self->mins[2] = (1 - trace.plane.normal[2]) * 72 - 8 + TB_UP_OFFSET;
|
|
}
|
|
}
|
|
}*/
|
|
self->groundentity = trace.ent;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
qboolean TB_CheckJump (edict_t *self)//, edict_t *other)
|
|
{
|
|
vec3_t forward, start, end, start2, end2, mins, maxs;
|
|
trace_t trace;
|
|
float z_diff;
|
|
qboolean skiplow = false;
|
|
|
|
if(self->enemy)
|
|
{
|
|
if(!ahead(self, self->enemy))
|
|
return false;
|
|
|
|
if(vhlen(self->enemy->s.origin, self->s.origin)<200)
|
|
{
|
|
z_diff = self->s.origin[2] + TB_HIBITE_U + TB_UP_OFFSET - self->enemy->s.origin[2];
|
|
if(z_diff < -128)
|
|
{
|
|
SetAnim(self, ANIM_BITEUP2);
|
|
return true;
|
|
}
|
|
else if(Q_fabs(z_diff)<=32)
|
|
{
|
|
SetAnim(self, ANIM_BITEUP);
|
|
return true;
|
|
}
|
|
else if(z_diff < -32 && z_diff > -200)
|
|
skiplow = true;
|
|
else if(z_diff > 40 && z_diff < -24)
|
|
{
|
|
SetAnim(self, ANIM_BITELOW);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(self->enemy->s.origin[2] < self->s.origin[2])
|
|
return false;
|
|
}
|
|
|
|
if(self->monsterinfo.jump_time > level.time)
|
|
return false;
|
|
|
|
VectorCopy(self->s.origin, start);
|
|
VectorCopy(start, end);
|
|
|
|
//try a jump of 186
|
|
end[2] += self->size[2];
|
|
|
|
if(!skiplow)
|
|
{
|
|
gi.trace(start, self->mins, self->maxs, end, self, MASK_SOLID,&trace);
|
|
|
|
if(trace.fraction == 1.0 && !trace.startsolid && !trace.allsolid)
|
|
{
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
|
|
VectorCopy(end, start2);
|
|
VectorMA(end, 64, forward, end2);
|
|
VectorScale(self->maxs, 0.5, maxs);
|
|
VectorCopy(self->mins, mins);
|
|
mins[0]*=0.5;
|
|
mins[1]*=0.5;
|
|
gi.trace(start2, self->mins, self->maxs, end2, self, MASK_SOLID,&trace);
|
|
if(trace.fraction == 1.0 && !trace.startsolid && !trace.allsolid)
|
|
{
|
|
// gi.dprintf("Beast blocked low jump!\n");
|
|
VectorScale(forward, 250, self->movedir);
|
|
self->movedir[2] = 400;
|
|
self->monsterinfo.jump_time = level.time + 7;
|
|
SetAnim(self, ANIM_FJUMP);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//try a jump of 372
|
|
end[2] += self->size[2];
|
|
|
|
gi.trace(start, self->mins, self->maxs, end, self, MASK_SOLID,&trace);
|
|
|
|
if(trace.fraction == 1.0 && !trace.startsolid && !trace.allsolid)
|
|
{
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
|
|
VectorCopy(end, start2);
|
|
VectorMA(end, 64, forward, end2);
|
|
VectorScale(self->maxs, 0.5, maxs);
|
|
VectorCopy(self->mins, mins);
|
|
mins[0]*=0.5;
|
|
mins[1]*=0.5;
|
|
gi.trace(start2, self->mins, self->maxs, end2, self, MASK_SOLID,&trace);
|
|
if(trace.fraction == 1.0 && !trace.startsolid && !trace.allsolid)
|
|
{
|
|
// gi.dprintf("Beast blocked high jump!\n");
|
|
VectorScale(forward, 300, self->movedir);
|
|
self->movedir[2] = 600;
|
|
self->monsterinfo.jump_time = level.time + 7;
|
|
SetAnim(self, ANIM_FJUMP);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MG_Pathfind(edict_t *self, qboolean check_clear_path);
|
|
void tbeast_run_think (edict_t *self, float dist)
|
|
{
|
|
vec3_t angles, forward, start, end, mins;
|
|
trace_t trace;
|
|
/*
|
|
matrix3_t RotationMatrix;
|
|
vec3_t lfootoffset, rfootoffset, newlfootpos, newrfootpos;
|
|
vec3_t save_lf_org, save_rf_org, save_my_org;
|
|
float save_yaw;*/
|
|
|
|
if(!M_ValidTarget(self, self->enemy))
|
|
{
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
return;
|
|
}
|
|
|
|
//see if I'm on ground
|
|
TB_CheckBottom(self);
|
|
|
|
if(self->monsterinfo.aiflags &AI_USING_BUOYS)
|
|
MG_Pathfind(self, false);
|
|
|
|
if(!MG_MoveToGoal (self, dist))
|
|
{
|
|
VectorSet(angles, 0, self->s.angles[YAW], 0);
|
|
AngleVectors(angles, forward, NULL, NULL);
|
|
|
|
VectorCopy(self->s.origin, start);
|
|
VectorMA(start, 128, forward, end);
|
|
VectorCopy(self->mins, mins);
|
|
mins[2]+=54;//his step height
|
|
gi.trace(start, mins, self->maxs, end, self, MASK_SOLID,&trace);
|
|
if(trace.ent)
|
|
{
|
|
if(movable(trace.ent) || trace.ent->solid!=SOLID_BSP)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if(trace.fraction == 1.0 || //nothing there - ledge
|
|
(&trace.plane && !Vec3IsZero(trace.plane.normal) && trace.plane.normal[2]<0.7))//not a slope can go up
|
|
{
|
|
#ifdef _DEVEL
|
|
if(TB_CheckJump(self))
|
|
gi.dprintf("Enemy was ahead!\n");
|
|
#else
|
|
TB_CheckJump(self);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
========================================
|
|
TBEAST PICK UP AND GORE SOMETHING
|
|
========================================
|
|
*/
|
|
void tbeast_ready_catch (edict_t *self)
|
|
{
|
|
float enemy_zdist, ok_zdist;
|
|
|
|
if(!self->targetEnt)
|
|
return;
|
|
|
|
ok_zdist = 128;
|
|
if(ok_zdist<48)
|
|
ok_zdist = 48;
|
|
|
|
enemy_zdist = (self->targetEnt->s.origin[2] + self->targetEnt->mins[2]) - self->s.origin[2];
|
|
if(enemy_zdist<=self->maxs[2] + ok_zdist && self->targetEnt->velocity[2]<=-60)
|
|
SetAnim(self,ANIM_CATCH);
|
|
else
|
|
SetAnim(self,ANIM_READY_CATCH);
|
|
}
|
|
|
|
void tbeast_throw_toy(edict_t *self)
|
|
{
|
|
if(!self->targetEnt)
|
|
return;
|
|
|
|
self->targetEnt->flags &= ~FL_FLY;
|
|
self->targetEnt->velocity[0] = self->targetEnt->velocity[1] = 0;
|
|
self->targetEnt->velocity[2] = 500;
|
|
if(self->targetEnt->movetype>NUM_PHYSICSTYPES)
|
|
self->targetEnt->movetype = PHYSICSTYPE_STEP;
|
|
VectorRandomCopy(vec3_origin,self->targetEnt->avelocity,300);
|
|
|
|
if(stricmp(self->targetEnt->classname,"player"))
|
|
QPostMessage(self->targetEnt, MSG_DEATH, PRI_DIRECTIVE, NULL);
|
|
|
|
if(self->targetEnt->client)
|
|
gi.sound(self->targetEnt, CHAN_VOICE, sounds[SND_CORVUS_DIE], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void tbeast_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;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
VectorMA(self->s.origin, ofsf + TB_FWD_OFFSET - 32, forward, enemy_ofs);
|
|
VectorMA(enemy_ofs, ofsr, right, enemy_ofs);
|
|
VectorMA(enemy_ofs, ofsu + TB_UP_OFFSET, up, self->targetEnt->s.origin);
|
|
VectorSubtract(self->targetEnt->s.origin, self->s.origin, blooddir);
|
|
|
|
VectorScale(blooddir, -1, enemy_face);
|
|
enemy_face[2]/=10;
|
|
vectoangles(enemy_face, self->targetEnt->s.angles);
|
|
|
|
switch(self->targetEnt->count)
|
|
{
|
|
case 1:
|
|
self->targetEnt->s.angles[PITCH]=anglemod(self->targetEnt->s.angles[PITCH]+90);//can't do roll?
|
|
break;
|
|
case 2:
|
|
self->targetEnt->s.angles[PITCH]=anglemod(self->targetEnt->s.angles[PITCH]-90);//can't do roll?
|
|
break;
|
|
case 3:
|
|
self->targetEnt->s.angles[ROLL]=anglemod(self->targetEnt->s.angles[ROLL]+90);//can't do roll?
|
|
break;
|
|
case 4:
|
|
self->targetEnt->s.angles[ROLL]=anglemod(self->targetEnt->s.angles[ROLL]-90);//can't do roll?
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
VectorClear(self->targetEnt->velocity);
|
|
VectorClear(self->targetEnt->avelocity);
|
|
|
|
if(flrand(0,1)<0.5)
|
|
{
|
|
if(self->targetEnt->materialtype == MAT_INSECT)
|
|
gi.CreateEffect(&self->targetEnt->s, FX_BLOOD, CEF_FLAG8, self->targetEnt->s.origin, "ub", blooddir, 200);
|
|
else
|
|
gi.CreateEffect(&self->targetEnt->s, FX_BLOOD, 0, self->targetEnt->s.origin, "ub", blooddir, 200);
|
|
}
|
|
}
|
|
|
|
void tbeast_check_snatch(edict_t *self, float ofsf, float ofsr, float ofsu)
|
|
{
|
|
float enemy_dist, ok_dist;
|
|
vec3_t forward, right, up, startpos, endpos;
|
|
edict_t *found = NULL;
|
|
// trace_t trace;
|
|
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
ok_dist = 64;
|
|
|
|
AngleVectors(self->s.angles,forward,right,up);
|
|
VectorMA(self->s.origin, ofsf + TB_FWD_OFFSET, forward, startpos);
|
|
VectorMA(startpos, ofsr, right, startpos);
|
|
VectorMA(startpos, ofsu + TB_UP_OFFSET, up, startpos);
|
|
|
|
VectorSubtract(self->enemy->s.origin, startpos, endpos);
|
|
|
|
enemy_dist = VectorLength(endpos);
|
|
if(enemy_dist>ok_dist || flrand(0, 50)>self->enemy->health)
|
|
{//if missed or health is low, just chomp it now
|
|
while(found = findradius(found, startpos, ok_dist))
|
|
{
|
|
if(found->takedamage&&movable(found))
|
|
{
|
|
if(found->health<=0)
|
|
T_Damage (found, self, self, endpos, found->s.origin, endpos, 2000, 300, DAMAGE_DISMEMBER,MOD_DIED);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
// gi.dprintf("Snatch missed by %4.2f!\n", enemy_dist - ok_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;
|
|
}
|
|
}
|
|
}*/
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
found = self->enemy;
|
|
|
|
self->msgHandler = DeadMsgHandler;
|
|
// gi.dprintf("SNAGGED!\n");
|
|
|
|
if(ofsu == TB_HIBITE_U + 128)
|
|
SetAnim(self, ANIM_BITEUP2_SFIN);
|
|
else if(ofsu == TB_HIBITE_U)
|
|
SetAnim(self, ANIM_BITEUP_SFIN);
|
|
else
|
|
SetAnim(self, ANIM_BITELOW_SFIN);
|
|
|
|
self->targetEnt = found;
|
|
self->targetEnt->flags |= FL_FLY;
|
|
self->targetEnt->movetype = PHYSICSTYPE_FLY;
|
|
|
|
if(!found->client)
|
|
{
|
|
found->monsterinfo.aiflags |= AI_DONT_THINK;
|
|
}
|
|
else
|
|
{
|
|
found->nextthink = level.time + 10;//stuck for 10 seconds.
|
|
if(found->health > 0)
|
|
{
|
|
if(found->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN)
|
|
{
|
|
P_KnockDownPlayer(&found->client->playerinfo);
|
|
}
|
|
}
|
|
gi.sound(found, CHAN_VOICE, sounds[irand(SND_CORVUS_SCREAM1, SND_CORVUS_SCREAM3)], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
VectorClear(found->velocity);
|
|
VectorClear(found->avelocity);
|
|
}
|
|
|
|
void tbeast_go_snatch (edict_t *self)
|
|
{
|
|
SetAnim(self, ANIM_SNATCH);
|
|
}
|
|
|
|
void tbeast_gore_toy(edict_t *self, float jumpht)
|
|
{
|
|
float enemy_zdist, ok_zdist;
|
|
byte num_chunks;
|
|
vec3_t dir, forward;
|
|
|
|
if(jumpht!=-1)
|
|
{
|
|
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->targetEnt)
|
|
return;
|
|
|
|
if(self->targetEnt->health<0)
|
|
return;
|
|
|
|
if(self->count)
|
|
return;
|
|
|
|
ok_zdist = 128;
|
|
enemy_zdist = self->targetEnt->s.origin[2] - self->s.origin[2];
|
|
if(enemy_zdist <= self->maxs[2] + ok_zdist || jumpht == -1)
|
|
{//FIXME: waits grabs it too low, waits too long
|
|
self->wait = self->targetEnt->materialtype;
|
|
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_SNATCH], 1, ATTN_NORM, 0);
|
|
if(jumpht!=-1)
|
|
self->count = 1;
|
|
VectorCopy(self->velocity,dir);
|
|
VectorNormalize(dir);
|
|
num_chunks = (byte)(self->targetEnt->health/4);
|
|
if(num_chunks>15)
|
|
num_chunks = 15;
|
|
SprayDebris(self->targetEnt, self->targetEnt->s.origin, num_chunks, self->targetEnt->health*4);//self->enemy is thingtype wood?!
|
|
|
|
if(stricmp(self->targetEnt->classname,"player"))
|
|
{
|
|
gi.sound(self->targetEnt, CHAN_WEAPON, sounds[SND_CATCH], 1, ATTN_NORM, 0);
|
|
BecomeDebris(self->targetEnt);
|
|
}
|
|
else
|
|
{
|
|
self->targetEnt->nextthink = level.time;
|
|
T_Damage (self->targetEnt, self, self, self->velocity, self->targetEnt->s.origin, dir, 2000, 300, DAMAGE_DISMEMBER|DAMAGE_NO_PROTECTION,MOD_DIED);
|
|
}
|
|
if(self->enemy == self->targetEnt)
|
|
self->enemy = NULL;
|
|
self->targetEnt = NULL;
|
|
}
|
|
}
|
|
|
|
void tbeast_miss_sound (edict_t *self)
|
|
{
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_SNATCH], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void tbeast_anger_sound (edict_t *self)
|
|
{
|
|
byte chance;
|
|
|
|
chance = irand(0,100);
|
|
if (chance < 10)
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_SNORT1], 1, ATTN_NORM, 0);
|
|
else if (chance < 20)
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_SNORT2], 1, ATTN_NORM, 0);
|
|
else if (chance < 30)
|
|
gi.sound(self, CHAN_ITEM, sounds[SND_TEAR1], 1, ATTN_NORM, 0);
|
|
else if (chance < 40)
|
|
gi.sound(self, CHAN_ITEM, sounds[SND_TEAR2], 1, ATTN_NORM, 0);
|
|
else if (chance < 50)
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_CHOMP], 1, ATTN_NORM, 0);
|
|
else if (chance < 60)
|
|
tbeast_growl(self);
|
|
|
|
if(self->targetEnt)
|
|
{
|
|
chance = (byte)irand(1,3);
|
|
|
|
SprayDebris(self->targetEnt, self->targetEnt->s.origin, chance, 100);
|
|
if(!self->targetEnt->client)
|
|
{
|
|
QPostMessage(self->targetEnt, MSG_DISMEMBER, PRI_DIRECTIVE, "ii", self->targetEnt->health*0.5, irand(1,13));//do I need last three if not sending them?
|
|
QPostMessage(self->targetEnt, MSG_PAIN, PRI_DIRECTIVE, "eeiii", self, self, true, 200, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tbeast_gibs(edict_t *self)
|
|
{//FIXME: keep making gubs
|
|
vec3_t spot, mins, forward;
|
|
byte numchunks;
|
|
int flags = 0;
|
|
|
|
if(!self->wait)
|
|
return;
|
|
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
VectorMA(self->s.origin, 56, forward, spot);
|
|
spot[2] -= 8;
|
|
|
|
if(self->wait == MAT_INSECT)
|
|
{
|
|
flags |= CEF_FLAG8;
|
|
flags |= CEF_FLAG7;//use male insect skin on chunks
|
|
}
|
|
|
|
numchunks = (byte)(irand(3, 7));
|
|
VectorSet(mins, -1, -1, -1);
|
|
gi.CreateEffect(NULL,
|
|
FX_FLESH_DEBRIS,
|
|
flags,
|
|
spot,
|
|
"bdb",
|
|
numchunks, self->mins, 16);
|
|
|
|
tbeast_anger_sound (self);
|
|
}
|
|
|
|
void tbeast_done_gore (edict_t *self)
|
|
{
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->count = 0;
|
|
M_ValidTarget(self, self->enemy);
|
|
|
|
self->monsterinfo.aiflags |= AI_EATING;
|
|
SetAnim(self, ANIM_EATDOWN);
|
|
}
|
|
|
|
void tbeast_inair (edict_t *self)
|
|
{
|
|
SetAnim(self, ANIM_INAIR);
|
|
}
|
|
|
|
void tbeast_check_landed (edict_t *self)
|
|
{
|
|
if(TB_CheckBottom(self))
|
|
SetAnim(self, ANIM_LAND);
|
|
}
|
|
|
|
void tbeast_ginair (edict_t *self)
|
|
{
|
|
SetAnim(self, ANIM_GINAIR);
|
|
}
|
|
|
|
void tbeast_gcheck_landed (edict_t *self)
|
|
{
|
|
if(TB_CheckBottom(self))
|
|
SetAnim(self, ANIM_GLAND);
|
|
}
|
|
|
|
void tbeast_chomp(edict_t *self, float ofsf, float ofsr, float ofsu)
|
|
{
|
|
float enemy_dist, ok_dist, damage;
|
|
vec3_t forward, right, up, startpos, endpos, v;
|
|
|
|
if(!self->enemy)
|
|
return;
|
|
|
|
ok_dist = 64;
|
|
|
|
AngleVectors(self->s.angles,forward,right,up);
|
|
VectorMA(self->s.origin, ofsf + TB_FWD_OFFSET, forward, startpos);
|
|
VectorMA(startpos, ofsr, right, startpos);
|
|
VectorMA(startpos, ofsu + TB_UP_OFFSET, up, startpos);
|
|
|
|
VectorSubtract(self->enemy->s.origin, startpos, endpos);
|
|
|
|
enemy_dist = VectorLength(endpos);
|
|
if(enemy_dist>ok_dist)
|
|
{//if missed or health is low, just chomp it now
|
|
// gi.dprintf("Chomp missed by %4.2f!\n", enemy_dist - ok_dist);
|
|
if(enemy_dist - ok_dist < 64)
|
|
{//let them know it was close and we tried - spittle effect?
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_SNATCH], 1, ATTN_NORM, 0);
|
|
}
|
|
return;
|
|
}
|
|
gi.sound(self, CHAN_WEAPON, sounds[SND_CHOMP], 1, ATTN_NORM, 0);
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, v);
|
|
VectorNormalize(v);
|
|
damage = irand(TB_DMG_BITE_MIN, TB_DMG_BITE_MAX);
|
|
T_Damage (self->enemy, self, self, forward, endpos, v, damage, damage/2, DAMAGE_DISMEMBER,MOD_DIED);
|
|
}
|
|
|
|
void tbeast_leap (edict_t *self, float fwdf, float rghtf, float upf)
|
|
{
|
|
vec3_t forward, right, up, angles;
|
|
|
|
if(!self->groundentity)
|
|
{
|
|
if(!TB_CheckBottom(self))
|
|
return;
|
|
}
|
|
|
|
if(self->s.frame == FRAME_jumpb7)
|
|
tbeast_chomp(self, 36, 0, 232);
|
|
|
|
// self->gravity = TB_JUMP_GRAV;
|
|
VectorSet(angles, 0, self->s.angles[YAW], 0);
|
|
AngleVectors(angles, forward, right, up);
|
|
VectorScale(forward, fwdf, self->velocity);
|
|
VectorMA(self->velocity, rghtf, right, self->velocity);
|
|
VectorMA(self->velocity, upf, up, self->velocity);
|
|
}
|
|
|
|
/*========================
|
|
|
|
Hacky Trial Beast fake collision and slope-standing code
|
|
|
|
========================*/
|
|
|
|
/*
|
|
|
|
LerpAngleChange
|
|
|
|
*/
|
|
|
|
float LerpAngleChange (float curangle, float endangle, float step)
|
|
{
|
|
float final, diff;
|
|
|
|
curangle = anglemod(curangle);
|
|
endangle = anglemod(endangle);
|
|
|
|
if(curangle>180)
|
|
curangle-=360;
|
|
else if(curangle<-180)
|
|
curangle+=360;
|
|
|
|
if(endangle>180)
|
|
endangle-=360;
|
|
else if(endangle<-180)
|
|
endangle+=360;
|
|
|
|
if(curangle == endangle)
|
|
return 0;
|
|
|
|
diff = endangle - curangle;
|
|
|
|
if(diff > 180)
|
|
diff -= 360;
|
|
else if(diff < -180)
|
|
diff += 360;
|
|
|
|
final = anglemod(curangle + diff/step);
|
|
|
|
if(final>180)
|
|
final-=360;
|
|
else if(final<-180)
|
|
final+=360;
|
|
|
|
return final;
|
|
}
|
|
|
|
int tbeast_inwalkframes(edict_t *self)
|
|
{
|
|
if(self->curAnimID == ANIM_CHARGE||self->curAnimID==ANIM_QUICK_CHARGE)
|
|
{
|
|
switch(self->s.frame)
|
|
{
|
|
case FRAME_charge1:
|
|
return 2;
|
|
break;
|
|
case FRAME_charge2:
|
|
return 4;
|
|
break;
|
|
case FRAME_charge3:
|
|
return 6;
|
|
break;
|
|
case FRAME_charge4:
|
|
return 8;
|
|
break;
|
|
case FRAME_charge5:
|
|
return 9;
|
|
break;
|
|
case FRAME_charge6:
|
|
return 7;
|
|
break;
|
|
case FRAME_charge7:
|
|
return 13;
|
|
break;
|
|
case FRAME_charge8:
|
|
return 15;
|
|
break;
|
|
case FRAME_charge9:
|
|
return 1;
|
|
break;
|
|
case FRAME_charge10:
|
|
return 0;
|
|
break;
|
|
default:
|
|
return -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(self->s.frame>=FRAME_walk1 && self->s.frame<=FRAME_walk18)
|
|
return self->monsterinfo.currframeindex;
|
|
|
|
if(self->s.frame>=FRAME_wlklft1 && self->s.frame<=FRAME_wlklft18)
|
|
return self->monsterinfo.currframeindex;
|
|
|
|
if(self->s.frame>=FRAME_wlkrt1 && self->s.frame<=FRAME_wlkrt18)
|
|
return self->monsterinfo.currframeindex;
|
|
|
|
if(self->s.frame>=FRAME_wlkatk1 && self->s.frame<=FRAME_wlkatk18)
|
|
return self->monsterinfo.currframeindex;
|
|
|
|
if(self->s.frame>=FRAME_wait1 && self->s.frame<=FRAME_wait14)
|
|
return 16;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
|
|
LevelToGround
|
|
|
|
I'm a big guy, level me out
|
|
What slope am I on
|
|
?
|
|
*/
|
|
void LevelToGround (edict_t *self, float fscale, float rscale, qboolean z_adjust)
|
|
{
|
|
vec3_t forward, right, backpos, frontpos, leftpos, rightpos, dir, angles, bottom1, bottom2;
|
|
vec3_t lfootoffset, rfootoffset, up;
|
|
trace_t trace;
|
|
qboolean right_front;
|
|
int leg_check_index;
|
|
int count = 0;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
|
|
leg_check_index = tbeast_inwalkframes(self);
|
|
|
|
if(leg_check_index > -1)
|
|
{//set up leg checks - only if in these frames
|
|
if(leg_check_index > 5 && leg_check_index < 15)
|
|
right_front = true;
|
|
else
|
|
right_front = false;
|
|
|
|
//left leg
|
|
VectorCopy(GetLeftFootOffsetForFrameIndex[leg_check_index], lfootoffset);
|
|
VectorMA(self->s.origin, lfootoffset[0] + TB_FWD_OFFSET, forward, leftpos);
|
|
VectorMA(leftpos, lfootoffset[1] + TB_RT_OFFSET, right, leftpos);
|
|
VectorMA(leftpos, lfootoffset[2] + TB_UP_OFFSET, up, leftpos);
|
|
|
|
//right leg
|
|
VectorCopy(GetRightFootOffsetForFrameIndex[leg_check_index], rfootoffset);
|
|
VectorMA(self->s.origin, rfootoffset[0] + TB_FWD_OFFSET, forward, rightpos);
|
|
VectorMA(rightpos, rfootoffset[1] + TB_RT_OFFSET, right, rightpos);
|
|
VectorMA(rightpos, rfootoffset[2] + TB_UP_OFFSET, up, rightpos);
|
|
|
|
if(right_front)
|
|
{//this is also the front check
|
|
VectorCopy(rightpos, frontpos);
|
|
VectorCopy(leftpos, backpos);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(leftpos, frontpos);
|
|
VectorCopy(rightpos, backpos);
|
|
}
|
|
}
|
|
else return;
|
|
/*{
|
|
VectorCopy(self->s.origin, backpos);
|
|
backpos[2] += self->mins[2] + 10;
|
|
|
|
VectorMA(backpos, self->maxs[0] * fscale, forward, frontpos);
|
|
VectorMA(backpos, self->mins[0] * fscale, forward, backpos);
|
|
|
|
VectorCopy(self->s.origin, leftpos);
|
|
leftpos[2] += self->mins[2] + 10;
|
|
|
|
VectorMA(leftpos, self->maxs[0] * rscale, right, rightpos);
|
|
VectorMA(leftpos, self->mins[0] * rscale, right, leftpos);
|
|
}*/
|
|
|
|
VectorCopy(frontpos, bottom1);
|
|
bottom1[2] -= self->size[2] * 2;
|
|
gi.trace(frontpos, vec3_origin, vec3_origin, bottom1, self, MASK_SOLID,&trace);
|
|
if(trace.fraction == 1.0)
|
|
{
|
|
self->s.angles[PITCH] = LerpAngleChange (self->s.angles[PITCH], 0, 8);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(trace.endpos, bottom1);
|
|
|
|
VectorCopy(backpos, bottom2);
|
|
bottom2[2] -= self->size[2] * 2;
|
|
gi.trace(backpos, vec3_origin, vec3_origin, bottom2, self, MASK_SOLID,&trace);
|
|
if(trace.fraction == 1.0)
|
|
{
|
|
self->s.angles[PITCH] = LerpAngleChange (self->s.angles[PITCH], 0, 8);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(trace.endpos, bottom2);
|
|
|
|
VectorSubtract(bottom1, bottom2, dir);
|
|
vectoangles(dir, angles);
|
|
|
|
self->s.angles[PITCH] = LerpAngleChange (self->s.angles[PITCH], angles[PITCH], 8);
|
|
}
|
|
}
|
|
|
|
VectorCopy(rightpos, bottom1);
|
|
bottom1[2] -= self->size[2] * 2;
|
|
gi.trace(rightpos, vec3_origin, vec3_origin, bottom1, self, MASK_SOLID,&trace);
|
|
if(trace.fraction == 1.0)
|
|
{
|
|
self->s.angles[ROLL] = LerpAngleChange (self->s.angles[ROLL], 0, 8);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(trace.endpos, bottom1);
|
|
|
|
VectorCopy(leftpos, bottom2);
|
|
bottom2[2] -= self->size[2] * 2;
|
|
gi.trace(leftpos, vec3_origin, vec3_origin, bottom2, self, MASK_SOLID,&trace);
|
|
if(trace.fraction == 1.0)
|
|
{
|
|
self->s.angles[ROLL] = LerpAngleChange (self->s.angles[ROLL], 0, 8);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(trace.endpos, bottom2);
|
|
|
|
VectorSubtract(bottom1, bottom2, dir);
|
|
vectoangles(dir, angles);
|
|
|
|
self->s.angles[ROLL] = LerpAngleChange (self->s.angles[ROLL], angles[PITCH], 8);
|
|
}
|
|
}
|
|
|
|
/*
|
|
if(z_adjust)
|
|
{
|
|
if((gi.pointcontents(rightpos) & MASK_SOLID) || (gi.pointcontents(leftpos) & MASK_SOLID))
|
|
{
|
|
gi.dprintf("Beast feet in ground, raising up\n");
|
|
while(((gi.pointcontents(rightpos) & MASK_SOLID) ||
|
|
(gi.pointcontents(leftpos) & MASK_SOLID)) && count < 10)
|
|
{
|
|
self->mins[2]++;
|
|
self->s.origin[2]++;
|
|
rightpos[2]++;
|
|
leftpos[2]++;
|
|
count++;
|
|
}
|
|
gi.linkentity(self);
|
|
}
|
|
else
|
|
{
|
|
leftpos[2] -= 4;
|
|
rightpos[2] -= 4;
|
|
if(!(gi.pointcontents(rightpos) & MASK_SOLID) && !(gi.pointcontents(leftpos) & MASK_SOLID))
|
|
{
|
|
gi.dprintf("Beast feet not on ground, lowering\n");
|
|
while(!(gi.pointcontents(rightpos) & MASK_SOLID) &&
|
|
!(gi.pointcontents(leftpos) & MASK_SOLID) && count < 10)
|
|
{
|
|
self->mins[2]--;
|
|
self->s.origin[2]--;
|
|
rightpos[2]--;
|
|
leftpos[2]--;
|
|
count++;
|
|
}
|
|
gi.linkentity(self);
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
|
|
void DoImpactDamage(edict_t *self, trace_t *trace);
|
|
void tbeast_fake_impact(edict_t *self, trace_t *trace, qboolean crush)
|
|
{
|
|
trace_t tr;
|
|
vec3_t dir, bottom;
|
|
qboolean throwthem = true;
|
|
|
|
if(trace->ent->svflags & SVF_TOUCHED_BEAST)
|
|
return;
|
|
|
|
if(trace->ent == self->targetEnt)
|
|
return;
|
|
|
|
if(trace->ent->classID == CID_FUNC_DOOR)
|
|
return;
|
|
|
|
if(trace->ent->classID == CID_TCHECKRIK)
|
|
return;//we want to pick up and eat insects
|
|
|
|
if(trace->ent && trace->ent->movetype && trace->ent !=world && stricmp(trace->ent->classname, "worldspawn"))
|
|
{
|
|
if(trace->ent->client||trace->ent->svflags&SVF_MONSTER)
|
|
{
|
|
if(trace->ent->s.origin[2] > self->absmax[2] - 10)
|
|
{//FIXME: chance of throwing them off
|
|
trace->ent->s.origin[2] = self->absmax[2];
|
|
trace->ent->velocity[2] = 0;
|
|
trace->ent->groundentity = self;
|
|
throwthem = false;
|
|
}
|
|
}
|
|
if(throwthem)
|
|
{
|
|
VectorCopy(self->s.origin, bottom);
|
|
bottom[2] += self->mins[2];
|
|
VectorSubtract(trace->ent->s.origin, bottom, dir);
|
|
VectorNormalize(dir);
|
|
}
|
|
|
|
if(movable(trace->ent) || trace->ent->takedamage)
|
|
{
|
|
if(throwthem)
|
|
VectorScale(dir, 200, trace->ent->velocity);
|
|
}
|
|
else if(Vec3NotZero(self->velocity) && trace->fraction < 0.7)
|
|
{
|
|
if(infront(self, trace->ent))
|
|
{
|
|
if(trace->ent->targetname && !stricmp(trace->ent->targetname, "pillar"))
|
|
{//FIXME: In higher skills, less chance of breaking it? Or debounce time?
|
|
|
|
if(visible_to_client(self))
|
|
{
|
|
// gi.dprintf("Beast hit pillar!\n");
|
|
G_UseTargets (trace->ent, self);
|
|
trace->ent->targetname = "";//so we don't hit them again
|
|
trace->ent->target = "stop";//so it doesn't fire the target when it's broken later
|
|
self->monsterinfo.attack_finished = level.time + 3;
|
|
|
|
self->velocity[0] = self->velocity[1] = 0;
|
|
self->sounds++;
|
|
|
|
self->red_rain_count++;
|
|
|
|
if(self->red_rain_count >= 2)//got both pillars, now die
|
|
{
|
|
//self->clipmask = 0;
|
|
self->solid = SOLID_NOT;
|
|
self->takedamage = DAMAGE_NO;
|
|
}
|
|
}
|
|
gi.CreateEffect(&self->s,
|
|
FX_QUAKE,
|
|
0,
|
|
vec3_origin,
|
|
"bbb",
|
|
4,
|
|
3,
|
|
7);
|
|
|
|
if(self->sounds!=2 && irand(0, 1))
|
|
SetAnim(self, ANIM_STUN);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(trace->ent->touch&&trace->ent->solid!=SOLID_NOT)
|
|
trace->ent->touch (trace->ent, self, &trace->plane, trace->surface);
|
|
|
|
if(trace->ent->isBlocked&&trace->ent->solid!=SOLID_NOT)
|
|
{
|
|
tr = *trace;
|
|
tr.ent = self;
|
|
trace->ent->isBlocked(trace->ent, &tr);
|
|
}
|
|
|
|
if(throwthem && trace->ent->takedamage)
|
|
{
|
|
float damage;
|
|
|
|
if(Vec3NotZero(self->velocity))
|
|
{
|
|
if(trace->ent->client)
|
|
{
|
|
if(trace->ent->health > 30)
|
|
DoImpactDamage(self, trace);
|
|
|
|
if(trace->ent->groundentity && trace->ent->health)
|
|
{
|
|
if(trace->ent->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN)
|
|
{
|
|
P_KnockDownPlayer(&trace->ent->client->playerinfo);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
DoImpactDamage(self, trace);
|
|
}
|
|
else
|
|
{
|
|
if(trace->ent->client)
|
|
{
|
|
if(crush)
|
|
damage = flrand(20, 100);
|
|
else if(trace->ent->health > 30)
|
|
damage = flrand(10, 30) * skill->value/2;
|
|
else
|
|
damage = 0;
|
|
|
|
if(!irand(0, 5) || (crush && !irand(0,1)))
|
|
{
|
|
if(trace->ent->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN)
|
|
P_KnockDownPlayer(&trace->ent->client->playerinfo);
|
|
}
|
|
if(damage)
|
|
T_Damage(trace->ent, self, self, dir, trace->endpos, dir,
|
|
flrand(TB_DMG_IMPACT_MIN, TB_DMG_IMPACT_MAX), TB_DMG_IMPACT_KB, 0,MOD_DIED);
|
|
}
|
|
else
|
|
{
|
|
if(crush)
|
|
damage = flrand(1000, 3000);
|
|
else
|
|
damage = flrand(20, 100);
|
|
T_Damage(trace->ent, self, self, dir, trace->endpos, dir, 1000, 250, 0,MOD_DIED);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(trace->ent->client)
|
|
{
|
|
if(trace->ent->groundentity && trace->ent->health)
|
|
{
|
|
if(trace->ent->client->playerinfo.lowerseq != ASEQ_KNOCKDOWN)
|
|
{
|
|
P_KnockDownPlayer(&trace->ent->client->playerinfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean boxes_overlap(vec3_t mins1, vec3_t maxs1, vec3_t mins2, vec3_t maxs2)
|
|
{
|
|
if(mins1[0]>maxs2[0])
|
|
return false;
|
|
|
|
if(mins1[1]>maxs2[1])
|
|
return false;
|
|
|
|
if(mins1[2]>maxs2[2])
|
|
return false;
|
|
|
|
if(maxs1[0]<mins2[0])
|
|
return false;
|
|
|
|
if(maxs1[1]<mins2[1])
|
|
return false;
|
|
|
|
if(maxs1[2]<mins2[2])
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void tbeast_check_impacts(edict_t *self)
|
|
{//Used by Trial Beast for Simulated Complex Bounding Box Collision
|
|
|
|
vec3_t forward, right, up, start, end, mins, maxs, lfootoffset, rfootoffset;
|
|
vec3_t lstart, lend, rstart, rend, lfootmins, lfootmaxs, rfootmins, rfootmaxs, fmins, fmaxs;
|
|
int leg_check_index;
|
|
qboolean hitme = true;
|
|
qboolean hitother = false;
|
|
trace_t trace;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
|
|
//set up body check
|
|
VectorCopy(self->s.origin, start);
|
|
start[2] += 64 + TB_UP_OFFSET;//bottom of torso
|
|
start[2] += 61;//halfway up to the top of torso
|
|
VectorMA(start, 150 + TB_FWD_OFFSET, forward, end);
|
|
VectorMA(start, -120, forward, start);// + TB_FWD_OFFSET???
|
|
VectorSet(mins, -50, -50, -61);
|
|
VectorSet(maxs, 50, 50, 70);
|
|
|
|
leg_check_index = tbeast_inwalkframes(self);
|
|
|
|
if(leg_check_index > -1)
|
|
{//set up leg checks - FIXME: trace from last footpos to current one
|
|
VectorSet(fmins, -8, -8, 0);
|
|
VectorSet(fmaxs, 8, 8, 1);
|
|
|
|
//left leg
|
|
VectorCopy(GetLeftFootOffsetForFrameIndex[leg_check_index], lfootoffset);
|
|
VectorMA(self->s.origin, lfootoffset[0] + TB_FWD_OFFSET, forward, lend);
|
|
VectorMA(lend, lfootoffset[1] + TB_RT_OFFSET, right, lend);
|
|
VectorMA(lend, lfootoffset[2] + TB_UP_OFFSET, up, lend);
|
|
VectorCopy(lend, lstart);
|
|
lstart[2]+=63;
|
|
|
|
VectorAdd(lend, fmins, lfootmins);
|
|
VectorCopy(lfootmins, lfootmaxs);
|
|
lfootmaxs[2] += 64;
|
|
|
|
//right leg
|
|
VectorCopy(GetRightFootOffsetForFrameIndex[leg_check_index], rfootoffset);
|
|
VectorMA(self->s.origin, rfootoffset[0] + TB_FWD_OFFSET, forward, rend);
|
|
VectorMA(rend, rfootoffset[1] + TB_RT_OFFSET, right, rend);
|
|
VectorMA(rend, rfootoffset[2] + TB_UP_OFFSET, up, rend);
|
|
VectorCopy(rend, rstart);
|
|
rstart[2]+=63;
|
|
|
|
VectorAdd(rend, fmins, rfootmins);
|
|
VectorCopy(rfootmins, rfootmaxs);
|
|
rfootmaxs[2] += 64;
|
|
}
|
|
|
|
//BODY
|
|
//Fix me: continue the trace if less than 1.0 or save for next touch?
|
|
gi.trace(start, mins, maxs, end, self, MASK_MONSTERSOLID,&trace);
|
|
//Hey! Check and see if they're close to my mouth and chomp 'em!
|
|
tbeast_fake_impact(self, &trace, false);
|
|
|
|
if(leg_check_index == -1)
|
|
{
|
|
VectorCopy(self->s.origin, end);
|
|
end[2] += self->mins[2];
|
|
VectorCopy(end, start);
|
|
start[2] += 63;
|
|
VectorSet(mins, -32, -32, 0);
|
|
VectorSet(maxs, 32, 32, 1);
|
|
|
|
gi.trace(start, mins, maxs, end, self, MASK_MONSTERSOLID,&trace);
|
|
tbeast_fake_impact(self, &trace, false);
|
|
return;
|
|
}
|
|
|
|
//Do leg checks
|
|
//left leg
|
|
//Fix me: continue the trace if less than 1.0 or save for next touch?
|
|
gi.trace(lstart, fmins, fmaxs, lend, self, MASK_MONSTERSOLID,&trace);
|
|
tbeast_fake_impact(self, &trace, true);
|
|
|
|
//right leg
|
|
//Fix me: continue the trace if less than 1.0 or save for next touch?
|
|
gi.trace(rstart, fmins, fmaxs, rend, self, MASK_MONSTERSOLID,&trace);
|
|
tbeast_fake_impact(self, &trace, true);
|
|
}
|
|
|
|
void tbeast_fake_touch(edict_t *self)
|
|
{//Used by Trial Beast for Simulated Complex Bounding Box Collision
|
|
|
|
vec3_t forward, right, up, start, end, dir, mins, maxs;
|
|
vec3_t lfootoffset, rfootoffset, omins, omaxs;
|
|
vec3_t lstart, lend, rstart, rend, lfootmins, lfootmaxs, rfootmins, rfootmaxs, fmins, fmaxs;
|
|
vec3_t melee_point;
|
|
int leg_check_index;
|
|
qboolean hitme = true;
|
|
qboolean hitother = false;
|
|
trace_t trace;
|
|
int osolid, ocm;
|
|
int i, num;
|
|
edict_t *touch[MAX_EDICTS], *other;
|
|
|
|
num = gi.BoxEdicts (self->absmin, self->absmax, touch, MAX_EDICTS, AREA_SOLID);
|
|
// be careful, it is possible to have an entity in this
|
|
// list removed before we get to it (killtriggered)
|
|
|
|
if(!touch[0])
|
|
goto finish;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
|
|
leg_check_index = tbeast_inwalkframes(self);
|
|
|
|
if(leg_check_index > -1)
|
|
{//set up leg checks - FIXME: trace from last footpos to current one
|
|
|
|
//Walking, Check melee point in front
|
|
VectorMA(self->s.origin, self->maxs[2]*0.5 + TB_UP_OFFSET, up, melee_point);
|
|
VectorMA(melee_point, 150 + TB_FWD_OFFSET, forward, melee_point);
|
|
|
|
VectorSet(fmins, -8, -8, 0);
|
|
VectorSet(fmaxs, 8, 8, 1);
|
|
|
|
//left leg
|
|
VectorCopy(GetLeftFootOffsetForFrameIndex[leg_check_index], lfootoffset);
|
|
VectorMA(self->s.origin, lfootoffset[0] + TB_FWD_OFFSET, forward, lend);
|
|
VectorMA(lend, lfootoffset[1] + TB_RT_OFFSET, right, lend);
|
|
VectorMA(lend, lfootoffset[2] + TB_UP_OFFSET, up, lend);
|
|
VectorCopy(lend, lstart);
|
|
lstart[2]+=63;
|
|
|
|
VectorAdd(lend, fmins, lfootmins);
|
|
VectorCopy(lfootmins, lfootmaxs);
|
|
lfootmaxs[2] += 64;
|
|
|
|
//right leg
|
|
VectorCopy(GetRightFootOffsetForFrameIndex[leg_check_index], rfootoffset);
|
|
VectorMA(self->s.origin, rfootoffset[0] + TB_FWD_OFFSET, forward, rend);
|
|
VectorMA(rend, rfootoffset[1] + TB_RT_OFFSET, right, rend);
|
|
VectorMA(rend, rfootoffset[2] + TB_UP_OFFSET, up, rend);
|
|
VectorCopy(rend, rstart);
|
|
rstart[2]+=63;
|
|
|
|
VectorAdd(rend, fmins, rfootmins);
|
|
VectorCopy(rfootmins, rfootmaxs);
|
|
rfootmaxs[2] += 64;
|
|
}
|
|
|
|
//set up body check
|
|
VectorCopy(self->s.origin, start);
|
|
start[2] += 64 + TB_UP_OFFSET;//bottom of torso
|
|
start[2] += 35;//halfway up to the top of torso
|
|
VectorMA(start, 150 + TB_FWD_OFFSET, forward, end);
|
|
VectorMA(start, -120, forward, start);// + TB_FWD_OFFSET???
|
|
VectorSet(mins, -50, -50, -61);
|
|
VectorSet(maxs, 50, 50, 70);
|
|
|
|
for (i=0 ; i<num ; i++)
|
|
{
|
|
other = touch[i];
|
|
if (!other->inuse)
|
|
continue;
|
|
|
|
if(other==self)
|
|
continue;
|
|
|
|
if(!stricmp(other->classname, "worldspawn"))
|
|
continue;
|
|
|
|
if(other == self->targetEnt)
|
|
continue;
|
|
|
|
if(self->curAnimID != ANIM_CHARGE && self->curAnimID != ANIM_QUICK_CHARGE)
|
|
{
|
|
if(leg_check_index > -1 && other->takedamage && movable(other))
|
|
{//Hey! Check and see if they're close to my mouth and chomp 'em!
|
|
if(vhlen (other->s.origin, melee_point) < 100)
|
|
{
|
|
self->oldenemy = self->enemy;
|
|
self->enemy = other;
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
goto finish;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(other->classID == CID_TCHECKRIK)
|
|
continue;//we want to pick up and eat insects
|
|
|
|
//make other solid and size temp for trace
|
|
ocm = other->clipmask;
|
|
osolid = other->solid;
|
|
if(!Vec3IsZero(other->mins))
|
|
VectorCopy(other->mins, omins);
|
|
else
|
|
{
|
|
VectorCopy(other->mins, omins);
|
|
VectorSet(other->mins, -1, -1, -1);
|
|
}
|
|
|
|
if(!Vec3IsZero(other->maxs))
|
|
VectorCopy(other->maxs, omaxs);
|
|
else
|
|
{
|
|
VectorCopy(other->maxs, omaxs);
|
|
VectorSet(other->maxs, 1, 1, 1);
|
|
}
|
|
|
|
other->solid = SOLID_BBOX;
|
|
other->clipmask = MASK_ALL;
|
|
//BODY
|
|
//Fix me: continue the trace if less than 1.0 or save for next touch?
|
|
gi.trace(start, mins, maxs, end, self, MASK_MONSTERSOLID,&trace);
|
|
//put other back to normal
|
|
other->solid = osolid;
|
|
other->clipmask = ocm;
|
|
VectorCopy(omins, other->mins);
|
|
VectorCopy(omaxs, other->maxs);
|
|
|
|
if(trace.ent==other)//hit something with BODY , touch it
|
|
hitother = true;//hit other!
|
|
|
|
if(!hitother && other->absmin[2]>self->absmax[2] - 10)
|
|
{
|
|
if(!irand(0,10))
|
|
{
|
|
// gi.dprintf("Jump to throw off something\n");
|
|
SetAnim(self, ANIM_JUMP);
|
|
VectorSubtract(other->s.origin, self->s.origin, dir);
|
|
VectorNormalize(dir);
|
|
VectorScale(dir, 500, other->velocity);
|
|
// other->groundentity = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(hitother)
|
|
hitme = true;
|
|
else
|
|
{
|
|
hitme = false;
|
|
if(leg_check_index > -1)
|
|
{
|
|
VectorAdd(other->s.origin, other->mins, omins);
|
|
VectorAdd(other->s.origin, other->maxs, omaxs);
|
|
if(boxes_overlap(omins, omaxs, lfootmins, lfootmaxs))
|
|
hitme = true;
|
|
else if(boxes_overlap(omins, omaxs, rfootmins, rfootmaxs))
|
|
hitme = true;
|
|
}
|
|
}
|
|
|
|
if(hitme)
|
|
{
|
|
if(other->isBlocked&&other->solid!=SOLID_NOT)
|
|
{
|
|
gi.trace(other->s.origin, vec3_origin, vec3_origin, self->s.origin, other, MASK_ALL,&trace);
|
|
trace.ent = self;
|
|
VectorCopy(other->s.origin, trace.endpos);
|
|
other->isBlocked(other, &trace);
|
|
}
|
|
if(other->touch&&other->solid!=SOLID_NOT)
|
|
{
|
|
gi.trace(other->s.origin, vec3_origin, vec3_origin, self->s.origin, other, MASK_ALL,&trace);
|
|
trace.ent = self;
|
|
VectorCopy(other->s.origin, trace.endpos);
|
|
other->touch (other, self, &trace.plane, trace.surface);
|
|
}
|
|
if(other && other == trace.ent)
|
|
{//if other still valid, do my impact with it
|
|
tbeast_fake_impact(self, &trace, false);
|
|
other->svflags |= SVF_TOUCHED_BEAST;//so check_impacts doesn't do anything with it
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
finish:
|
|
tbeast_check_impacts(self);
|
|
|
|
for (i=0 ; i<num ; i++)
|
|
{
|
|
touch[i]->svflags &= ~SVF_TOUCHED_BEAST;
|
|
}
|
|
|
|
}
|
|
|
|
void tbeast_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
|
{
|
|
tbeast_fake_touch(self);
|
|
}
|
|
|
|
void tbeast_post_think (edict_t *self)
|
|
{
|
|
trace_t trace;
|
|
vec3_t end, mins, maxs;
|
|
float omins2;
|
|
qboolean go_jump = false;
|
|
|
|
if(self->monsterinfo.awake)
|
|
{
|
|
if(self->volume < (float)(self->max_health))
|
|
{
|
|
M_ShowLifeMeter(self, (int)(ceil(self->volume/self->max_health*TBEAST_SBAR_SIZE)), (int)(ceil(self->volume/self->max_health*TBEAST_SBAR_SIZE)));
|
|
self->volume += (float)(self->max_health) / 10;
|
|
}
|
|
else if(self->health > 0)
|
|
{
|
|
M_ShowLifeMeter(self, (int)(ceil((float)(self->health)/(float)(self->max_health)*TBEAST_SBAR_SIZE)), TBEAST_SBAR_SIZE);
|
|
}
|
|
}
|
|
|
|
if(self->s.origin[0] != self->s.old_origin[0] || self->s.origin[1] != self->s.old_origin[1])
|
|
LevelToGround(self, 0.5, 0.25, true);
|
|
|
|
if(Q_fabs(self->s.angles[PITCH])>45 || Q_fabs(self->s.angles[ROLL])>45)
|
|
go_jump = true;
|
|
else
|
|
{
|
|
//raise him up if on flat ground, lower is on slope - to keep feet on ground!
|
|
//FIXME - use checkbottom plane instead?
|
|
|
|
if(self->s.origin[0] != self->s.old_origin[0] || self->s.origin[1] != self->s.old_origin[1])
|
|
{
|
|
omins2 = self->mins[2];
|
|
self->mins[2] = ((Q_fabs(self->s.angles[PITCH]) + Q_fabs(self->s.angles[ROLL]))*0.5)/45 * 144 - 6 + TB_UP_OFFSET;
|
|
omins2 -= self->mins[2];
|
|
if(omins2)
|
|
{
|
|
VectorCopy(self->s.origin, end);
|
|
end[2] += omins2;
|
|
gi.trace(self->s.origin, self->mins, self->maxs, end, self, MASK_SOLID,&trace);
|
|
VectorCopy(trace.endpos, self->s.origin);
|
|
}
|
|
gi.linkentity(self);
|
|
}
|
|
}
|
|
|
|
VectorCopy(self->s.origin, self->s.old_origin);
|
|
// if(!TB_CheckBottom(self))
|
|
// {
|
|
// gi.dprintf("Beast not on ground jump!\n");
|
|
// SetAnim(self, ANIM_JUMP);
|
|
// }
|
|
|
|
if(!irand(0, 10))
|
|
{
|
|
if(self->curAnimID == ANIM_WALK ||
|
|
self->curAnimID == ANIM_WALKLEFT ||
|
|
self->curAnimID == ANIM_WALKRT ||
|
|
self->curAnimID == ANIM_WALKATK)
|
|
{
|
|
VectorCopy(self->s.origin, end);
|
|
end[2] -= 64;
|
|
|
|
VectorSet(mins, -8, -8, 0);
|
|
VectorSet(maxs, 8, 8, 2);
|
|
|
|
gi.trace(self->s.origin, mins, maxs, end, self, MASK_SOLID,&trace);
|
|
if(trace.fraction == 1.0 && !trace.startsolid && !trace.allsolid)
|
|
go_jump = true;
|
|
}
|
|
}
|
|
|
|
if(go_jump)
|
|
{
|
|
// gi.dprintf("Beast not on ground jump!\n");
|
|
TB_CheckJump (self);
|
|
//SetAnim(self, ANIM_JUMP);
|
|
}
|
|
|
|
tbeast_fake_touch(self);
|
|
|
|
self->next_post_think = level.time + 0.1;
|
|
}
|
|
|
|
edict_t *check_hit_beast(vec3_t start, vec3_t end)
|
|
{
|
|
edict_t *found = NULL;
|
|
int i = 0;
|
|
vec3_t shot_dir, beast_dir, checkpos, diffvec;
|
|
float diff1, diff2;
|
|
|
|
VectorSubtract(end, start, shot_dir);
|
|
diff1 = VectorNormalize(shot_dir);
|
|
|
|
while(found = G_Find(found, FOFS(classname), "monster_trial_beast"))
|
|
{
|
|
VectorSubtract(found->s.origin, start, beast_dir);
|
|
diff2 = VectorLength(beast_dir) - 128;
|
|
|
|
if(diff2 > diff1)
|
|
continue;
|
|
|
|
//beast closer than trace endpos, let's do an incremental check
|
|
|
|
VectorCopy(start, checkpos);
|
|
|
|
for(i = 16; i < diff1; i+=16)
|
|
{
|
|
VectorMA(checkpos, 16, shot_dir, checkpos);
|
|
VectorSubtract(checkpos, found->s.origin, diffvec);
|
|
if(VectorLengthSquared(diffvec) < 16384)//128 squared
|
|
{//this spot is within 128 of beast origin, so you hit him, ok?
|
|
VectorCopy(checkpos, end);
|
|
return found;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void tbeast_go_charge (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->enemy = activator;//are we certain activator is client?
|
|
//do a FoundTarget(self, false);?
|
|
self->dmg = true;
|
|
SetAnim(self, ANIM_CHARGE);
|
|
self->use = tbeast_go_die;
|
|
}
|
|
|
|
void TBeastStaticsInit()
|
|
{
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_STAND] = tbeast_stand;
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_WALK] = tbeast_walk;
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_RUN] = tbeast_run;
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_EAT] = tbeast_eat;
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_MELEE] = tbeast_melee;
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_MISSILE] = tbeast_start_charge;
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_WATCH] = tbeast_walk;
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_PAIN] = tbeast_pain;
|
|
classStatics[CID_TBEAST].msgReceivers[MSG_DEATH] = tbeast_death;
|
|
|
|
resInfo.numAnims = NUM_ANIMS;
|
|
resInfo.animations = animations;
|
|
resInfo.modelIndex = gi.modelindex("models/monsters/beast/tris.fm");
|
|
|
|
sounds[SND_ROAR] = gi.soundindex ("monsters/tbeast/roar.wav");
|
|
sounds[SND_ROAR2] = gi.soundindex ("monsters/tbeast/roar2.wav");
|
|
sounds[SND_SNORT1] = gi.soundindex ("monsters/tbeast/snort1.wav");
|
|
sounds[SND_SNORT2] = gi.soundindex ("monsters/tbeast/snort2.wav");
|
|
|
|
sounds[SND_STEP1] = gi.soundindex ("monsters/tbeast/step1.wav");
|
|
sounds[SND_STEP2] = gi.soundindex ("monsters/tbeast/step2.wav");
|
|
sounds[SND_LAND] = gi.soundindex ("monsters/tbeast/land.wav");
|
|
|
|
sounds[SND_GROWL1] = gi.soundindex ("monsters/tbeast/growl1.wav");
|
|
sounds[SND_GROWL2] = gi.soundindex ("monsters/tbeast/growl2.wav");
|
|
sounds[SND_GROWL3] = gi.soundindex ("monsters/tbeast/growl3.wav");
|
|
|
|
// sounds[SND_SWIPE]= gi.soundindex ("monsters/tbeast/swipe.wav");
|
|
sounds[SND_SLAM]= gi.soundindex ("monsters/tbeast/slam.wav");
|
|
sounds[SND_SNATCH]= gi.soundindex ("monsters/tbeast/snatch.wav");
|
|
sounds[SND_CHOMP]= gi.soundindex ("monsters/tbeast/chomp.wav");
|
|
sounds[SND_TEAR1]= gi.soundindex ("monsters/tbeast/tear1.wav");
|
|
sounds[SND_TEAR2]= gi.soundindex ("monsters/tbeast/tear2.wav");
|
|
sounds[SND_THROW]= gi.soundindex ("monsters/tbeast/throw.wav");
|
|
sounds[SND_CATCH]= gi.soundindex ("monsters/tbeast/catch.wav");
|
|
// sounds[SND_SWALLOW]= gi.soundindex ("monsters/tbeast/swallow.wav");
|
|
|
|
sounds[SND_PAIN1]= gi.soundindex ("monsters/tbeast/pain1.wav");
|
|
sounds[SND_PAIN2]= gi.soundindex ("monsters/tbeast/pain2.wav");
|
|
sounds[SND_DIE]= gi.soundindex ("monsters/tbeast/die.wav");
|
|
|
|
sounds[SND_CORVUS_SCREAM1] = gi.soundindex ("corvus/bdeath1.wav");
|
|
sounds[SND_CORVUS_SCREAM2] = gi.soundindex ("corvus/bdeath2.wav");
|
|
sounds[SND_CORVUS_SCREAM3] = gi.soundindex ("corvus/bdeath3.wav");
|
|
sounds[SND_CORVUS_DIE] = gi.soundindex ("player/falldeath1.wav");
|
|
|
|
resInfo.numSounds = NUM_SOUNDS;
|
|
resInfo.sounds = sounds;
|
|
|
|
classStatics[CID_TBEAST].resInfo = &resInfo;
|
|
}
|
|
|
|
|
|
/*QUAKED monster_trial_beast (1 .5 0) (-100 -100 -36) (100 100 150) ?
|
|
|
|
The Trial Beastie
|
|
|
|
"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 = 100
|
|
melee_range = 400 (bite)
|
|
missile_range = 1500 (charge)
|
|
min_missile_range = 100
|
|
bypass_missile_chance = 77
|
|
jump_chance = 100
|
|
wakeup_distance = 3000
|
|
|
|
NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1
|
|
*/
|
|
void SP_monster_trial_beast (edict_t *self)
|
|
{
|
|
// Generic Monster Initialization
|
|
if ((deathmatch->value == 1) && !((int)sv_cheats->value & self_spawn))
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
if (!walkmonster_start (self)) // Incomplete initialization
|
|
return;
|
|
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->classID = CID_TBEAST;
|
|
self->monsterinfo.aiflags |= AI_BRUTAL|AI_AGRESSIVE|AI_SHOVE;
|
|
self->monsterinfo.otherenemyname = "monster_tcheckrik_male";
|
|
|
|
self->health = TB_HEALTH * (skill->value + 1) / 3;
|
|
|
|
self->mass = TB_MASS;
|
|
self->yaw_speed = 10;
|
|
self->isBlocked = tbeast_blocked;
|
|
self->bounced = tbeast_blocked;
|
|
|
|
self->movetype=PHYSICSTYPE_STEP;
|
|
VectorClear(self->knockbackvel);
|
|
|
|
//problem- staff won't work on him!
|
|
self->solid=SOLID_TRIGGER;//BBOX;
|
|
self->materialtype = MAT_FLESH;
|
|
|
|
// VectorSet(self->mins, -116, -116, -4);
|
|
// VectorSet(self->maxs, 116, 116, 182);
|
|
VectorSet(self->mins, -100, -100, -36);
|
|
VectorSet(self->maxs, 100, 100, 150);
|
|
|
|
self->viewheight = 104 + TB_UP_OFFSET;
|
|
|
|
self->s.modelindex = classStatics[CID_TBEAST].resInfo->modelIndex;
|
|
|
|
//Big guy can be stood on top of perhaps?
|
|
//self->touch = M_Touch;
|
|
|
|
if(!self->wakeup_distance)
|
|
self->wakeup_distance = 3000;
|
|
|
|
MG_InitMoods(self);
|
|
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
self->dmg = false;
|
|
|
|
self->svflags|=(SVF_BOSS|SVF_NO_AUTOTARGET);
|
|
|
|
// self->monsterinfo.aiflags &= ~AI_USING_BUOYS;
|
|
|
|
if(!irand(0,1))
|
|
self->ai_mood_flags |= AI_MOOD_FLAG_PREDICT;
|
|
|
|
self->monsterinfo.aiflags |= AI_NIGHTVISION;
|
|
|
|
self->touch = tbeast_touch;
|
|
self->post_think = tbeast_post_think;
|
|
self->next_post_think = level.time + 0.1;
|
|
self->elasticity = ELASTICITY_SLIDE;
|
|
self->count = self->sounds = 0;
|
|
self->clipmask = CONTENTS_SOLID;
|
|
self->solid = SOLID_TRIGGER;//WHY IS HE BEING PUSHED BY BSP entities now???!
|
|
self->red_rain_count = 0;//pillar init
|
|
self->use = tbeast_go_charge;
|
|
self->delay = true;
|
|
|
|
self->max_health = self->health;
|
|
self->volume = 0;
|
|
self->wait = 0;
|
|
|
|
level.fighting_beast = true;//sorry, only one beast per level
|
|
}
|