/*------------------------------------------------------------------- m_imp.c Heretic II Copyright 1998 Raven Software AI: HOVER1 :hovering in midair FLY1 :flying forwards FLYBACK :flying backwards -------------------------------------------------------------------*/ #include "g_local.h" #include "m_imp.h" #include "m_imp_anim.h" #include "Utilities.h" #include "g_DefaultMessageHandler.h" #include "g_monster.h" #include "Random.h" #include "vector.h" #include "fx.h" #include "g_HitLocation.h" #include "g_misc.h" #include "m_stats.h" #include "p_anim_branch.h" #include "g_playstats.h" #include "p_actions.h" #define IMP_CHECK_DIST 128 #define IMP_COLLISION_DIST 148 #define IMP_MIN_SSWOOP_DIST 128 #define IMP_MIN_HOVER_DIST 128 #define IMP_MAX_HOVER_DIST 512 #define IMP_MIN_SWOOP_DIST 108 #define IMP_DRIFT_AMOUNT_X 128 #define IMP_DRIFT_AMOUNT_Y 128 #define IMP_DRIFT_AMOUNT_Z 64 #define IMP_SWOOP_INCR 2 #define IMP_SWOOP_SPEED_MAX 512 #define IMP_PROJECTILE_RADIUS 1024 void imp_blocked (edict_t *self, struct trace_s *trace); /*----------------------------------------------------------------- imp base info -----------------------------------------------------------------*/ static animmove_t *animations[NUM_ANIMS] = { &imp_move_die1, &imp_move_fly1, &imp_move_flyback, &imp_move_hover1, &imp_move_fireball, &imp_move_dive_go, &imp_move_dive_loop, &imp_move_dive_end, &imp_move_dive_out, &imp_move_pain1, &imp_move_tumble, &imp_move_perch, &imp_move_takeoff, &imp_move_dup, &imp_move_ddown, }; static int sounds[NUM_SOUNDS]; static ClassResourceInfo_t resInfo; /*=============================================================== Imp Helper Functions ===============================================================*/ void imp_blocked (edict_t *self, struct trace_s *trace) { vec3_t vf; int damage; if (self->health <= 0) return; if (!trace->ent) return; if(self->curAnimID == ANIM_DIVE_GO || self->curAnimID == ANIM_DIVE_LOOP || self->curAnimID == ANIM_DIVE_END) { if(!stricmp(trace->ent->classname, "player")) { if(!irand(0,4)) P_KnockDownPlayer(&trace->ent->client->playerinfo); } damage = irand(IMP_DMG_MIN, IMP_DMG_MAX); T_Damage (trace->ent, self, self, vf, trace->ent->s.origin, trace->plane.normal, damage, damage*2, 0,MOD_DIED); gi.sound(self, CHAN_BODY, sounds[SND_HIT], 1, ATTN_NORM, 0); if(self->curAnimID != ANIM_DIVE_END) SetAnim(self, ANIM_DIVE_END); } } //Various sound functions void imp_flap_noise(edict_t *self) { gi.sound(self,CHAN_ITEM,sounds[SND_FLAP],1,ATTN_NORM,0); } void imp_death_noise(edict_t *self) { gi.sound(self,CHAN_VOICE,sounds[SND_DEATH],1,ATTN_NORM,0); } void imp_dive_noise(edict_t *self) { gi.sound(self,CHAN_VOICE,sounds[SND_DIVE],1,ATTN_NORM,0); } int imp_check_move(edict_t *self, float dist) { trace_t trace; vec3_t vec, vf; VectorCopy(self->s.origin, vec); AngleVectors(self->s.angles, vf, NULL, NULL); VectorMA(vec, dist, vf, vec); gi.trace(self->s.origin, self->mins, self->maxs, vec, self, MASK_SHOT|MASK_WATER,&trace); if (trace.fraction < 1) { if (trace.ent == self->enemy) return true; return false; } return true; } //replaces ai_walk and ai_run for imp void imp_ai_glide (edict_t *self, float fd, float rd, float ud) { vec3_t vec, vf, vr, nvec; float yaw_delta, roll, dot, rdot; if (!self->enemy) return; //Find our ideal yaw to the player and correct to it VectorSubtract(self->enemy->s.origin, self->s.origin, vec); VectorCopy(vec, nvec); VectorNormalize(nvec); AngleVectors(self->s.angles, vf, vr, NULL); dot = DotProduct(vf, nvec); rdot = DotProduct(vr, nvec); self->ideal_yaw = vectoyaw(vec); M_ChangeYaw(self); yaw_delta = self->ideal_yaw - self->s.angles[YAW]; //If enough, roll the creature to simulate gliding if (Q_fabs(yaw_delta) > self->yaw_speed) { if (dot < 0) { roll = Q_fabs(yaw_delta / 4); } else { roll = yaw_delta / 4; } //Going right? if (roll > 0) { self->s.angles[ROLL] += roll; if (self->s.angles[ROLL] > 65) self->s.angles[ROLL] = 65; } else { self->s.angles[ROLL] += roll; if (self->s.angles[ROLL] < -65) self->s.angles[ROLL] = -65; } } else { self->s.angles[ROLL] *= 0.75; } } void imp_ai_fly (edict_t *self, float fd, float rd, float ud) { vec3_t vec, vf, vr, vu; if (!self->enemy) return; //Add "friction" to the movement to allow graceful flowing motion, not jittering self->velocity[0] *= 0.8; self->velocity[1] *= 0.8; self->velocity[2] *= 0.8; //Find our ideal yaw to the player and correct to it VectorSubtract(self->enemy->s.origin, self->s.origin, vec); self->ideal_yaw = vectoyaw(vec); M_ChangeYaw(self); if (!imp_check_move(self, fd/10)) { SetAnim(self, ANIM_HOVER1); return; } if(self->spawnflags&MSF_FIXED) return; //Add in the movements relative to the creature's facing AngleVectors(self->s.angles, vf, vr, vu); VectorMA(self->velocity, fd, vf, self->velocity); VectorMA(self->velocity, rd, vr, self->velocity); VectorMA(self->velocity, ud, vu, self->velocity); if (self->groundentity) self->velocity[2] += 32; } //replaces ai_stand for imp void imp_ai_hover(edict_t *self, float dist) { vec3_t vec; if (!self->enemy) { if (!FindTarget(self)) return; } //Add "friction" to the movement to allow graceful flowing motion, not jittering self->velocity[0] *= 0.8; self->velocity[1] *= 0.8; self->velocity[2] *= 0.8; //Make sure we're not tilted after a turn self->s.angles[ROLL] *= 0.25; //Find our ideal yaw to the player and correc to it VectorSubtract(self->enemy->s.origin, self->s.origin, vec); self->ideal_yaw = vectoyaw(vec); M_ChangeYaw(self); imp_ai_glide(self,0,0,0); } //receiver for MSG_FLYBACK void imp_flyback(edict_t *self) { SetAnim(self, ANIM_FLYBACK1); return; } void imp_ai_pirch(edict_t *self) { monsterinfo_t *monsterinfo = &self->monsterinfo; vec3_t vec, vf, vr; float dot, len; if (!M_ValidTarget(self, self->enemy)) return; if(!visible(self, self->enemy)) return; VectorSubtract(self->enemy->s.origin, self->s.origin, vec); len = VectorNormalize(vec); if (len < 150) { SetAnim(self, ANIM_TAKEOFF); return; } /* if (irand(0,100) < 10 && self->monsterinfo.attack_finished < level.time) { self->monsterinfo.attack_finished = level.time + 5; if (irand(0,1)) gi.sound (self, CHAN_VOICE, sounds[SND_IDLE1], 1, ATTN_NORM, 0); else gi.sound (self, CHAN_VOICE, sounds[SND_IDLE2], 1, ATTN_NORM, 0); } */ AngleVectors(self->s.angles, vf, vr, NULL); dot = DotProduct(vec, vf); if (dot < 0) { SetAnim(self, ANIM_TAKEOFF); return; } } void move_imp_tumble(edict_t *self) { self->movetype = PHYSICSTYPE_STEP; self->gravity = 1; VectorSet(self->mins, -16, -16, 0); VectorSet(self->maxs, 16, 16, 16); if (!self->avelocity[PITCH] && !self->avelocity[YAW] && !self->avelocity[ROLL]) { self->avelocity[PITCH] = flrand(128.0F, 256.0F); self->avelocity[YAW] = flrand(64.0F, 512.0F); self->avelocity[ROLL] = flrand(64.0F, 512.0F); } if (self->groundentity != NULL || self->monsterinfo.jump_time < level.time) { gi.CreateEffect(&self->s, FX_DUST_PUFF, CEF_OWNERS_ORIGIN, self->s.origin, NULL); VectorCopy(self->s.angles, self->movedir); imp_death_noise(self); SetAnim(self, ANIM_DIE); } } void imp_fix_angles(edict_t *self) { float pitch_delta, roll_delta; pitch_delta = self->movedir[PITCH]; roll_delta = self->movedir[ROLL]; if (pitch_delta > 0) { self->s.angles[PITCH] -= pitch_delta / 2; if (self->s.angles[PITCH] < 2) { self->s.angles[PITCH] = 0; } } else { self->s.angles[PITCH] += pitch_delta / 2; if (self->s.angles[PITCH] > 2) { self->s.angles[PITCH] = 0; } } //Roll if (roll_delta > 0) { self->s.angles[ROLL] -= roll_delta / 2; if (self->s.angles[ROLL] < 2) { self->s.angles[ROLL] = 0; } } else { self->s.angles[ROLL] += roll_delta / 15; if (self->s.angles[ROLL] > 2) { self->s.angles[ROLL] = 0; } } } /*=============================================================== Imp Message Functions ===============================================================*/ void imp_death_pain (edict_t *self, G_Message_t *msg) { if(self->health <= -40) //gib death { BecomeDebris(self); return; } } //receiver for MSG_DEATH void imp_die(edict_t *self, G_Message_t *msg) { if(self->monsterinfo.aiflags&AI_DONT_THINK) { SetAnim(self, ANIM_DIE); return; } self->movetype = PHYSICSTYPE_STEP; self->gravity = 1; self->elasticity = 1.1; VectorSet(self->mins, -16, -16, 0); VectorSet(self->maxs, 16, 16, 16); if(self->health <= -40) //gib death { BecomeDebris(self); self->think = NULL; self->nextthink = 0; gi.linkentity (self); return; } self->msgHandler = DeadMsgHandler; SetAnim(self, ANIM_DIE); return; } //receiver for MSG_PAIN void imp_pain(edict_t *self, G_Message_t *msg) { int temp, damage; qboolean force_pain; ParseMsgParms(msg, "eeiii", &temp, &temp, &force_pain, &damage, &temp); if (self->curAnimID == ANIM_PERCH) { SetAnim(self, ANIM_TAKEOFF); return; } if (force_pain||((irand(0,10) < 2) && (self->pain_debounce_time < level.time))) { /* if (irand(0,1)) imp_pain1_noise(self); else imp_pain2_noise(self);*/ self->pain_debounce_time = level.time + 2; if(self->curAnimID == ANIM_DIVE_GO || self->curAnimID == ANIM_DIVE_LOOP) SetAnim(self, ANIM_DIVE_END); else SetAnim(self, ANIM_PAIN1); } } //receiver for MSG_STAND, MSG_HOVER //FIXME -- is MSG_HOVER redundant? void imp_hover(edict_t *self, G_Message_t *msg) { if (self->curAnimID == ANIM_PERCH) return; SetAnim(self, ANIM_HOVER1); } void imp_stand(edict_t *self, G_Message_t *msg) { if (self->spawnflags & MSF_PERCHING) return; SetAnim(self, ANIM_HOVER1); } void imp_fly(edict_t *self, G_Message_t *msg) { SetAnim(self, ANIM_FLY1); } void imp_perch(edict_t *self, G_Message_t *msg) { SetAnim(self, ANIM_PERCH); } void imp_hit(edict_t *self, float stop_swoop) { trace_t trace; edict_t *victim; vec3_t vf;//, hitPos, mins, maxs; float movedist, damage; AngleVectors(self->s.angles, vf, NULL, NULL); movedist = VectorLength(self->velocity); victim = M_CheckMeleeHit( self, movedist, &trace); if (victim) { if (victim == self && stop_swoop) { SetAnim(self, ANIM_DIVE_OUT); } else { damage = irand(IMP_DMG_MIN, IMP_DMG_MAX); gi.sound(self, CHAN_BODY, sounds[SND_HIT], 1, ATTN_NORM, 0); T_Damage (victim, self, self, vf, self->enemy->s.origin, trace.plane.normal, damage, damage*2, 0,MOD_DIED); SetAnim(self, ANIM_DIVE_END); } } } void imp_pause (edict_t *self) { if (M_ValidTarget(self, self->enemy)) QPostMessage(self, MSG_RUN, PRI_DIRECTIVE, NULL); } //end of anim func for death anim void imp_dead(edict_t *self) { VectorSet(self->mins, -16, -16, 0); VectorSet(self->maxs, 16, 16, 16); M_EndDeath(self); } qboolean imp_check_directions(edict_t *self, vec3_t goal, vec3_t vf, vec3_t vr, vec3_t vu, float checkdist, vec3_t ret) { trace_t trace; vec3_t goalpos; //Check right and left first VectorCopy(self->s.origin, goalpos); //Don't always check one direction first (looks mechanical) if (irand(0,1)) VectorScale(vr, -1, vr); VectorMA(goalpos, checkdist, vr, goalpos); gi.trace(goalpos, self->mins, self->maxs, goal, self, MASK_SHOT|MASK_WATER,&trace); //We've found somewhere to go if (trace.ent == self->enemy) { VectorCopy(vr, ret); return true; } else //Check the other directions { VectorScale(vr, -1, vr); VectorMA(goalpos, checkdist, vr, goalpos); gi.trace(goalpos, self->mins, self->maxs, goal, self, MASK_SHOT|MASK_WATER,&trace); if (trace.ent == self->enemy) { VectorCopy(vr, ret); return true; } } //Check up and down VectorCopy(self->s.origin, goalpos); //Don't always check one direction first (looks mechanical) if (irand(0,1)) VectorScale(vu, -1, vu); VectorMA(goalpos, checkdist, vu, goalpos); gi.trace(goalpos, self->mins, self->maxs, goal, self, MASK_SHOT|MASK_WATER,&trace); //We've found somewhere to go if (trace.ent == self->enemy) { VectorCopy(vu, ret); return true; } else //Check the other directions { VectorScale(vu, -1, vu); VectorMA(goalpos, checkdist, vu, goalpos); gi.trace(goalpos, self->mins, self->maxs, goal, self, MASK_SHOT|MASK_WATER,&trace); if (trace.ent == self->enemy) { VectorCopy(vu, ret); return true; } } //Check forward and back VectorCopy(self->s.origin, goalpos); //Don't always check one direction first (looks mechanical) if (irand(0,1)) VectorScale(vf, -1, vf); VectorMA(goalpos, checkdist, vf, goalpos); gi.trace(goalpos, self->mins, self->maxs, goal, self, MASK_SHOT|MASK_WATER,&trace); //We've found somewhere to go if (trace.ent == self->enemy) { VectorCopy(vf, ret); return true; } else //Check the other directions { VectorScale(vf, -1, vf); VectorMA(goalpos, checkdist, vf, goalpos); gi.trace(goalpos, self->mins, self->maxs, goal, self, MASK_SHOT|MASK_WATER,&trace); if (trace.ent == self->enemy) { VectorCopy(vf, ret); return true; } } return false; } qboolean imp_check_swoop(edict_t *self, vec3_t goal) { trace_t trace; vec3_t checkpos; float zd; //Find the difference in the target's height and the creature's height zd = Q_fabs(self->enemy->s.origin[2] - self->s.origin[2]); if (zd < IMP_MIN_SSWOOP_DIST) return false; zd -= zd/4; VectorCopy(self->s.origin, checkpos); checkpos[2] -= zd; //Trace down about that far and about one forth the distance to the target gi.trace(self->s.origin, self->mins, self->maxs, checkpos, self, MASK_SHOT|MASK_WATER,&trace); if (trace.fraction < 1) { //gi.dprintf("imp_check_swoop: failed down check\n"); return false; } //Trace straight to the target gi.trace(checkpos, self->mins, self->maxs, goal, self, MASK_SHOT|MASK_WATER,&trace); if (trace.ent != self->enemy) { //gi.dprintf("imp_check_swoop: failed out check\n"); return false; } //There's a clear path return true; } void move_imp_dive(edict_t *self) { vec3_t vec, vf; float dist, zd, hd, forward; //Find out the Z and Horizontal deltas to target zd = Q_fabs(self->s.origin[2] - self->enemy->s.origin[2]); AngleVectors(self->s.angles, vf, NULL, NULL); VectorCopy(self->s.origin, vec); vec[2] = self->enemy->s.origin[2]; VectorSubtract(self->enemy->s.origin, vec, vec); hd = VectorLength(vec); if ((self->groundentity != NULL) || (!imp_check_move(self, 64))) { if (self->groundentity == self->enemy) imp_hit(self, true); return; } dist = Q_fabs(self->s.origin[2] - self->enemy->s.origin[2]); forward = (256 - (dist*0.85)); if (forward > 256) forward = 256; else if (forward < 0) forward = 0; // if (dist > IMP_MIN_SWOOP_DIST) // { VectorMA(vf, forward, vf, self->velocity); self->velocity[2] = -dist*2.25; if (self->velocity[2] < -300) self->velocity[2] = -300; // } /* else { SetAnim(self, ANIM_DIVE_TRANS); return; }*/ imp_ai_glide(self, 0, 0, 0); } void move_imp_dive_end(edict_t *self) { vec3_t vec, vf, vr, vu, nvec; float hd, fd, dot; VectorCopy(self->s.origin, vec); vec[2] = self->enemy->s.origin[2]; VectorSubtract(self->enemy->s.origin, vec, vec); hd = VectorLength(vec); self->ideal_yaw = vectoyaw(vec); M_ChangeYaw(self); AngleVectors(self->s.angles, vf, vr, vu); self->velocity[2] *= 0.75; self->monsterinfo.jump_time *= IMP_SWOOP_INCR; fd = self->monsterinfo.jump_time; if (fd > IMP_SWOOP_SPEED_MAX) fd = IMP_SWOOP_SPEED_MAX; if ((self->groundentity != NULL) || (!imp_check_move(self, 128))) { if (self->groundentity == self->enemy) SetAnim(self, ANIM_DIVE_END); SetAnim(self, ANIM_FLYBACK1); return; } VectorSubtract(self->enemy->s.origin, self->s.origin, vec); VectorCopy(vec, nvec); VectorNormalize(nvec); AngleVectors(self->s.angles, vf, vr, NULL); dot = DotProduct(vf, nvec); if (dot < -0.5) { SetAnim(self, ANIM_FLYBACK1); return; } VectorMA(self->velocity, fd, vf, self->velocity); //Are we about to hit the target? /* VectorSubtract(self->enemy->s.origin, self->s.origin, vec); dist = VectorLength(vec); if (dist < IMP_COLLISION_DIST) { SetAnim(self, ANIM_DIVE_END); return; } */ imp_ai_glide(self, 0, 0, 0); } void imp_dive_loop(edict_t *self) { SetAnim(self, ANIM_DIVE_LOOP); } void imp_check_dodge(edict_t *self) { qboolean dodge = false; trace_t trace; edict_t *ent = NULL; vec3_t vec, vr, projvec, ddir, goalpos, vu; float dodgedot; qboolean vert = false; if (!self->enemy) return; if(self->spawnflags&MSF_FIXED) return; VectorSubtract(self->enemy->s.origin, self->s.origin, vec); VectorNormalize(vec); while ((ent = findradius(ent, self->s.origin, IMP_PROJECTILE_RADIUS)) != NULL) { //We're only interested in his projectiles if (ent->owner != self->enemy) continue; VectorCopy(ent->velocity, projvec); VectorNormalize(projvec); dodgedot = DotProduct(projvec, vec); //gi.dprintf("Found projectile with dot %f\n", dodgedot); if (dodgedot < -0.85 && irand(0,1)) { //gi.dprintf("Dodge it!\n"); dodge = true; AngleVectors(self->s.angles, NULL, vr, vu); VectorCopy(self->s.origin, goalpos); if (irand(0,1)) { if (irand(0,1)) VectorScale(vr, -1, ddir); else VectorScale(vr, 1, ddir); } else { vert = true; if (irand(0,1)) VectorScale(vu, -1, ddir); else VectorScale(vu, 1, ddir); } VectorMA(goalpos, 100, ddir, goalpos); gi.trace(self->s.origin, self->mins, self->maxs, goalpos, self, MASK_SHOT|MASK_WATER,&trace); if (trace.fraction < 1)//bad dir, try other VectorScale(ddir, -1, ddir); if(vert) {//ok, better check this new opposite dir gi.trace(self->s.origin, self->mins, self->maxs, goalpos, self, MASK_SHOT|MASK_WATER,&trace); if (trace.fraction < 1) {//uh-oh, let's go for a side dir if (irand(0,1)) VectorScale(vr, 1, ddir); else VectorScale(vr, -1, ddir); } gi.trace(self->s.origin, self->mins, self->maxs, goalpos, self, MASK_SHOT|MASK_WATER,&trace); if (trace.fraction < 1)//what the hell, just go the other way VectorScale(ddir, -1, ddir); } } } if (dodge) { //If he is, dodge! if (self->monsterinfo.misc_debounce_time < level.time) { if(self->curAnimID!=ANIM_FIREBALL) { if(ddir[2] > 0.1) SetAnim(self, ANIM_DUP); else if(ddir[2] < -0.1) SetAnim(self, ANIM_DDOWN); } VectorMA(self->velocity, irand(300, 500), ddir, self->velocity); self->monsterinfo.misc_debounce_time = level.time + irand(2,4); } } imp_ai_glide(self, 0, 0, 0); } void move_imp_hover(edict_t *self) { qboolean canmove = false, dodge = false; trace_t trace; edict_t *ent = NULL; vec3_t goal, dodgedir, mins, maxs, vf, vr, vu, vec, projvec, goalpos; float dist, zd, dodgedot, enemy_dist; if (!self->enemy) { if (!FindTarget(self)) { return; } } //First check to see that the player is at least 128 units away in (discounting z height) VectorCopy(self->enemy->s.origin, goal); goal[2] = self->s.origin[2]; VectorSubtract(goal, self->s.origin, goal); dist = VectorLength(goal); //Face target self->ideal_yaw = vectoyaw(goal); M_ChangeYaw(self); //If he is... if (dist > IMP_MIN_HOVER_DIST && dist < IMP_MAX_HOVER_DIST) { //Make sure we've got line of sight VectorSet(mins, -1, -1, -1); VectorSet(maxs, 1, 1, 1); gi.trace(self->s.origin, mins, maxs, self->enemy->s.origin, self, MASK_SHOT|MASK_WATER,&trace); //If not, try looking from a bit to the side in all six directions if (trace.ent != self->enemy) { //Setup the directions AngleVectors(self->s.angles, vf, vr, vu); canmove = imp_check_directions(self, self->enemy->s.origin, vf, vr, vu, IMP_CHECK_DIST, goal); //If we can see him from one of these, go there if (canmove) { VectorMA(self->velocity, flrand(300.0F, 400.0F), goal, self->velocity); return; } //Otherwise just flap around and wait, perhaps lower yourself a bit if high up self->velocity[0] = flrand(-IMP_DRIFT_AMOUNT_X, IMP_DRIFT_AMOUNT_X); self->velocity[1] = flrand(-IMP_DRIFT_AMOUNT_Y, IMP_DRIFT_AMOUNT_Y); self->velocity[2] = flrand(-IMP_DRIFT_AMOUNT_Z, IMP_DRIFT_AMOUNT_Z); return; } else { //Check to make sure the player isn't shooting anything //This won't change over the calculations VectorSubtract(self->enemy->s.origin, self->s.origin, vec); enemy_dist = VectorNormalize(vec); while ((ent = findradius(ent, self->s.origin, IMP_PROJECTILE_RADIUS)) != NULL) { //We're only interested in his projectiles if (ent->owner != self->enemy) continue; VectorCopy(ent->velocity, projvec); VectorNormalize(projvec); dodgedot = DotProduct(projvec, vec); //gi.dprintf("Found projectile with dot %f\n", dodgedot); if (dodgedot < -0.6) { //gi.dprintf("Dodge it!\n"); dodge = true; AngleVectors(self->s.angles, NULL, vr, NULL); if (irand(0,1)) VectorScale(vr, -1, vr); VectorMA(self->s.origin, 100, vr, goalpos); gi.trace(self->s.origin, self->mins, self->maxs, goalpos, self, MASK_SHOT|MASK_WATER,&trace); if (trace.fraction < 1) VectorScale(vr, -1, vr); VectorCopy(vr, dodgedir); } } if (dodge) { //If he is, dodge! VectorMA(self->velocity, irand(300, 500), dodgedir, self->velocity); return; } //see if he's too close if(enemy_dist < Q_fabs(self->melee_range)) SetAnim(self, ANIM_FLYBACK1); else if(enemy_dist < self->missile_range) {//see if we can and want to attack him if(enemy_dist > self->min_missile_range) { if(flrand(0, 100) > self->bypass_missile_chance) { SetAnim(self, ANIM_FIREBALL); return; } } } //If nothing is happening, check to swoop canmove = imp_check_swoop(self, self->enemy->s.origin); //If you can--nail um if (canmove) { self->monsterinfo.jump_time = 2; SetAnim(self, ANIM_DIVE_GO); return; } //If not, check to see if there's somewhere that you can get to that will allow it //FIXME: Too many checks.. just try something simple //If all else fails, then just pick a random direction to nudge yourself to else { //Find the difference in the target's height and the creature's height zd = Q_fabs(self->enemy->s.origin[2] - self->s.origin[2]); //We can't swoop because we're too low, so fly upwards if possible if (zd < IMP_MIN_SSWOOP_DIST) { if (!imp_check_move(self, -64)) { SetAnim(self, ANIM_FLY1); return; } else { //gi.dprintf("Moveback ok\n"); SetAnim(self, ANIM_FLYBACK1); return; } } else { //Otherwise just flap around and wait, perhaps lower yourself a bit if high up self->velocity[0] = flrand(-IMP_DRIFT_AMOUNT_X, IMP_DRIFT_AMOUNT_X); self->velocity[1] = flrand(-IMP_DRIFT_AMOUNT_Y, IMP_DRIFT_AMOUNT_Y); self->velocity[2] = flrand(-IMP_DRIFT_AMOUNT_Z, IMP_DRIFT_AMOUNT_Z); AngleVectors(self->s.angles, vec, NULL, NULL); VectorMA(self->velocity, irand(200,300), vec, self->velocity); } return; } } //If he's too far away trace a line (expanded) to see if you can move at him } else if (dist < IMP_MIN_HOVER_DIST) { if (!imp_check_move(self, -64)) { SetAnim(self, ANIM_FLY1); } else { SetAnim(self, ANIM_FLYBACK1); } } else { if (!imp_check_move(self, 64)) { SetAnim(self, ANIM_FLYBACK1); } else { SetAnim(self, ANIM_FLY1); } } return; } //New physics call that modifies the imp's velocity and angles based on aerodynamics void imp_flight_model(edict_t *self) { } void move_imp_fly(edict_t *self) { edict_t *dummy; dummy = self; if(!irand(0,3)) imp_check_dodge(self); return; } void move_imp_die(edict_t *self) { //fall to the floor return; } void imp_hover_anim(edict_t *self) { SetAnim(self, ANIM_HOVER1); } //=============================== //IMP FIREBALL //=============================== void FireFizzle (edict_t *self) { vec3_t dir; gi.sound (self, CHAN_BODY, sounds[SND_FIZZLE], 1, ATTN_NORM, 0); VectorSet(dir, flrand(0, 1),flrand(0, 1), flrand(0.5, 1)); VectorNormalize(dir); gi.CreateEffect(&self->s, FX_ENVSMOKE, CEF_BROADCAST,self->s.origin, "bdbbb",irand(1,3),dir,irand(1,2),irand(3, 4),irand(1,2)); G_SetToFree(self); } void fireball_blocked( edict_t *self, trace_t *trace ); void create_imp_proj(edict_t *self,edict_t *proj) { proj->svflags |= SVF_ALWAYS_SEND; proj->movetype = PHYSICSTYPE_FLY; proj->gravity = 0; proj->solid = SOLID_BBOX; proj->classname = "imp fireball"; proj->s.scale = 1.0; proj->clipmask = (MASK_SHOT|CONTENTS_WATER); proj->s.effects=EF_MARCUS_FLAG1; proj->enemy = self->enemy; proj->reflect_debounce_time = MAX_REFLECT; proj->isBlocked = fireball_blocked; proj->isBlocking = fireball_blocked; proj->bounced = fireball_blocked; VectorSet(proj->mins, -1.0, -1.0, -1.0); VectorSet(proj->maxs, 1.0, 1.0, 1.0); VectorCopy(self->s.origin, proj->s.origin); } edict_t *ImpFireballReflect(edict_t *self, edict_t *other, vec3_t vel) { edict_t *fireball; fireball = G_Spawn(); create_imp_proj(self, fireball); fireball->s.modelindex = self->s.modelindex; VectorCopy(self->s.origin, fireball->s.origin); fireball->owner = other; fireball->enemy = self->owner; fireball->nextthink=self->nextthink; VectorScale(self->avelocity, -0.5, fireball->avelocity); VectorCopy(vel, fireball->velocity); VectorNormalize2(vel, fireball->movedir); AnglesFromDir(fireball->movedir, fireball->s.angles); fireball->classID = self->classID; fireball->reflect_debounce_time = self->reflect_debounce_time -1; fireball->reflected_time=self->reflected_time; fireball->ideal_yaw = self->ideal_yaw; gi.CreateEffect(&fireball->s, FX_M_EFFECTS, CEF_OWNERS_ORIGIN, NULL, "bv", FX_IMP_FIRE, fireball->velocity); G_LinkMissile(fireball); G_SetToFree(self); gi.CreateEffect(&fireball->s, FX_LIGHTNING_HIT, CEF_OWNERS_ORIGIN, NULL, "t", vel); return(fireball); } void fireball_blocked( edict_t *self, trace_t *trace ) { if(trace->surface) { if(trace->surface->flags & SURF_SKY) { SkyFly(self); return; } } if(trace->contents&CONTENTS_WATER || trace->contents&CONTENTS_SLIME) { FireFizzle(self); return; } if(trace->ent) { if (EntReflecting(trace->ent, true, true) && self->reflect_debounce_time) { Create_rand_relect_vect(self->velocity, self->velocity); Vec3ScaleAssign(self->ideal_yaw, self->velocity); ImpFireballReflect(self, trace->ent, self->velocity); return; } } if (trace->ent->takedamage ) { vec3_t hitDir; float damage = flrand(2,5); if(self->dmg) damage += self->dmg; VectorCopy( self->velocity, hitDir ); VectorNormalize( hitDir ); T_Damage(trace->ent, self, self->owner, hitDir, self->s.origin, trace->plane.normal, damage, 0, DAMAGE_SPELL | DAMAGE_NO_KNOCKBACK,MOD_DIED); } gi.sound(self, CHAN_BODY, sounds[SND_FBHIT], 1, ATTN_NORM, 0); gi.CreateEffect(&self->s, FX_M_EFFECTS, CEF_OWNERS_ORIGIN, self->s.origin, "bv", FX_IMP_FBEXPL, vec3_origin); G_SetToFree(self); } void imp_fireball (edict_t *self) { edict_t *proj; vec3_t vf, vr, check_lead; // Spawn the projectile proj = G_Spawn(); create_imp_proj(self,proj); proj->reflect_debounce_time = MAX_REFLECT; proj->owner = self; proj->dmg = irand(10, 20); AngleVectors(self->s.angles, vf, vr, NULL); if(self->classID == CID_IMP) { VectorMA(self->s.origin, -4*self->monsterinfo.scale, vf, proj->s.origin); VectorMA(proj->s.origin, 16*self->monsterinfo.scale, vr, proj->s.origin); proj->s.origin[2] += 32*self->monsterinfo.scale; gi.sound(proj,CHAN_BODY,sounds[SND_ATTACK],1,ATTN_NORM,0); } else { VectorCopy(self->s.origin, proj->s.origin); VectorMA(proj->s.origin, 16, vf, proj->s.origin); proj->s.origin[2] += 12; gi.sound(proj, CHAN_BODY, gi.soundindex("monsters/imp/fireball.wav"), 1, ATTN_NORM, 0); } extrapolateFiredir (self, proj->s.origin, 666, self->enemy, 0.3, check_lead); if(Vec3IsZero(check_lead)) { VectorScale(vf, 666, proj->velocity); } else { VectorScale(check_lead, 666, proj->velocity); } VectorCopy(proj->velocity, proj->movedir); VectorNormalize(proj->movedir); vectoangles(proj->movedir, proj->s.angles); gi.CreateEffect(&proj->s, FX_M_EFFECTS,//just so I don't have to make a new FX_ id CEF_OWNERS_ORIGIN, NULL, "bv", FX_IMP_FIRE, proj->velocity); gi.linkentity(proj); } /*=============================================================== Imp Spawn Functions ===============================================================*/ void ImpStaticsInit() { classStatics[CID_IMP].msgReceivers[MSG_DEATH] = imp_die; classStatics[CID_IMP].msgReceivers[MSG_FLY] = imp_hover; classStatics[CID_IMP].msgReceivers[MSG_STAND] = imp_stand; classStatics[CID_IMP].msgReceivers[MSG_RUN] = imp_hover; classStatics[CID_IMP].msgReceivers[MSG_PAIN] = imp_pain; classStatics[CID_IMP].msgReceivers[MSG_WATCH] = imp_perch; classStatics[CID_IMP].msgReceivers[MSG_DEATH_PAIN] = imp_death_pain; resInfo.numAnims = NUM_ANIMS; resInfo.animations = animations; resInfo.modelIndex = gi.modelindex("models/monsters/imp/tris.fm"); resInfo.numSounds = NUM_SOUNDS; resInfo.sounds = sounds; sounds[SND_GIB]=gi.soundindex("misc/fleshbreak.wav"); sounds[SND_FLAP]=gi.soundindex("monsters/imp/fly.wav"); sounds[SND_SCREAM]=gi.soundindex("monsters/imp/up.wav"); sounds[SND_DIVE]=gi.soundindex("monsters/imp/swoop.wav"); sounds[SND_DEATH]=gi.soundindex("monsters/imp/die.wav"); sounds[SND_HIT]=gi.soundindex("monsters/imp/swoophit.wav"); sounds[SND_ATTACK]=gi.soundindex("monsters/imp/fireball.wav"); sounds[SND_FIZZLE]=gi.soundindex("monsters/imp/fout.wav"); sounds[SND_FBHIT]=gi.soundindex("monsters/imp/fbfire.wav"); classStatics[CID_IMP].resInfo = &resInfo; } /*QUAKED monster_imp(1 .5 0) (-16 -16 0) (16 16 32) AMBUSH ASLEEP Perching 8 16 32 64 FIXED Our old pal, the fire imp! AMBUSH - Will not be woken up by other monsters or shots from player ASLEEP - will not appear until triggered PERCHING - Will watch player until get too close or get behind the imp "wakeup_target" - monsters will fire this target the first time it wakes up (only once) "pain_target" - monsters will fire this target the first time it gets hurt (only once) mintel - monster intelligence- this basically tells a monster how many buoys away an enemy has to be for it to give up. melee_range - How close the player has to be, maximum, for the monster to go into melee. If this is zero, the monster will never melee. If it is negative, the monster will try to keep this distance from the player. If the monster has a backup, he'll use it if too clode, otherwise, a negative value here means the monster will just stop running at the player at this distance. Examples: melee_range = 60 - monster will start swinging it player is closer than 60 melee_range = 0 - monster will never do a mele attack melee_range = -100 - monster will never do a melee attack and will back away (if it has that ability) when player gets too close missile_range - Maximum distance the player can be from the monster to be allowed to use it's ranged attack. min_missile_range - Minimum distance the player can be from the monster to be allowed to use it's ranged attack. bypass_missile_chance - Chance that a monster will NOT fire it's ranged attack, even when it has a clear shot. This, in effect, will make the monster come in more often than hang back and fire. A percentage (0 = always fire/never close in, 100 = never fire/always close in).- must be whole number jump_chance - every time the monster has the opportunity to jump, what is the chance (out of 100) that he will... (100 = jump every time)- must be whole number wakeup_distance - How far (max) the player can be away from the monster before it wakes up. This just means that if the monster can see the player, at what distance should the monster actually notice him and go for him. DEFAULTS: mintel = 14 melee_range = -64 missile_range = 1024 min_missile_range = 32 bypass_missile_chance = 20 jump_chance = 0 (flying, no jump) wakeup_distance = 1024 NOTE: A value of zero will result in defaults, if you actually want zero as the value, use -1 */ void SP_monster_imp(edict_t *self) { if (!flymonster_start(self)) return; // Failed initialization self->msgHandler = DefaultMsgHandler; self->classID = CID_IMP; if (!self->health) self->health = IMP_HEALTH; self->max_health = self->health = MonsterHealth(self->health); self->mass = IMP_MASS; self->yaw_speed = 14; self->movetype = PHYSICSTYPE_FLY; self->gravity = 0; self->flags |= FL_FLY; self->solid = SOLID_BBOX; self->clipmask = MASK_MONSTERSOLID; VectorCopy(STDMinsForClass[self->classID], self->mins); VectorCopy(STDMaxsForClass[self->classID], self->maxs); self->svflags |= SVF_TAKE_NO_IMPACT_DMG; self->materialtype = MAT_FLESH; self->s.modelindex = classStatics[CID_IMP].resInfo->modelIndex; self->s.skinnum = 0; self->isBlocked = imp_blocked; if (!self->s.scale) self->monsterinfo.scale = self->s.scale = flrand(0.7, 1.2); self->monsterinfo.otherenemyname = "monster_rat"; if (self->spawnflags & MSF_PERCHING) { SetAnim(self, ANIM_PERCH); } else { QPostMessage(self, MSG_STAND, PRI_DIRECTIVE, NULL); } if(!self->melee_range) self->melee_range = AttackRangesForClass[self->classID * 4 + 0]; if(!self->missile_range) self->missile_range = AttackRangesForClass[self->classID * 4 + 1]; if(!self->min_missile_range) self->min_missile_range = AttackRangesForClass[self->classID * 4 + 2]; if(!self->bypass_missile_chance) self->bypass_missile_chance = AttackRangesForClass[self->classID * 4 + 3]; if(!self->jump_chance) self->jump_chance = JumpChanceForClass[self->classID]; if(!self->wakeup_distance) self->wakeup_distance = MAX_SIGHT_PLAYER_DIST; }