/* ============================================================================== stalker ============================================================================== */ #include "g_local.h" #include "m_stalker.h" #include static int sound_pain; static int sound_die; static int sound_sight; static int sound_punch_hit1; static int sound_punch_hit2; static int sound_idle; int stalker_do_pounce(edict_t *self, vec3_t dest); void stalker_stand (edict_t *self); void stalker_run (edict_t *self); void stalker_walk (edict_t *self); void stalker_jump (edict_t *self); void stalker_dodge_jump (edict_t *self); void stalker_swing_check_l (edict_t *self); void stalker_swing_check_r (edict_t *self); void stalker_swing_attack (edict_t *self); void stalker_jump_straightup (edict_t *self); void stalker_jump_wait_land (edict_t *self); void stalker_false_death (edict_t *self); void stalker_false_death_start (edict_t *self); qboolean stalker_ok_to_transition (edict_t *self); #define STALKER_ON_CEILING(ent) ( ent->gravityVector[2] > 0 ? 1 : 0 ) //extern qboolean SV_StepDirection (edict_t *ent, float yaw, float dist); extern qboolean SV_PointCloseEnough (edict_t *ent, vec3_t goal, float dist); extern void drawbbox(edict_t *self); //========================= //========================= qboolean stalker_ok_to_transition (edict_t *self) { trace_t trace; vec3_t pt, start; float max_dist; float margin; float end_height; if(STALKER_ON_CEILING(self)) { max_dist = -384; margin = self->mins[2] - 8; } else { // her stalkers are just better if (self->monsterinfo.aiflags & AI_SPAWNED_WIDOW) max_dist = 256; else max_dist = 180; margin = self->maxs[2] + 8; } VectorCopy(self->s.origin, pt); pt[2] += max_dist; trace = gi.trace (self->s.origin, self->mins, self->maxs, pt, self, MASK_MONSTERSOLID); if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) { if(STALKER_ON_CEILING(self)) { if(trace.plane.normal[2] < 0.9) return false; } else { if(trace.plane.normal[2] > -0.9) return false; } } // gi.dprintf("stalker_check_pt: main check ok\n"); end_height = trace.endpos[2]; // check the four corners, tracing only to the endpoint of the center trace (vertically). pt[0] = self->absmin[0]; pt[1] = self->absmin[1]; pt[2] = trace.endpos[2] + margin; // give a little margin of error to allow slight inclines VectorCopy(pt, start); start[2] = self->s.origin[2]; trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) { // gi.dprintf("stalker_check_pt: absmin/absmin failed\n"); return false; } if(abs(end_height + margin - trace.endpos[2]) > 8) return false; pt[0] = self->absmax[0]; pt[1] = self->absmin[1]; VectorCopy(pt, start); start[2] = self->s.origin[2]; trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) { // gi.dprintf("stalker_check_pt: absmax/absmin failed\n"); return false; } if(abs(end_height + margin - trace.endpos[2]) > 8) return false; pt[0] = self->absmax[0]; pt[1] = self->absmax[1]; VectorCopy(pt, start); start[2] = self->s.origin[2]; trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) { // gi.dprintf("stalker_check_pt: absmax/absmax failed\n"); return false; } if(abs(end_height + margin - trace.endpos[2]) > 8) return false; pt[0] = self->absmin[0]; pt[1] = self->absmax[1]; VectorCopy(pt, start); start[2] = self->s.origin[2]; trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) { // gi.dprintf("stalker_check_pt: absmin/absmax failed\n"); return false; } if(abs(end_height + margin - trace.endpos[2]) > 8) return false; return true; } //========================= //========================= void stalker_sight (edict_t *self, edict_t *other) { gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); } // ****************** // IDLE // ****************** void stalker_idle_noise (edict_t *self) { gi.sound (self, CHAN_WEAPON, sound_idle, 0.5, ATTN_IDLE, 0); } mframe_t stalker_frames_idle [] = { 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, stalker_idle_noise, 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 stalker_move_idle = {FRAME_idle01, FRAME_idle21, stalker_frames_idle, stalker_stand}; mframe_t stalker_frames_idle2 [] = { 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 stalker_move_idle2 = {FRAME_idle201, FRAME_idle213, stalker_frames_idle2, stalker_stand}; void stalker_idle (edict_t *self) { if (random() < 0.35) self->monsterinfo.currentmove = &stalker_move_idle; else self->monsterinfo.currentmove = &stalker_move_idle2; } // ****************** // STAND // ****************** mframe_t stalker_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, stalker_idle_noise, 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 stalker_move_stand = {FRAME_idle01, FRAME_idle21, stalker_frames_stand, stalker_stand}; void stalker_stand (edict_t *self) { if (random() < 0.25) self->monsterinfo.currentmove = &stalker_move_stand; else self->monsterinfo.currentmove = &stalker_move_idle2; } // ****************** // RUN // ****************** mframe_t stalker_frames_run [] = { ai_run, 13, NULL, ai_run, 17, NULL, ai_run, 21, NULL, ai_run, 18, NULL /* ai_run, 15, NULL, ai_run, 20, NULL, ai_run, 18, NULL, ai_run, 14, NULL*/ }; mmove_t stalker_move_run = {FRAME_run01, FRAME_run04, stalker_frames_run, NULL}; void stalker_run (edict_t *self) { // gi.dprintf("stalker_run %5.1f\n", level.time); if (self->monsterinfo.aiflags & AI_STAND_GROUND) self->monsterinfo.currentmove = &stalker_move_stand; else self->monsterinfo.currentmove = &stalker_move_run; } // ****************** // WALK // ****************** mframe_t stalker_frames_walk [] = { ai_walk, 4, NULL, ai_walk, 6, NULL, ai_walk, 8, NULL, ai_walk, 5, NULL, ai_walk, 4, NULL, ai_walk, 6, NULL, ai_walk, 8, NULL, ai_walk, 4, NULL }; mmove_t stalker_move_walk = {FRAME_walk01, FRAME_walk08, stalker_frames_walk, stalker_walk}; void stalker_walk (edict_t *self) { // gi.dprintf("stalker_walk\n"); self->monsterinfo.currentmove = &stalker_move_walk; } // ****************** // false death // ****************** mframe_t stalker_frames_reactivate [] = { ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL }; mmove_t stalker_move_false_death_end = { FRAME_reactive01, FRAME_reactive04, stalker_frames_reactivate, stalker_run }; void stalker_reactivate (edict_t *self) { self->monsterinfo.aiflags &= ~AI_STAND_GROUND; self->monsterinfo.currentmove = &stalker_move_false_death_end; } void stalker_heal (edict_t *self) { if(skill->value == 2) self->health+=2; else if(skill->value == 3) self->health+=3; else self->health++; // gi.dprintf("stalker_heal: %d\n", self->health); if(self->health > (self->max_health/2)) self->s.skinnum = 0; if(self->health >= self->max_health) { self->health = self->max_health; stalker_reactivate(self); } } mframe_t stalker_frames_false_death [] = { ai_move, 0, stalker_heal, ai_move, 0, stalker_heal, ai_move, 0, stalker_heal, ai_move, 0, stalker_heal, ai_move, 0, stalker_heal, ai_move, 0, stalker_heal, ai_move, 0, stalker_heal, ai_move, 0, stalker_heal, ai_move, 0, stalker_heal, ai_move, 0, stalker_heal }; mmove_t stalker_move_false_death = {FRAME_twitch01, FRAME_twitch10, stalker_frames_false_death, stalker_false_death}; void stalker_false_death (edict_t *self) { self->monsterinfo.currentmove = &stalker_move_false_death; } mframe_t stalker_frames_false_death_start [] = { ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, }; mmove_t stalker_move_false_death_start = {FRAME_death01, FRAME_death09, stalker_frames_false_death_start, stalker_false_death}; void stalker_false_death_start (edict_t *self) { self->s.angles[2] = 0; VectorSet(self->gravityVector, 0, 0, -1); self->monsterinfo.aiflags |= AI_STAND_GROUND; self->monsterinfo.currentmove = &stalker_move_false_death_start; } // ****************** // PAIN // ****************** mframe_t stalker_frames_pain [] = { ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL }; mmove_t stalker_move_pain = {FRAME_pain01, FRAME_pain04, stalker_frames_pain, stalker_run}; void stalker_pain (edict_t *self, edict_t *other, float kick, int damage) { if (self->deadflag == DEAD_DEAD) return; if (self->health < (self->max_health / 2)) { self->s.skinnum = 1; } if (skill->value == 3) return; // no pain anims in nightmare // if (self->monsterinfo.aiflags & AI_DODGING) // monster_done_dodge (self); if (self->groundentity == NULL) return; // if we're reactivating or false dying, ignore the pain. if (self->monsterinfo.currentmove == &stalker_move_false_death_end || self->monsterinfo.currentmove == &stalker_move_false_death_start ) return; if (self->monsterinfo.currentmove == &stalker_move_false_death) { stalker_reactivate(self); return; } if ((self->health > 0) && (self->health < (self->max_health / 4))) { if(random() < (0.2 * skill->value)) { if( !STALKER_ON_CEILING(self) || stalker_ok_to_transition(self) ) { // gi.dprintf("starting false death sequence\n"); stalker_false_death_start(self); return; } } } if (level.time < self->pain_debounce_time) return; self->pain_debounce_time = level.time + 3; // gi.dprintf("stalker_pain\n"); if (damage > 10) // don't react unless the damage was significant { // stalker should dodge jump periodically to help avoid damage. if(self->groundentity && (random() < 0.5)) stalker_dodge_jump(self); else self->monsterinfo.currentmove = &stalker_move_pain; gi.sound (self, CHAN_WEAPON, sound_pain, 1, ATTN_NORM, 0); } } // ****************** // STALKER ATTACK // ****************** //extern qboolean infront (edict_t *self, edict_t *other); void stalker_shoot_attack (edict_t *self) { vec3_t offset, start, f, r, dir; vec3_t end; float time, dist; trace_t trace; if(!has_valid_enemy(self)) return; if(self->groundentity && random() < 0.33) { VectorSubtract (self->enemy->s.origin, self->s.origin, dir); dist = VectorLength (dir); if((dist > 256) || (random() < 0.5)) stalker_do_pounce(self, self->enemy->s.origin); else stalker_jump_straightup (self); } // FIXME -- keep this but use a custom one // if (!infront(self, self->enemy)) // return; AngleVectors (self->s.angles, f, r, NULL); VectorSet (offset, 24, 0, 6); G_ProjectSource (self->s.origin, offset, f, r, start); VectorSubtract(self->enemy->s.origin, start, dir); if(random() < (0.20 + 0.1 * skill->value)) { dist = VectorLength(dir); time = dist / 1000; VectorMA(self->enemy->s.origin, time, self->enemy->velocity, end); VectorSubtract(end, start, dir); } else VectorCopy(self->enemy->s.origin, end); trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT); if(trace.ent == self->enemy || trace.ent == world) monster_fire_blaster2(self, start, dir, 15, 800, MZ2_STALKER_BLASTER, EF_BLASTER); // else // gi.dprintf("blocked by entity %s\n", trace.ent->classname); } void stalker_shoot_attack2 (edict_t *self) { // if (random() < (0.4+(float)skill->value)) // stalker_shoot_attack (self); if (random() < (0.4 + (0.1 * (float)skill->value))) stalker_shoot_attack (self); } mframe_t stalker_frames_shoot [] = { ai_charge, 13, NULL, ai_charge, 17, stalker_shoot_attack, ai_charge, 21, NULL, ai_charge, 18, stalker_shoot_attack2 }; mmove_t stalker_move_shoot = {FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run}; void stalker_attack_ranged (edict_t *self) { if(!has_valid_enemy(self)) return; // PMM - circle strafe stuff if (random() > (1.0 - (0.5/(float)(skill->value)))) { self->monsterinfo.attack_state = AS_STRAIGHT; } else { if (random () <= 0.5) // switch directions self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; self->monsterinfo.attack_state = AS_SLIDING; } self->monsterinfo.currentmove = &stalker_move_shoot; } // ****************** // close combat // ****************** void stalker_swing_attack (edict_t *self) { vec3_t aim; VectorSet (aim, MELEE_DISTANCE, 0, 0); if (fire_hit (self, aim, (5 + (rand() % 5)), 50)) if (self->s.frame < FRAME_attack08) gi.sound (self, CHAN_WEAPON, sound_punch_hit2, 1, ATTN_NORM, 0); else gi.sound (self, CHAN_WEAPON, sound_punch_hit1, 1, ATTN_NORM, 0); } mframe_t stalker_frames_swing_l [] = { ai_charge, 2, NULL, ai_charge, 4, NULL, ai_charge, 6, NULL, ai_charge, 10, NULL, ai_charge, 5, stalker_swing_attack, ai_charge, 5, NULL, ai_charge, 5, NULL, ai_charge, 5, NULL // stalker_swing_check_l }; mmove_t stalker_move_swing_l = {FRAME_attack01, FRAME_attack08, stalker_frames_swing_l, stalker_run}; mframe_t stalker_frames_swing_r [] = { ai_charge, 4, NULL, ai_charge, 6, NULL, ai_charge, 6, stalker_swing_attack, ai_charge, 10, NULL, ai_charge, 5, NULL // stalker_swing_check_r }; mmove_t stalker_move_swing_r = {FRAME_attack11, FRAME_attack15, stalker_frames_swing_r, stalker_run}; void stalker_attack_melee (edict_t *self) { if(!has_valid_enemy(self)) return; if(random() < 0.5) { self->monsterinfo.currentmove = &stalker_move_swing_l; } else { self->monsterinfo.currentmove = &stalker_move_swing_r; } } // ****************** // POUNCE // ****************** #define PI 3.14159 #define RAD2DEG(x) (x * (float)180.0 / (float)PI) #define DEG2RAD(x) (x * (float)PI / (float)180.0) #define FAUX_GRAVITY 800.0 // ==================== // ==================== void calcJumpAngle(vec3_t start, vec3_t end, float velocity, vec3_t angles) { float distV, distH; float one, cosU; float l, U; vec3_t dist; VectorSubtract(end, start, dist); distH = (float)sqrt(dist[0]*dist[0] + dist[1]*dist[1]); distV = dist[2]; if(distV < 0) distV = 0 - distV; if(distV) { l = (float) sqrt(distH*distH + distV*distV); U = (float) atan(distV / distH); if(dist[2] > 0) U = (float)0.0 - U; angles[2] = 0.0; cosU = (float)cos(U); one = l * FAUX_GRAVITY * (cosU * cosU); one = one / (velocity * velocity); one = one - (float)sin(U); // one = ((l * FAUX_GRAVITY * (cosU * cosU)) / (velocity * velocity)) - (float)sin(U); angles[0] = (float)asin(one); if(_isnan(angles[0])) angles[2] = 1.0; angles[1] = (float)PI - angles[0]; if(_isnan(angles[1])) angles[2] = 1.0; angles[0] = RAD2DEG ( (angles[0] - U) / 2.0 ); angles[1] = RAD2DEG ( (angles[1] - U) / 2.0 ); } else { l = (float) sqrt(distH*distH + distV*distV); angles[2] = 0.0; one = l * FAUX_GRAVITY; one = one / (velocity * velocity); angles[0] = (float)asin(one); if(_isnan(angles[0])) angles[2] = 1.0; angles[1] = (float)PI - angles[0]; if(_isnan(angles[1])) angles[2] = 1.0; angles[0] = RAD2DEG ( (angles[0]) / 2.0 ); angles[1] = RAD2DEG ( (angles[1]) / 2.0 ); } } // ==================== // ==================== int stalker_check_lz (edict_t *self, edict_t *target, vec3_t dest) { vec3_t jumpLZ; if( (gi.pointcontents (dest) & MASK_WATER) || (target->waterlevel)) { // gi.dprintf ("you won't make me jump in water!\n"); return false; } if( !target->groundentity ) { // gi.dprintf( "I'll wait until you land..\n"); return false; } // check under the player's four corners // if they're not solid, bail. jumpLZ[0] = self->enemy->mins[0]; jumpLZ[1] = self->enemy->mins[1]; jumpLZ[2] = self->enemy->mins[2] - 0.25; if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) return false; jumpLZ[0] = self->enemy->maxs[0]; jumpLZ[1] = self->enemy->mins[1]; if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) return false; jumpLZ[0] = self->enemy->maxs[0]; jumpLZ[1] = self->enemy->maxs[1]; if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) return false; jumpLZ[0] = self->enemy->mins[0]; jumpLZ[1] = self->enemy->maxs[1]; if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) ) return false; return true; } // ==================== // ==================== int stalker_do_pounce(edict_t *self, vec3_t dest) { vec3_t forward, right; vec3_t dist; vec_t length; vec3_t jumpAngles; vec3_t jumpLZ; float velocity = 400.1; trace_t trace; int preferHighJump; // don't pounce when we're on the ceiling if(STALKER_ON_CEILING(self)) return false; if(!stalker_check_lz (self, self->enemy, dest)) return false; VectorSubtract(dest, self->s.origin, dist); // make sure we're pointing in that direction 15deg margin of error. vectoangles2 (dist, jumpAngles); if(abs(jumpAngles[YAW] - self->s.angles[YAW]) > 45) return false; // not facing the player... self->ideal_yaw = jumpAngles[YAW]; M_ChangeYaw(self); length = VectorLength(dist); if(length > 450) return false; // can't jump that far... VectorCopy(dest, jumpLZ); preferHighJump = 0; // if we're having to jump up a distance, jump a little too high to compensate. if(dist[2] >= 32.0) { preferHighJump = 1; jumpLZ[2] += 32; } trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, dest, self, MASK_MONSTERSOLID); if((trace.fraction < 1) && (trace.ent != self->enemy)) { // gi.dprintf("prefer high jump angle\n"); preferHighJump = 1; } // find a valid angle/velocity combination while(velocity <= 800) { calcJumpAngle(self->s.origin, jumpLZ, velocity, jumpAngles); if((!_isnan(jumpAngles[0])) || (!_isnan(jumpAngles[1]))) break; velocity+=200; }; if(!preferHighJump && (!_isnan(jumpAngles[0])) ) { AngleVectors (self->s.angles, forward, right, NULL); VectorNormalize ( forward ) ; VectorScale( forward, velocity * cos(DEG2RAD(jumpAngles[0])), self->velocity); self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[0])) + (0.5 * sv_gravity->value * FRAMETIME); // gi.dprintf(" pouncing! %0.1f,%0.1f (%0.1f) --> %0.1f, %0.1f, %0.1f\n", // jumpAngles[0], jumpAngles[1], jumpAngles[0], // self->velocity[0], self->velocity[1], self->velocity[2]); return 1; } if(!_isnan(jumpAngles[1])) { AngleVectors (self->s.angles, forward, right, NULL); VectorNormalize ( forward ) ; VectorScale( forward, velocity * cos(DEG2RAD(jumpAngles[1])), self->velocity); self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[1])) + (0.5 * sv_gravity->value * FRAMETIME); // gi.dprintf(" pouncing! %0.1f,%0.1f (%0.1f) --> %0.1f, %0.1f, %0.1f\n", // jumpAngles[0], jumpAngles[1], jumpAngles[1], // self->velocity[0], self->velocity[1], self->velocity[2]); return 1; } // gi.dprintf(" nan\n"); return 0; } // ****************** // DODGE // ****************** //=================== // stalker_jump_straightup //=================== void stalker_jump_straightup (edict_t *self) { if (self->deadflag == DEAD_DEAD) return; if(STALKER_ON_CEILING(self)) { if(stalker_ok_to_transition(self)) { // gi.dprintf("falling off ceiling %d\n", self->health); self->gravityVector[2] = -1; self->s.angles[2] += 180.0; if(self->s.angles[2] > 360.0) self->s.angles[2] -= 360.0; self->groundentity = NULL; } } else if(self->groundentity) // make sure we're standing on SOMETHING... { self->velocity[0] += ((random() * 10) - 5); self->velocity[1] += ((random() * 10) - 5); self->velocity[2] += -400 * self->gravityVector[2]; if(stalker_ok_to_transition(self)) { // gi.dprintf("falling TO ceiling %d\n", self->health); self->gravityVector[2] = 1; self->s.angles[2] = 180.0; self->groundentity = NULL; } } } mframe_t stalker_frames_jump_straightup [] = { ai_move, 1, stalker_jump_straightup, ai_move, 1, stalker_jump_wait_land, ai_move, -1, NULL, ai_move, -1, NULL }; mmove_t stalker_move_jump_straightup = {FRAME_jump04, FRAME_jump07, stalker_frames_jump_straightup, stalker_run}; //=================== // stalker_dodge_jump - abstraction so pain function can trigger a dodge jump too without // faking the inputs to stalker_dodge //=================== void stalker_dodge_jump (edict_t *self) { self->monsterinfo.currentmove = &stalker_move_jump_straightup; } mframe_t stalker_frames_dodge_run [] = { ai_run, 13, NULL, ai_run, 17, NULL, ai_run, 21, NULL, ai_run, 18, monster_done_dodge }; mmove_t stalker_move_dodge_run = {FRAME_run01, FRAME_run04, stalker_frames_dodge_run, NULL}; void stalker_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr) { if (!self->groundentity || self->health <= 0) return; if (!self->enemy) { self->enemy = attacker; FoundTarget(self); return; } // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was // seeing numbers like 13 and 14) if ((eta < 0.1) || (eta > 5)) return; // this will override the foundtarget call of stalker_run stalker_dodge_jump(self); } // ****************** // Jump onto / off of things // ****************** //=================== //=================== void stalker_jump_down (edict_t *self) { vec3_t forward,up; monster_jump_start (self); AngleVectors (self->s.angles, forward, NULL, up); VectorMA(self->velocity, 100, forward, self->velocity); VectorMA(self->velocity, 300, up, self->velocity); } //=================== //=================== void stalker_jump_up (edict_t *self) { vec3_t forward,up; monster_jump_start (self); AngleVectors (self->s.angles, forward, NULL, up); VectorMA(self->velocity, 200, forward, self->velocity); VectorMA(self->velocity, 450, up, self->velocity); } //=================== //=================== void stalker_jump_wait_land (edict_t *self) { if ((random() < (0.3 + (0.1*(float)(skill->value)))) && (level.time >= self->monsterinfo.attack_finished)) { self->monsterinfo.attack_finished = level.time + 0.3; stalker_shoot_attack(self); } if(self->groundentity == NULL) { self->gravity = 1.3; self->monsterinfo.nextframe = self->s.frame; if(monster_jump_finished (self)) { self->gravity = 1; self->monsterinfo.nextframe = self->s.frame + 1; } } else { self->gravity = 1; self->monsterinfo.nextframe = self->s.frame + 1; } } mframe_t stalker_frames_jump_up [] = { ai_move, -8, NULL, ai_move, -8, NULL, ai_move, -8, NULL, ai_move, -8, NULL, ai_move, 0, stalker_jump_up, ai_move, 0, stalker_jump_wait_land, ai_move, 0, NULL }; mmove_t stalker_move_jump_up = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_up, stalker_run }; mframe_t stalker_frames_jump_down [] = { ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, NULL, ai_move, 0, stalker_jump_down, ai_move, 0, stalker_jump_wait_land, ai_move, 0, NULL }; mmove_t stalker_move_jump_down = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_down, stalker_run }; //============ // stalker_jump - this is only used for jumping onto or off of things. for dodge jumping, // use stalker_dodge_jump //============ void stalker_jump (edict_t *self) { if(!self->enemy) return; if(self->enemy->s.origin[2] >= self->s.origin[2]) { // gi.dprintf("stalker_jump_up\n"); self->monsterinfo.currentmove = &stalker_move_jump_up; } else { // gi.dprintf("stalker_jump_down\n"); self->monsterinfo.currentmove = &stalker_move_jump_down; } } // ****************** // Blocked // ****************** qboolean stalker_blocked (edict_t *self, float dist) { qboolean onCeiling; // gi.dprintf("stalker_blocked\n"); if(!has_valid_enemy(self)) return false; onCeiling = false; if(self->gravityVector[2] > 0) onCeiling = true; if(!onCeiling) { if(blocked_checkshot(self, 0.25 + (0.05 * skill->value) )) { // gi.dprintf("blocked: shooting\n"); return true; } if(visible (self, self->enemy)) { // gi.dprintf("blocked: jumping at player!\n"); stalker_do_pounce(self, self->enemy->s.origin); return true; } if(blocked_checkjump (self, dist, 256, 68)) { // gi.dprintf("blocked: jumping up/down\n"); stalker_jump (self); return true; } if(blocked_checkplat (self, dist)) return true; } else { if(blocked_checkshot(self, 0.25 + (0.05 * skill->value) )) { // gi.dprintf("blocked: shooting\n"); return true; } else if(stalker_ok_to_transition(self)) { self->gravityVector[2] = -1; self->s.angles[2] += 180.0; if(self->s.angles[2] > 360.0) self->s.angles[2] -= 360.0; self->groundentity = NULL; // gi.dprintf("falling off ceiling\n"); return true; } // else // gi.dprintf("Not OK to fall!\n"); } return false; } // ****************** // Death // ****************** void stalker_dead (edict_t *self) { VectorSet (self->mins, -28, -28, -18); VectorSet (self->maxs, 28, 28, -4); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; self->nextthink = 0; gi.linkentity (self); // drawbbox(self); } mframe_t stalker_frames_death [] = { ai_move, 0, NULL, ai_move, -5, NULL, ai_move, -10, NULL, ai_move, -20, NULL, ai_move, -10, NULL, ai_move, -10, NULL, ai_move, -5, NULL, ai_move, -5, NULL, ai_move, 0, NULL }; mmove_t stalker_move_death = {FRAME_death01, FRAME_death09, stalker_frames_death, stalker_dead}; void stalker_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) { int n; // gi.dprintf("stalker_die: %d\n", self->health); // dude bit it, make him fall! self->movetype = MOVETYPE_TOSS; self->s.angles[2] = 0; VectorSet(self->gravityVector, 0, 0, -1); // check for gib if (self->health <= self->gib_health) { gi.sound (self, CHAN_VOICE, 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, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; self->monsterinfo.currentmove = &stalker_move_death; } // ****************** // SPAWN // ****************** /*QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof Spider Monster ONROOF - Monster starts sticking to the roof. */ void SP_monster_stalker (edict_t *self) { if (deathmatch->value) { G_FreeEdict (self); return; } sound_pain = gi.soundindex ("stalker/pain.wav"); sound_die = gi.soundindex ("stalker/death.wav"); sound_sight = gi.soundindex("stalker/sight.wav"); sound_punch_hit1 = gi.soundindex ("stalker/melee1.wav"); sound_punch_hit2 = gi.soundindex ("stalker/melee2.wav"); sound_idle = gi.soundindex ("stalker/idle.wav"); // PMM - precache bolt2 gi.modelindex ("models/proj/laser2/tris.md2"); self->s.modelindex = gi.modelindex ("models/monsters/stalker/tris.md2"); VectorSet (self->mins, -28, -28, -18); VectorSet (self->maxs, 28, 28, 18); self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; self->health = 250; self->gib_health = -50; // FIXME self->mass = 250; self->pain = stalker_pain; self->die = stalker_die; self->monsterinfo.stand = stalker_stand; self->monsterinfo.walk = stalker_walk; self->monsterinfo.run = stalker_run; self->monsterinfo.attack = stalker_attack_ranged; self->monsterinfo.sight = stalker_sight; self->monsterinfo.idle = stalker_idle; self->monsterinfo.dodge = stalker_dodge; self->monsterinfo.blocked = stalker_blocked; self->monsterinfo.melee = stalker_attack_melee; gi.linkentity (self); self->monsterinfo.currentmove = &stalker_move_stand; self->monsterinfo.scale = MODEL_SCALE; self->monsterinfo.aiflags |= AI_WALK_WALLS; if(self->spawnflags & 8) { self->s.angles[2] = 180; self->gravityVector[2] = 1; } walkmonster_start (self); }