From 719bb753a2a8ed5df9ca387fa040470ece668329 Mon Sep 17 00:00:00 2001
From: Allanis <allanis@saracraft.net>
Date: Wed, 14 Aug 2013 16:48:32 +0100
Subject: [PATCH] [Change] Improved enemy and turret ai, they hit a lot now! 
 -- also improved the global attack() ai.

---
 scripts/ai/include/basic.lua | 39 +++++++++++-------
 src/ai.c                     | 78 +++++++++++++++++++++++++-----------
 src/outfit.c                 |  7 ++++
 src/outfit.h                 |  1 +
 src/pilot.c                  | 14 +++++++
 src/pilot.h                  |  4 ++
 src/weapon.c                 | 22 +++++++++-
 7 files changed, 125 insertions(+), 40 deletions(-)

diff --git a/scripts/ai/include/basic.lua b/scripts/ai/include/basic.lua
index b448bf1..f779913 100644
--- a/scripts/ai/include/basic.lua
+++ b/scripts/ai/include/basic.lua
@@ -17,24 +17,33 @@ function attack()
     return
   end
 
+  ai.settarget(target)
+
   -- Get stats about enemy.
-  dir     = ai.face(target)           -- Face target.
-  dist    = ai.dist(ai.pos(target))   -- Get distance.
-  second  = ai.secondary()            -- Get best secondary weapon.
+  dist = ai.dist(ai.pos(target))   -- Get distance.
 
-  -- Shoot missiles if in range.
-  if ai.secondary() == "Launcher" and
-      dist < ai.getweaprange(1) and dir < 30 then -- More lenient with aiming.
-    ai.settarget(target)
-    ai.shoot(2)
-  end
-
-  -- Attack if in range.
   range = ai.getweaprange()
-  if dir < 10 and dist > range then
-    ai.accel()
-  elseif(dir < 10 or ai.hasturrets()) and dist < range then
-    ai.shoot()
+
+  -- We first bias towards range.
+  if dist > range then 
+    dir = ai.face(target) -- Normal face the target.
+
+    -- Shoot missiles if in range.
+    if ai.secondary() == "Launcher" and
+        dist < ai.getweaprange(1) and  dir < 30 then -- More leniant with aiming.
+      ai.shoot(2)
+    end
+
+    if dir < 10 then
+      ai.accel()
+    end
+
+    -- Close enough to melee.
+  else
+    dir = ai.aim(target) -- we aim instead of face.
+    if (dir < 10 or ai.hasturret()) then 
+      ai.shoot()
+    end
   end
 end
 
diff --git a/src/ai.c b/src/ai.c
index 0ba1e66..6d882ce 100644
--- a/src/ai.c
+++ b/src/ai.c
@@ -118,6 +118,7 @@ static int ai_haslockon(lua_State* L);          /* boolean haslockon() */
 static int ai_accel(lua_State* L);              /* accel(number); nuimber <= 1. */
 static int ai_turn(lua_State* L);               /* turn(number); abs(number) <= 1. */
 static int ai_face(lua_State* L);               /* face(number/pointer) */
+static int ai_aim(lua_State* L);                /* aim(number). */
 static int ai_brake(lua_State* L);              /* Brake() */
 static int ai_getnearestplanet(lua_State* L);   /* pointer getnearestplanet() */
 static int ai_getrndplanet(lua_State* L);       /* pointer getrndplanet() */
@@ -180,6 +181,7 @@ static const luaL_Reg ai_methods[] = {
   { "stop",                 ai_stop             },
   { "hyperspace",           ai_hyperspace       },
   /* Combat. */
+  { "aim",                  ai_aim              },
   { "combat",               ai_combat           },
   { "settarget",            ai_settarget        },
   { "secondary",            ai_secondary        },
@@ -243,7 +245,7 @@ int ai_init(void) {
                    strlen(AI_INCLUDE)) != 0) &&
           (strncmp(files[i] + strlen(files[i]) - strlen(AI_SUFFIX), /* Suffixed. */
                    AI_SUFFIX, strlen(AI_SUFFIX))==0))
-      if(ai_loadProfile(files[i]))
+      if(ai_loadProfile(files[i])) /* Load the profiles. */
         WARN("Error loading AI profile '%s'", files[i]);
 
   /* Free the char allocated by pack. */
