sof-sdk/Source/Game/gamecpp/ai_body.cpp
2000-06-15 00:00:00 +00:00

1933 lines
55 KiB
C++

//body--handle all super-basic animation stuff
#include "g_local.h"
#include "matrix4.h"
#include "ai_body.h"
#include "ai_bodynoghoul.h"
#include "ai_bodyhuman.h"
#include "ai_bodydekker.h"
#include "ai_bodydog.h"
#include "ai_bodycow.h"
#include "ai.h"
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
extern int ai_loadAllBolts;
body_c *body_c::NewClassForCode(int code)
{
switch (code)
{
default:
case BODY:
case BODY_HUMAN:
case BODY_DOG:
case BODY_ORGANIC:
gi.dprintf("ERROR: invalid body class code: %d\n",code);
case BODY_MESO:
return new bodymeso_c();
case BODY_ECTO:
return new bodyecto_c();
case BODY_HUSKY:
return new bodyhusky_c();
case BODY_ROTT:
return new bodyrott_c();
case BODY_FEMALE:
return new bodyfemale_c();
case BODY_HELI:
return new body_heli();
case BODY_TANK:
return new body_tank();
case BODY_SNOWCAT:
return new body_snowcat();
case BODY_COW:
return new bodycow_c();
case BODY_DEKKER:
return new bodydekker_c();
}
}
//static GroundTransformationCallBack TheGroundTransformationCallBack;
body_c::body_c()
{
currentmove = NULL;
owner = NULL;
currentMoveFinished = true;
holdCode = HOLDCODE_NO;
frame_flags = 0;
emotion_expire=level.time;
emotion = EMOTION_NORMAL;
emotionScripted=false;
rLastFireTime=level.time-10;
lLastFireTime=level.time-10;
InitialKilledTime = 0.0;
LastKilledTime = 0.0;
lastStartTime=level.time;
LastDFlags = 0;
ForceNextAnim = false;
TrainDeath = false;
}
void body_c::FinishMove(edict_t &monster)
{
// gi.dprintf("finishing anim at %f\n",level.time);
currentMoveFinished = true;
}
void body_c::HoldAnimation(edict_t &monster, hold_code whyHold, float holdTime)
{
GhoulID curAnim;
curAnim = GetSequence(monster, currentmove);
if (!curAnim)
{
return;
}
monster.ghoulInst->Pause(holdTime);
holdCode = whyHold;
}
void body_c::ResumeAnimation(edict_t &monster)
{
GhoulID curAnim;
if ((holdCode == HOLDCODE_NO) || (holdCode == HOLDCODE_GIBBED))
{
return;
}
curAnim = GetSequence(monster, currentmove);
if (!curAnim)
{
return;
}
//fixme: ghoul glitch makes motion track go nuts while held
// VectorClear(monster.intend_velocity);
monster.ghoulInst->Resume(level.time);
holdCode = HOLDCODE_NO;
}
qboolean body_c::IsAnimationHeld(hold_code queryCode)
{
if (queryCode == HOLDCODE_NO)
{
return (holdCode != HOLDCODE_NO);
}
return (holdCode == queryCode);
}
qboolean body_c::IsAnimationFinished()
{
if (!currentmove)
{
return true;
}
if (currentmove->actionFlags & ACTFLAG_LOOPANIM && !(currentmove->actionFlags&ACTFLAG_FULLANIM))
{
return true;
}
return currentMoveFinished;
}
//super-secret version of IsAnimationFinished--doesn't make exceptions for looping animations.
//use sparingly--i only added this for path_corners, so you specify a cluster of animations (including looping ones)
// and it could wait until each one finishes before going to the next.
qboolean body_c::HasAnimationHitEOS()
{
if (!currentmove)
{
return true;
}
return currentMoveFinished;
}
qboolean body_c::IsAnimationReversed()
{
if (currentmove)
{
return currentmove->actionFlags & ACTFLAG_REVERSEANIM;
}
return false;
}
GhoulID body_c::GetSequence(edict_t &monster, mmove_t *newanim)
{
ggObjC *myObject;
if (monster.ghoulInst && newanim && newanim->ghoulSeqName && (myObject=game_ghoul.FindObject(monster.ghoulInst->GetGhoulObject())))
{
char seqName[GGHOUL_OBJ_NAME_LEN];
Com_sprintf(seqName, GGHOUL_OBJ_NAME_LEN, "ghoul/%s/%s.ghl",myObject->GetName(),newanim->ghoulSeqName);
return monster.ghoulInst->GetGhoulObject()->FindSequence(seqName);
}
return NULL_GhoulID;
}
qboolean body_c::IsAvailableSequence(edict_t &monster, mmove_t *newanim)
{
if(monster.ai)
{
ai_c *the_ai = (ai_c*)((ai_public_c*)monster.ai);
if(the_ai)
{
ggObjC *myObj = the_ai->GetGhoulObj();
if(myObj)
{
if(!strncmp(myObj->GetName(), "enemy/meso", strlen("enemy/meso")) ||
!strncmp(myObj->GetName(), "enemy/ecto", strlen("enemy/ecto")) ||
!strncmp(myObj->GetName(), "enemy/female", strlen("enemy/female")))
{
return myObj->IsSequenceAvailable(newanim - MMoves);
}
}
}
}
return (GetSequence(monster, newanim)!=NULL_GhoulID);
}
qboolean body_c::PlayAnimation(edict_t &monster, mmove_t *newanim, bool forceRestart)
{
//if monster has valid ghoul model, and animseq has valid ghoul seq., then play the sequence
GhoulID curAnim;
bool reverseAnim=false;
float playPos=newanim->playPos;
bool matchAnim=(1&&(newanim->actionFlags&ACTFLAG_MATCHANIM));
float startTime=level.time;
//what goes on in here (GetSequence calls mostly, it looks like) seems to be expensive, so just accept it if the play call isn't going to do anything anyway
if (newanim==currentmove && !forceRestart && !currentMoveFinished)
{
//just make sure i'm not paused
ResumeAnimation(monster);
return true;
}
//don't match the end of a sequence--if the new anim isn't looping, it'll never finish!!!
if (currentmove && !(currentmove->actionFlags&ACTFLAG_LOOPANIM) && currentMoveFinished && matchAnim)
{
matchAnim=false;
forceRestart=true;
}
if(newanim->actionFlags & ACTFLAG_FIREFROMGUN)
{
startTime = level.time + .1;
}
//SFS--Note: the contents of the next set of brackets is a trashy hack. if we can do without it, that would be nice.
else if (currentmove && (currentmove->actionFlags & ACTFLAG_FIREFROMGUN))//just coming from a firefromgun anim, consider screwing with angles and messing everything up
{
if ((newanim->suggested_action == ACTCODE_PAIN)||(newanim->suggested_action == ACTCODE_DEATH))//only bother with pains & deaths; other anims will wait till firfromguns sort theyselves out
{
Matrix4 m;
vec3_t mouthface, mouthangs;
ai_c *my_ai = (ai_c *)((ai_public_c *)monster.ai);
int taxis;
monster.ghoulInst->GetBoltMatrix(level.time, m, monster.ghoulInst->GetGhoulObject()->FindPart("sbolt_mouth"), IGhoulInst::MatrixType::Entity);
if (my_ai)
{
m.GetRow(0,*(Vect3 *)mouthface);//forward vector, hopefully
mouthface[2] = 0;
vectoangles(mouthface,mouthangs);
if (GetClassCode() == BODY_HUSKY || GetClassCode() == BODY_ROTT)
{
mouthangs[YAW] -= 90;
}
VectorAdd(mouthangs, monster.s.angles,mouthangs);
for (taxis=0;taxis<3;taxis++)
{
mouthangs[taxis]=anglemod(mouthangs[taxis]);
}
VectorCopy(mouthangs, monster.s.angles);
// VectorCopy(mouthangs, mouthface);
// my_ai->SetLookAngles(mouthface);
// AngleVectors(mouthangs,mouthface,NULL,NULL);
}
}
}
if (holdCode == HOLDCODE_GIBBED)
{
return false;
}
curAnim = GetSequence(monster, newanim);
if (!curAnim)
{
if (monster.classname && monster.targetname)
{
gi.dprintf("%s %s couldn't play animation %s!\n", monster.classname, monster.targetname, newanim->ghoulSeqName);
}
else if (monster.classname)
{
gi.dprintf("Some %s couldn't play animation %s!\n", monster.classname, newanim->ghoulSeqName);
}
else
{
gi.dprintf("Unknown monster couldn't play animation %s!\n", newanim->ghoulSeqName);
}
return false;
}
if (newanim->actionFlags & ACTFLAG_MATCHABS)
{
//only match for similar sequences
if (newanim!=currentmove && currentmove->suggested_action==newanim->suggested_action)
{
startTime=lastStartTime;
}
}
//reverse the animation?
if (newanim->actionFlags & ACTFLAG_REVERSEANIM)
{
reverseAnim = true;
// fixme? kef -- given that you can now store the playPos as part of the mmove_t struct,
//I don't know if we want to just plain override it here.
playPos = 1.0f;
}
// get the old quakeorigin -- THIS WAS A WEAK ATTEMPT TO SOLVE THE POPPING BETWEEN ANIMATIONS. DIDN'T WORK!
/* Matrix4 BoltMatrix;
bool doit = false;
vec3_t oldOrigin;
if (monster.ghoulInst->GetStateSequence())
{
Vect3 OldQuakeOrigin;
int oldGhoulID;
doit = true;
oldGhoulID = monster.ghoulInst->GetGhoulObject()->FindPart("quake_ground");
monster.ghoulInst->GetBoltMatrix(level.time, BoltMatrix, oldGhoulID, IGhoulInst::MatrixType::Entity);
BoltMatrix.GetRow(3,OldQuakeOrigin);
VectorCopy((float *)&OldQuakeOrigin, oldOrigin);
}
*/
//check to see if this should loop
if (newanim->actionFlags & ACTFLAG_LOOPANIM)
{
monster.ghoulInst->Play(curAnim,startTime,playPos,forceRestart,IGhoulInst::Loop, matchAnim, reverseAnim);
}
else
{
monster.ghoulInst->Play(curAnim,startTime,playPos,forceRestart,IGhoulInst::Hold, matchAnim, reverseAnim);
}
lastStartTime=startTime;
if (curAnim!=GetSequence(monster, currentmove)
/*(!newanim || !currentmove || stricmp(newanim->ghoulSeqName, currentmove->ghoulSeqName))*/
/*newanim != currentmove*/
||forceRestart)
{
currentMoveFinished = false;
//quake_ground may be drastically changed--i'll try to adjust bbox again this frame, so set the ground for that
if (monster.ai)
{
// get the new quake origin
Matrix4 m;
int newGhoulID;
vec3_t curPos;
ai_c *my_ai = (ai_c *)((ai_public_c *)monster.ai);
newGhoulID = monster.ghoulInst->GetGhoulObject()->FindPart("quake_ground");
if (newGhoulID)
{
monster.ghoulInst->GetBoltMatrix(level.time, m, newGhoulID, IGhoulInst::MatrixType::Entity);
m.GetRow(3,*(Vect3 *)curPos);
// if (self->groundentity)
{
if (my_ai)
{
my_ai->SetGround(curPos[2]);
}
}
}
}
}
/* if (doit)
{
// get the new quake origin
Vect3 NewQuakeOrigin;
vec3_t newOrigin;
int newGhoulID;
newGhoulID = monster.ghoulInst->GetGhoulObject()->FindPart("quake_ground");
monster.ghoulInst->GetBoltMatrix(level.time, BoltMatrix, newGhoulID, IGhoulInst::MatrixType::Entity);
BoltMatrix.GetRow(3,NewQuakeOrigin);
VectorCopy((float *)&NewQuakeOrigin, newOrigin);
float change = oldOrigin[2] - newOrigin[2];
// monster.s.origin[2] += 5;
monster.s.origin[2] += change;
}
*/ SetMove(newanim);
ResumeAnimation(monster);
return true;
}
qboolean body_c::MatchAnimation(edict_t &monster, mmove_t *newanim)
{
return PlayAnimation(monster, newanim, false);
}
qboolean body_c::SetAnimation(edict_t &monster, mmove_t *newanim)
{
return PlayAnimation(monster, newanim, false);
}
qboolean body_c::ForceAnimation(edict_t &monster, mmove_t *newanim)
{
return PlayAnimation(monster, newanim, true);
}
#define MAX_VEL_COMPONENT 50.0
void body_c::NextMovement(edict_t &monster, vec3_t curDist, float scale)
{
vec3_t forward,right,up,tempdist,distaccum;
int i;
AngleVectors(monster.s.angles,forward,right,up);
VectorScale(monster.intend_velocity, FRAMETIME, curDist);
// VectorCopy(monster.intend_velocity, curDist);
for (i = 0; i < 3; i++)
{
if (curDist[i]>MAX_VEL_COMPONENT)
{
// gi.dprintf("warning! capping monster %d vel component to %f (from %f)!\n", i, MAX_VEL_COMPONENT, curDist[i]);
curDist[i]=MAX_VEL_COMPONENT;
}
if (curDist[i]<-MAX_VEL_COMPONENT)
{
// gi.dprintf("warning! capping monster %d vel component to -%f (from %f)!\n", i, MAX_VEL_COMPONENT, curDist[i]);
curDist[i]=-MAX_VEL_COMPONENT;
}
}
//plop in default vel, too--fixme, shouldn't be here
VectorScale(forward, curDist[0]*scale, tempdist);
VectorCopy(tempdist,distaccum);
VectorScale(right, curDist[1]*scale, tempdist);
VectorAdd(distaccum,tempdist,distaccum);
VectorScale(up, curDist[2]*scale, tempdist);
VectorAdd(distaccum,tempdist,curDist);
}
#define TURNDIST_MIN 64
#define TURNDIST_MAX 192
void body_c::NextTurn(float scale, edict_t &monster, float default_turn, turninfo_s &turninfo)
{
// return scale * default_turn + VectorLength(monster.intend_avelocity)*FRAMETIME;
ai_c *the_ai=NULL;
if (monster.ai)
{
the_ai = (ai_c*)((ai_public_c*)monster.ai);
}
if (!currentmove || !the_ai)
{
turninfo.max_turn = /*scale * */default_turn /*+ VectorLength(monster.intend_avelocity)*FRAMETIME*/;
turninfo.min_turn = /*scale * */-default_turn /*+ VectorLength(monster.intend_avelocity)*FRAMETIME*/;
}
else
{
float scale = 1.0;
if(the_ai->getTarget()&&monster.health>0)
{
vec3_t dif;
vec3_t targ_pos;
the_ai->getTargetPos(targ_pos);
VectorSubtract(monster.s.origin, targ_pos, dif);
float dist = VectorLength(dif);
if(the_ai->getTarget()->client)
{ // turn faster the closer a player is
if(dist < TURNDIST_MIN)
{
scale = 2.0;
}
else if(dist > TURNDIST_MAX)
{
scale = 1.0;
}
else
{ // the internal value runs from 0 up close to 1 far away
scale = 2.0 - (dist - TURNDIST_MIN)/(TURNDIST_MAX - TURNDIST_MIN);
}
scale *= the_ai->GetMySkills()->getTurnSpeed();//different guys turn at different speeds
}
else
{ // turn faster the closer the guy is - more severe than for regular player turning
if(dist < TURNDIST_MIN)
{
scale = 3.0;
}
else if(dist > TURNDIST_MAX)
{
scale = 1.0;
}
else
{ // the internal value runs from 0 up close to 1 far away
scale = 3.0 - (dist - TURNDIST_MIN)/(TURNDIST_MAX - TURNDIST_MIN) * 2.0;
}
}
}
VectorScale(monster.intend_avelocity,0.1,monster.intend_avelocity);
VectorRadiansToDegrees(monster.intend_avelocity, monster.intend_avelocity);
turninfo.max_turn = currentmove->maxTurn + monster.intend_avelocity[YAW] * scale;
turninfo.min_turn = currentmove->minTurn + monster.intend_avelocity[YAW] * scale;
// if (monster.intend_avelocity[YAW] > 1.0 && ai_pathtest->value)
// {
// gi.dprintf("screwy turn value: %3.3f degrees!\n", monster.intend_avelocity[YAW]*RADTODEG);
// }
VectorClear(monster.intend_avelocity);
}
}
int body_c::NextFlags(edict_t &monster)
{
int returnflags=frame_flags;
frame_flags=0;
return returnflags;
}
void body_c::Emote(edict_t &monster, emotion_index new_emotion, float emotion_duration, qboolean scripted_emoting)
{
//allow overriding of current emotion if: 1) new is higher priority, or 2) current emotion is about to expire anyway
//--also, make sure I don't interrupt scripted emotions with non-scripted emotions.
// if (((new_emotion<emotion) && (emotion_expire-level.time>0.25) && (!scripted_emoting || emotion==EMOTION_DEAD))
// || (emotionScripted && !scripted_emoting && new_emotion!=EMOTION_DEAD))
if (/*((emotion_expire-level.time>0.25) && (!scripted_emoting || emotion==EMOTION_DEAD))
|| */(emotionScripted && !scripted_emoting && new_emotion!=EMOTION_DEAD) ||
(!scripted_emoting&&(emotion==EMOTION_DEAD||emotion==EMOTION_PAIN)&&(new_emotion!=EMOTION_DEAD)))
{
return;
}
emotion=new_emotion;
emotion_expire = level.time+emotion_duration;
emotionScripted=scripted_emoting;
}
emotion_index body_c::GetMood(edict_t &monster)
{
return emotion;
}
//fixme: this could probably be better done with an array of ghoulid's, generated upon face setting
void body_c::UpdateFace(edict_t &monster)
{
if (level.time > emotion_expire)
{
emotion=EMOTION_NORMAL;
emotionScripted=false;
}
}
qboolean body_c::ApplySkin(edict_t &monster, ggOinstC *myInstance, char *matName, char *skinName)
{
//fixme? go off myInstance, or get rid of it?
return monster.ghoulInst->SetFrameOverride(matName, skinName);
}
//yurk, fixme: these arguments are trash! they're worse than delaware!
qboolean body_c::AddRandomBoltedItem(float percent_chance, edict_t &monster, char *myBolt, char *itemClass, char *itemSeq, char *itemBolt, ggOinstC *myInstance, char *itemSkinName, float scale)
{
if ((gi.flrand(0.0, 1.0) < percent_chance)||(ai_loadAllBolts))
{
if(ai_loadAllBolts)
{
AddBoltedItem(monster, myBolt, itemClass, itemSeq, itemBolt, myInstance, itemSkinName, scale);
return false;
}
return (NULL != AddBoltedItem(monster, myBolt, itemClass, itemSeq, itemBolt, myInstance, itemSkinName, scale));
}
return false;
}
//yurk, fixme: these arguments are trash! they're worse than delaware!
ggBinstC *body_c::AddBoltedItem(edict_t &monster, GhoulID mybolt, ggObjC *gun, char *itemSeq, GhoulID itemBolt, ggOinstC *myInstance, char *itemSkinName, float scale)
{
GhoulID gunSeq=0;
if (gun && itemBolt)
{
if (itemSkinName && itemSkinName[0])
{
gun->RegisterSkin(itemSkinName);
}
gun->RegistrationLock();
gunSeq=game_ghoul.FindObjectSequence(gun,itemSeq);
if (gunSeq)
{
//fixme? just call this off myInstance? for more flexibility, a game_ghoul func could be made that goes off the edict
return game_ghoul.AddBoltInstance(myInstance, mybolt, gun, itemBolt, gunSeq, itemSkinName, scale);
}
else
{
return game_ghoul.AddBoltInstance(myInstance, mybolt, gun, itemBolt, itemSkinName, scale);
}
}
return NULL;
}
//yurk, fixme: these arguments are trash! they're worse than delaware!
ggBinstC *body_c::AddBoltedItem(edict_t &monster, GhoulID mybolt, char *itemClass, char *itemSeq, GhoulID itemBolt, ggOinstC *myInstance, char *itemSkinName, float scale)
{
ggObjC *gun=NULL;
char accessoryName[GGHOUL_ID_NAME_LEN];
GhoulID gunSeq=0;
// sprintf(accessoryName,"simple%s",itemSeq);
strcpy(accessoryName,itemSeq);
gun=game_ghoul.FindObject(itemClass,accessoryName,false,itemSkinName);
if (gun && itemBolt)
{
if (itemSkinName && itemSkinName[0])
{
gun->RegisterSkin(itemSkinName);
}
gun->RegistrationLock();
gunSeq=game_ghoul.FindObjectSequence(gun,itemSeq);
if (gunSeq)
{
//fixme? just call this off myInstance? for more flexibility, a game_ghoul func could be made that goes off the edict
return game_ghoul.AddBoltInstance(myInstance, mybolt, gun, itemBolt, gunSeq, itemSkinName, scale);
}
else
{
return game_ghoul.AddBoltInstance(myInstance, mybolt, gun, itemBolt, itemSkinName, scale);
}
}
return NULL;
}
//yurk, fixme: these arguments are trash! they're worse than delaware!
ggBinstC *body_c::AddBoltedItem(edict_t &monster, GhoulID mybolt, char *itemClass, char *itemSeq, char *itemBolt, ggOinstC *myInstance, char *itemSkinName, float scale)
{
ggObjC *gun=NULL;
GhoulID gunBolt=0;
char accessoryName[GGHOUL_ID_NAME_LEN];
GhoulID gunSeq=0;
// sprintf(accessoryName,"simple%s",itemSeq);
strcpy(accessoryName,itemSeq);
gun=game_ghoul.FindObject(itemClass,accessoryName,false,itemSkinName);
if (gun)
{
if (itemSkinName && itemSkinName[0])
{
gun->RegisterSkin(itemSkinName);
}
gun->RegistrationLock();
gunSeq=game_ghoul.FindObjectSequence(gun,itemSeq);
if (gunSeq)
{
gunBolt=gun->GetMyObject()->FindPart(itemBolt);
if (gunBolt)
{
//fixme? just call this off myInstance? for more flexibility, a game_ghoul func could be made that goes off the edict
return game_ghoul.AddBoltInstance(myInstance, mybolt, gun, gunBolt, gunSeq, itemSkinName, scale);
}
}
else
{
gunBolt=gun->GetMyObject()->FindPart(itemBolt);
if (gunBolt)
{
return game_ghoul.AddBoltInstance(myInstance, mybolt, gun, gunBolt, itemSkinName, scale);
}
}
}
return NULL;
}
//yurk, fixme: these arguments are trash! they're worse than delaware!
ggBinstC *body_c::AddBoltedItem(edict_t &monster, char *myBolt, char *itemClass, char *itemSeq, char *itemBolt, ggOinstC *myInstance, char *itemSkinName, float scale)
{
if (monster.ghoulInst&&monster.ghoulInst->GetGhoulObject())
{
GhoulID tbolt=monster.ghoulInst->GetGhoulObject()->FindPart(myBolt);
if(tbolt)
{
return AddBoltedItem(monster, tbolt, itemClass, itemSeq, itemBolt, myInstance, itemSkinName, scale);
}
}
return NULL;
}
//this is 'cording to design doc, hope that's ok
float body_c::FireInterval(attacks_e weaponType, int curAmmo)
{
//just pickin' values that feel good in the game now
switch(weaponType)
{
case ATK_PISTOL2:
//return gi.flrand(1.125F, 1.375F);
//return 4.0/9.0;
return 1.5;
case ATK_PISTOL1:
//return gi.flrand(0.75F, 1.25F);
// return gi.flrand(0.075F, 0.125F);
//return 5.0/18.0;
return .8;
case ATK_SNIPER:
//return gi.flrand(0.9F, 1.1F);
return 2.5;
case ATK_MACHINEGUN:
//return gi.flrand(0.054F, 0.066F);
return gi.flrand(0.5F, 1.0F); // this is essentially the pause between bursts //.01;
case ATK_KNIFE:
return gi.flrand(1.35F, 1.65F);
case ATK_FLAMEGUN:
//return gi.flrand(0.75, 1.25F);
//return gi.flrand(0.0774F, 0.0946F);
return .01;
// return gi.flrand(0.0225F, 0.0275F);
case ATK_ASSAULTRIFLE:
//return gi.flrand(0.0774F, 0.0946F);
return gi.flrand(.1F, .5F); // little bursts for SMG fire.01;
case ATK_MACHINEPISTOL:
//return gi.flrand(0.045F, 0.055F);
return .01;
case ATK_AUTOSHOTGUN:
//this will cause it to fire in 3 shot bursts - not accurate for the weapon, but cool for enemies
if((curAmmo%3) == 1)
{
return 2.0;
}
else
{
return .6;
}
return 1.0;//projectiles, so these need to be dodged for funness
case ATK_SHOTGUN:
//return gi.flrand(2.25F, 2.75F);
//return 5.0/8.0;
//return 4.0;
return 4.0;
case ATK_ROCKET:
//return gi.flrand(2, 3);
return 4.0;
case ATK_MICROWAVE:
case ATK_MICROWAVE_ALT:
return 6.0;
case ATK_DEKKER:
//return gi.flrand(6.25F, 8.75F);
return 2.5;
case ATK_NOTHING:
return 0;
}
return 0;
}
weapons_t body_c::GetWeaponType(ggBinstC *weapon)
{
if (!weapon)
{
return SFW_EMPTYSLOT;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_pistol2"))
{
return SFW_PISTOL2;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_pistol1"))
{
return SFW_PISTOL1;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_sniperrifle"))
{
return SFW_SNIPER;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_machinegun"))
{
return SFW_MACHINEGUN;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_knife"))
{
return SFW_KNIFE;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_bat"))
{
return SFW_KNIFE;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_flamethrower"))
{
return SFW_FLAMEGUN;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_assault_rifle"))
{
return SFW_ASSAULTRIFLE;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_machinepistol"))
{
return SFW_MACHINEPISTOL;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_autoshotgun"))
{
return SFW_AUTOSHOTGUN;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_kantana"))
{
return SFW_KNIFE;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_shotgun"))
{
return SFW_SHOTGUN;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_rocket"))
{
return SFW_ROCKET;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_mpg"))
{
return SFW_MICROWAVEPULSE;
}
return SFW_EMPTYSLOT;
}
attacks_e body_c::GetWeaponAttackType(ggBinstC *weapon)
{
if (!weapon)
{
return ATK_NOTHING;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_pistol2"))
{
return ATK_PISTOL2;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_pistol1"))
{
return ATK_PISTOL1;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_sniperrifle"))
{
return ATK_SNIPER;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_machinegun"))
{
return ATK_MACHINEGUN;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_knife"))
{
return ATK_KNIFE;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_bat"))
{
return ATK_KNIFE;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_flamethrower"))
{
return ATK_FLAMEGUN;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_assault_rifle"))
{
return ATK_ASSAULTRIFLE;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_machinepistol"))
{
return ATK_MACHINEPISTOL;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_autoshotgun"))
{
return ATK_AUTOSHOTGUN;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_kantana"))
{
return ATK_KNIFE;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_shotgun"))
{
return ATK_SHOTGUN;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_rocket"))
{
return ATK_ROCKET;
}
if (!stricmp(weapon->GetBolteeObject()->GetSubName(),"w_mpg"))
{
return ATK_MICROWAVE;
}
return ATK_NOTHING;
}
qboolean body_c::RightHandWeaponReady(edict_t &monster)
{
return false;
}
void body_c::FireRightHandWeapon(edict_t &monster, bool isLethal, int NullTarget, qboolean forceFireRate)
{
}
body_c::body_c(body_c *orig)
{
*(int *)&currentmove = GetMmoveNum(orig->currentmove);
*(int *)&owner = GetEdictNum(orig->owner);
currentMoveFinished = orig->currentMoveFinished;
holdCode = orig->holdCode;
frame_flags = orig->frame_flags;
emotion_expire = orig->emotion_expire;
emotion = orig->emotion;
emotionScripted = orig->emotionScripted;
rLastFireTime = orig->rLastFireTime;
lLastFireTime = orig->lLastFireTime;
InitialKilledTime = orig->InitialKilledTime;
LastKilledTime = orig->LastKilledTime;
lastStartTime = orig->lastStartTime;
LastDFlags = orig->LastDFlags;
ForceNextAnim = orig->ForceNextAnim;
TrainDeath = orig->TrainDeath;
}
void body_c::Evaluate(body_c *orig)
{
currentmove = GetMmovePtr((int)orig->currentmove);
owner = GetEdictPtr((int)orig->owner);
currentMoveFinished = orig->currentMoveFinished;
holdCode = orig->holdCode;
frame_flags = orig->frame_flags;
emotion_expire = orig->emotion_expire;
emotion = orig->emotion;
emotionScripted = orig->emotionScripted;
rLastFireTime = orig->rLastFireTime;
lLastFireTime = orig->lLastFireTime;
InitialKilledTime = orig->InitialKilledTime;
LastKilledTime = orig->LastKilledTime;
lastStartTime = orig->lastStartTime;
LastDFlags = orig->LastDFlags;
ForceNextAnim = orig->ForceNextAnim;
TrainDeath = orig->TrainDeath;
}
// ----------------------------------------------------------
/// Someone please put this in the right header
void EntToWorldMatrix(vec3_t org, vec3_t angles, Matrix4 &m);
extern Vect3 trStartKnarlyGilHack;
body_heli::body_heli(edict_t* self)
{
m_gunBarrelInst = NULL;
m_gunBarrelNULLInst = NULL;
m_gunnerHeadInst = NULL;
m_pilotHeadInst = NULL;
m_cockpitInst = NULL;
m_mainRotorInst = NULL;
m_tailRotorInst = NULL;
m_fuselageObj = NULL;
m_gunBarrelObj = NULL;
m_mainRotorObj = NULL;
m_tailRotorObj = NULL;
m_gunBolt = 0;
m_gunNULLBolt = 0;
m_gunnerHeadBolt = 0;
m_pilotHeadBolt = 0;
m_mainRotorBoltInst = NULL;
m_tailRotorBoltInst = NULL;
VectorClear(m_vGunBoltOffset);
VectorClear(m_vGunnerBoltOffset);
VectorClear(m_vPilotBoltOffset);
m_bPilotDead = m_bGunnerDead = false;
VectorClear(m_v3HeliDesiredPos_Start);
VectorClear(m_v3HeliDesiredPos_Stop);
VectorClear(m_v3HeliDesiredPos_Halfway);
VectorClear(m_v3HeliPadRestPos);
VectorClear(m_v3HeliFireAtCoords);
VectorClear(m_v3HeliDesiredPos_Smart);
VectorClear(m_v3HeliHeadStop);
VectorClear(m_v3HeliTraceMins);
VectorClear(m_v3HeliTraceMaxs);
m_fHeliTurnStartYaw = 0;
m_fHeliMoveSpeed = 0;
m_fHeliFireAtTimer = 0;
m_fHeliFirePitch = 0;
m_fHeliHoverTimer = 0;
m_iHeliHealth = 100;
m_fRotorSoundTimer = 0;
m_fLastDamageTime = 0.0f;
m_fHeliRocketFireDebounceTime = 0;
m_iHeliRocketsLeft = 8;
m_nRandGunWait = 0;
m_bGunFiring = false;
m_bLOS = false;
m_bRotorEOS = false;
m_bFuselageEOS = false;
// variables passed into script actions
m_target = NULL;
VectorClear(m_vPos);
m_fArg = 0.0f;
m_flags = 0;
m_iLastEntFoundIndex=0;
m_fLastGetEntTime=0;
m_AITarget=NULL;
m_fNewTargetTimer=0;
m_bFiring = 0;
owner = NULL;
m_iHeliAITable = -1;
m_iHeliAIIndex = 0;
m_fSideStrafeDist = 0.0f;
m_iSideStrafeDir = 0.0f;
m_iWhichRandVolume = 0;
VectorClear(m_v3HeliFireAtCoords_Original);
m_fHeliAITimer = 0;
m_nCanopyHealth = 1; // 1 shot to kill the pilot with the sniper rifle
m_bCanBeSniped = true;
}
//do all inst-related stuff here, rather than in constructor: ghoulInst won't exist when body gets alloc'd
void body_heli::SetHeliEdict(edict_t *self)
{
if (NULL == self || NULL == self->ghoulInst)
{
return;
}
m_heli = self;
GetHeliInst()->TurnCallBacksOn(level.time);
}
int body_heli::ShowDamage(edict_t &monster, edict_t *inflictor,
edict_t *attacker, vec3_t dir, vec3_t point, vec3_t origin,
int damage, int knockback, int dflags, int mod, float penetrate, float absorb)
{
// vec3_t bodyloc;//, footorigin;
// gz_info *frontHole=NULL;
// gz_code frontCode;
// gz_info *backHole=NULL;
// gz_code backCode;
// vec3_t bloodDir;
// vec3_t forward, right, up;
// vec3_t to_impact;
// float forward_back, right_left, up_down;
// kef ggOinstC *myInstance=game_ghoul.FindOInst(monster.ghoulInst);
// ggOinstC *blownInst;
int i;
int take;
// float curGoreDist,bestGoreDist,tdist;
// vec3_t EntryPos,ExitPos,tvec;
Vect3 zonePos;
Matrix4 ZoneMatrix;
Matrix4 ToEnt,ToWorld;
EntToWorldMatrix(monster.s.origin,monster.s.angles,ToWorld);
//but we want to put the ray into ent space, need inverse
ToEnt.Inverse(ToWorld);
Vect3 EntDir,EntStart;
VectorCopy(origin,*((vec3_t*)&trStartKnarlyGilHack));
ToEnt.XFormPoint(EntStart,trStartKnarlyGilHack);
ToEnt.XFormVect(EntDir,*(Vect3 *)dir);
take = 0;
Vect3 vNormal, vImpact, vSparksNormal, vSparksPos;
typedef enum
{
hit_NONE = 0,
hit_COCKPIT,
hit_PILOT,
hit_GUNNER,
hit_FUSELAGE
} heliHit_e;
heliHit_e whatDidIHit = hit_NONE;
// kef int numHits=myInstance->GetInstPtr()->RayTrace(level.time,EntStart,EntDir,Hits,20);
// bullet_numHits = monster.ghoulInst->RayTrace(level.time,EntStart,EntDir,bullet_Hits,20);
//NO HITS!!!
if (bullet_numHits == 0)
{
return 0;
}
qboolean bSniper = false;
if (attacker && attacker->client && attacker->client->inv)
{
if ( (SFW_SNIPER == attacker->client->inv->getCurWeaponType()) &&
(IsSnipeable()) )
{
bSniper = true;
}
}
//get entrance wound
for (i=0;i<bullet_numHits;i++)
{
//hit my instance
/*
if (bullet_Hits[i].Inst==monster.ghoulInst)
{
//hit something
take = damage;
vNormal.Set(bullet_Hits[i].Normal[0], bullet_Hits[i].Normal[1], bullet_Hits[i].Normal[2]);
vImpact.Set(bullet_Hits[i].ContactPoint[0], bullet_Hits[i].ContactPoint[1], bullet_Hits[i].ContactPoint[2]);
break;
}
//hit a bolt-on
else
*/
{ // if we hit the pilot or gunner with the sniper rifle, deal with that first.
//otherwise, if we hit the cockpit assess the damage normally. if we hit anywhere
//else, do 1/10 the damage (I'm totally making up that fraction)
if (bullet_Hits[i].Inst == GetPilotInst())
{ // we don't want to keep looping through the hits if we get here
whatDidIHit = hit_PILOT;
break; // this break is intentional
}
else if (bullet_Hits[i].Inst == GetGunnerInst())
{ // we don't want to keep looping through the hits if we get here
whatDidIHit = hit_GUNNER;
break; // this break is intentional
}
else if (bullet_Hits[i].Inst == GetCockpitInst())
{ // if we get here assess the damage normally
whatDidIHit = hit_COCKPIT;
}
else if (whatDidIHit != hit_COCKPIT)
{ // do 1/10 the damage
whatDidIHit = hit_FUSELAGE;
}
}
}
switch(whatDidIHit)
{
case hit_PILOT:
if (bSniper)
{
if ( 0 >= --m_nCanopyHealth )
{
// capped the pilot
m_bPilotDead = true;
break;
}
}
take = damage;
break;
case hit_GUNNER:
if (bSniper)
{
if ( 0 >= --m_nCanopyHealth )
{
// capped the gunner
m_bGunnerDead = true;
break;
}
}
take = damage;
break;
case hit_COCKPIT:
take = damage;
break;
case hit_FUSELAGE:
take = (int)(0.1 * damage);
break;
}
if (0 == take)
{
take = damage;
vNormal.Set(bullet_Hits[i].Normal[0], bullet_Hits[i].Normal[1], bullet_Hits[i].Normal[2]);
vImpact.Set(bullet_Hits[i].ContactPoint[0], bullet_Hits[i].ContactPoint[1], bullet_Hits[i].ContactPoint[2]);
}
if (take && (level.time - m_fLastDamageTime) > 0.3f && !bSniper)
{
// make sparks at point of impact
ToWorld.XFormPoint(vSparksPos, vImpact);
ToWorld.XFormVect(vSparksNormal, vNormal);
FX_MakeSparks(*(vec3_t*)&vSparksPos, *(vec3_t*)&vNormal, gi.irand(0,2));
// ricochet sounds are already precached in cacheweaponcommon()
gi.sound(&monster, CHAN_ENT1, gi.soundindex(va("Weapons/FX/Rics/ric%d.wav", gi.irand(1,6))), .9, ATTN_NORM, 0);
// gi.sound (self, CHAN_ENT1, gi.soundindex("Impact/Surfs/Metal1.wav"), .6, ATTN_NORM, 0);
m_fLastDamageTime = level.time;
}
return take;
}
void body_heli::UpdateSmoke(edict_t* self, float fHealthPercentage)
{
IGhoulObj *obj = GetHeliInst()->GetGhoulObject();
GhoulID smoke1 = (obj?obj->FindPart("smoke_engine"):NULL);
GhoulID smoke2 = (obj?obj->FindPart("smoke_tail"):NULL);
if (NULL == smoke1 || NULL == smoke2)
{
return;
}
if (fHealthPercentage < 0)
{ // turn off the smoke
fxRunner.stopContinualEffect("environ/helismoke", self, smoke1);
fxRunner.stopContinualEffect("environ/helismoke", self, smoke2);
}
else if (fHealthPercentage < 0.25f)
{
fxRunner.editContinualEffect("environ/helismoke", self, smoke1, 2);
fxRunner.editContinualEffect("environ/helismoke", self, smoke2, 1);
}
else if (fHealthPercentage < 0.5f)
{
fxRunner.editContinualEffect("environ/helismoke", self, smoke1, 1.5);
fxRunner.editContinualEffect("environ/helismoke", self, smoke2, .75);
}
else if (fHealthPercentage < 0.75f)
{
if (!fxRunner.hasEffect("environ/helismoke", self, smoke1))
{
// prepare smoke effects for damage
smoke2 = obj->FindPart("smoke_tail");
fxRunner.execContinualEffect("environ/helismoke", self, smoke1);
fxRunner.execContinualEffect("environ/helismoke", self, smoke2);
}
fxRunner.editContinualEffect("environ/helismoke", self, smoke1, 1);
fxRunner.editContinualEffect("environ/helismoke", self, smoke2, 0.5);
}
}
void body_heli::ChangeCockpitSkin(int nSkin)
{
IGhoulObj* cockpitObj = GetCockpitInst()->GetGhoulObject();
if (NULL == cockpitObj)
{
return;
}
GhoulID matID = cockpitObj->FindMaterial("canopy");
GhoulID skinID = 0;
switch(nSkin)
{
case HC_SKIN_GUNNER:
{
skinID = cockpitObj->FindSkin(matID, "glass_blood_fnt");
break;
}
case HC_SKIN_PILOT:
{
skinID = cockpitObj->FindSkin(matID, "glass_blood_bk");
break;
}
case HC_SKIN_BOTH:
{
skinID = cockpitObj->FindSkin(matID, "glass_bloody");
break;
}
default:
{
break;
}
}
GetCockpitInst()->SetFrameOverride(matID, skinID);
}
void body_heli::SetFiring(bool bFiring)
{
if (m_bFiring = bFiring)
{
// turn on firing sequence
if (GetBarrelInst())
{
SimpleModelSetSequence2(GetBarrelInst(), "barrel", SMSEQ_LOOP);
}
}
else
{
// turn off firing sequence
if (GetBarrelInst())
{
SimpleModelSetSequence2(GetBarrelInst(), "barrel_still", SMSEQ_HOLD);
}
}
}
body_heli::body_heli(body_heli *orig)
: body_c(orig)
{
*(int *)&m_heli = GetEdictNum(orig->m_heli);
m_gunBarrelInst.MakeIndex(orig->m_gunBarrelInst);
m_gunBarrelNULLInst.MakeIndex(orig->m_gunBarrelNULLInst);
m_gunnerHeadInst.MakeIndex(orig->m_gunnerHeadInst);
m_pilotHeadInst.MakeIndex(orig->m_pilotHeadInst);
m_cockpitInst.MakeIndex(orig->m_cockpitInst);
m_mainRotorInst.MakeIndex(orig->m_mainRotorInst);
m_tailRotorInst.MakeIndex(orig->m_tailRotorInst);
m_fuselageObj.MakeIndex(orig->m_fuselageObj);
m_gunBarrelObj.MakeIndex(orig->m_gunBarrelObj);
m_mainRotorObj.MakeIndex(orig->m_mainRotorObj);
m_tailRotorObj.MakeIndex(orig->m_tailRotorObj);
m_gunBolt = orig->m_gunBolt;
m_gunNULLBolt = orig->m_gunNULLBolt;
m_gunnerHeadBolt = orig->m_gunnerHeadBolt;
m_pilotHeadBolt = orig->m_pilotHeadBolt;
m_mainRotorBoltInst.MakeIndex(orig->m_mainRotorBoltInst);
m_tailRotorBoltInst.MakeIndex(orig->m_tailRotorBoltInst);
VectorCopy(orig->m_vGunBoltOffset, m_vGunBoltOffset);
VectorCopy(orig->m_vGunnerBoltOffset, m_vGunnerBoltOffset);
VectorCopy(orig->m_vPilotBoltOffset, m_vPilotBoltOffset);
m_nCanopyHealth = orig->m_nCanopyHealth;
m_bPilotDead = orig->m_bPilotDead;
m_bGunnerDead = orig->m_bGunnerDead;
m_bFiring = orig->m_bFiring;
VectorCopy(orig->m_v3HeliDesiredPos_Start,m_v3HeliDesiredPos_Start);
VectorCopy(orig->m_v3HeliDesiredPos_Stop,m_v3HeliDesiredPos_Stop);
VectorCopy(orig->m_v3HeliDesiredPos_Halfway,m_v3HeliDesiredPos_Halfway);
VectorCopy(orig->m_v3HeliPadRestPos, m_v3HeliPadRestPos);
VectorCopy(orig->m_v3HeliFireAtCoords, m_v3HeliFireAtCoords);
VectorCopy(orig->m_v3HeliDesiredPos_Smart, m_v3HeliDesiredPos_Smart);
VectorCopy(orig->m_v3HeliHeadStop, m_v3HeliHeadStop);
VectorCopy(orig->m_v3HeliTraceMins, m_v3HeliTraceMins);
VectorCopy(orig->m_v3HeliTraceMaxs, m_v3HeliTraceMaxs);
VectorCopy(orig->m_v3HeliDeathDest, m_v3HeliDeathDest);
m_fHeliTurnStartYaw = orig->m_fHeliTurnStartYaw;
m_fHeliMoveSpeed = orig->m_fHeliMoveSpeed;
m_fHeliFireAtTimer = orig->m_fHeliFireAtTimer;
m_fHeliFirePitch = orig->m_fHeliFirePitch;
m_fHeliHoverTimer = orig->m_fHeliHoverTimer;
m_iHeliHealth = orig->m_iHeliHealth;
m_fRotorSoundTimer = orig->m_fRotorSoundTimer;
VectorCopy(orig->m_v3HeliFireAtCoords_Original, m_v3HeliFireAtCoords_Original);
m_fHeliAITimer = orig->m_fHeliAITimer;
m_iHeliAITable = orig->m_iHeliAITable;
m_iHeliAIIndex = orig->m_iHeliAIIndex;
m_fLastDamageTime = orig->m_fLastDamageTime;
m_fHeliRocketFireDebounceTime = orig->m_fHeliRocketFireDebounceTime;
m_iHeliRocketsLeft = orig->m_iHeliRocketsLeft;
m_nRandGunWait = orig->m_nRandGunWait;
m_bGunFiring = orig->m_bGunFiring;
m_bLOS = orig->m_bLOS;
m_bRotorEOS = orig->m_bRotorEOS;
m_bFuselageEOS = orig->m_bFuselageEOS;
*(int *)&m_target = GetEdictNum(orig->m_target);
VectorCopy(orig->m_vPos, m_vPos);
m_fArg = orig->m_fArg;
m_flags = orig->m_flags;
m_iLastEntFoundIndex = orig->m_iLastEntFoundIndex;
m_fLastGetEntTime = orig->m_fLastGetEntTime;
*(int *)&m_AITarget = GetEdictNum(orig->m_AITarget);
m_fNewTargetTimer = orig->m_fNewTargetTimer;
m_fSideStrafeDist = orig->m_fSideStrafeDist;
m_iSideStrafeDir = orig->m_iSideStrafeDir;
m_iWhichRandVolume = orig->m_iWhichRandVolume;
m_fAIAggressiveness = orig->m_fAIAggressiveness;
m_bCanBeSniped = orig->m_bCanBeSniped;
}
void body_heli::Evaluate(body_heli *orig)
{
m_heli = GetEdictPtr((int)orig->m_heli);
m_gunBarrelInst.MakePtr(*(int *)&orig->m_gunBarrelInst);
m_gunBarrelNULLInst.MakePtr(*(int *)&orig->m_gunBarrelNULLInst);
m_gunnerHeadInst.MakePtr(*(int *)&orig->m_gunnerHeadInst);
m_pilotHeadInst.MakePtr(*(int *)&orig->m_pilotHeadInst);
m_cockpitInst.MakePtr(*(int *)&orig->m_cockpitInst);
m_mainRotorInst.MakePtr(*(int *)&orig->m_mainRotorInst);
m_tailRotorInst.MakePtr(*(int *)&orig->m_tailRotorInst);
m_fuselageObj.MakePtr(*(int *)&orig->m_fuselageObj);
m_gunBarrelObj.MakePtr(*(int *)&orig->m_gunBarrelObj);
m_mainRotorObj.MakePtr(*(int *)&orig->m_mainRotorObj);
m_tailRotorObj.MakePtr(*(int *)&orig->m_tailRotorObj);
m_gunBolt = orig->m_gunBolt;
m_gunNULLBolt = orig->m_gunNULLBolt;
m_gunnerHeadBolt = orig->m_gunnerHeadBolt;
m_pilotHeadBolt = orig->m_pilotHeadBolt;
m_mainRotorBoltInst.MakePtr(*(int *)&orig->m_mainRotorBoltInst);
m_tailRotorBoltInst.MakePtr(*(int *)&orig->m_tailRotorBoltInst);
VectorCopy(orig->m_vGunBoltOffset, m_vGunBoltOffset);
VectorCopy(orig->m_vGunnerBoltOffset, m_vGunnerBoltOffset);
VectorCopy(orig->m_vPilotBoltOffset, m_vPilotBoltOffset);
m_nCanopyHealth = orig->m_nCanopyHealth;
m_bPilotDead = orig->m_bPilotDead;
m_bGunnerDead = orig->m_bGunnerDead;
m_bFiring = orig->m_bFiring;
VectorCopy(orig->m_v3HeliDesiredPos_Start,m_v3HeliDesiredPos_Start);
VectorCopy(orig->m_v3HeliDesiredPos_Stop,m_v3HeliDesiredPos_Stop);
VectorCopy(orig->m_v3HeliDesiredPos_Halfway,m_v3HeliDesiredPos_Halfway);
VectorCopy(orig->m_v3HeliPadRestPos, m_v3HeliPadRestPos);
VectorCopy(orig->m_v3HeliFireAtCoords, m_v3HeliFireAtCoords);
VectorCopy(orig->m_v3HeliDesiredPos_Smart, m_v3HeliDesiredPos_Smart);
VectorCopy(orig->m_v3HeliHeadStop, m_v3HeliHeadStop);
VectorCopy(orig->m_v3HeliTraceMins, m_v3HeliTraceMins);
VectorCopy(orig->m_v3HeliTraceMaxs, m_v3HeliTraceMaxs);
VectorCopy(orig->m_v3HeliDeathDest, m_v3HeliDeathDest);
m_fHeliTurnStartYaw = orig->m_fHeliTurnStartYaw;
m_fHeliMoveSpeed = orig->m_fHeliMoveSpeed;
m_fHeliFireAtTimer = orig->m_fHeliFireAtTimer;
m_fHeliFirePitch = orig->m_fHeliFirePitch;
m_fHeliHoverTimer = orig->m_fHeliHoverTimer;
m_iHeliHealth = orig->m_iHeliHealth;
m_fRotorSoundTimer = orig->m_fRotorSoundTimer;
VectorCopy(orig->m_v3HeliFireAtCoords_Original, m_v3HeliFireAtCoords_Original);
m_fHeliAITimer = orig->m_fHeliAITimer;
m_iHeliAITable = orig->m_iHeliAITable;
m_iHeliAIIndex = orig->m_iHeliAIIndex;
m_fLastDamageTime = orig->m_fLastDamageTime;
m_fHeliRocketFireDebounceTime = orig->m_fHeliRocketFireDebounceTime;
m_iHeliRocketsLeft = orig->m_iHeliRocketsLeft;
m_nRandGunWait = orig->m_nRandGunWait;
m_bGunFiring = orig->m_bGunFiring;
m_bLOS = orig->m_bLOS;
m_bRotorEOS = orig->m_bRotorEOS;
m_bFuselageEOS = orig->m_bFuselageEOS;
m_target = GetEdictPtr((int)orig->m_target);
VectorCopy(orig->m_vPos, m_vPos);
m_fArg = orig->m_fArg;
m_flags = orig->m_flags;
m_iLastEntFoundIndex = orig->m_iLastEntFoundIndex;
m_fLastGetEntTime = orig->m_fLastGetEntTime;
m_AITarget = GetEdictPtr((int)orig->m_AITarget);
m_fNewTargetTimer = orig->m_fNewTargetTimer;
m_fSideStrafeDist = orig->m_fSideStrafeDist;
m_iSideStrafeDir = orig->m_iSideStrafeDir;
m_iWhichRandVolume = orig->m_iWhichRandVolume;
m_fAIAggressiveness = orig->m_fAIAggressiveness;
m_bCanBeSniped = orig->m_bCanBeSniped;
body_c::Evaluate(orig);
}
void body_heli::Read()
{
char loaded[sizeof(body_heli)];
gi.ReadFromSavegame('HELI', loaded, sizeof(body_heli));
Evaluate((body_heli *)loaded);
}
void body_heli::Write()
{
body_heli *savable;
savable = new body_heli(this);
gi.AppendToSavegame('HELI', savable, sizeof(*this));
delete savable;
}
/////////////////////////////////////////////////////////////////////
// //
// tank stuff //
// //
/////////////////////////////////////////////////////////////////////
body_tank::body_tank(edict_t* self)
{
m_target = NULL;
VectorClear(m_vPos);
m_fArg = 0.0f;
m_tank = NULL; // the tank body is the root object (hopefully)
m_turretInst = NULL; // the turret
m_cannonInst = NULL; // the barrel of the main gun
m_machGunInst = NULL; // the machine gun on the turret
m_machGunNullInst = NULL; // goes between machine gun and the turret
m_tankObj = NULL;
m_turretObj = NULL;
m_turretBolt = NULL_GhoulID;
m_cannonBolt = NULL_GhoulID;
m_machGunBolt = NULL_GhoulID;
m_machGunNullBolt = NULL_GhoulID;
m_turretBoltInst = NULL;
m_bDeactivated = false;
m_bTreads = false;
m_bTreadFire = false;
m_fLastDamageTime = 0.0f;
m_fMaxYawSpeed = 20.0f; // degrees
m_fMaxFwdSpeed = 50.0f;
m_fTurretSpeed = 0.04f; // radians
m_fCannonSpeed = 0.03f; // radians
m_fMachGunSpeed = 0.05f; // radians
m_fShellFlightDist = 0.0f; // world units
m_nMachGunHealth = 150;
VectorClear(m_vGroundZero);
}
//do all inst-related stuff here, rather than in constructor: ghoulInst won't exist when body gets alloc'd
void body_tank::SetTankEdict(edict_t *self)
{
if (NULL == self || NULL == self->ghoulInst)
{
return;
}
m_tank = self;
GetTankInst()->TurnCallBacksOn(level.time);
}
body_tank::body_tank(body_tank *orig)
: body_c(orig)
{
*(int *)&m_target = GetEdictNum(orig->m_target);
VectorCopy(orig->m_vPos, m_vPos);
m_fArg = orig->m_fArg;
*(int *)&m_tank = GetEdictNum(orig->m_tank);
m_turretInst.MakeIndex(orig->m_turretInst);
m_cannonInst.MakeIndex(orig->m_cannonInst);
m_machGunInst.MakeIndex(orig->m_machGunInst);
m_machGunNullInst.MakeIndex(orig->m_machGunNullInst);
m_tankObj.MakeIndex(orig->m_tankObj);
m_turretObj.MakeIndex(orig->m_turretObj);
m_turretBolt = orig->m_turretBolt;
m_cannonBolt = orig->m_cannonBolt;
m_machGunBolt = orig->m_machGunBolt;
m_machGunNullBolt = orig->m_machGunNullBolt;
m_turretBoltInst.MakeIndex(orig->m_turretBoltInst);
m_bDeactivated = orig->m_bDeactivated;
m_bTreads = orig->m_bTreads;
m_bTreadFire = orig->m_bTreadFire;
m_fLastDamageTime = orig->m_fLastDamageTime;
m_fMaxYawSpeed = orig->m_fMaxYawSpeed;
m_fMaxFwdSpeed = orig->m_fMaxFwdSpeed;
m_fTurretSpeed = orig->m_fTurretSpeed;
m_fCannonSpeed = orig->m_fCannonSpeed;
m_fMachGunSpeed = orig->m_fMachGunSpeed;
m_fShellFlightDist = orig->m_fShellFlightDist;
m_nMachGunHealth = orig->m_nMachGunHealth;
VectorCopy(orig->m_vGroundZero, m_vGroundZero);
}
void body_tank::Evaluate(body_tank *orig)
{
m_target = GetEdictPtr((int)orig->m_target);
VectorCopy(orig->m_vPos, m_vPos);
m_fArg = orig->m_fArg;
m_tank = GetEdictPtr((int)orig->m_tank);
m_turretInst.MakePtr(*(int *)&orig->m_turretInst);
m_cannonInst.MakePtr(*(int *)&orig->m_cannonInst);
m_machGunInst.MakePtr(*(int *)&orig->m_machGunInst);
m_machGunNullInst.MakePtr(*(int *)&orig->m_machGunNullInst);
m_tankObj.MakePtr(*(int *)&orig->m_tankObj);
m_turretObj.MakePtr(*(int *)&orig->m_turretObj);
m_turretBolt = orig->m_turretBolt;
m_cannonBolt = orig->m_cannonBolt;
m_machGunBolt = orig->m_machGunBolt;
m_machGunNullBolt = orig->m_machGunNullBolt;
m_turretBoltInst.MakePtr(*(int *)&orig->m_turretBoltInst);
m_bDeactivated = orig->m_bDeactivated;
m_bTreads = orig->m_bTreads;
m_bTreadFire = orig->m_bTreadFire;
m_fLastDamageTime = orig->m_fLastDamageTime;
m_fMaxYawSpeed = orig->m_fMaxYawSpeed;
m_fMaxFwdSpeed = orig->m_fMaxFwdSpeed;
m_fTurretSpeed = orig->m_fTurretSpeed;
m_fCannonSpeed = orig->m_fCannonSpeed;
m_fMachGunSpeed = orig->m_fMachGunSpeed;
m_fShellFlightDist = orig->m_fShellFlightDist;
m_nMachGunHealth = orig->m_nMachGunHealth;
VectorCopy(orig->m_vGroundZero, m_vGroundZero);
body_c::Evaluate(orig);
}
void body_tank::Read()
{
char loaded[sizeof(body_tank)];
gi.ReadFromSavegame('TANK', loaded, sizeof(body_tank));
Evaluate((body_tank *)loaded);
}
void body_tank::Write()
{
body_tank *savable;
savable = new body_tank(this);
gi.AppendToSavegame('TANK', savable, sizeof(*this));
delete savable;
}
void body_tank::SetTreads(bool bOn)
{
if (m_bTreads = bOn)
{ // set the treads in motion
SimpleModelSetSequence2(GetTankInst(), "tank", SMSEQ_LOOP);
}
else
{ // stop the treads
SimpleModelSetSequence2(GetTankInst(), "tank", SMSEQ_HOLD);
}
}
int body_tank::ShowDamage(edict_t &monster, edict_t *inflictor,
edict_t *attacker, vec3_t dir, vec3_t point, vec3_t origin,
int damage, int knockback, int dflags, int mod, float penetrate, float absorb)
{
int i;
int take;
Vect3 zonePos;
Matrix4 ZoneMatrix;
Matrix4 ToEnt,ToWorld;
EntToWorldMatrix(monster.s.origin,monster.s.angles,ToWorld);
//but we want to put the ray into ent space, need inverse
ToEnt.Inverse(ToWorld);
Vect3 EntDir,EntStart;
VectorCopy(origin,*((vec3_t*)&trStartKnarlyGilHack));
ToEnt.XFormPoint(EntStart,trStartKnarlyGilHack);
ToEnt.XFormVect(EntDir,*(Vect3 *)dir);
take = 0;
Vect3 vNormal, vImpact, vSparksNormal, vSparksPos;
//NO HITS!!!
if (bullet_numHits == 0)
{
return 0;
}
qboolean bSniper = false;
if (attacker && attacker->client && attacker->client->inv)
{
// knives can't hurt tanks, silly.
if (SFW_KNIFE == attacker->client->inv->getCurWeaponType())
{
return 0;
}
if (SFW_SNIPER == attacker->client->inv->getCurWeaponType())
{
bSniper = true;
}
}
//get entrance wound
for (i=0;i<bullet_numHits;i++)
{
//hit my instance
if (bullet_Hits[i].Inst==monster.ghoulInst)
{
//hit something
if (true /*bullet_Hits[i].Mesh*/)
{
take = damage;
vNormal.Set(bullet_Hits[i].Normal[0], bullet_Hits[i].Normal[1], bullet_Hits[i].Normal[2]);
vImpact.Set(bullet_Hits[i].ContactPoint[0], bullet_Hits[i].ContactPoint[1], bullet_Hits[i].ContactPoint[2]);
break;
}
}
//hit a bolt-on
else
{
if (bullet_Hits[i].Inst == GetMachGunInst() && m_nMachGunHealth > 0)
{
m_nMachGunHealth -= damage;
if (m_nMachGunHealth <= 0)
{
m_nMachGunHealth = 0;
// killed the machine gun -- attach a smoke effect
IGhoulObj *obj = bullet_Hits[i].Inst->GetGhoulObject();
if (obj)
{
fxRunner.execWithInst("environ/machgun_smoke2", GetTankEdict(),
bullet_Hits[i].Inst, obj->FindPart("to_bolt_machine_gun"));
}
}
}
if (0 == take)
{
take = damage;
vNormal.Set(bullet_Hits[i].Normal[0], bullet_Hits[i].Normal[1], bullet_Hits[i].Normal[2]);
vImpact.Set(bullet_Hits[i].ContactPoint[0], bullet_Hits[i].ContactPoint[1], bullet_Hits[i].ContactPoint[2]);
}
}
}
if (take && (level.time - m_fLastDamageTime) > 0.3f && !bSniper)
{
// make sparks at point of impact
ToWorld.XFormPoint(vSparksPos, vImpact);
ToWorld.XFormVect(vSparksNormal, vNormal);
FX_MakeSparks(*(vec3_t*)&vSparksPos, *(vec3_t*)&vNormal, gi.irand(0,2));
m_fLastDamageTime = level.time;
}
return take;
}
void body_tank::UpdateSmoke(edict_t* self, float fHealthPercentage)
{
// presumably we'll eventually want a visible indication of damage (like the heli)
}
/////////////////////////////////////////////////////////////////////
// //
// snowcat stuff //
// //
/////////////////////////////////////////////////////////////////////
body_snowcat::body_snowcat(edict_t* self)
:body_tank(self)
{
m_machGun2Inst = NULL; // the second machine gun on the turret
m_leftHeadLightInst = NULL;
m_rightHeadLightInst = NULL;
m_machGun2Bolt = NULL_GhoulID;
m_leftHeadLightBolt = NULL_GhoulID;
m_rightHeadLightBolt = NULL_GhoulID;
m_fMaxYawSpeed = 30.0f; // degrees
m_fMaxFwdSpeed = 70.0f;
m_fTurretSpeed = 0.06f+(game.playerSkills.getEnemyValue()*.01); // radians
m_fCannonSpeed = 0.00f; // radians (no cannon for the snowcat)
m_fMachGunSpeed = 0.00f; // radians (no separate mach gun for the snowcat)
m_fShellFlightDist = 0.0f; // world units
m_nMachGunHealth = 150;
}
body_snowcat::body_snowcat(body_snowcat *orig)
: body_tank(orig)
{
m_machGun2Inst.MakeIndex(orig->m_machGun2Inst);
m_leftHeadLightInst.MakeIndex(orig->m_leftHeadLightInst);
m_rightHeadLightInst.MakeIndex(orig->m_rightHeadLightInst);
m_machGun2Bolt = orig->m_machGun2Bolt;
m_leftHeadLightBolt = orig->m_leftHeadLightBolt;
m_rightHeadLightBolt = orig->m_rightHeadLightBolt;
}
void body_snowcat::SetTreads(bool bOn)
{
GhoulID cBolterBolt=0;
IGhoulObj *boltObj = NULL;
if (m_bTreads = bOn)
{ // set the treads in motion
SimpleModelSetSequence2(GetTankInst(), "body", SMSEQ_LOOP);
// turn on snowspray effect
if ( m_tank->ghoulInst && (boltObj = m_tank->ghoulInst->GetGhoulObject()) )
{
// left
if (cBolterBolt = boltObj->FindPart("snow_effect_left"))
{
fxRunner.execContinualEffect("environ/snowspray", m_tank, cBolterBolt);
}
// right
if (cBolterBolt = boltObj->FindPart("snow_effect_right"))
{
fxRunner.execContinualEffect("environ/snowspray", m_tank, cBolterBolt);
}
}
}
else
{ // stop the treads
SimpleModelSetSequence2(GetTankInst(), "body", SMSEQ_HOLD);
// turn off snowspray effect
if ( m_tank->ghoulInst && (boltObj = m_tank->ghoulInst->GetGhoulObject()) )
{
// left
if (cBolterBolt = boltObj->FindPart("snow_effect_left"))
{
fxRunner.stopContinualEffect("environ/snowspray", m_tank, cBolterBolt);
}
// right
if (cBolterBolt = boltObj->FindPart("snow_effect_right"))
{
fxRunner.stopContinualEffect("environ/snowspray", m_tank, cBolterBolt);
}
}
}
}
void body_snowcat::Evaluate(body_snowcat *orig)
{
m_machGun2Inst.MakePtr(*(int *)&orig->m_machGun2Inst);
m_leftHeadLightInst.MakePtr(*(int *)&orig->m_leftHeadLightInst);
m_rightHeadLightInst.MakePtr(*(int *)&orig->m_rightHeadLightInst);
m_machGun2Bolt = orig->m_machGun2Bolt;
m_leftHeadLightBolt = orig->m_leftHeadLightBolt;
m_rightHeadLightBolt = orig->m_rightHeadLightBolt;
// gotta have this at the end of body_snowcat::Evaluate() cuz it calls
//body_c::Evaluate()
body_tank::Evaluate(orig);
}
void body_snowcat::Read()
{
char loaded[sizeof(body_snowcat)];
gi.ReadFromSavegame('SCAT', loaded, sizeof(body_snowcat));
Evaluate((body_snowcat *)loaded);
}
void body_snowcat::Write()
{
body_snowcat *savable;
savable = new body_snowcat(this);
gi.AppendToSavegame('SCAT', savable, sizeof(*this));
delete savable;
}
void body_snowcat::UpdateSmoke(edict_t* self, float fHealthPercentage)
{
// presumably we'll eventually want a visible indication of damage (like the heli)
}
void body_snowcat::Deactivate(bool bDeactivate)
{
GhoulID cBolterBolt=0;
IGhoulObj *boltObj = NULL;
if (m_bDeactivated = bDeactivate)
{ // we're deactive so turn off headlights and exhaust
// turn off beams
SimpleModelTurnOnOff(GetLeftHeadLightInst(), false);
SimpleModelTurnOnOff(GetRightHeadLightInst(), false);
// headlights
SimpleModelRemoveObject(m_tank, "front_left");
SimpleModelRemoveObject(m_tank, "front_right");
// taillights
SimpleModelRemoveObject(m_tank, "rear_left");
SimpleModelRemoveObject(m_tank, "rear_right");
// exhaust effects
if (m_tank->ghoulInst && (boltObj = m_tank->ghoulInst->GetGhoulObject()) )
{
// left
if (cBolterBolt = boltObj->FindPart("exhuast_left")) // love Joe's spelling of exhaust
{
fxRunner.stopContinualEffect("environ/kf_exhaust01", m_tank, cBolterBolt);
}
// right
if (cBolterBolt = boltObj->FindPart("exhuast_right")) // love Joe's spelling of exhaust
{
fxRunner.stopContinualEffect("environ/kf_exhaust01", m_tank, cBolterBolt);
}
}
}
else
{ // we're live so turn on headlights and exhaust
// turn on beams
SimpleModelTurnOnOff(GetLeftHeadLightInst(), true);
SimpleModelTurnOnOff(GetRightHeadLightInst(), true);
// headlights
SimpleModelAddObject(m_tank, "front_left");
SimpleModelAddObject(m_tank, "front_right");
// taillights
SimpleModelAddObject(m_tank, "rear_left");
SimpleModelAddObject(m_tank, "rear_right");
// exhaust effects
if (m_tank->ghoulInst && (boltObj = m_tank->ghoulInst->GetGhoulObject()) )
{
// left
if (cBolterBolt = boltObj->FindPart("exhuast_left")) // love Joe's spelling of exhaust
{
fxRunner.execContinualEffect("environ/kf_exhaust01", m_tank, cBolterBolt, 0);
}
// right
if (cBolterBolt = boltObj->FindPart("exhuast_right")) // love Joe's spelling of exhaust
{
fxRunner.execContinualEffect("environ/kf_exhaust01", m_tank, cBolterBolt, 0);
}
}
}
}