mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-15 00:41:21 +00:00
2348 lines
62 KiB
C
2348 lines
62 KiB
C
|
// m_actor.c
|
||
|
//
|
||
|
// Lazarus 1.4: Adopted Mappack misc_actor code
|
||
|
//
|
||
|
#include "g_local.h"
|
||
|
#include "m_actor.h"
|
||
|
#include "pak.h"
|
||
|
|
||
|
static char wavname[NUM_ACTOR_SOUNDS][32] =
|
||
|
{ "jump1.wav",
|
||
|
"pain25_1.wav",
|
||
|
"pain25_2.wav",
|
||
|
"pain50_1.wav",
|
||
|
"pain50_2.wav",
|
||
|
"pain75_1.wav",
|
||
|
"pain75_2.wav",
|
||
|
"pain100_1.wav",
|
||
|
"pain100_2.wav",
|
||
|
"death1.wav",
|
||
|
"death2.wav",
|
||
|
"death3.wav",
|
||
|
"death4.wav"
|
||
|
};
|
||
|
|
||
|
#define ACTOR_SOUND_JUMP 0 // Do NOT change this one
|
||
|
#define ACTOR_SOUND_PAIN_25_1 1
|
||
|
#define ACTOR_SOUND_PAIN_25_2 2
|
||
|
#define ACTOR_SOUND_PAIN_50_1 3
|
||
|
#define ACTOR_SOUND_PAIN_50_2 4
|
||
|
#define ACTOR_SOUND_PAIN_75_1 5
|
||
|
#define ACTOR_SOUND_PAIN_75_2 6
|
||
|
#define ACTOR_SOUND_PAIN_100_1 7
|
||
|
#define ACTOR_SOUND_PAIN_100_2 8
|
||
|
#define ACTOR_SOUND_DEATH1 9
|
||
|
#define ACTOR_SOUND_DEATH2 10
|
||
|
#define ACTOR_SOUND_DEATH3 11
|
||
|
#define ACTOR_SOUND_DEATH4 12
|
||
|
|
||
|
mframe_t actor_frames_stand [] =
|
||
|
{
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL
|
||
|
};
|
||
|
|
||
|
mmove_t actor_move_stand = {FRAME_stand01, FRAME_stand40, actor_frames_stand, NULL};
|
||
|
|
||
|
void actor_stand (edict_t *self)
|
||
|
{
|
||
|
self->s.sound = 0;
|
||
|
if(self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
self->monsterinfo.currentmove = &actor_move_crouch;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_stand;
|
||
|
|
||
|
// randomize on startup
|
||
|
if (level.time < 1.0)
|
||
|
self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
|
||
|
}
|
||
|
|
||
|
mframe_t actor_frames_walk [] =
|
||
|
{
|
||
|
/* ai_walk, 4, NULL,
|
||
|
ai_walk, 15, NULL,
|
||
|
ai_walk, 15, NULL,
|
||
|
ai_walk, 8, NULL,
|
||
|
ai_walk, 20, NULL,
|
||
|
ai_walk, 15, NULL,
|
||
|
ai_walk, 8, NULL,
|
||
|
ai_walk, 17, NULL,
|
||
|
ai_walk, 12, NULL,
|
||
|
ai_walk, -2, NULL,
|
||
|
ai_walk, -2, NULL,
|
||
|
ai_walk, -1, NULL */
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL
|
||
|
};
|
||
|
mmove_t actor_move_walk = {FRAME_run1, FRAME_run6, actor_frames_walk, NULL};
|
||
|
|
||
|
mmove_t actor_move_run;
|
||
|
void actor_walk (edict_t *self)
|
||
|
{
|
||
|
// prevent foolishness:
|
||
|
if (self->monsterinfo.aiflags & AI_FOLLOW_LEADER)
|
||
|
{
|
||
|
if(!self->movetarget || !self->movetarget->inuse || (self->movetarget == world))
|
||
|
self->movetarget = self->monsterinfo.leader;
|
||
|
}
|
||
|
|
||
|
if( (self->monsterinfo.aiflags & AI_FOLLOW_LEADER) &&
|
||
|
(self->movetarget) &&
|
||
|
(self->movetarget->inuse) &&
|
||
|
(self->movetarget->health > 0) )
|
||
|
{
|
||
|
float R;
|
||
|
|
||
|
R = realrange(self,self->movetarget);
|
||
|
if (R > ACTOR_FOLLOW_RUN_RANGE || self->enemy)
|
||
|
{
|
||
|
self->monsterinfo.currentmove = &actor_move_run;
|
||
|
if(self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
{
|
||
|
self->monsterinfo.aiflags &= ~AI_CROUCH;
|
||
|
self->maxs[2] += 28;
|
||
|
self->viewheight += 28;
|
||
|
self->move_origin[2] += 28;
|
||
|
}
|
||
|
}
|
||
|
else if (R <= ACTOR_FOLLOW_STAND_RANGE && self->movetarget->client)
|
||
|
{
|
||
|
self->monsterinfo.pausetime = level.time + 0.5;
|
||
|
if(self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
self->monsterinfo.currentmove = &actor_move_crouch;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_stand;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
self->monsterinfo.currentmove = &actor_move_crouchwalk;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_walk;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
self->monsterinfo.currentmove = &actor_move_crouchwalk;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_walk;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mframe_t actor_frames_walk_back [] =
|
||
|
{
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL
|
||
|
};
|
||
|
mmove_t actor_move_walk_back = {FRAME_run1, FRAME_run6, actor_frames_walk_back, NULL};
|
||
|
|
||
|
mframe_t actor_frames_crouchwalk_back [] =
|
||
|
{
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL,
|
||
|
ai_walk, -10, NULL
|
||
|
};
|
||
|
mmove_t actor_move_crouchwalk_back = {FRAME_crwalk1, FRAME_crwalk6, actor_frames_crouchwalk_back, NULL};
|
||
|
|
||
|
void actor_walk_back (edict_t *self)
|
||
|
{
|
||
|
// prevent foolishness:
|
||
|
if (self->monsterinfo.aiflags & AI_FOLLOW_LEADER)
|
||
|
{
|
||
|
if(!self->movetarget || !self->movetarget->inuse || (self->movetarget == world))
|
||
|
self->movetarget = self->monsterinfo.leader;
|
||
|
}
|
||
|
|
||
|
if( (self->monsterinfo.aiflags & AI_FOLLOW_LEADER) &&
|
||
|
(self->movetarget) &&
|
||
|
(self->movetarget->inuse) &&
|
||
|
(self->movetarget->health > 0) )
|
||
|
{
|
||
|
float R;
|
||
|
|
||
|
R = realrange(self,self->movetarget);
|
||
|
if(R <= ACTOR_FOLLOW_STAND_RANGE && self->movetarget->client)
|
||
|
{
|
||
|
self->monsterinfo.pausetime = level.time + 0.5;
|
||
|
if(self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
self->monsterinfo.currentmove = &actor_move_crouch;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_stand;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
self->monsterinfo.currentmove = &actor_move_crouchwalk_back;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_walk_back;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
self->monsterinfo.currentmove = &actor_move_crouchwalk_back;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_walk_back;
|
||
|
}
|
||
|
}
|
||
|
mframe_t actor_frames_crouch [] =
|
||
|
{
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL,
|
||
|
ai_stand, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_crouch = {FRAME_crstnd01, FRAME_crstnd19, actor_frames_crouch, NULL};
|
||
|
|
||
|
mframe_t actor_frames_crouchwalk [] =
|
||
|
{
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL,
|
||
|
ai_walk, 10, NULL
|
||
|
};
|
||
|
mmove_t actor_move_crouchwalk = {FRAME_crwalk1, FRAME_crwalk6, actor_frames_crouchwalk, NULL};
|
||
|
|
||
|
// DWH: Changed running speed to a constant (equal to player running speed) for normal
|
||
|
// misc_actor and 2/3 that for "bad guys". Also eliminated excess frames.
|
||
|
mframe_t actor_frames_run [] =
|
||
|
{
|
||
|
ai_run, 40, NULL,
|
||
|
ai_run, 40, NULL,
|
||
|
ai_run, 40, NULL,
|
||
|
ai_run, 40, NULL,
|
||
|
ai_run, 40, NULL,
|
||
|
ai_run, 40, NULL
|
||
|
};
|
||
|
mmove_t actor_move_run = {FRAME_run1, FRAME_run6, actor_frames_run, NULL};
|
||
|
|
||
|
mframe_t actor_frames_run_bad [] =
|
||
|
{
|
||
|
ai_run, 30, NULL,
|
||
|
ai_run, 30, NULL,
|
||
|
ai_run, 30, NULL,
|
||
|
ai_run, 30, NULL,
|
||
|
ai_run, 30, NULL,
|
||
|
ai_run, 30, NULL
|
||
|
};
|
||
|
|
||
|
mmove_t actor_move_run_bad = {FRAME_run1, FRAME_run6, actor_frames_run_bad, NULL};
|
||
|
|
||
|
void actor_run (edict_t *self)
|
||
|
{
|
||
|
// prevent foolishness
|
||
|
if (self->monsterinfo.aiflags & AI_FOLLOW_LEADER)
|
||
|
{
|
||
|
if(!self->movetarget || !self->movetarget->inuse || (self->movetarget == world))
|
||
|
self->movetarget = self->monsterinfo.leader;
|
||
|
}
|
||
|
if ((level.time < self->pain_debounce_time) && (!self->enemy))
|
||
|
{
|
||
|
if (self->movetarget)
|
||
|
actor_walk(self);
|
||
|
else
|
||
|
actor_stand(self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( self->monsterinfo.aiflags & AI_STAND_GROUND )
|
||
|
{
|
||
|
actor_stand(self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
{
|
||
|
self->monsterinfo.aiflags &= ~AI_CROUCH;
|
||
|
self->maxs[2] += 28;
|
||
|
self->viewheight += 28;
|
||
|
self->move_origin[2] += 28;
|
||
|
}
|
||
|
|
||
|
if( self->monsterinfo.aiflags & AI_GOOD_GUY ) {
|
||
|
self->monsterinfo.currentmove = &actor_move_run;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->monsterinfo.currentmove = &actor_move_run_bad;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mframe_t actor_frames_run_back [] =
|
||
|
{
|
||
|
ai_run, -40, NULL,
|
||
|
ai_run, -40, NULL,
|
||
|
ai_run, -40, NULL,
|
||
|
ai_run, -40, NULL,
|
||
|
ai_run, -40, NULL,
|
||
|
ai_run, -40, NULL
|
||
|
};
|
||
|
mmove_t actor_move_run_back = {FRAME_run1, FRAME_run6, actor_frames_run_back, NULL};
|
||
|
|
||
|
void actor_run_back (edict_t *self)
|
||
|
{
|
||
|
// prevent foolishness:
|
||
|
if (self->monsterinfo.aiflags & AI_FOLLOW_LEADER)
|
||
|
{
|
||
|
if(!self->movetarget || !self->movetarget->inuse || (self->movetarget == world))
|
||
|
self->movetarget = self->monsterinfo.leader;
|
||
|
}
|
||
|
if ((level.time < self->pain_debounce_time) && (!self->enemy))
|
||
|
{
|
||
|
if (self->movetarget)
|
||
|
actor_walk_back(self);
|
||
|
else
|
||
|
actor_stand(self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( self->monsterinfo.aiflags & AI_STAND_GROUND )
|
||
|
{
|
||
|
actor_stand(self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( self->monsterinfo.aiflags & AI_CROUCH)
|
||
|
{
|
||
|
self->monsterinfo.aiflags &= ~AI_CROUCH;
|
||
|
self->maxs[2] += 28;
|
||
|
self->viewheight += 28;
|
||
|
self->move_origin[2] += 28;
|
||
|
}
|
||
|
|
||
|
self->monsterinfo.currentmove = &actor_move_run_back;
|
||
|
}
|
||
|
mframe_t actor_frames_pain1 [] =
|
||
|
{
|
||
|
ai_move, -5, NULL,
|
||
|
ai_move, 4, NULL,
|
||
|
ai_move, 1, NULL,
|
||
|
ai_move, 1, NULL
|
||
|
};
|
||
|
mmove_t actor_move_pain1 = {FRAME_pain101, FRAME_pain104, actor_frames_pain1, actor_run};
|
||
|
|
||
|
mframe_t actor_frames_pain2 [] =
|
||
|
{
|
||
|
ai_move, -4, NULL,
|
||
|
ai_move, 4, NULL,
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_pain2 = {FRAME_pain201, FRAME_pain204, actor_frames_pain2, actor_run};
|
||
|
|
||
|
mframe_t actor_frames_pain3 [] =
|
||
|
{
|
||
|
ai_move, -1, NULL,
|
||
|
ai_move, 1, NULL,
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 1, NULL
|
||
|
};
|
||
|
mmove_t actor_move_pain3 = {FRAME_pain301, FRAME_pain304, actor_frames_pain3, actor_run};
|
||
|
|
||
|
mframe_t actor_frames_flipoff [] =
|
||
|
{
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_flipoff = {FRAME_flip01, FRAME_flip12, actor_frames_flipoff, actor_run};
|
||
|
|
||
|
mframe_t actor_frames_taunt [] =
|
||
|
{
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_taunt = {FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run};
|
||
|
|
||
|
void actor_ideal_range(edict_t *self)
|
||
|
{
|
||
|
int weapon;
|
||
|
|
||
|
weapon = self->actor_weapon[self->actor_current_weapon];
|
||
|
|
||
|
switch(weapon)
|
||
|
{
|
||
|
case 2:
|
||
|
self->monsterinfo.ideal_range[0] = 0;
|
||
|
self->monsterinfo.ideal_range[1] = 270;
|
||
|
break;
|
||
|
case 3:
|
||
|
self->monsterinfo.ideal_range[0] = 0;
|
||
|
self->monsterinfo.ideal_range[1] = 90;
|
||
|
break;
|
||
|
case 4:
|
||
|
case 5:
|
||
|
self->monsterinfo.ideal_range[0] = 0;
|
||
|
self->monsterinfo.ideal_range[1] = 450;
|
||
|
break;
|
||
|
case 6:
|
||
|
self->monsterinfo.ideal_range[0] = 200;
|
||
|
self->monsterinfo.ideal_range[1] = 450;
|
||
|
break;
|
||
|
case 7:
|
||
|
self->monsterinfo.ideal_range[0] = 300;
|
||
|
self->monsterinfo.ideal_range[1] = 1000;
|
||
|
break;
|
||
|
case 8:
|
||
|
self->monsterinfo.ideal_range[0] = 200;
|
||
|
self->monsterinfo.ideal_range[1] = 500;
|
||
|
break;
|
||
|
case 9:
|
||
|
case 10:
|
||
|
self->monsterinfo.ideal_range[0] = 300;
|
||
|
self->monsterinfo.ideal_range[1] = 1000;
|
||
|
break;
|
||
|
// Knightmare- added mission pack weapon support
|
||
|
case 11:
|
||
|
self->monsterinfo.ideal_range[0] = 0;
|
||
|
self->monsterinfo.ideal_range[1] = 600;
|
||
|
break;
|
||
|
case 12:
|
||
|
self->monsterinfo.ideal_range[0] = 300;
|
||
|
self->monsterinfo.ideal_range[1] = 1000;
|
||
|
break;
|
||
|
case 13:
|
||
|
self->monsterinfo.ideal_range[0] = 20;
|
||
|
self->monsterinfo.ideal_range[1] = 1000;
|
||
|
break;
|
||
|
case 14:
|
||
|
self->monsterinfo.ideal_range[0] = 0;
|
||
|
self->monsterinfo.ideal_range[1] = 1500;
|
||
|
break;
|
||
|
case 15:
|
||
|
self->monsterinfo.ideal_range[0] = 0;
|
||
|
self->monsterinfo.ideal_range[1] = 1000;
|
||
|
break;
|
||
|
// end Knightmare
|
||
|
default:
|
||
|
self->monsterinfo.ideal_range[0] = 0;
|
||
|
self->monsterinfo.ideal_range[1] = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void actor_attack(edict_t *self);
|
||
|
void actor_switch (edict_t *self)
|
||
|
{
|
||
|
self->actor_current_weapon = 1 - self->actor_current_weapon;
|
||
|
self->s.modelindex2 = self->actor_model_index[self->actor_current_weapon];
|
||
|
actor_ideal_range(self);
|
||
|
gi.linkentity(self);
|
||
|
}
|
||
|
mframe_t actor_frames_switch [] =
|
||
|
{
|
||
|
ai_run, 0, actor_switch,
|
||
|
ai_run, 0, NULL,
|
||
|
ai_run, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_switch = {FRAME_jump4, FRAME_jump6, actor_frames_switch, actor_attack};
|
||
|
|
||
|
void actor_pain (edict_t *self, edict_t *other, float kick, int damage)
|
||
|
{
|
||
|
int n;
|
||
|
int r, l;
|
||
|
|
||
|
// DWH: Players don't have pain skins!
|
||
|
// if (self->health < (self->max_health / 2))
|
||
|
// self->s.skinnum = 1;
|
||
|
|
||
|
// Stop weapon sound, if any
|
||
|
self->s.sound = 0;
|
||
|
|
||
|
if (level.time < self->pain_debounce_time)
|
||
|
return;
|
||
|
|
||
|
self->pain_debounce_time = level.time + 1;
|
||
|
|
||
|
// DWH: Use same scheme used for player pain sounds
|
||
|
if (!(self->flags & FL_GODMODE))
|
||
|
{
|
||
|
r = (rand()&1);
|
||
|
if (self->health < 25)
|
||
|
l = 0;
|
||
|
else if (self->health < 50)
|
||
|
l = 2;
|
||
|
else if (self->health < 75)
|
||
|
l = 4;
|
||
|
else
|
||
|
l = 6;
|
||
|
gi.sound (self, CHAN_VOICE, self->actor_sound_index[ACTOR_SOUND_PAIN_25_1 + l + r],
|
||
|
1, ATTN_NORM, 0);
|
||
|
}
|
||
|
|
||
|
// Lazarus: Removed printed message, but keep taunt (not for monster actors, though)
|
||
|
if ((other->client) && (random() < 0.4) && (self->monsterinfo.aiflags & AI_GOOD_GUY))
|
||
|
{
|
||
|
vec3_t v;
|
||
|
VectorSubtract (other->s.origin, self->s.origin, v);
|
||
|
self->ideal_yaw = vectoyaw (v);
|
||
|
if (random() < 0.5) {
|
||
|
self->monsterinfo.currentmove = &actor_move_flipoff;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->monsterinfo.currentmove = &actor_move_taunt;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
n = rand() % 3;
|
||
|
if (n == 0)
|
||
|
self->monsterinfo.currentmove = &actor_move_pain1;
|
||
|
else if (n == 1)
|
||
|
self->monsterinfo.currentmove = &actor_move_pain2;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_pain3;
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Attack code moved to m_actor_weap.c
|
||
|
//
|
||
|
|
||
|
void actor_dead (edict_t *self)
|
||
|
{
|
||
|
VectorSet (self->mins, -16, -16, -24);
|
||
|
VectorSet (self->maxs, 16, 16, -8);
|
||
|
self->movetype = MOVETYPE_TOSS;
|
||
|
self->svflags |= SVF_DEADMONSTER;
|
||
|
self->nextthink = 0;
|
||
|
gi.linkentity (self);
|
||
|
M_FlyCheck (self);
|
||
|
|
||
|
// Lazarus monster fade
|
||
|
if(world->effects & FX_WORLDSPAWN_CORPSEFADE)
|
||
|
{
|
||
|
self->think=FadeDieSink;
|
||
|
self->nextthink=level.time+corpse_fadetime->value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mframe_t actor_frames_death1 [] =
|
||
|
{
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, -13, NULL,
|
||
|
ai_move, 14, NULL,
|
||
|
ai_move, 3, NULL,
|
||
|
ai_move, -2, NULL
|
||
|
};
|
||
|
mmove_t actor_move_death1 = {FRAME_death101, FRAME_death106, actor_frames_death1, actor_dead};
|
||
|
|
||
|
mframe_t actor_frames_death2 [] =
|
||
|
{
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 7, NULL,
|
||
|
ai_move, -6, NULL,
|
||
|
ai_move, -5, NULL,
|
||
|
ai_move, 1, NULL,
|
||
|
ai_move, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_death2 = {FRAME_death201, FRAME_death206, actor_frames_death2, actor_dead};
|
||
|
|
||
|
void actor_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
||
|
{
|
||
|
int n;
|
||
|
|
||
|
//Remove the weapon model and turn off weapon sound, if any
|
||
|
self->s.modelindex2 = 0;
|
||
|
self->s.sound = 0;
|
||
|
|
||
|
// check for gib
|
||
|
if (self->health <= self->gib_health && !(self->spawnflags & SF_MONSTER_NOGIB))
|
||
|
{
|
||
|
gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
|
||
|
for (n= 0; n < 2; n++)
|
||
|
ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
|
||
|
for (n= 0; n < 4; n++)
|
||
|
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
|
||
|
ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
|
||
|
self->deadflag = DEAD_DEAD;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (self->deadflag == DEAD_DEAD)
|
||
|
return;
|
||
|
|
||
|
// Knightmare- hack to prevent falling through floor
|
||
|
self->s.origin[2] += 1;
|
||
|
|
||
|
// regular death
|
||
|
gi.sound (self, CHAN_VOICE,
|
||
|
self->actor_sound_index[ACTOR_SOUND_DEATH1 + (rand()%4)], 1, ATTN_NORM, 0);
|
||
|
self->deadflag = DEAD_DEAD;
|
||
|
self->takedamage = DAMAGE_YES;
|
||
|
|
||
|
if(self->monsterinfo.aiflags & AI_CHASE_THING)
|
||
|
{
|
||
|
if(self->movetarget && !Q_stricmp(self->movetarget->classname,"thing"))
|
||
|
{
|
||
|
G_FreeEdict(self->movetarget);
|
||
|
self->movetarget = NULL;
|
||
|
}
|
||
|
}
|
||
|
self->monsterinfo.aiflags &= ~(AI_FOLLOW_LEADER | AI_CHASE_THING | AI_CHICKEN | AI_EVADE_GRENADE);
|
||
|
if (random() > 0.5)
|
||
|
self->monsterinfo.currentmove = &actor_move_death1;
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_death2;
|
||
|
}
|
||
|
|
||
|
void actor_fire (edict_t *self)
|
||
|
{
|
||
|
int weapon;
|
||
|
|
||
|
weapon = self->actor_weapon[self->actor_current_weapon];
|
||
|
|
||
|
switch(weapon) {
|
||
|
case 1:
|
||
|
actorBlaster (self);
|
||
|
break;
|
||
|
case 2:
|
||
|
actorShotgun (self);
|
||
|
break;
|
||
|
case 3:
|
||
|
actorSuperShotgun (self);
|
||
|
break;
|
||
|
case 4:
|
||
|
actorMachineGun (self);
|
||
|
if (level.time >= self->monsterinfo.pausetime)
|
||
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||
|
else
|
||
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
||
|
break;
|
||
|
case 5:
|
||
|
actorChaingun (self);
|
||
|
if (level.time >= self->monsterinfo.pausetime)
|
||
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||
|
else
|
||
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
||
|
break;
|
||
|
case 6:
|
||
|
actorGrenadeLauncher (self);
|
||
|
break;
|
||
|
case 7:
|
||
|
actorRocket(self);
|
||
|
break;
|
||
|
case 8:
|
||
|
actorHyperblaster(self);
|
||
|
if (level.time >= self->monsterinfo.pausetime)
|
||
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||
|
else
|
||
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
||
|
break;
|
||
|
case 9:
|
||
|
actorRailGun(self);
|
||
|
break;
|
||
|
case 10:
|
||
|
actorBFG(self);
|
||
|
if (level.time >= self->monsterinfo.pausetime)
|
||
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||
|
else
|
||
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
||
|
break;
|
||
|
case 11:
|
||
|
actorIonripper(self);
|
||
|
break;
|
||
|
case 12:
|
||
|
actorPhalanx(self);
|
||
|
if (level.time >= self->monsterinfo.pausetime)
|
||
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||
|
else
|
||
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
||
|
break;
|
||
|
case 13:
|
||
|
actorETF_Rifle(self);
|
||
|
if (level.time >= self->monsterinfo.pausetime)
|
||
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||
|
else
|
||
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
||
|
break;
|
||
|
case 14:
|
||
|
actorPlasmaBeam(self);
|
||
|
if (level.time >= self->monsterinfo.pausetime)
|
||
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
||
|
else
|
||
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
||
|
break;
|
||
|
case 15:
|
||
|
actorDisintegrator(self);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void actor_no_weapon_sound(edict_t *self)
|
||
|
{
|
||
|
self->s.sound = 0;
|
||
|
gi.linkentity(self);
|
||
|
}
|
||
|
|
||
|
static int chase_angle[] = {360,315,405,270,450,225,495,540};
|
||
|
void actor_seekcover (edict_t *self)
|
||
|
{
|
||
|
int i;
|
||
|
edict_t *thing;
|
||
|
vec3_t atk, dir, best_dir, end, forward;
|
||
|
vec_t travel, yaw;
|
||
|
vec3_t mins, maxs;
|
||
|
vec3_t testpos;
|
||
|
vec_t best_dist=0;
|
||
|
trace_t trace1, trace2;
|
||
|
|
||
|
// No point in hiding from enemy if.. we don't have an enemy
|
||
|
if (!self->enemy || !self->enemy->inuse)
|
||
|
{
|
||
|
actor_run(self);
|
||
|
return;
|
||
|
}
|
||
|
if (!actorscram->value)
|
||
|
{
|
||
|
actor_run(self);
|
||
|
return;
|
||
|
}
|
||
|
// Don't hide from non-humanoid stuff
|
||
|
if (!self->enemy->client && !(self->enemy->svflags & SVF_MONSTER))
|
||
|
{
|
||
|
actor_run(self);
|
||
|
return;
|
||
|
}
|
||
|
// This shouldn't happen, we're just being cautious. Quit now if
|
||
|
// already chasing a "thing"
|
||
|
if (self->movetarget && !Q_stricmp(self->movetarget->classname,"thing"))
|
||
|
{
|
||
|
actor_run(self);
|
||
|
return;
|
||
|
}
|
||
|
// Don't bother finding cover if we're within melee range of enemy
|
||
|
VectorSubtract(self->enemy->s.origin,self->s.origin,atk);
|
||
|
if (VectorLength(atk) < 80)
|
||
|
{
|
||
|
actor_run(self);
|
||
|
return;
|
||
|
}
|
||
|
VectorCopy(self->mins,mins);
|
||
|
mins[2] += 18;
|
||
|
if (mins[2] > 0)
|
||
|
mins[2] = 0;
|
||
|
VectorCopy(self->maxs,maxs);
|
||
|
|
||
|
// Find a vector that will hide the actor from his enemy
|
||
|
VectorCopy(self->enemy->s.origin,atk);
|
||
|
atk[2] += self->enemy->viewheight;
|
||
|
VectorClear(best_dir);
|
||
|
AngleVectors(self->s.angles,forward,NULL,NULL);
|
||
|
dir[2] = 0;
|
||
|
for (travel=64; travel<257 && best_dist == 0; travel *= 2)
|
||
|
{
|
||
|
for (i=0; i<8 && best_dist == 0; i++)
|
||
|
{
|
||
|
yaw = self->s.angles[YAW] + chase_angle[i];
|
||
|
yaw = (int)(yaw/45)*45;
|
||
|
yaw = anglemod(yaw);
|
||
|
yaw *= M_PI/180;
|
||
|
dir[0] = cos(yaw);
|
||
|
dir[1] = sin(yaw);
|
||
|
VectorMA(self->s.origin,travel,dir,end);
|
||
|
trace1 = gi.trace(self->s.origin,mins,maxs,end,self,MASK_MONSTERSOLID);
|
||
|
// Test whether proposed position can be seen by enemy. Test
|
||
|
// isn't foolproof - tests against 1) new origin, 2-5) each corner of top
|
||
|
// of bounding box.
|
||
|
trace2 = gi.trace(trace1.endpos,NULL,NULL,atk,self,MASK_SOLID);
|
||
|
if (trace2.fraction == 1.0) continue;
|
||
|
|
||
|
VectorAdd(trace1.endpos,self->maxs,testpos);
|
||
|
trace2 = gi.trace(testpos,NULL,NULL,atk,self,MASK_SOLID);
|
||
|
if (trace2.fraction == 1.0) continue;
|
||
|
|
||
|
testpos[0] = trace1.endpos[0] + self->mins[0];
|
||
|
trace2 = gi.trace(testpos,NULL,NULL,atk,self,MASK_SOLID);
|
||
|
if (trace2.fraction == 1.0) continue;
|
||
|
|
||
|
testpos[1] = trace1.endpos[1] + self->mins[1];
|
||
|
trace2 = gi.trace(testpos,NULL,NULL,atk,self,MASK_SOLID);
|
||
|
if (trace2.fraction == 1.0) continue;
|
||
|
|
||
|
testpos[0] = trace1.endpos[0] + self->maxs[0];
|
||
|
trace2 = gi.trace(testpos,NULL,NULL,atk,self,MASK_SOLID);
|
||
|
if (trace2.fraction == 1.0) continue;
|
||
|
|
||
|
best_dist = trace1.fraction * travel;
|
||
|
if (best_dist < 32) // not much point to this move
|
||
|
continue;
|
||
|
VectorCopy(dir,best_dir);
|
||
|
}
|
||
|
}
|
||
|
if (best_dist < 32)
|
||
|
{
|
||
|
actor_run(self);
|
||
|
return;
|
||
|
}
|
||
|
// This snaps the angles, which may not be all that good but it sure
|
||
|
// is quicker than turning in place
|
||
|
vectoangles(best_dir,self->s.angles);
|
||
|
thing = SpawnThing();
|
||
|
VectorMA(self->s.origin,best_dist,best_dir,thing->s.origin);
|
||
|
thing->touch_debounce_time = level.time + 3.0;
|
||
|
thing->target_ent = self;
|
||
|
ED_CallSpawn(thing);
|
||
|
self->movetarget = self->goalentity = thing;
|
||
|
self->monsterinfo.aiflags &= ~(AI_SOUND_TARGET | AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
|
||
|
self->monsterinfo.aiflags |= (AI_SEEK_COVER | AI_CHASE_THING);
|
||
|
gi.linkentity(self);
|
||
|
actor_run(self);
|
||
|
}
|
||
|
|
||
|
mframe_t actor_frames_attack [] =
|
||
|
{
|
||
|
ai_charge, 0, actor_fire,
|
||
|
ai_charge, 0, actor_no_weapon_sound,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_attack = {FRAME_attack1, FRAME_attack8, actor_frames_attack, actor_seekcover};
|
||
|
|
||
|
mframe_t actor_frames_crattack [] =
|
||
|
{
|
||
|
ai_charge, 0, actor_fire,
|
||
|
ai_charge, 0, actor_no_weapon_sound,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL,
|
||
|
ai_charge, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_crattack = {FRAME_crattak1, FRAME_crattak9, actor_frames_crattack, actor_run};
|
||
|
|
||
|
void actor_attack (edict_t *self)
|
||
|
{
|
||
|
int n;
|
||
|
int weapon, w_select;
|
||
|
mmove_t *attackmove;
|
||
|
vec3_t v;
|
||
|
|
||
|
w_select = self->actor_current_weapon;
|
||
|
weapon = self->actor_weapon[w_select];
|
||
|
|
||
|
if (self->enemy)
|
||
|
{
|
||
|
if (w_select == 0 && self->actor_weapon[1] > 0)
|
||
|
{
|
||
|
VectorSubtract(self->s.origin,self->enemy->s.origin,v);
|
||
|
if(VectorLength(v) < 200)
|
||
|
{
|
||
|
self->monsterinfo.currentmove = &actor_move_switch;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else if (w_select == 1 && self->actor_weapon[0] > 0)
|
||
|
{
|
||
|
VectorSubtract(self->s.origin,self->enemy->s.origin,v);
|
||
|
if(VectorLength(v) > 300)
|
||
|
{
|
||
|
self->monsterinfo.currentmove = &actor_move_switch;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self->actor_gunframe = 0;
|
||
|
|
||
|
// temporary deal to toggle crouch
|
||
|
/* if(self->actor_crouch_time < level.time)
|
||
|
self->actor_crouch_time = level.time + 5;
|
||
|
else
|
||
|
self->actor_crouch_time = 0; */
|
||
|
// end temp
|
||
|
|
||
|
if(self->actor_crouch_time < level.time)
|
||
|
attackmove = &actor_move_attack;
|
||
|
else
|
||
|
attackmove = &actor_move_crattack;
|
||
|
|
||
|
switch (weapon)
|
||
|
{
|
||
|
case 1:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
self->monsterinfo.pausetime = level.time + 2 * FRAMETIME;
|
||
|
break;
|
||
|
case 2:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
self->monsterinfo.pausetime = level.time + 6 * FRAMETIME;
|
||
|
break;
|
||
|
case 3:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
self->monsterinfo.pausetime = level.time + 10 * FRAMETIME;
|
||
|
break;
|
||
|
case 4:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
n = (rand() & 15) + 10;
|
||
|
self->monsterinfo.pausetime = level.time + n * FRAMETIME;
|
||
|
break;
|
||
|
case 5:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
n = (rand() & 20) + 20;
|
||
|
self->monsterinfo.pausetime = level.time + n * FRAMETIME;
|
||
|
break;
|
||
|
case 6:
|
||
|
case 7:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
||
|
{ // if hes just standing there refire rate is normal
|
||
|
self->monsterinfo.pausetime = level.time + 7;
|
||
|
}
|
||
|
else
|
||
|
{ // otherwise, allow the target to fire back
|
||
|
self->monsterinfo.pausetime = level.time + 2;
|
||
|
}
|
||
|
break;
|
||
|
case 8:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
n = (rand() & 15) + 3 + 7;
|
||
|
self->monsterinfo.pausetime = level.time + n * FRAMETIME;
|
||
|
break;
|
||
|
case 9:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
self->monsterinfo.pausetime = level.time + 3;
|
||
|
break;
|
||
|
case 10:
|
||
|
if(level.time > self->endtime)
|
||
|
{
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
self->monsterinfo.pausetime = level.time + 1.5;
|
||
|
}
|
||
|
else
|
||
|
self->monsterinfo.currentmove = &actor_move_stand;
|
||
|
break;
|
||
|
case 11:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
self->monsterinfo.pausetime = level.time + 6 * FRAMETIME;
|
||
|
break;
|
||
|
case 12:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
// self->monsterinfo.pausetime = level.time + 4;
|
||
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
||
|
{ // if hes just standing there refire rate is normal
|
||
|
self->monsterinfo.pausetime = level.time + 5;
|
||
|
}
|
||
|
else
|
||
|
{ // otherwise, allow the target to fire back
|
||
|
self->monsterinfo.pausetime = level.time + 2;
|
||
|
}
|
||
|
break;
|
||
|
case 13:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
n = (rand() & 11) + 7;
|
||
|
self->monsterinfo.pausetime = level.time + n * FRAMETIME;
|
||
|
break;
|
||
|
case 14:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
n = (rand() & 10) + 5;
|
||
|
self->monsterinfo.pausetime = level.time + n * FRAMETIME;
|
||
|
break;
|
||
|
case 15:
|
||
|
self->monsterinfo.currentmove = attackmove;
|
||
|
self->monsterinfo.pausetime = level.time + 10 * FRAMETIME;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void actor_use (edict_t *self, edict_t *other, edict_t *activator)
|
||
|
{
|
||
|
vec3_t v;
|
||
|
|
||
|
self->goalentity = self->movetarget = G_PickTarget(self->target);
|
||
|
if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0))
|
||
|
{
|
||
|
gi.dprintf ("%s has bad target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
|
||
|
self->target = NULL;
|
||
|
self->monsterinfo.pausetime = 100000000;
|
||
|
self->monsterinfo.stand (self);
|
||
|
return;
|
||
|
}
|
||
|
VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
|
||
|
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
|
||
|
self->monsterinfo.walk (self);
|
||
|
self->target = NULL;
|
||
|
if (self->pathtarget)
|
||
|
{
|
||
|
char *savetarget;
|
||
|
|
||
|
savetarget = self->target;
|
||
|
self->target = self->pathtarget;
|
||
|
G_UseTargets (self, other);
|
||
|
self->target = savetarget;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Lazarus: checkattack - higher probabilities than normal monsters,
|
||
|
// also weapon-based
|
||
|
|
||
|
static float chancefar[16] = { 0.0, 0.2, 0.0, 0.0, 0.0, 0.0, 0.2, 0.4, 0.2, 0.5, 0.8, 0.3, 0.4, 0.4, 0.5, 0.3 };
|
||
|
static float chancenear[16] = { 0.0, 0.4, 0.0, 0.0, 0.0, 0.0, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4 };
|
||
|
qboolean actor_checkattack (edict_t *self)
|
||
|
{
|
||
|
vec3_t v;
|
||
|
vec3_t forward, right, start, end;
|
||
|
float chance;
|
||
|
float range;
|
||
|
float goodchance, poorchance, lorange, hirange;
|
||
|
trace_t tr;
|
||
|
int weapon;
|
||
|
|
||
|
// Paranoia check
|
||
|
if (!self->enemy)
|
||
|
return false;
|
||
|
|
||
|
// If running to "thing", never attack
|
||
|
if (self->monsterinfo.aiflags & AI_CHASE_THING)
|
||
|
return false;
|
||
|
|
||
|
weapon = self->actor_weapon[self->actor_current_weapon];
|
||
|
// If actor has no weapon, well then of course he should not attack
|
||
|
if (weapon < 1 || weapon > 15) // Knightmare- mission pack weapon support, was > 10
|
||
|
return false;
|
||
|
|
||
|
if (self->enemy->health > 0)
|
||
|
{
|
||
|
// see if any entities are in the way of the shot
|
||
|
AngleVectors (self->s.angles, forward, right, NULL);
|
||
|
G_ProjectSource (self->s.origin, self->muzzle, forward, right, start);
|
||
|
VectorCopy (self->enemy->s.origin, end);
|
||
|
|
||
|
tr = gi.trace (start, NULL, NULL, end, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
|
||
|
|
||
|
// do we have a clear shot?
|
||
|
if (tr.ent != self->enemy)
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
VectorSubtract (self->s.origin, self->enemy->s.origin, v);
|
||
|
range = VectorLength (v);
|
||
|
|
||
|
// melee attack
|
||
|
if (range <= MELEE_DISTANCE)
|
||
|
{
|
||
|
// don't always melee in easy mode
|
||
|
if (skill->value == 0 && (rand()&3) )
|
||
|
return false;
|
||
|
self->monsterinfo.attack_state = AS_MISSILE;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// missile attack
|
||
|
if (!self->monsterinfo.attack)
|
||
|
return false;
|
||
|
|
||
|
if (level.time < self->monsterinfo.attack_finished)
|
||
|
return false;
|
||
|
|
||
|
if (range > self->monsterinfo.max_range)
|
||
|
return false;
|
||
|
|
||
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
||
|
{
|
||
|
chance = 0.4;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (weapon >= 2 && weapon <= 5)
|
||
|
{
|
||
|
// Scatter guns - probability of firing based on percentage of rounds
|
||
|
// that will hit target at a given range.
|
||
|
if(skill->value == 1)
|
||
|
goodchance = 0.6;
|
||
|
else if(skill->value > 1)
|
||
|
goodchance = 0.9;
|
||
|
else
|
||
|
goodchance = 0.3;
|
||
|
poorchance = 0.01;
|
||
|
switch(weapon)
|
||
|
{
|
||
|
case 2: lorange=270; hirange=500; break;
|
||
|
case 3: lorange= 90; hirange=200; break;
|
||
|
case 4: lorange=450; hirange=628; break;
|
||
|
case 5: lorange=450; hirange=628; break;
|
||
|
}
|
||
|
if(range <= lorange)
|
||
|
chance = goodchance;
|
||
|
else if(range > hirange)
|
||
|
chance = poorchance;
|
||
|
else
|
||
|
chance = goodchance + (range-lorange)/(hirange-lorange)*(poorchance-goodchance);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (range <= 500)
|
||
|
chance = chancenear[weapon];
|
||
|
else
|
||
|
chance = chancefar[weapon];
|
||
|
if(self->monsterinfo.aiflags & AI_GOOD_GUY)
|
||
|
{
|
||
|
if(skill->value == 0)
|
||
|
chance *= 2;
|
||
|
else if(skill->value == 2)
|
||
|
chance *= 0.5;
|
||
|
else if(skill->value == 3)
|
||
|
chance *= 0.25;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(skill->value == 0)
|
||
|
chance *= 0.5;
|
||
|
else if(skill->value == 2)
|
||
|
chance *= 2;
|
||
|
else if(skill->value == 3)
|
||
|
chance *= 4;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (random () < chance)
|
||
|
{
|
||
|
self->monsterinfo.attack_state = AS_MISSILE;
|
||
|
self->monsterinfo.attack_finished = level.time + 2*random();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
mmove_t actor_move_jump;
|
||
|
void actor_end_jump (edict_t *self)
|
||
|
{
|
||
|
if(self->flags & FL_ROBOT)
|
||
|
{
|
||
|
if(self->monsterinfo.savemove)
|
||
|
{
|
||
|
actor_run(self);
|
||
|
// self->monsterinfo.currentmove = self->monsterinfo.savemove;
|
||
|
/* gi.dprintf("savemove=%d\n",self->monsterinfo.currentmove);
|
||
|
gi.dprintf("actor_move_jump=%d\n",&actor_move_jump);
|
||
|
gi.dprintf("actor_move_run=%d\n",&actor_move_run);
|
||
|
gi.dprintf("actor_move_walk=%d\n",&actor_move_walk);
|
||
|
gi.dprintf("actor_move_stand=%d\n",&actor_move_stand); */
|
||
|
}
|
||
|
else if(self->enemy)
|
||
|
actor_run(self);
|
||
|
else if(self->movetarget)
|
||
|
actor_walk(self);
|
||
|
else
|
||
|
actor_stand(self);
|
||
|
}
|
||
|
else
|
||
|
actor_run(self);
|
||
|
}
|
||
|
|
||
|
mframe_t actor_frames_jump [] =
|
||
|
{
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 0, NULL,
|
||
|
ai_move, 0, actor_end_jump
|
||
|
};
|
||
|
|
||
|
mmove_t actor_move_jump = {FRAME_jump1, FRAME_jump6, actor_frames_jump, actor_jump};
|
||
|
|
||
|
void actor_jump (edict_t *self)
|
||
|
{
|
||
|
gi.sound (self, CHAN_VOICE, self->actor_sound_index[ACTOR_SOUND_JUMP], 1, ATTN_NORM, 0);
|
||
|
self->monsterinfo.currentmove = &actor_move_jump;
|
||
|
}
|
||
|
qboolean actor_blocked (edict_t *self, float dist)
|
||
|
{
|
||
|
if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
|
||
|
return true;
|
||
|
|
||
|
if(blocked_checkjump (self, dist, self->monsterinfo.jumpdn, self->monsterinfo.jumpup))
|
||
|
return true;
|
||
|
|
||
|
if(blocked_checkplat (self, dist))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
mframe_t actor_frames_salute [] =
|
||
|
{
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL,
|
||
|
ai_turn, 0, NULL
|
||
|
};
|
||
|
mmove_t actor_move_salute = {FRAME_salute01, FRAME_salute11, actor_frames_salute, actor_run};
|
||
|
|
||
|
void actor_salute (edict_t *self)
|
||
|
{
|
||
|
self->monsterinfo.currentmove = &actor_move_salute;
|
||
|
}
|
||
|
|
||
|
/*QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32) Ambush TrigSpawn Sight GoodGuy NoGib Homing BeMonster IgnoreFire
|
||
|
"angle" Specifies the facing angle of the actor on the XY plane. Default=0.
|
||
|
"angles" Specifies the facing angle of the actor in 3 dimensions as defined by pitch, yaw, and roll. Default=0 0 0.
|
||
|
"bleft" Specifies bottom-left (minY/minX/minZ) bounding box coordinates for the actor relative to the actor's origin.
|
||
|
"combattarget" targetname of the point_combat the actor will move to when angered.
|
||
|
"deathtarget" targetname of the entity to be triggered upon the actor's death.
|
||
|
"dmgteam" An actor with a dmgteam value set will treat all attacks upon other actor(s) or monster(s) with the same dmgteam value as an attack on himself. May also be used to trigger a trigger_relay.
|
||
|
"flies" Specifies the probability that the actor's corpse will produce flies. When they appear, they will have a duration of 60 seconds before they expire. Flies will never appear if the actor's corpse is in a liquid. Normal probability values range from 0.00-1.00; 0.00=0% probability (never produce flies); 1.00=100% probability (always produce flies). If flies>1, the actor will have flies buzzing about him even while alive. Default=0.
|
||
|
Ignored if health<0, unless flies=1. In that event, the initially dead actor will have flies which never expire.
|
||
|
"gib_health" Specifies the number of hit points required to gib the actor's corpse. Ignored when NO_GIB is set. Default=40.
|
||
|
"health" Specifies the number of hit points required to kill the actor. Default=100
|
||
|
If health=0 it will be reset to the default.
|
||
|
If health<0 the actor will be dead on startup.
|
||
|
If health=100000 or more the actor is invulnerable, and will ignore attacks.
|
||
|
"item" entity to be spawned by the actor upon his death.
|
||
|
"killtarget" targetname of the entity to be removed from the map upon the actor's death.
|
||
|
"mass" weight of the actor. Default=200.
|
||
|
"movewith" targetname of the parent entity the actor is to movewith.
|
||
|
"muzzle" Specifies weapon firing origin, offset from the actor origin.
|
||
|
"muzzle2" Specifies an optional 2nd weapon firing origin, offset from the actor origin.
|
||
|
"powerarmor" Specifies amount of optional power shield armor the actor has. Default=0. Experiment with settings using the weapons the actor will face - it looks odd for a actor to die before his power shield runs out.
|
||
|
"sounds" weapon to use. Default=0
|
||
|
0 : None
|
||
|
100 : Blaster
|
||
|
200 : Shotgun
|
||
|
300 : Super Shotgun
|
||
|
400 : Machinegun
|
||
|
500 : Chaingun
|
||
|
603 : GL/SSG
|
||
|
703 : RL/SSG
|
||
|
800 : Hyperblaster
|
||
|
900 : Railgun
|
||
|
1003 : BFG/SSG
|
||
|
1100 : ION Ripper
|
||
|
1203 : Phalanx/SSG
|
||
|
1300 : ETF Rifle
|
||
|
1400 : Plasma beam
|
||
|
1500 : Disintegrator
|
||
|
"style" Specifies the 0-based index of the skin the actor will use. Default=0. The valid range for style is 0 to the number of skins referenced from within the model itself minus one. What number references what skin is dependent on the individual model. If the style value +1 exceeds the number of referenced skins, skin 0 will be used. The number of available skins for all player models distributed with Lazarus are given in the ActorPak documentation.
|
||
|
"target" targetname of the path_corner the actor will move to.
|
||
|
"targetname" Name of the specific actor.
|
||
|
"tright" Specifies top-right (maxY/maxX/maxZ) bounding box coordinates for the actor relative to the actor's origin. If no value is specified for either tright or bleft, and the value of usermodel is a player model in the Lazarus ActorPak, then the suggested bounding box as listed on the ActorPak page for that model will be used.
|
||
|
"usermodel" Specifies the player model used. Value is the name of the model's "player" folder. No default is specified for usermodel. If the model folder contains custom sounds, misc_actor will use them; otherwise it will fall back to using male model sounds.
|
||
|
*/
|
||
|
|
||
|
#define ACTOR_ALIEN 1
|
||
|
#define ACTOR_HUNTER 2
|
||
|
#define ACTOR_PARANOID 3
|
||
|
#define ACTOR_RATAMAHATTA 4
|
||
|
#define ACTOR_RHINO 5
|
||
|
#define ACTOR_SAS 6
|
||
|
#define ACTOR_SLITH 7
|
||
|
#define ACTOR_TERRAN 8
|
||
|
#define ACTOR_WALKER 9
|
||
|
#define ACTOR_WASTE 10
|
||
|
#define ACTOR_XENOID 11
|
||
|
#define ACTOR_ZUMLIN 12
|
||
|
|
||
|
#define NUM_ACTORPAK_ACTORS 12
|
||
|
char ActorNames[NUM_ACTORPAK_ACTORS][32] =
|
||
|
{ "alien", "hunter", "paranoid","ratamahatta",
|
||
|
"rhino", "sas", "slith", "terran",
|
||
|
"walker", "waste", "xenoid", "zumlin" };
|
||
|
|
||
|
void SP_misc_actor (edict_t *self)
|
||
|
{
|
||
|
char modelpath[256];
|
||
|
char *p;
|
||
|
int i;
|
||
|
int ActorID = 0;
|
||
|
|
||
|
if (deathmatch->value)
|
||
|
{
|
||
|
G_FreeEdict (self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self->movetype = MOVETYPE_STEP;
|
||
|
self->solid = SOLID_BBOX;
|
||
|
|
||
|
if(self->usermodel)
|
||
|
{
|
||
|
p = strstr(self->usermodel,"/tris.md2");
|
||
|
if(p) *p = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->usermodel = gi.TagMalloc(5, TAG_LEVEL);
|
||
|
strcpy(self->usermodel, "male");
|
||
|
}
|
||
|
if( (!Q_stricmp(self->usermodel,"male")) ||
|
||
|
(!Q_stricmp(self->usermodel,"female")) ||
|
||
|
(!Q_stricmp(self->usermodel,"cyborg")) )
|
||
|
{
|
||
|
self->actor_id_model = true;
|
||
|
if(PatchPlayerModels(self->usermodel))
|
||
|
level.restart_for_actor_models = true;
|
||
|
}
|
||
|
else
|
||
|
self->actor_id_model = false;
|
||
|
|
||
|
Com_sprintf(modelpath, sizeof(modelpath), "players/%s/tris.md2",self->usermodel);
|
||
|
self->s.modelindex = gi.modelindex(modelpath);
|
||
|
|
||
|
for(i=0; i<NUM_ACTORPAK_ACTORS && !ActorID; i++)
|
||
|
{
|
||
|
if(!Q_stricmp(self->usermodel,ActorNames[i]))
|
||
|
ActorID = i+1;
|
||
|
}
|
||
|
|
||
|
if(!VectorLength(self->bleft) && !VectorLength(self->tright))
|
||
|
{
|
||
|
switch(ActorID)
|
||
|
{
|
||
|
case ACTOR_ALIEN:
|
||
|
VectorSet (self->mins, -28, -28, -24);
|
||
|
VectorSet (self->maxs, 28, 28, 32);
|
||
|
break;
|
||
|
case ACTOR_HUNTER:
|
||
|
VectorSet (self->mins, -24, -24, -24);
|
||
|
VectorSet (self->maxs, 24, 24, 32);
|
||
|
break;
|
||
|
case ACTOR_RATAMAHATTA:
|
||
|
case ACTOR_TERRAN:
|
||
|
VectorSet (self->mins, -20, -20, -24);
|
||
|
VectorSet (self->maxs, 20, 20, 32);
|
||
|
break;
|
||
|
case ACTOR_RHINO:
|
||
|
VectorSet (self->mins, -30, -30, -24);
|
||
|
VectorSet (self->maxs, 30, 30, 32);
|
||
|
break;
|
||
|
case ACTOR_SAS:
|
||
|
case ACTOR_XENOID:
|
||
|
case ACTOR_ZUMLIN:
|
||
|
VectorSet (self->mins, -18, -18, -24);
|
||
|
VectorSet (self->maxs, 18, 18, 32);
|
||
|
break;
|
||
|
case ACTOR_WALKER:
|
||
|
VectorSet (self->mins, -24, -24, -24);
|
||
|
VectorSet (self->maxs, 24, 24, 30);
|
||
|
break;
|
||
|
default:
|
||
|
VectorSet (self->mins, -16, -16, -24);
|
||
|
VectorSet (self->maxs, 16, 16, 32);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
VectorCopy (self->bleft, self->mins);
|
||
|
VectorCopy (self->tright, self->maxs);
|
||
|
}
|
||
|
|
||
|
if (!self->health)
|
||
|
self->health = 100;
|
||
|
if (!self->gib_health)
|
||
|
self->gib_health = -40;
|
||
|
if (!self->mass)
|
||
|
self->mass = 200;
|
||
|
|
||
|
if (self->sounds < 0)
|
||
|
{
|
||
|
self->actor_weapon[0] = 0;
|
||
|
self->actor_weapon[1] = -self->sounds;
|
||
|
}
|
||
|
else if (self->sounds < 15) // Knightmare- added mission pack weapon support, was < 10
|
||
|
{
|
||
|
self->actor_weapon[0] = self->sounds;
|
||
|
self->actor_weapon[1] = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->actor_weapon[0] = self->sounds/100;
|
||
|
self->actor_weapon[1] = self->sounds % 100;
|
||
|
}
|
||
|
|
||
|
if(!VectorLength(self->muzzle))
|
||
|
{
|
||
|
switch(ActorID)
|
||
|
{
|
||
|
case ACTOR_ALIEN:
|
||
|
VectorSet(self->muzzle,42,5,15);
|
||
|
break;
|
||
|
case ACTOR_HUNTER:
|
||
|
switch(self->actor_weapon[0])
|
||
|
{
|
||
|
case 1: VectorSet(self->muzzle,32,5,15);break;
|
||
|
case 2: VectorSet(self->muzzle,36,5,15);break;
|
||
|
case 3: VectorSet(self->muzzle,36,5,15);break;
|
||
|
case 4: VectorSet(self->muzzle,38,4,19);break;
|
||
|
case 5: VectorSet(self->muzzle,45,4.5,15);break;
|
||
|
case 6: VectorSet(self->muzzle,32,5,15);break;
|
||
|
case 7: VectorSet(self->muzzle,40,5,15);break;
|
||
|
case 8: VectorSet(self->muzzle,41,4,19);break;
|
||
|
case 9: VectorSet(self->muzzle,40,4,19);break;
|
||
|
case 10: VectorSet(self->muzzle,42,5,20);break;
|
||
|
}
|
||
|
break;
|
||
|
case ACTOR_PARANOID:
|
||
|
switch(self->actor_weapon[0])
|
||
|
{
|
||
|
case 1: VectorSet(self->muzzle,18,7,10);break;
|
||
|
case 2: VectorSet(self->muzzle,22,7,10);break;
|
||
|
case 3: VectorSet(self->muzzle,22,7,10);break;
|
||
|
case 4: VectorSet(self->muzzle,18,7,12);break;
|
||
|
case 5: VectorSet(self->muzzle,26,7,16);break;
|
||
|
case 6: VectorSet(self->muzzle,24,7,10);break;
|
||
|
case 7: VectorSet(self->muzzle,26,7,10);break;
|
||
|
case 8: VectorSet(self->muzzle,18,7,14);break;
|
||
|
case 9: VectorSet(self->muzzle,28,7,10);break;
|
||
|
case 10: VectorSet(self->muzzle,28,7,10);break;
|
||
|
}
|
||
|
break;
|
||
|
case ACTOR_RATAMAHATTA:
|
||
|
VectorSet(self->muzzle,24,13,10);
|
||
|
break;
|
||
|
case ACTOR_RHINO:
|
||
|
VectorSet(self->muzzle,29,7,10);
|
||
|
break;
|
||
|
case ACTOR_SAS:
|
||
|
VectorSet(self->muzzle,17,6.5,17);
|
||
|
break;
|
||
|
case ACTOR_SLITH:
|
||
|
switch(self->actor_weapon[0])
|
||
|
{
|
||
|
case 1: VectorSet(self->muzzle,32,7,10);break;
|
||
|
case 2: VectorSet(self->muzzle,32,7,10);break;
|
||
|
case 3: VectorSet(self->muzzle,32,7,10);break;
|
||
|
case 4: VectorSet(self->muzzle,25,5,-1);break;
|
||
|
case 5: VectorSet(self->muzzle,25,5,-1);break;
|
||
|
case 6: VectorSet(self->muzzle,32,7,10);break;
|
||
|
case 7: VectorSet(self->muzzle,32,7,10);break;
|
||
|
case 8: VectorSet(self->muzzle,12,6,-1);break;
|
||
|
case 9: VectorSet(self->muzzle,32,7,10);break;
|
||
|
case 10: VectorSet(self->muzzle,20,5,-1);break;
|
||
|
}
|
||
|
break;
|
||
|
case ACTOR_TERRAN:
|
||
|
VectorSet(self->muzzle,42,7,11.5);
|
||
|
break;
|
||
|
case ACTOR_WALKER:
|
||
|
VectorSet(self->muzzle,9,16,7);
|
||
|
break;
|
||
|
case ACTOR_WASTE:
|
||
|
switch(self->actor_weapon[0])
|
||
|
{
|
||
|
case 1: VectorSet(self->muzzle,12, 9,9);break;
|
||
|
case 2: VectorSet(self->muzzle,22, 9,9);break;
|
||
|
case 3: VectorSet(self->muzzle,20, 9,9);break;
|
||
|
case 4: VectorSet(self->muzzle,11,11,7);break;
|
||
|
case 5: VectorSet(self->muzzle,26, 8,8);break;
|
||
|
case 6: VectorSet(self->muzzle,18, 9,7);break;
|
||
|
case 7: VectorSet(self->muzzle,26, 9,7);break;
|
||
|
case 8: VectorSet(self->muzzle,26, 7.5,8);break;
|
||
|
case 9: VectorSet(self->muzzle,26, 9,7);break;
|
||
|
case 10: VectorSet(self->muzzle,22,11,7);break;
|
||
|
}
|
||
|
break;
|
||
|
case ACTOR_XENOID:
|
||
|
VectorSet(self->muzzle,20,12,7);
|
||
|
break;
|
||
|
case ACTOR_ZUMLIN:
|
||
|
switch(self->actor_weapon[0])
|
||
|
{
|
||
|
case 1: VectorSet(self->muzzle,22, 3,8);break;
|
||
|
case 2: VectorSet(self->muzzle,20, 2,9);break;
|
||
|
case 3: VectorSet(self->muzzle,20, 2,9);break;
|
||
|
case 4: VectorSet(self->muzzle, 8, 5,4);break;
|
||
|
case 5: VectorSet(self->muzzle,22, 2,4);break;
|
||
|
case 6: VectorSet(self->muzzle,20, 2,7);break;
|
||
|
case 7: VectorSet(self->muzzle,30, 2,9);break;
|
||
|
case 8: VectorSet(self->muzzle,20, 3,2);break;
|
||
|
case 9: VectorSet(self->muzzle,26, 2,9);break;
|
||
|
case 10: VectorSet(self->muzzle,16, 5,-2);break;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
switch(self->actor_weapon[0])
|
||
|
{
|
||
|
case 4: VectorSet(self->muzzle, 6 ,9 ,6 );break;
|
||
|
case 5: VectorSet(self->muzzle,20 ,9 ,8 );break;
|
||
|
case 8: VectorSet(self->muzzle,18 ,8 ,6 );break;
|
||
|
default:VectorSet(self->muzzle,18.4,7.4,9.6);break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!VectorLength(self->muzzle2))
|
||
|
{
|
||
|
switch(ActorID)
|
||
|
{
|
||
|
case ACTOR_RHINO:
|
||
|
VectorSet(self->muzzle2,27,-15,13);
|
||
|
break;
|
||
|
case ACTOR_WALKER:
|
||
|
VectorSet(self->muzzle2,9,-11,7);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(VectorLength(self->muzzle2))
|
||
|
self->monsterinfo.aiflags |= AI_TWO_GUNS;
|
||
|
|
||
|
self->pain = actor_pain;
|
||
|
self->die = actor_die;
|
||
|
|
||
|
self->monsterinfo.stand = actor_stand;
|
||
|
self->monsterinfo.walk = actor_walk;
|
||
|
self->monsterinfo.run = actor_run;
|
||
|
self->monsterinfo.dodge = NULL;
|
||
|
self->monsterinfo.attack = actor_attack;
|
||
|
self->monsterinfo.melee = NULL;
|
||
|
self->monsterinfo.sight = NULL;
|
||
|
self->monsterinfo.idle = NULL;
|
||
|
self->monsterinfo.checkattack = actor_checkattack;
|
||
|
if(actorjump->value)
|
||
|
{
|
||
|
self->monsterinfo.jump = actor_jump;
|
||
|
self->monsterinfo.jumpup = 48;
|
||
|
self->monsterinfo.jumpdn = 160;
|
||
|
}
|
||
|
// self->monsterinfo.blocked = actor_blocked;
|
||
|
|
||
|
// There are several actions (mainly following a player leader) that
|
||
|
// are only applicable to misc_actor (not other monsters)
|
||
|
self->monsterinfo.aiflags |= AI_ACTOR;
|
||
|
if(!(self->spawnflags & SF_ACTOR_BAD_GUY) || (self->spawnflags & SF_MONSTER_GOODGUY))
|
||
|
self->monsterinfo.aiflags |= AI_GOOD_GUY;
|
||
|
|
||
|
if(self->powerarmor)
|
||
|
{
|
||
|
if (self->powerarmortype == 1)
|
||
|
self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
|
||
|
else
|
||
|
self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
|
||
|
self->monsterinfo.power_armor_power = self->powerarmor;
|
||
|
}
|
||
|
|
||
|
// Minimum distance
|
||
|
if(self->actor_weapon[1])
|
||
|
self->monsterinfo.min_range = 0;
|
||
|
else
|
||
|
{
|
||
|
int weapon;
|
||
|
weapon = self->actor_weapon[0];
|
||
|
if (weapon == 6 || weapon == 7 || weapon == 10 || weapon == 12) // Knightmare- added phalanx support
|
||
|
self->monsterinfo.min_range = 200;
|
||
|
else
|
||
|
self->monsterinfo.min_range = 0;
|
||
|
}
|
||
|
// Ideal range
|
||
|
actor_ideal_range(self);
|
||
|
|
||
|
gi.linkentity (self);
|
||
|
|
||
|
self->monsterinfo.currentmove = &actor_move_stand;
|
||
|
if(self->health < 0)
|
||
|
{
|
||
|
mmove_t *deathmoves[] = {&actor_move_death1,
|
||
|
&actor_move_death2,
|
||
|
NULL};
|
||
|
M_SetDeath(self,(mmove_t **)&deathmoves);
|
||
|
}
|
||
|
self->monsterinfo.scale = 0.8;
|
||
|
walkmonster_start (self);
|
||
|
|
||
|
// We've built the misc_actor model to include the standard
|
||
|
// Q2 male skins, specified with the style key. Default=grunt
|
||
|
self->s.skinnum = self->style;
|
||
|
|
||
|
// actors always start in a dormant state, they *must* be used to get going
|
||
|
self->use = actor_use;
|
||
|
|
||
|
// If health > 100000, actor is invulnerable
|
||
|
if(self->health >= 100000)
|
||
|
self->takedamage = DAMAGE_NO;
|
||
|
|
||
|
self->common_name = "Actor";
|
||
|
|
||
|
// Muzzle flash
|
||
|
self->flash = G_Spawn();
|
||
|
self->flash->classname = "muzzleflash";
|
||
|
self->flash->model = "models/objects/flash/tris.md2";
|
||
|
gi.setmodel(self->flash,self->flash->model);
|
||
|
self->flash->solid = SOLID_NOT;
|
||
|
self->flash->s.skinnum = 0;
|
||
|
self->flash->s.effects = EF_PLASMA;
|
||
|
self->flash->s.renderfx = RF_FULLBRIGHT;
|
||
|
self->flash->svflags |= SVF_NOCLIENT;
|
||
|
VectorCopy(self->s.origin,self->flash->s.origin);
|
||
|
gi.linkentity(self->flash);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD BRUTAL
|
||
|
JUMP jump in set direction upon reaching this target
|
||
|
SHOOT take a single shot at the pathtarget
|
||
|
ATTACK attack pathtarget until it or actor is dead
|
||
|
|
||
|
"target" next target_actor
|
||
|
"pathtarget" target of any action to be taken at this point
|
||
|
"wait" amount of time actor should pause at this point
|
||
|
|
||
|
for JUMP only:
|
||
|
"speed" speed thrown forward (default 200)
|
||
|
"height" speed thrown upwards (default 200)
|
||
|
*/
|
||
|
|
||
|
void target_actor_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
|
||
|
{
|
||
|
vec3_t v;
|
||
|
|
||
|
if (other->movetarget != self)
|
||
|
return;
|
||
|
|
||
|
if (other->enemy)
|
||
|
return;
|
||
|
|
||
|
other->goalentity = other->movetarget = NULL;
|
||
|
|
||
|
if (self->spawnflags & 1) //jump
|
||
|
{
|
||
|
other->velocity[0] = self->movedir[0] * self->speed;
|
||
|
other->velocity[1] = self->movedir[1] * self->speed;
|
||
|
|
||
|
if (other->groundentity)
|
||
|
{
|
||
|
other->groundentity = NULL;
|
||
|
other->velocity[2] = self->movedir[2];
|
||
|
if(other->monsterinfo.aiflags & AI_ACTOR)
|
||
|
gi.sound (self, CHAN_VOICE, other->actor_sound_index[ACTOR_SOUND_JUMP], 1, ATTN_NORM, 0);
|
||
|
}
|
||
|
// NOTE: The jump animation won't work UNLESS this target_actor has a target. If this
|
||
|
// is the last target_actor in a sequence, the actor's run takes over and prevents
|
||
|
// the jump.
|
||
|
// if (!Q_stricmp(other->classname,"misc_actor"))
|
||
|
// other->monsterinfo.currentmove = &actor_move_jump;
|
||
|
}
|
||
|
|
||
|
if (self->spawnflags & 2) //shoot
|
||
|
{
|
||
|
if(self->pathtarget)
|
||
|
{
|
||
|
if( G_Find(NULL,FOFS(targetname),self->pathtarget) != NULL )
|
||
|
{
|
||
|
other->enemy = G_PickTarget(self->pathtarget);
|
||
|
if (self->spawnflags & 8)
|
||
|
{
|
||
|
other->monsterinfo.aiflags |= AI_STAND_GROUND;
|
||
|
actor_stand (other);
|
||
|
}
|
||
|
else
|
||
|
actor_attack(other);
|
||
|
}
|
||
|
else
|
||
|
other->enemy = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
other->enemy = NULL;
|
||
|
}
|
||
|
}
|
||
|
else if (self->spawnflags & 4) //attack
|
||
|
{
|
||
|
if(self->pathtarget)
|
||
|
{
|
||
|
if( G_Find(NULL,FOFS(targetname),self->pathtarget) != NULL )
|
||
|
other->enemy = G_PickTarget(self->pathtarget);
|
||
|
else
|
||
|
other->enemy = NULL;
|
||
|
}
|
||
|
else
|
||
|
other->enemy = NULL;
|
||
|
|
||
|
if (other->enemy)
|
||
|
{
|
||
|
other->goalentity = other->enemy;
|
||
|
if (self->spawnflags & 32)
|
||
|
other->monsterinfo.aiflags |= AI_BRUTAL;
|
||
|
if (self->spawnflags & 16)
|
||
|
{
|
||
|
other->monsterinfo.aiflags |= AI_STAND_GROUND;
|
||
|
actor_stand (other);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
actor_run (other);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!(self->spawnflags & 6) && (self->pathtarget))
|
||
|
{
|
||
|
char *savetarget;
|
||
|
|
||
|
savetarget = self->target;
|
||
|
self->target = self->pathtarget;
|
||
|
G_UseTargets (self, other);
|
||
|
self->target = savetarget;
|
||
|
}
|
||
|
|
||
|
// DWH: Allow blank target field
|
||
|
if(self->target)
|
||
|
other->movetarget = G_PickTarget(self->target);
|
||
|
else
|
||
|
other->movetarget = NULL;
|
||
|
|
||
|
if(!other->goalentity)
|
||
|
other->goalentity = other->movetarget;
|
||
|
|
||
|
if (self->wait)
|
||
|
{
|
||
|
other->monsterinfo.pausetime = level.time + self->wait;
|
||
|
other->monsterinfo.stand (other);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
if (!other->movetarget && !other->enemy)
|
||
|
{
|
||
|
other->monsterinfo.pausetime = level.time + 100000000;
|
||
|
other->monsterinfo.stand (other);
|
||
|
}
|
||
|
else if (other->movetarget == other->goalentity)
|
||
|
{
|
||
|
// DWH: Bug fix here... possible to get here with NULL movetarget and goalentity
|
||
|
if (other->movetarget)
|
||
|
{
|
||
|
VectorSubtract (other->movetarget->s.origin, other->s.origin, v);
|
||
|
other->ideal_yaw = vectoyaw (v);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self->count--;
|
||
|
if(!self->count)
|
||
|
{
|
||
|
self->think = G_FreeEdict;
|
||
|
self->nextthink = level.time + 1;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void SP_target_actor (edict_t *self)
|
||
|
{
|
||
|
if (deathmatch->value)
|
||
|
{
|
||
|
G_FreeEdict(self);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!self->targetname)
|
||
|
gi.dprintf ("%s with no targetname at %s\n", self->classname, vtos(self->s.origin));
|
||
|
|
||
|
self->solid = SOLID_TRIGGER;
|
||
|
self->touch = target_actor_touch;
|
||
|
VectorSet (self->mins, -8, -8, -8);
|
||
|
VectorSet (self->maxs, 8, 8, 8);
|
||
|
self->svflags = SVF_NOCLIENT;
|
||
|
|
||
|
if (self->spawnflags & 1)
|
||
|
{
|
||
|
if (!self->speed)
|
||
|
self->speed = 200;
|
||
|
|
||
|
if (self->height)
|
||
|
st.height = self->height;
|
||
|
if (!st.height)
|
||
|
st.height = 200;
|
||
|
if (self->s.angles[YAW] == 0)
|
||
|
self->s.angles[YAW] = 360;
|
||
|
G_SetMovedir (self->s.angles, self->movedir);
|
||
|
self->movedir[2] = st.height;
|
||
|
}
|
||
|
gi.linkentity (self);
|
||
|
}
|
||
|
|
||
|
|
||
|
qboolean InPak(char *basedir, char *gamedir, char *filename)
|
||
|
{
|
||
|
char pakfile[256];
|
||
|
int k, kk;
|
||
|
int num, numitems;
|
||
|
FILE *f;
|
||
|
pak_header_t pakheader;
|
||
|
pak_item_t pakitem;
|
||
|
qboolean found = false;
|
||
|
|
||
|
#ifdef KMQUAKE2_ENGINE_MOD // *.pak/pk3 support
|
||
|
char *file_data;
|
||
|
int file_size = 0;
|
||
|
|
||
|
file_size = gi.LoadFile(filename, (void **)&file_data);
|
||
|
|
||
|
if (file_data)
|
||
|
gi.FreeFile(file_data);
|
||
|
|
||
|
if (file_size > 2)
|
||
|
return true;
|
||
|
#endif
|
||
|
|
||
|
// Search paks in game folder
|
||
|
for(k=9; k>=0 && !found; k--)
|
||
|
{
|
||
|
strncpy(pakfile, basedir, sizeof(pakfile));
|
||
|
if(strlen(gamedir))
|
||
|
{
|
||
|
Com_strcat(pakfile, sizeof(pakfile), "/");
|
||
|
Com_strcat(pakfile, sizeof(pakfile), gamedir);
|
||
|
}
|
||
|
Com_strcat(pakfile, sizeof(pakfile), va("/pak%d.pak",k));
|
||
|
if (NULL != (f = fopen(pakfile, "rb")))
|
||
|
{
|
||
|
num = (int)fread(&pakheader, 1, sizeof(pak_header_t), f);
|
||
|
if(num >= sizeof(pak_header_t))
|
||
|
{
|
||
|
if( pakheader.id[0] == 'P' &&
|
||
|
pakheader.id[1] == 'A' &&
|
||
|
pakheader.id[2] == 'C' &&
|
||
|
pakheader.id[3] == 'K' )
|
||
|
{
|
||
|
numitems = pakheader.dsize/sizeof(pak_item_t);
|
||
|
fseek(f,pakheader.dstart,SEEK_SET);
|
||
|
for(kk=0; kk<numitems && !found; kk++)
|
||
|
{
|
||
|
fread(&pakitem,1,sizeof(pak_item_t),f);
|
||
|
if(!Q_stricmp(pakitem.name,filename))
|
||
|
found = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fclose(f);
|
||
|
}
|
||
|
}
|
||
|
return found;
|
||
|
}
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
int index;
|
||
|
} actorlist;
|
||
|
|
||
|
void actor_files (void)
|
||
|
{
|
||
|
char path[256];
|
||
|
char filename[256];
|
||
|
int s_match, w_match[2];
|
||
|
int i, j, k;
|
||
|
int num_actors = 0;
|
||
|
actorlist actors[MAX_EDICTS];
|
||
|
cvar_t *basedir, *cddir, *gamedir;
|
||
|
edict_t *e, *e0;
|
||
|
FILE *f;
|
||
|
|
||
|
if(deathmatch->value)
|
||
|
return;
|
||
|
|
||
|
basedir = gi.cvar("basedir", "", 0);
|
||
|
cddir = gi.cvar("cddir", "", 0);
|
||
|
gamedir = gi.cvar("gamedir", "", 0);
|
||
|
|
||
|
memset(&actors,0,MAX_EDICTS*sizeof(actorlist));
|
||
|
|
||
|
for (i=game.maxclients+1; i<globals.num_edicts; i++)
|
||
|
{
|
||
|
e = &g_edicts[i];
|
||
|
if (!e->inuse) continue;
|
||
|
if (!e->classname) continue;
|
||
|
if (!(e->monsterinfo.aiflags & AI_ACTOR)) continue;
|
||
|
|
||
|
for (j=0; j<NUM_ACTOR_SOUNDS; j++)
|
||
|
e->actor_sound_index[j] = 0;
|
||
|
|
||
|
s_match = 0;
|
||
|
w_match[0] = 0;
|
||
|
w_match[1] = 0;
|
||
|
if (num_actors > 0)
|
||
|
{
|
||
|
for (j=0; j<num_actors && (s_match == 0 || w_match[0] == 0 || w_match[1] == 0); j++)
|
||
|
{
|
||
|
e0 = &g_edicts[actors[j].index];
|
||
|
if (!Q_stricmp(e->usermodel,e0->usermodel))
|
||
|
{
|
||
|
s_match = j+1;
|
||
|
if(e->actor_weapon[0] == e0->actor_weapon[0])
|
||
|
w_match[0] = j*2+1;
|
||
|
else if(e->actor_weapon[0] == e0->actor_weapon[1])
|
||
|
w_match[0] = j*2+2;
|
||
|
if(e->actor_weapon[1] == e0->actor_weapon[0])
|
||
|
w_match[1] = j*2+1;
|
||
|
else if(e->actor_weapon[1] == e0->actor_weapon[1])
|
||
|
w_match[1] = j*2+2;
|
||
|
}
|
||
|
}
|
||
|
if (s_match)
|
||
|
{
|
||
|
// copy sound indices from previous actor
|
||
|
e0 = &g_edicts[actors[s_match-1].index];
|
||
|
for(j=0; j<NUM_ACTOR_SOUNDS; j++)
|
||
|
e->actor_sound_index[j] = e0->actor_sound_index[j];
|
||
|
}
|
||
|
if (w_match[0])
|
||
|
{
|
||
|
k = (w_match[0]-1) % 2;
|
||
|
e0 = &g_edicts[actors[ (w_match[0]-k-1)/2 ].index];
|
||
|
e->s.modelindex2 = e->actor_model_index[0] = e0->actor_model_index[k];
|
||
|
}
|
||
|
if (w_match[1])
|
||
|
{
|
||
|
k = (w_match[1]-1) % 2;
|
||
|
e0 = &g_edicts[actors[ (w_match[1]-k-1)/2 ].index];
|
||
|
e->actor_model_index[1] = e0->actor_model_index[k];
|
||
|
}
|
||
|
}
|
||
|
if (!s_match)
|
||
|
{
|
||
|
// search for sounds on hard disk and in paks
|
||
|
actors[num_actors].index = i;
|
||
|
num_actors++;
|
||
|
|
||
|
if (!Q_stricmp(e->usermodel,"male") || !Q_stricmp(e->usermodel,"female"))
|
||
|
{
|
||
|
Com_sprintf(path, sizeof(path), "player/%s/",e->usermodel);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Com_sprintf(path, sizeof(path), "../players/%s/",e->usermodel);
|
||
|
}
|
||
|
|
||
|
for (j=0; j<NUM_ACTOR_SOUNDS; j++)
|
||
|
{
|
||
|
if(e->actor_sound_index[j])
|
||
|
continue;
|
||
|
|
||
|
// If it's NOT a custom model, start by looking in game folder
|
||
|
if (strlen(gamedir->string))
|
||
|
{
|
||
|
Com_sprintf(filename, sizeof(filename), "%s/%s/sound/%s%s",basedir->string,gamedir->string,path,wavname[j]);
|
||
|
f = fopen(filename,"r");
|
||
|
if (f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
Com_strcpy(filename, sizeof(filename), path);
|
||
|
Com_strcat(filename, sizeof(filename), wavname[j]);
|
||
|
e->actor_sound_index[j] = gi.soundindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
// Search paks in game folder
|
||
|
Com_sprintf(filename, sizeof(filename), "sound/%s%s",path,wavname[j]);
|
||
|
if (InPak(basedir->string,gamedir->string,filename))
|
||
|
{
|
||
|
Com_strcpy(filename, sizeof(filename), path);
|
||
|
Com_strcat(filename, sizeof(filename), wavname[j]);
|
||
|
e->actor_sound_index[j] = gi.soundindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Search in baseq2 for external file
|
||
|
Com_sprintf(filename, sizeof(filename), "%s/baseq2/sound/%s%s",basedir->string,path,wavname[j]);
|
||
|
f = fopen(filename,"r");
|
||
|
if (f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
Com_strcpy(filename, sizeof(filename), path);
|
||
|
Com_strcat(filename, sizeof(filename), wavname[j]);
|
||
|
e->actor_sound_index[j] = gi.soundindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
// Search paks in baseq2
|
||
|
Com_sprintf(filename, sizeof(filename), "sound/%s%s",path,wavname[j]);
|
||
|
if (InPak(basedir->string,"baseq2",filename))
|
||
|
{
|
||
|
Com_strcpy(filename, sizeof(filename), path);
|
||
|
Com_strcat(filename, sizeof(filename), wavname[j]);
|
||
|
e->actor_sound_index[j] = gi.soundindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
if (strlen(cddir->string))
|
||
|
{
|
||
|
// Search in cddir (minimal installation)
|
||
|
Com_sprintf(filename, sizeof(filename), "%s/baseq2/sound/%s%s",cddir->string,path,wavname[j]);
|
||
|
f = fopen(filename,"r");
|
||
|
if(f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
Com_strcpy(filename, sizeof(filename), path);
|
||
|
Com_strcat(filename, sizeof(filename), wavname[j]);
|
||
|
e->actor_sound_index[j] = gi.soundindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
// Search paks in baseq2
|
||
|
Com_sprintf(filename, sizeof(filename), "sound/%s%s",path,wavname[j]);
|
||
|
if (InPak(cddir->string,"baseq2",filename))
|
||
|
{
|
||
|
Com_strcpy(filename, sizeof(filename), path);
|
||
|
Com_strcat(filename, sizeof(filename), wavname[j]);
|
||
|
e->actor_sound_index[j] = gi.soundindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
// If sound is STILL not found, use normal male sounds
|
||
|
Com_sprintf(filename, sizeof(filename), "player/male/%s",wavname[j]);
|
||
|
e->actor_sound_index[j] = gi.soundindex(filename);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// repeat this WHOLE DAMN THING for weapons
|
||
|
|
||
|
for(k=0; k<2; k++)
|
||
|
{
|
||
|
if (w_match[k]) continue;
|
||
|
if (!e->actor_weapon[k]) continue;
|
||
|
if ((k==1) && (e->actor_weapon[0] == e->actor_weapon[1]))
|
||
|
{
|
||
|
e->actor_model_index[1] = e->actor_model_index[0];
|
||
|
continue;
|
||
|
}
|
||
|
if(s_match)
|
||
|
{
|
||
|
// Wasn't added to table on account of sounds
|
||
|
if(k==0 || w_match[0] > 0)
|
||
|
{
|
||
|
// Either this is weapon 0, or weapon 0 was a match. Either
|
||
|
// way, this guy has something unique and hasn't been added
|
||
|
// to the table
|
||
|
actors[num_actors].index = i;
|
||
|
num_actors++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Com_sprintf(filename, sizeof(filename), "players/%s/",e->usermodel);
|
||
|
switch(e->actor_weapon[k])
|
||
|
{
|
||
|
case 2: Com_strcat(filename, sizeof(filename), "w_shotgun.md2"); break;
|
||
|
case 3: Com_strcat(filename, sizeof(filename), "w_sshotgun.md2"); break;
|
||
|
case 4: Com_strcat(filename, sizeof(filename), "w_machinegun.md2"); break;
|
||
|
case 5: Com_strcat(filename, sizeof(filename), "w_chaingun.md2"); break;
|
||
|
case 6: Com_strcat(filename, sizeof(filename), "w_glauncher.md2"); break;
|
||
|
case 7: Com_strcat(filename, sizeof(filename), "w_rlauncher.md2"); break;
|
||
|
case 8: Com_strcat(filename, sizeof(filename), "w_hyperblaster.md2"); break;
|
||
|
case 9: Com_strcat(filename, sizeof(filename), "w_railgun.md2"); break;
|
||
|
case 10:Com_strcat(filename, sizeof(filename), "w_bfg.md2"); break;
|
||
|
// Knightmare- mission pack weapon support
|
||
|
case 11:Com_strcat(filename, sizeof(filename), "w_ripper.md2"); break;
|
||
|
case 12:Com_strcat(filename, sizeof(filename), "w_phalanx.md2"); break;
|
||
|
case 13:Com_strcat(filename, sizeof(filename), "w_etfrifle.md2"); break;
|
||
|
case 14:Com_strcat(filename, sizeof(filename), "w_plasma.md2"); break;
|
||
|
case 15:Com_strcat(filename, sizeof(filename), "w_disrupt.md2"); break;
|
||
|
// end Knightmare
|
||
|
default:Com_strcat(filename, sizeof(filename), "w_blaster.md2"); break;
|
||
|
}
|
||
|
|
||
|
if(strlen(gamedir->string))
|
||
|
{ // Start in game folder
|
||
|
Com_sprintf(path, sizeof(path), "%s/%s/%s",basedir->string,gamedir->string,filename);
|
||
|
f = fopen(path,"r");
|
||
|
if(f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
// Search pak files in gamedir
|
||
|
if (InPak(basedir->string,gamedir->string,filename))
|
||
|
{
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
// Search in baseq2 for external file
|
||
|
Com_sprintf(path, sizeof(path), "%s/baseq2/%s",basedir->string,filename);
|
||
|
f = fopen(path,"r");
|
||
|
if(f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
// Search paks in baseq2
|
||
|
if (InPak(basedir->string,"baseq2",filename))
|
||
|
{
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(strlen(cddir->string))
|
||
|
{
|
||
|
// Search CD for minimal installations
|
||
|
Com_sprintf(path, sizeof(path), "%s/baseq2/%s",cddir->string,filename);
|
||
|
f = fopen(path,"r");
|
||
|
if(f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Search paks in baseq2
|
||
|
if (InPak(cddir->string,"baseq2",filename))
|
||
|
{
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If sound is STILL not found, start the fuck over and look for weapon.md2
|
||
|
Com_sprintf(filename, sizeof(filename), "players/%s/weapon.md2",e->usermodel);
|
||
|
|
||
|
if(strlen(gamedir->string))
|
||
|
{
|
||
|
// Start in game folder
|
||
|
Com_sprintf(path, sizeof(path), "%s/%s/%s",basedir->string,gamedir->string,filename);
|
||
|
f = fopen(path,"r");
|
||
|
if(f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
// Search pak files in gamedir
|
||
|
if (InPak(basedir->string,gamedir->string,filename))
|
||
|
{
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
// Search in baseq2 for external file
|
||
|
Com_sprintf(path, sizeof(path), "%s/baseq2/%s",basedir->string,filename);
|
||
|
f = fopen(path,"r");
|
||
|
if(f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
// Search paks in baseq2
|
||
|
if (InPak(basedir->string,"baseq2",filename))
|
||
|
{
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(strlen(cddir->string))
|
||
|
{
|
||
|
// Search CD for minimal installations
|
||
|
Com_sprintf(path, sizeof(path), "%s/baseq2/%s",cddir->string,filename);
|
||
|
f = fopen(path,"r");
|
||
|
if(f)
|
||
|
{
|
||
|
fclose(f);
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
// Search paks in baseq2
|
||
|
if (InPak(cddir->string,"baseq2",filename))
|
||
|
{
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
// And if it's STILL not found, use
|
||
|
Com_sprintf(filename, sizeof(filename), "players/male/weapon.md2");
|
||
|
e->actor_model_index[k] = gi.modelindex(filename);
|
||
|
}
|
||
|
if(e->health > 0)
|
||
|
e->s.modelindex2 = e->actor_model_index[e->actor_current_weapon];
|
||
|
else
|
||
|
e->s.modelindex2 = 0;
|
||
|
gi.linkentity(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void actor_moveit (edict_t *player, edict_t *actor)
|
||
|
{
|
||
|
edict_t *thing;
|
||
|
trace_t tr;
|
||
|
vec3_t dir, end;
|
||
|
vec_t d[3];
|
||
|
vec_t temp;
|
||
|
vec_t travel;
|
||
|
int best=0;
|
||
|
|
||
|
if (!(actor->monsterinfo.aiflags & AI_FOLLOW_LEADER))
|
||
|
return;
|
||
|
if (actor->enemy)
|
||
|
return;
|
||
|
if (actor->health <= 0)
|
||
|
return;
|
||
|
travel = 256 + 128*crandom();
|
||
|
thing = actor->vehicle;
|
||
|
if(!thing || !thing->inuse || Q_stricmp(thing->classname,"thing"))
|
||
|
thing = actor->vehicle = SpawnThing();
|
||
|
VectorSubtract(actor->s.origin,player->s.origin,dir);
|
||
|
dir[2] = 0;
|
||
|
VectorNormalize(dir);
|
||
|
if(!VectorLength(dir))
|
||
|
VectorSet(dir,1.0,0.,0.);
|
||
|
VectorMA(actor->s.origin,travel,dir,end);
|
||
|
tr = gi.trace(actor->s.origin,NULL,NULL,end,actor,MASK_MONSTERSOLID);
|
||
|
d[best] = tr.fraction * travel;
|
||
|
if(d[best] < 64)
|
||
|
{
|
||
|
temp = dir[0];
|
||
|
dir[0] = -dir[1];
|
||
|
dir[1] = temp;
|
||
|
VectorMA(actor->s.origin,travel,dir,end);
|
||
|
tr = gi.trace(actor->s.origin,NULL,NULL,end,actor,MASK_MONSTERSOLID);
|
||
|
best = 1;
|
||
|
d[best] = tr.fraction * travel;
|
||
|
if(d[best] < 64)
|
||
|
{
|
||
|
dir[0] = -dir[0];
|
||
|
dir[1] = -dir[1];
|
||
|
VectorMA(actor->s.origin,travel,dir,end);
|
||
|
tr = gi.trace(actor->s.origin,NULL,NULL,end,actor,MASK_MONSTERSOLID);
|
||
|
best = 2;
|
||
|
d[best] = tr.fraction * travel;
|
||
|
if(d[best] < 64)
|
||
|
{
|
||
|
if(d[0] > d[1] && d[0] > d[2])
|
||
|
best = 0;
|
||
|
else if(d[1] > d[0] && d[1] > d[2])
|
||
|
best = 1;
|
||
|
if(best==1)
|
||
|
{
|
||
|
dir[0] = -dir[0];
|
||
|
dir[1] = -dir[1];
|
||
|
}
|
||
|
else if(best==0)
|
||
|
{
|
||
|
temp = -dir[1];
|
||
|
dir[1] = dir[0];
|
||
|
dir[0] = temp;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
VectorCopy(tr.endpos, thing->s.origin);
|
||
|
thing->touch_debounce_time = level.time + max(5.0,d[best]/50.);
|
||
|
thing->target_ent = actor;
|
||
|
ED_CallSpawn(thing);
|
||
|
actor->monsterinfo.aiflags |= AI_CHASE_THING;
|
||
|
actor->movetarget = actor->goalentity = thing;
|
||
|
actor->monsterinfo.old_leader = player;
|
||
|
actor->monsterinfo.leader = thing;
|
||
|
VectorSubtract (thing->s.origin, actor->s.origin, dir);
|
||
|
actor->ideal_yaw = vectoyaw(dir);
|
||
|
actor->monsterinfo.run(actor);
|
||
|
}
|