455 lines
13 KiB
C
455 lines
13 KiB
C
//==============================================================================
|
|
//
|
|
// m_chicken.c
|
|
//
|
|
// Heretic II
|
|
// Copyright 1998 Raven Software
|
|
//
|
|
//
|
|
// AI :
|
|
//
|
|
// STAND1 : Looking straight ahead
|
|
//
|
|
//==============================================================================
|
|
|
|
#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_Physics.h"
|
|
#include "g_teleport.h"
|
|
#include "g_Skeletons.h"
|
|
#include "p_anims2.h"
|
|
|
|
#include "m_chicken.h"
|
|
#include "m_chicken_anim.h"
|
|
#include "m_stats.h"
|
|
|
|
extern void BecomeDebris(edict_t *self);
|
|
extern void ED_CallSpawn (edict_t *ent);
|
|
|
|
/*----------------------------------------------------------------------
|
|
Chicken Base Info
|
|
-----------------------------------------------------------------------*/
|
|
|
|
static animmove_t *animations[NUM_ANIMS] =
|
|
{
|
|
&chicken_move_stand1,
|
|
&chicken_move_walk,
|
|
&chicken_move_run,
|
|
&chicken_move_cluck,
|
|
&chicken_move_attack,
|
|
&chicken_move_eat,
|
|
&chicken_move_jump,
|
|
};
|
|
|
|
static int sounds[NUM_SOUNDS];
|
|
|
|
static ClassResourceInfo_t resInfo;
|
|
|
|
void ChickenStaticsInit()
|
|
{
|
|
classStatics[CID_CHICKEN].msgReceivers[MSG_STAND] = chicken_stand;
|
|
classStatics[CID_CHICKEN].msgReceivers[MSG_WALK] = chicken_walk;
|
|
classStatics[CID_CHICKEN].msgReceivers[MSG_RUN] = chicken_run;
|
|
classStatics[CID_CHICKEN].msgReceivers[MSG_MELEE] = chicken_attack;
|
|
classStatics[CID_CHICKEN].msgReceivers[MSG_DEATH] = chicken_death;
|
|
classStatics[CID_CHICKEN].msgReceivers[MSG_WATCH] = chicken_cluck;
|
|
classStatics[CID_CHICKEN].msgReceivers[MSG_EAT] = chicken_eat;
|
|
classStatics[CID_CHICKEN].msgReceivers[MSG_JUMP] = chicken_jump;
|
|
|
|
resInfo.numAnims = NUM_ANIMS;
|
|
resInfo.animations = animations;
|
|
resInfo.modelIndex = gi.modelindex("models/monsters/chicken2/tris.fm");
|
|
|
|
//for the cluck animation
|
|
sounds[SND_CLUCK1]= gi.soundindex ("monsters/chicken/cluck1.wav");
|
|
sounds[SND_CLUCK2]= gi.soundindex ("monsters/chicken/cluck2.wav");
|
|
|
|
//for getting hit - even though right now, it dies immediately - they want this changed
|
|
sounds[SND_PAIN1]= gi.soundindex ("monsters/chicken/pain1.wav");
|
|
sounds[SND_PAIN2]= gi.soundindex ("monsters/chicken/pain2.wav");
|
|
|
|
//for dying - we only ever get gibbed, so no other sound is required
|
|
sounds[SND_DIE]= gi.soundindex ("monsters/chicken/die.wav");
|
|
|
|
//for biting the player
|
|
sounds[SND_BITE1]= gi.soundindex ("monsters/chicken/bite1.wav");
|
|
sounds[SND_BITE2]= gi.soundindex ("monsters/chicken/bite2.wav");
|
|
|
|
//for pecking the ground
|
|
sounds[SND_PECK1]= gi.soundindex ("monsters/chicken/peck1.wav");
|
|
sounds[SND_PECK2]= gi.soundindex ("monsters/chicken/peck2.wav");
|
|
|
|
//and lastly, I thought it might be cool to have some cries for when the chicken jumps
|
|
sounds[SND_JUMP1]= gi.soundindex ("monsters/chicken/jump1.wav");
|
|
sounds[SND_JUMP2]= gi.soundindex ("monsters/chicken/jump2.wav");
|
|
sounds[SND_JUMP3]= gi.soundindex ("monsters/chicken/jump3.wav");
|
|
|
|
resInfo.numSounds = NUM_SOUNDS;
|
|
resInfo.sounds = sounds;
|
|
|
|
classStatics[CID_CHICKEN].resInfo = &resInfo;
|
|
}
|
|
|
|
/*QUAKED monster_chicken (1 .5 0) (-16 -16 -0) (16 16 32) AMBUSH ASLEEP EATING
|
|
|
|
The chicken
|
|
|
|
"wakeup_target" - monsters will fire this target the first time it wakes up (only once)
|
|
|
|
"pain_target" - monsters will fire this target the first time it gets hurt (only once)
|
|
|
|
*/
|
|
void SP_monster_chicken (edict_t *self)
|
|
{
|
|
// Generic Monster Initialization
|
|
if (!monster_start(self)) // Failed initialization
|
|
return;
|
|
|
|
self->msgHandler = DefaultMsgHandler;
|
|
self->classID = CID_CHICKEN;
|
|
self->think = walkmonster_start_go;
|
|
self->materialtype = MAT_FLESH;
|
|
|
|
self->health = CHICKEN_HEALTH;
|
|
self->mass = CHICKEN_MASS;
|
|
self->yaw_speed = 20;
|
|
|
|
self->movetype = PHYSICSTYPE_STEP;
|
|
VectorClear(self->knockbackvel);
|
|
|
|
self->solid=SOLID_BBOX;
|
|
|
|
VectorSet(self->mins,-12,-12,-16);
|
|
VectorSet(self->maxs,12,12,16);
|
|
|
|
self->s.modelindex = classStatics[CID_CHICKEN].resInfo->modelIndex;
|
|
|
|
self->s.skinnum = 0;
|
|
self->monsterinfo.scale = MODEL_SCALE;
|
|
|
|
self->monsterinfo.otherenemyname = "obj_barrel";
|
|
|
|
//Spawn off the guide
|
|
// self->guide = AI_SpawnGuide(self);
|
|
MG_InitMoods(self);
|
|
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
|
|
gi.linkentity(self);
|
|
|
|
}
|
|
|
|
void chicken_death(edict_t *self, G_Message_t *msg)
|
|
{
|
|
self->msgHandler = DeadMsgHandler;
|
|
|
|
// if we have a use, fire it off.
|
|
// monster_death_use (self);
|
|
|
|
gi.sound (self, CHAN_BODY, sounds[SND_DIE], 1, ATTN_NORM, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
BecomeDebris(self);
|
|
gi.CreateEffect(&self->s, FX_CHICKEN_EXPLODE, CEF_OWNERS_ORIGIN, NULL, "" );
|
|
|
|
}
|
|
|
|
// fade the original monster back in again
|
|
void MorphOriginalIn(edict_t *self)
|
|
{
|
|
self->s.color.a += MORPH_TELE_FADE;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
if (!(--self->morph_timer))
|
|
{
|
|
self->think = self->oldthink;
|
|
}
|
|
}
|
|
|
|
//Fade out the existing model till its gone
|
|
void MorphChickenOut(edict_t *self)
|
|
{
|
|
edict_t *newent;
|
|
|
|
self->s.color.a -= MORPH_TELE_FADE;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
if (self->morph_timer < level.time)
|
|
{
|
|
// create the original bad guy
|
|
newent = G_Spawn();
|
|
newent->classname = self->map;
|
|
VectorCopy(self->s.origin, newent->s.origin);
|
|
VectorCopy(self->s.angles, newent->s.angles);
|
|
newent->enemy = self->enemy;
|
|
ED_CallSpawn(newent);
|
|
newent->s.color.c = 0xffffff;
|
|
newent->oldthink = newent->think;
|
|
newent->think = MorphOriginalIn;
|
|
newent->morph_timer = MORPH_TELE_TIME;
|
|
newent->target = self->target;
|
|
|
|
// make physics expand the bounding box for us, so the player doesn't get stuck inside the new bad guys box.
|
|
|
|
// store the old mins max's
|
|
|
|
VectorCopy(newent->mins,newent->intentMins);
|
|
VectorCopy(newent->maxs,newent->intentMaxs);
|
|
|
|
VectorCopy(self->mins,newent->mins);
|
|
VectorCopy(self->maxs,newent->maxs);
|
|
|
|
newent->physicsFlags |= PF_RESIZE;
|
|
|
|
// do the teleport sound
|
|
gi.sound(newent,CHAN_WEAPON,gi.soundindex("weapons/teleport.wav"),1,ATTN_NORM,0);
|
|
// do the teleport effect
|
|
gi.CreateEffect(&newent->s, FX_PLAYER_TELEPORT_IN, CEF_OWNERS_ORIGIN, NULL, "" );
|
|
G_FreeEdict(self);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
|
chicken checks to see if we are still a chicken, or is it time to return to
|
|
our original shape ?
|
|
-------------------------------------------------------------------------*/
|
|
void chicken_check (edict_t *self)
|
|
{
|
|
|
|
// are we done yet ?
|
|
if (self->time > level.time)
|
|
return;
|
|
|
|
// make that pretty effect around us
|
|
gi.CreateEffect(&self->s, FX_PLAYER_TELEPORT_OUT, CEF_OWNERS_ORIGIN, NULL, "" );
|
|
|
|
// deal with the existing chicken
|
|
self->think = MorphChickenOut;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->touch = NULL;
|
|
self->morph_timer = MORPH_TELE_TIME;
|
|
VectorClear(self->velocity);
|
|
|
|
// wipe out the guide, so we don't do anything else
|
|
// G_FreeEdict(self->guide);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
|
chicken_bites you
|
|
-------------------------------------------------------------------------*/
|
|
void chicken_bite (edict_t *self)
|
|
{
|
|
vec3_t v, off, dir, org;
|
|
float len;
|
|
|
|
// incase we try pecking at someone thats not there.
|
|
if (!self->enemy)
|
|
return;
|
|
|
|
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
|
len = VectorLength (v);
|
|
|
|
// determine if we've actually bitten the player, or just missed
|
|
if (len <= (self->maxs[0] + self->enemy->maxs[0] + 24) ) // A hit
|
|
{
|
|
VectorSet(off, 20.0, 0.0, 5.0);
|
|
VectorGetOffsetOrigin(off, self->s.origin, self->s.angles[YAW], org);
|
|
// this is not apparently used for anything ?
|
|
VectorClear(dir);
|
|
// cause us some damage.
|
|
T_Damage (self->enemy, self, self, dir, org, vec3_origin, 1, 0, 0,MOD_DIED);
|
|
|
|
if(!irand(0,1))
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_BITE1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_BITE2], 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
chicken_pause
|
|
-------------------------------------------------------------------------*/
|
|
void chicken_pause (edict_t *self)
|
|
{
|
|
vec3_t v;
|
|
float len;
|
|
int random_action;
|
|
|
|
self->mood_think(self);
|
|
|
|
if (self->ai_mood == AI_MOOD_NORMAL)
|
|
{
|
|
FindTarget(self);
|
|
|
|
if(self->enemy)
|
|
{
|
|
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
|
len = VectorLength (v);
|
|
|
|
if ((len > 60) || (self->monsterinfo.aiflags & AI_FLEE)) // Far enough to run after
|
|
{
|
|
QPostMessage(self, MSG_RUN,PRI_DIRECTIVE, NULL);
|
|
}
|
|
else // Close enough to Attack
|
|
{
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// decide if we will do a random action
|
|
random_action = (irand(0,10));
|
|
// ok, if we can attack, then we do that, no question
|
|
if (self->ai_mood == AI_MOOD_ATTACK)
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
// else, one chance in a remote time, we will just go "cluck"
|
|
else if (!random_action)
|
|
// make us cluck
|
|
QPostMessage(self, MSG_WATCH, PRI_DIRECTIVE, NULL);
|
|
else
|
|
{
|
|
random_action--;
|
|
if (!random_action)
|
|
// make us peck
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
// otherwise run or track the player target
|
|
else
|
|
{
|
|
switch (self->ai_mood)
|
|
{
|
|
case AI_MOOD_PURSUE:
|
|
QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
case AI_MOOD_NAVIGATE:
|
|
QPostMessage(self, MSG_WALK, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
case AI_MOOD_STAND:
|
|
QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
case AI_MOOD_JUMP:
|
|
VectorCopy(self->movedir, self->velocity);
|
|
VectorNormalize(self->movedir);
|
|
SetAnim(self, ANIM_JUMP);
|
|
break;
|
|
case AI_MOOD_EAT:
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
break;
|
|
|
|
default :
|
|
#ifdef _DEVEL
|
|
gi.dprintf("Chicken Unusable mood %d!\n", self->ai_mood);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------
|
|
chicken_eat again, possibly
|
|
-------------------------------------------------------------------------*/
|
|
void chicken_eat_again (edict_t *self)
|
|
{
|
|
// a one in three chance we will peck again :)
|
|
if (irand(0,2))
|
|
QPostMessage(self, MSG_EAT, PRI_DIRECTIVE, NULL);
|
|
else
|
|
chicken_pause(self);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Chicken attack - peck us to death
|
|
//----------------------------------------------------------------------
|
|
void chicken_attack(edict_t *self, G_Message_t *msg)
|
|
{
|
|
SetAnim(self, ANIM_ATTACK);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Chicken jump
|
|
//----------------------------------------------------------------------
|
|
void chicken_jump(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if(!irand(0,2))
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_JUMP1], 1, ATTN_NORM, 0);
|
|
else if (!irand(0, 1))
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_JUMP2], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_JUMP3], 1, ATTN_NORM, 0);
|
|
SetAnim(self, ANIM_JUMP);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Chicken Cluck
|
|
//----------------------------------------------------------------------
|
|
void chicken_eat(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if (!irand(0, 1))
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_PECK1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_PECK2], 1, ATTN_NORM, 0);
|
|
SetAnim(self, ANIM_EAT);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Chicken Cluck
|
|
//----------------------------------------------------------------------
|
|
void chicken_cluck(edict_t *self, G_Message_t *msg)
|
|
{
|
|
if (!irand(0, 1))
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_CLUCK1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sounds[SND_CLUCK2], 1, ATTN_NORM, 0);
|
|
|
|
SetAnim(self, ANIM_CLUCK);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Chicken Run - choose a run to use
|
|
//----------------------------------------------------------------------
|
|
void chicken_run(edict_t *self, G_Message_t *msg)
|
|
{
|
|
SetAnim(self, ANIM_RUN);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Chicken Walk - choose a walk to use
|
|
//----------------------------------------------------------------------
|
|
void chicken_walk(edict_t *self, G_Message_t *msg)
|
|
{
|
|
SetAnim(self, ANIM_WALK);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Chicken Stand -decide which standing animations to use
|
|
//----------------------------------------------------------------------
|
|
void chicken_stand(edict_t *self, G_Message_t *msg)
|
|
{
|
|
SetAnim(self, ANIM_STAND1);
|
|
}
|
|
|
|
void chickensqueal (edict_t *self)
|
|
{
|
|
if(!irand(0, 1))
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_PAIN1], 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound (self, CHAN_WEAPON, sounds[SND_PAIN2], 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void ChickenGlide ( playerinfo_t *playerinfo )
|
|
{
|
|
}
|
|
|
|
void chickenSound (edict_t *self, float channel, float sndindex, float atten)
|
|
{
|
|
gi.sound(self, channel, sounds[(int)(sndindex)], 1, atten, 0);
|
|
}
|