--- /dev/null
+
+/*
+
+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.
+
+*/
+
+void SUB_AttackFinished (float normal)
+{
+ self.cnt = 0; // refire count for nightmare
+ if (skill < 3)
+ ATTACK_FINISHED(self) = time + normal;
+}
+
+float CanDamage(entity targ, entity inflictor)
+{
+ if (targ.movetype == MOVETYPE_PUSH)
+ {
+ traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), TRUE, self);
+ if (trace_fraction == 1)
+ return TRUE;
+ if (trace_ent == targ)
+ return TRUE;
+ return FALSE;
+ }
+
+ traceline(inflictor.origin, targ.origin, TRUE, self);
+ if (trace_fraction == 1)
+ return TRUE;
+ traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self);
+ if (trace_fraction == 1)
+ return TRUE;
+ traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self);
+ if (trace_fraction == 1)
+ return TRUE;
+ traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self);
+ if (trace_fraction == 1)
+ return TRUE;
+ traceline(inflictor.origin, targ.origin + '15 -15 0', TRUE, self);
+ if (trace_fraction == 1)
+ return TRUE;
+
+ return FALSE;
+}
+
+float(float v) anglemod;
+
+void(vector dest) ChooseTurn;
+
+void() ai_face;
+
+
+float enemy_range;
+
+
+//=============================================================================
+
+/*
+===========
+GenericCheckAttack
+
+The player is in view, so decide to move or launch an attack
+Returns FALSE if movement should continue
+============
+*/
+float() GenericCheckAttack =
+{
+ vector spot1, spot2;
+ entity targ;
+ float chance;
+
+ if (self.health < 1)
+ return FALSE;
+ targ = self.enemy;
+
+ if (vlen(targ.origin - self.origin) > 5000) // long traces are slow
+ return FALSE;
+
+// 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 (time < ATTACK_FINISHED(self))
+ return FALSE;
+
+ if (!self.th_missile)
+ return FALSE;
+
+ if (enemy_range == RANGE_FAR)
+ return FALSE;
+
+ if (enemy_range == RANGE_MELEE)
+ {
+ chance = 0.9;
+ ATTACK_FINISHED(self) = 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)
+ if (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 ();
+}
+
+/*
+=============
+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 =
+{
+ if (self.health < 1)
+ return;
+ ai_face ();
+ movetogoal (d); // done in C code...
+}
+
+void() ai_charge_side =
+{
+ if (self.health < 1)
+ return;
+ vector dtemp;
+ 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 =
+{
+ vector delta;
+ float ldmg;
+
+ if (self.health < 1)
+ return;
+ if (!self.enemy)
+ return; // removed before stroke
+
+ delta = self.enemy.origin - self.origin;
+
+ if (vlen(delta) > 60)
+ return;
+
+ ldmg = DMG_KNIGHT_MELEE_BASE + DMG_KNIGHT_MELEE_RANDOM1 * random();
+ ldmg = ldmg + DMG_KNIGHT_MELEE_RANDOM2 * random();
+ ldmg = ldmg + DMG_KNIGHT_MELEE_RANDOM3 * random();
+ traceline(self.origin, self.enemy.origin, FALSE, self);
+
+ Damage (self.enemy, self, self, ldmg, self.projectiledeathtype, trace_endpos, '0 0 0'); // TODO add force to monster melee attacks?
+}
+
+
+void() ai_melee_side =
+{
+ vector delta;
+ float ldmg;
+
+ if (self.health < 1)
+ return;
+ 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 = DMG_KNIGHT_MELEE_BASE + DMG_KNIGHT_MELEE_RANDOM1 * random();
+ ldmg = ldmg + DMG_KNIGHT_MELEE_RANDOM2 * random();
+ ldmg = ldmg + DMG_KNIGHT_MELEE_RANDOM3 * random();
+ traceline(self.origin, self.enemy.origin, FALSE, self);
+ Damage (self.enemy, self, self, ldmg, self.projectiledeathtype, trace_endpos, '0 0 0');
+}
+