/* Copyright (C) 1997-2001 Id Software, Inc. Copyright (C) 2000-2002 Mr. Hyde and Mad Dog This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // 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; 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; // 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|AI_STAND_GROUND); self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; else // self->monsterinfo.aiflags |= (AI_HOLD_FRAME|AI_STAND_GROUND); 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; } } 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) + 3 + 7; 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; } } 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[11] = { 0.0, 0.2, 0.0, 0.0, 0.0, 0.0, 0.2, 0.4, 0.2, 0.5, 0.8 }; static float chancenear[11] = { 0.0, 0.4, 0.0, 0.0, 0.0, 0.0, 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 > 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(check_shot_blocked (self, 0.25 + (0.05 * skill->value) )) return true; if(check_jump_blocked (self, dist, self->monsterinfo.jumpdn, self->monsterinfo.jumpup)) return true; if(check_plat_blocked (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) */ #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 = malloc(5); 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; sprintf(modelpath,"players/%s/tris.md2",self->usermodel); self->s.modelindex = gi.modelindex(modelpath); for(i=0; iusermodel,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 < 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) { 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) 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 (!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--) { strcpy(pakfile,basedir); if(strlen(gamedir)) { strcat(pakfile,"/"); strcat(pakfile,gamedir); } strcat(pakfile,va("/pak%d.pak",k)); if (NULL != (f = fopen(pakfile, "rb"))) { num=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; kkvalue) 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; iinuse) continue; if(!e->classname) continue; if(!(e->monsterinfo.aiflags & AI_ACTOR)) continue; for(j=0; jactor_sound_index[j] = 0; s_match = 0; w_match[0] = 0; w_match[1] = 0; if(num_actors > 0) { for(j=0; jusermodel,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; jactor_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")) { sprintf(path, "player/%s/",e->usermodel); } else { sprintf(path, "../players/%s/",e->usermodel); } for(j=0; jactor_sound_index[j]) continue; // If it's NOT a custom model, start by looking in game folder if(strlen(gamedir->string)) { sprintf(filename,"%s/%s/sound/%s%s",basedir->string,gamedir->string,path,wavname[j]); f = fopen(filename,"r"); if(f) { fclose(f); strcpy(filename,path); strcat(filename,wavname[j]); e->actor_sound_index[j] = gi.soundindex(filename); continue; } // Search paks in game folder sprintf(filename,"sound/%s%s",path,wavname[j]); if (InPak(basedir->string,gamedir->string,filename)) { strcpy(filename,path); strcat(filename,wavname[j]); e->actor_sound_index[j] = gi.soundindex(filename); continue; } } // Search in baseq2 for external file sprintf(filename,"%s/baseq2/sound/%s%s",basedir->string,path,wavname[j]); f = fopen(filename,"r"); if(f) { fclose(f); strcpy(filename,path); strcat(filename,wavname[j]); e->actor_sound_index[j] = gi.soundindex(filename); continue; } // Search paks in baseq2 sprintf(filename,"sound/%s%s",path,wavname[j]); if (InPak(basedir->string,"baseq2",filename)) { strcpy(filename,path); strcat(filename,wavname[j]); e->actor_sound_index[j] = gi.soundindex(filename); continue; } if(strlen(cddir->string)) { // Search in cddir (minimal installation) sprintf(filename,"%s/baseq2/sound/%s%s",cddir->string,path,wavname[j]); f = fopen(filename,"r"); if(f) { fclose(f); strcpy(filename,path); strcat(filename,wavname[j]); e->actor_sound_index[j] = gi.soundindex(filename); continue; } // Search paks in baseq2 sprintf(filename,"sound/%s%s",path,wavname[j]); if (InPak(cddir->string,"baseq2",filename)) { strcpy(filename,path); strcat(filename,wavname[j]); e->actor_sound_index[j] = gi.soundindex(filename); continue; } } // If sound is STILL not found, use normal male sounds sprintf(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++; } } sprintf(filename,"players/%s/",e->usermodel); switch(e->actor_weapon[k]) { case 2: strcat(filename,"w_shotgun.md2"); break; case 3: strcat(filename,"w_sshotgun.md2"); break; case 4: strcat(filename,"w_machinegun.md2"); break; case 5: strcat(filename,"w_chaingun.md2"); break; case 6: strcat(filename,"w_glauncher.md2"); break; case 7: strcat(filename,"w_rlauncher.md2"); break; case 8: strcat(filename,"w_hyperblaster.md2"); break; case 9: strcat(filename,"w_railgun.md2"); break; case 10:strcat(filename,"w_bfg.md2"); break; default:strcat(filename,"w_blaster.md2"); break; } if(strlen(gamedir->string)) { // Start in game folder sprintf(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 sprintf(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 sprintf(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 sprintf(filename,"players/%s/weapon.md2",e->usermodel); if(strlen(gamedir->string)) { // Start in game folder sprintf(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 sprintf(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 sprintf(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 sprintf(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); }