mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
608814c830
Fixed Tactician Gunner's flechettes going thru player bbox at point blank. Made edict_t pointer arrays static in server, default Lazarus, and missionpack DLLs due to stack size concerns. Added contact grenade mode for special monster flag for gunners in default Lazarus and missionpack DLLs.
2351 lines
62 KiB
C
2351 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", 0, 0, damage, GIB_ORGANIC);
|
|
for (n = 0; n < 4; n++)
|
|
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 0, 0, damage, GIB_ORGANIC);
|
|
ThrowHead (self, "models/objects/gibs/head2/tris.md2", 0, 0, 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;
|
|
size_t modelSize;
|
|
|
|
if (deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_MISC_ACTOR;
|
|
self->movetype = MOVETYPE_STEP;
|
|
self->solid = SOLID_BBOX;
|
|
|
|
if(self->usermodel)
|
|
{
|
|
p = strstr(self->usermodel,"/tris.md2");
|
|
if(p) *p = 0;
|
|
}
|
|
else
|
|
{
|
|
modelSize = 5;
|
|
self->usermodel = gi.TagMalloc(modelSize, TAG_LEVEL);
|
|
Com_strcpy (self->usermodel, modelSize, "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;
|
|
}
|
|
self->class_id = ENTITY_TARGET_ACTOR;
|
|
|
|
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;
|
|
static actorlist actors[MAX_EDICTS]; // Knightmare- made static due to stack size
|
|
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);
|
|
}
|