/* A monster is in fight mode if it thinks it can effectively attack its enemy. When it decides it can't attack, it goes into hunt mode. */ float(float v) anglemod; void() knight_atk1; void() knight_runatk1; void() ogre_smash1; void() ogre_swing1; void() sham_smash1; void() sham_swingr1; void() sham_swingl1; float() DemonCheckAttack; void(float side) Demon_Melee; void() ai_face; float enemy_vis, enemy_infront, enemy_range; float enemy_yaw; entity sight_entity; float sight_entity_time; .vector enemy_last; //============================================================================= /* =========== CheckAttack The player is in view, so decide to move or launch an attack Returns FALSE if movement should continue ============ */ float() CheckAttack = { local vector spot1, spot2; local entity targ; local float chance; targ = self.enemy; // see if any entities are in the way of the shot spot1 = self.origin + self.view_ofs; spot2 = targ.origin + targ.view_ofs; traceline (spot1, spot2, FALSE, self); if (trace_ent != targ) return FALSE; // don't have a clear shot if (trace_inopen && trace_inwater) return FALSE; // sight line crossed contents if (enemy_range == RANGE_MELEE) { // melee attack if (self.th_melee) { self.th_melee (); return TRUE; } } // missile attack if (!self.th_missile) return FALSE; if (time < self.attack_finished) return FALSE; if (enemy_range == RANGE_FAR) return FALSE; if (enemy_range == RANGE_MELEE) { chance = 0.9; self.attack_finished = 0; } else if (enemy_range == RANGE_NEAR) { if (self.th_melee) chance = 0.2; else chance = 0.4; } else if (enemy_range == RANGE_MID) { if (self.th_melee) chance = 0.05; else chance = 0.1; } else chance = 0; if (random () < chance) { self.th_missile (); SUB_AttackFinished (2*random()); return TRUE; } return FALSE; }; /* ============= ai_face Stay facing the enemy ============= */ void() ai_face = { self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); changeyaw(); }; void() ai_face_last = { self.ideal_yaw = vectoyaw(self.enemy_last - self.origin); changeyaw(); }; /* ============= ai_charge The monster is in a melee attack, so get as close as possible to .enemy ============= */ float (entity targ) visible; float(entity targ) infront; float(entity targ) range; void(float d) ai_charge = { ai_face (); walkmove(self.angles_y, d); }; void() ai_charge_side = { local vector dtemp; local float heading; // aim to the left of the enemy for a flyby self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); changeyaw (); makevectors (self.angles); dtemp = self.enemy.origin - 30*v_right; heading = vectoyaw(dtemp - self.origin); walkmove(heading, 20); }; /* ============= ai_melee ============= */ void() ai_melee = { local vector delta; local float ldmg; if (!self.enemy) return; // removed before stroke delta = self.enemy.origin - self.origin; if (vlen(delta) > 60) return; ldmg = (random() + random() + random()) * 3; Damage (self.enemy, self, self, ldmg); }; void() ai_melee_side = { local vector delta; local float ldmg; if (!self.enemy) return; // removed before stroke ai_charge_side(); delta = self.enemy.origin - self.origin; if (vlen(delta) > 60) return; if (!CanDamage (self.enemy, self)) return; ldmg = (random() + random() + random()) * 3; Damage (self.enemy, self, self, ldmg); }; //============================================================================= /* =========== SoldierCheckAttack The player is in view, so decide to move or launch an attack Returns FALSE if movement should continue ============ */ float() SoldierCheckAttack = { local vector spot1, spot2; local entity targ; local float chance; targ = self.enemy; // see if any entities are in the way of the shot spot1 = self.origin + self.view_ofs; spot2 = targ.origin + targ.view_ofs; traceline (spot1, spot2, FALSE, self); if (trace_inopen && trace_inwater) return FALSE; // sight line crossed contents if (trace_ent != targ) return FALSE; // don't have a clear shot // missile attack if (time < self.attack_finished) return FALSE; if (enemy_range == RANGE_FAR) return FALSE; if (enemy_range == RANGE_MELEE) chance = 0.9; else if (enemy_range == RANGE_NEAR) chance = 0.4; else if (enemy_range == RANGE_MID) chance = 0.05; else chance = 0; if (random () < chance) { self.attack_state = AS_MISSILE; SUB_AttackFinished (1 + random()); return TRUE; } return FALSE; }; //============================================================================= /* =========== ShamCheckAttack The player is in view, so decide to move or launch an attack Returns FALSE if movement should continue ============ */ float() ShamCheckAttack = { local vector spot1, spot2; local entity targ; //local float enemy_yaw; if (enemy_range == RANGE_MELEE) { if (CanDamage (self.enemy, self)) { self.attack_state = AS_MELEE; return TRUE; } } if (time < self.attack_finished) return FALSE; if (!enemy_vis) return FALSE; targ = self.enemy; // see if any entities are in the way of the shot spot1 = self.origin + self.view_ofs; spot2 = targ.origin + targ.view_ofs; if (vlen(spot1 - spot2) > 600) return FALSE; traceline (spot1, spot2, FALSE, self); if (trace_inopen && trace_inwater) return FALSE; // sight line crossed contents if (trace_ent != targ) { return FALSE; // don't have a clear shot } // missile attack if (enemy_range == RANGE_FAR) return FALSE; self.attack_state = AS_MISSILE; SUB_AttackFinished (2 + 2*random()); return TRUE; }; //============================================================================ /* =========== OgreCheckAttack The player is in view, so decide to move or launch an attack Returns FALSE if movement should continue ============ */ float() OgreCheckAttack = { if (enemy_range == RANGE_MELEE) { if (CanDamage (self.enemy, self)) { self.attack_state = AS_MELEE; return TRUE; } } if (time < self.attack_finished) return FALSE; // aim at last known location on hard and nightmare if (!enemy_vis) { if (skill > 1) { if (time < self.search_time) { vector spot1 = self.origin; vector spot2 = self.enemy_last; traceline(spot1, spot2, MOVE_MISSILE, self); if (trace_ent != world) if (trace_ent.solid != SOLID_BSP) return FALSE; float chance = 0; if (vlen(self.origin - self.enemy_last) < 500) chance = 0.4; else chance = 0.1; if (random() < chance) { self.attack_state = AS_SPAM; SUB_AttackFinished (1 + 2*random()); return TRUE; } else { self.attack_finished = time + 0.5; return FALSE; } } } return FALSE; } spot1 = self.origin; spot2 = self.enemy.origin + self.enemy.view_ofs; traceline (spot1, spot2, MOVE_MISSILE, self); if (trace_inwater) if (trace_inopen) return FALSE; if (trace_ent != self.enemy) return FALSE; // missile attack chance = 0; if (enemy_range == RANGE_NEAR) chance = 0.5; else if (enemy_range == RANGE_MID) chance = 0.3; if (random() < chance) { self.attack_state = AS_MISSILE; SUB_AttackFinished (1 + 2*random()); return TRUE; } else { self.attack_finished = time + 0.5; return FALSE; } };