// 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; 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 < 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; 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")) { Com_sprintf(path, sizeof(path), "player/%s/",e->usermodel); } else { Com_sprintf(path, sizeof(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)) { 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); }