1933 lines
55 KiB
C++
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 *)¤tmove = 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);
|
|
}
|
|
}
|
|
}
|
|
}
|