@@ -358,7 +360,7 @@ void ai_think(Pilot* pilot) {
 
   cur_pilot->solid->dir_vel = 0.;
   if(pilot_turn) /* Set the turning velocity. */
-    cur_pilot->solid->dir_vel -= cur_pilot->turn * pilot_turn;
+    cur_pilot->solid->dir_vel += cur_pilot->turn * pilot_turn;
   vect_pset(&cur_pilot->solid->force, cur_pilot->thrust * pilot_acc,
             cur_pilot->solid->dir);
 
@@ -723,7 +725,7 @@ static int ai_face(lua_State* L) {
   }
   else if(lua_islightuserdata(L,1)) v = (Vec2*)lua_topointer(L,1);
 
-  mod = -10;
+  mod = 10;
   if(lua_gettop(L) > 1 && lua_isnumber(L,2)) invert = (int)lua_tonumber(L,2);
   if(invert) mod *= -1;
   vect_cset(&sv, VX(cur_pilot->solid->pos) + FACE_WVEL*VX(cur_pilot->solid->vel),
@@ -856,6 +858,54 @@ static int ai_stop(lua_State* L) {
   return 0;
 }
 
+/* Aim at the pilot, trying to hit it. */
+static int ai_aim(lua_State* L) {
+  int id;
+  double x, y;
+  double t;
+  Pilot* p;
+  Vec2 tv;
+  double dist, diff;
+  double mod;
+  LLUA_MIN_ARGS(1);
+
+  /* Only acceptable parameter is pilot id. */
+  if(lua_isnumber(L,1))
+    id = lua_tonumber(L,1);
+  else
+    LLUA_INVALID_PARAMETER();
+
+  /* Get the pilot. */
+  p = pilot_get(id);
+  if(p == NULL) {
+    WARN("Pilot is invalid");
+    return 0;
+  }
+
+  /* Get the  distance. */
+  dist = vect_dist(&cur_pilot->solid->pos, &p->solid->pos);
+
+  /* Time for shots to reach distance. */
+  t = dist / cur_pilot->weap_speed;
+
+  /* Position is calculated on where it should be. */
+  x = p->solid->pos.x + p->solid->vel.x*t
+    - (cur_pilot->solid->pos.x + cur_pilot->solid->vel.x*t);
+
+  y = p->solid->pos.y + p->solid->vel.y*t
+    - (cur_pilot->solid->pos.y + cur_pilot->solid->vel.y*t);
+  vect_cset(&tv, x, y);
+
+  /* Calculate what we need to turn. */
+  mod = 10.;
+  diff = angle_diff(cur_pilot->solid->dir, VANGLE(tv));
+  pilot_turn = mod * diff;
+
+  /* Return distance to target (in grad). */
+  lua_pushnumber(L, ABS(diff*180./M_PI));
+  return 1;
+}
+
 /* Toggle combat flag. Default is on. */
 static int ai_combat(lua_State* L) {
   int i;
@@ -952,9 +1002,7 @@ static int ai_hostile(lua_State* L) {
 
 /* Return the maximum range of weapons if parameter is 1, then do secondary. */
 static int ai_getweaprange(lua_State* L) {
-  int i;
-  double range, max;
-  Outfit* o;
+  double range;
 
   /* If 1 is passed as parameter, secondary weapon is checked. */
   if(lua_isnumber(L, 1) && ((int)lua_tonumber(L, 1) == 1))
@@ -972,23 +1020,7 @@ static int ai_getweaprange(lua_State* L) {
       return 1;
     }
 
-  max = -1.;
-  for(i = 0; i < cur_pilot->noutfits; i++) {
-    o = cur_pilot->outfits[i].outfit;
-
-    /* Not interested in secondary weapons nor ammunition. */
-    if(outfit_isProp(o, OUTFIT_PROP_WEAP_SECONDARY) || outfit_isAmmo(o))
-      continue;
-
-    /* Compare vs current outfit's range. */
-    range = outfit_range(o);
-    if(range > max)
-      max = range;
-  }
-
-  if(max < 0.) return 0; /* No ranged weapons. */
-
-  lua_pushnumber(L, max);
+  lua_pushnumber(L, cur_pilot->weap_range);
   return 1;
 }
 
diff --git a/src/outfit.c b/src/outfit.c
index 0fe06e6..eecb1fd 100644
--- a/src/outfit.c
+++ b/src/outfit.c
@@ -243,6 +243,13 @@ double outfit_range(const Outfit* o) {
   return -1.;
 }
 
+double outfit_speed(const Outfit* o) {
+  if(outfit_isWeapon(o))        return o->u.blt.speed;
+  else if(outfit_isAmmo(o))     return o->u.amm.speed;
+  else if(outfit_isTurret(o))   return o->u.blt.speed;
+  return -1.;
+}
+
 int outfit_isSeeker(const Outfit* o) {
   if((o->type == OUTFIT_TYPE_MISSILE_SEEK_AMMO) ||
       (o->type == OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO) ||
diff --git a/src/outfit.h b/src/outfit.h
index c972158..f7a6207 100644
--- a/src/outfit.h
+++ b/src/outfit.h
@@ -155,6 +155,7 @@ DamageType outfit_damageType(const Outfit* o);
 int outfit_delay(const Outfit* o);
 double outfit_energy(const Outfit* o);
 double outfit_range(const Outfit* o);
+double outfit_speed(const Outfit* o);
 int outfit_isSeeker(const Outfit* o);
 
 /* Load/free outfit stack. */
diff --git a/src/pilot.c b/src/pilot.c
index f66de93..683cf12 100644
--- a/src/pilot.c
+++ b/src/pilot.c
@@ -697,6 +697,8 @@ char* pilot_getOutfits(Pilot* pilot) {
 void pilot_calcStats(Pilot* pilot) {
   int i;
   double q;
+  double wrange, wspeed;
+  int nweaps;
   Outfit* o;
   double ac, sc, ec, fc; /* Temp health coeficients to set. */
 
@@ -725,6 +727,8 @@ void pilot_calcStats(Pilot* pilot) {
   pilot_calcCargo(pilot);
 
   /* Now add outfit changes. */
+  nweaps = 0;
+  wrange = wspeed = 0.;
   for(i = 0; i < pilot->noutfits; i++) {
     o = pilot->outfits[i].outfit;     
     q = (double) pilot->outfits[i].quantity;
@@ -756,8 +760,18 @@ void pilot_calcStats(Pilot* pilot) {
       }
       pilot->energy_regen -= o->u.jam.energy;
     }
+    else if((outfit_isWeapon(o) || outfit_isTurret(o)) && /* Primary weapon. */
+        !outfit_isProp(o, OUTFIT_PROP_WEAP_SECONDARY)) {
+      nweaps++;
+      wrange = MAX(wrange, outfit_range(o));
+      wspeed += outfit_speed(o);
+    }
   }
 
+  /* Set weapon range and speed. */
+  pilot->weap_range = wrange; /* Range is max. */
+  pilot->weap_speed = wspeed / (double)nweaps;
+
   /* Give the pilot her health proportion back. */
   pilot->armour = ac * pilot->armour_max;
   pilot->shield = sc * pilot->shield_max;
diff --git a/src/pilot.h b/src/pilot.h
index 2d0ead1..406e92c 100644
--- a/src/pilot.h
+++ b/src/pilot.h
@@ -106,6 +106,10 @@ typedef struct Pilot_ {
   int ncommodities;
   int cargo_free;
 
+  /* Weapon Properties. */
+  double weap_range; /* Average range of primary weapons */
+  double weap_speed; /* Average speed of primary weapons. */
+
   /* Misc. */
   uint32_t flags;       /* Used for AI etc. */
   unsigned int ptimer;  /* Generic timer for internal pilot use. */
diff --git a/src/weapon.c b/src/weapon.c
index 384272d..89df1e8 100644
--- a/src/weapon.c
+++ b/src/weapon.c
@@ -389,6 +389,7 @@ static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2*
   Vec2 v;
   double mass, rdir;
   Pilot* pilot_target;
+  double x, y, t, dist;
   Weapon* w;
 
   /* Create basic features. */
@@ -408,7 +409,23 @@ static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2*
       /* Only difference is the direction of fire. */
       if((outfit->type == OUTFIT_TYPE_TURRET_BOLT) && (w->parent != w->target) &&
           (w->target != 0)) { /* Must have a valid target. */
-        rdir = vect_angle(pos, &pilot_get(w->target)->solid->pos);
+        pilot_target = pilot_get(w->target);
+
+        /* Get the distance. */
+        dist = vect_dist(pos, &pilot_target->solid->pos);
+
+        /* Time for shots to reach that distance. */
+        t = dist / w->outfit->u.blt.speed;
+
+        /* Position is calculated on where it should be. */
+        x = (pilot_target->solid->pos.x + pilot_target->solid->vel.x*t)
+          - (pos->x + vel->x*t);
+
+        y = (pilot_target->solid->pos.y + pilot_target->solid->vel.y*t)
+          - (pos->y + vel->y*t);
+        vect_cset(&v, x, y);
+
+        rdir = VANGLE(v);
         rdir += RNG(-outfit->u.blt.accuracy/2.,
             outfit->u.blt.accuracy/2.)/180.*M_PI;
       } else /* Fire straight. */
@@ -424,7 +441,8 @@ static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2*
     w->solid = solid_create(mass, rdir, pos, &v);
     w->voice = sound_addVoice(VOICE_PRIORITY_BOLT,
                               w->solid->pos.x, w->solid->pos.y,
-                              w->solid->vel.x, w->solid->vel.y, w->outfit->u.blt.sound, 0);
+                              w->solid->vel.x, w->solid->vel.y,
+                              w->outfit->u.blt.sound, 0);
     break;
   
   /* Treat seekers togther. */