hedgewars/uAIMisc.pas
branchwebgl
changeset 9127 e350500c4edb
parent 8833 c13ebed437cb
parent 9080 9b42757d7e71
child 9136 78f087fd3e5b
--- a/hedgewars/uAIMisc.pas	Thu Apr 04 14:37:19 2013 +0200
+++ b/hedgewars/uAIMisc.pas	Tue Jun 04 22:28:12 2013 +0200
@@ -1,6 +1,6 @@
 (*
  * Hedgewars, a free turn based strategy game
- * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
+ * Copyright (c) 2004-2013 Andrey Korotaev <unC0Rr@gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,14 +30,18 @@
 
       BadTurn = Low(LongInt) div 4;
 
-type TTarget = record
+type TTarget = record // starting to look more and more like a gear
     Point: TPoint;
-    Score: LongInt;
-    skip, matters: boolean;
+    Score, Radius: LongInt;
+    State: LongWord;
+    Density: real;
+    skip, matters, dead: boolean;
+    Kind: TGearType;
     end;
 TTargets = record
     Count: Longword;
-    ar: array[0..Pred(cMaxHHs)] of TTarget;
+    ar: array[0..Pred(256)] of TTarget;
+    reset: boolean;
     end;
 TJumpType = (jmpNone, jmpHJump, jmpLJump);
 TGoInfo = record
@@ -65,6 +69,7 @@
 procedure freeModule;
 
 procedure FillTargets;
+procedure ResetTargets; inline;
 procedure AddBonus(x, y: LongInt; r: Longword; s: LongInt); inline;
 procedure FillBonuses(isAfterAttack: boolean);
 procedure AwareOfExplosion(x, y, r: LongInt); inline;
@@ -73,12 +78,11 @@
 function  TestColl(x, y, r: LongInt): boolean; inline;
 function  TestCollExcludingObjects(x, y, r: LongInt): boolean; inline;
 function  TestCollExcludingMe(Me: PGear; x, y, r: LongInt): boolean; inline;
-function  TraceShoveFall(x, y, dX, dY: Real): LongInt;
-function TestCollWithLand(x, y, r: LongInt): boolean; inline;
 
 function  RateExplosion(Me: PGear; x, y, r: LongInt): LongInt; inline;
-function  RateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt;
-function  RateShove(x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt;
+function  RateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt; inline;
+function  RealRateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt;
+function  RateShove(Me: PGear; x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt;
 function  RateShotgun(Me: PGear; gdX, gdY: real; x, y: LongInt): LongInt;
 function  RateHammer(Me: PGear): LongInt;
 
@@ -94,52 +98,82 @@
 
 const KillScore = 200;
 var friendlyfactor: LongInt = 300;
+var dmgMod: real = 1.0;
 
 implementation
 uses uCollisions, uVariables, uUtils, uLandTexture, uGearsUtils;
 
-var 
+var
     KnownExplosion: record
         X, Y, Radius: LongInt
         end = (X: 0; Y: 0; Radius: 0);
 
+procedure ResetTargets; inline;
+var i: LongWord;
+begin
+if Targets.reset then
+    for i:= 0 to Targets.Count do
+        Targets.ar[i].dead:= false;
+Targets.reset:= false;
+end;
 procedure FillTargets;
 var i, t: Longword;
     f, e: LongInt;
+    Gear: PGear;
 begin
 Targets.Count:= 0;
+Targets.reset:= false;
 f:= 0;
 e:= 0;
-for t:= 0 to Pred(TeamsCount) do
-    with TeamsArray[t]^ do
-        if not hasGone then
+Gear:= GearsList;
+while Gear <> nil do
+    begin
+    if  (((Gear^.Kind = gtHedgehog) and
+            (Gear <> ThinkingHH) and
+            (Gear^.Health > Gear^.Damage) and
+            not(Gear^.Hedgehog^.Team^.hasgone)) or
+        ((Gear^.Kind = gtExplosives) and
+            (Gear^.Health > Gear^.Damage)) or
+        ((Gear^.Kind = gtMine) and
+            (Gear^.Health = 0) and
+             (Gear^.Damage < 35))
+             )  and 
+        (Targets.Count < 256) then
+        begin
+        with Targets.ar[Targets.Count] do
             begin
-            for i:= 0 to cMaxHHIndex do
-                if (Hedgehogs[i].Gear <> nil)
-                and (Hedgehogs[i].Gear <> ThinkingHH)
-                and (Hedgehogs[i].Gear^.Health > Hedgehogs[i].Gear^.Damage) 
-                    then
+            skip:= false;
+            dead:= false;
+            Kind:= Gear^.Kind;
+            Radius:= Gear^.Radius;
+            Density:= hwFloat2Float(Gear^.Density)/3;
+            State:= Gear^.State;
+            matters:= (Gear^.AIHints and aihDoesntMatter) = 0;
+
+            Point.X:= hwRound(Gear^.X);
+            Point.Y:= hwRound(Gear^.Y);
+            if (Gear^.Kind = gtHedgehog) then
+                begin
+                if (Gear^.Hedgehog^.Team^.Clan = CurrentTeam^.Clan) then
                     begin
-                    with Targets.ar[Targets.Count], Hedgehogs[i] do
-                        begin
-                        skip:= false;
-                        matters:= (Hedgehogs[i].Gear^.AIHints and aihDoesntMatter) = 0;
-                        
-                        Point.X:= hwRound(Gear^.X);
-                        Point.Y:= hwRound(Gear^.Y);
-                        if Clan <> CurrentTeam^.Clan then
-                            begin
-                            Score:= Gear^.Health - Gear^.Damage;
-                            inc(e)
-                            end else
-                            begin
-                            Score:= Gear^.Damage - Gear^.Health;
-                            inc(f)
-                            end
-                        end;
-                    inc(Targets.Count)
+                    Score:= Gear^.Damage - Gear^.Health;
+                    inc(f)
+                    end
+                else 
+                    begin
+                    Score:= Gear^.Health - Gear^.Damage;
+                    inc(e)
                     end;
+                end
+            else if Gear^.Kind = gtExplosives then
+                Score:= Gear^.Health - Gear^.Damage
+            else if Gear^.Kind = gtMine then 
+                Score:= max(0,35-Gear^.Damage);
             end;
+        inc(Targets.Count)
+        end;
+    Gear:= Gear^.NextGear
+    end;
 
 if e > f then friendlyfactor:= 300 + (e - f) * 30
 else friendlyfactor:= max(30, 300 - f * 80 div max(1,e))
@@ -185,24 +219,24 @@
             gtFlame:
                 if (Gear^.State and gsttmpFlag) <> 0 then
                     AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 20, -50);
-// avoid mines unless they are very likely to be duds, or are duds. also avoid if they are about to blow 
+// avoid mines unless they are very likely to be duds, or are duds. also avoid if they are about to blow
             gtMine:
                 if ((Gear^.State and gstAttacking) = 0) and (((cMineDudPercent < 90) and (Gear^.Health <> 0))
                 or (isAfterAttack and (Gear^.Health = 0) and (Gear^.Damage > 30))) then
                     AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 50, -50)
                 else if (Gear^.State and gstAttacking) <> 0 then
                     AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 100, -50); // mine is on
-                    
+
             gtExplosives:
             if isAfterAttack then
                 AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 75, -60 + Gear^.Health);
-                
+
             gtSMine:
                 AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 50, -30);
-                
+
             gtDynamite:
                 AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 150, -75);
-                
+
             gtHedgehog:
                 begin
                 if Gear^.Damage >= Gear^.Health then
@@ -260,6 +294,58 @@
     RatePlace:= rate;
 end;
 
+function CheckBounds(x, y, r: Longint): boolean; inline;
+begin
+    CheckBounds := (((x-r) and LAND_WIDTH_MASK) = 0) and
+        (((x+r) and LAND_WIDTH_MASK) = 0) and
+        (((y-r) and LAND_HEIGHT_MASK) = 0) and
+        (((y+r) and LAND_HEIGHT_MASK) = 0);
+end;
+
+
+function TestCollWithEverything(x, y, r: LongInt): boolean; inline;
+begin
+    if not CheckBounds(x, y, r) then
+        exit(false);
+
+    if (Land[y-r, x-r] <> 0) or
+       (Land[y+r, x-r] <> 0) or
+       (Land[y-r, x+r] <> 0) or
+       (Land[y+r, x+r] <> 0) then
+       exit(true);
+
+    TestCollWithEverything := false;
+end;
+
+function TestCollExcludingObjects(x, y, r: LongInt): boolean; inline;
+begin
+    if not CheckBounds(x, y, r) then
+        exit(false);
+
+    if (Land[y-r, x-r] > lfAllObjMask) or
+       (Land[y+r, x-r] > lfAllObjMask) or
+       (Land[y-r, x-r] > lfAllObjMask) or
+       (Land[y+r, x+r] > lfAllObjMask) then
+       exit(true);
+
+    TestCollExcludingObjects:= false;
+end;
+
+function TestColl(x, y, r: LongInt): boolean; inline;
+begin
+    if not CheckBounds(x, y, r) then
+        exit(false);
+
+    if (Land[y-r, x-r] and lfNotCurrentMask <> 0) or
+       (Land[y+r, x-r] and lfNotCurrentMask <> 0) or
+       (Land[y+r, x-r] and lfNotCurrentMask <> 0) or
+       (Land[y+r, x+r] and lfNotCurrentMask <> 0) then
+       exit(true);
+
+    TestColl:= false;
+end;
+
+
 // Wrapper to test various approaches.  If it works reasonably, will just replace.
 // Right now, converting to hwFloat is a tad inefficient since the x/y were hwFloat to begin with...
 function TestCollExcludingMe(Me: PGear; x, y, r: LongInt): boolean; inline;
@@ -270,155 +356,134 @@
         MeX:= hwRound(Me^.X);
         MeY:= hwRound(Me^.Y);
         // We are still inside the hog. Skip radius test
-        if ((((x-MeX)*(x-MeX)) + ((y-MeY)*(y-MeY))) < 256) and (Land[y, x] <= lfAllObjMask) and ((Land[y, x] and lfObjMask) < 2) then
+        if ((sqr(x-MeX) + sqr(y-MeY)) < 256) and (Land[y, x] and lfObjMask = 0) then
             exit(false);
     end;
-    TestCollExcludingMe:= TestColl(x, y, r)
-end;
-
-function TestCollExcludingObjects(x, y, r: LongInt): boolean; inline;
-var b: boolean;
-begin
-    b:= (((x-r) and LAND_WIDTH_MASK) = 0) and (((y-r) and LAND_HEIGHT_MASK) = 0) and (Land[y-r, x-r] > lfAllObjMask);
-    if b then
-        exit(true);
-    
-    b:= (((x-r) and LAND_WIDTH_MASK) = 0) and (((y+r) and LAND_HEIGHT_MASK) = 0) and (Land[y+r, x-r] > lfAllObjMask);
-    if b then
-        exit(true);
-    
-    b:= (((x+r) and LAND_WIDTH_MASK) = 0) and (((y-r) and LAND_HEIGHT_MASK) = 0) and (Land[y-r, x+r] > lfAllObjMask);
-    if b then
-        exit(true);
-    
-    b:= (((x+r) and LAND_WIDTH_MASK) = 0) and (((y+r) and LAND_HEIGHT_MASK) = 0) and (Land[y+r, x+r] > lfAllObjMask);
-    if b then
-        exit(true);
-    
-    TestCollExcludingObjects:= false;
+    TestCollExcludingMe:= TestCollWithEverything(x, y, r)
 end;
 
-function TestColl(x, y, r: LongInt): boolean; inline;
-var b: boolean;
-begin
-    b:= (((x-r) and LAND_WIDTH_MASK) = 0) and (((y-r) and LAND_HEIGHT_MASK) = 0) and (Land[y-r, x-r] and lfNotCurrentMask <> 0);
-    if b then
-        exit(true);
-    
-    b:= (((x-r) and LAND_WIDTH_MASK) = 0) and (((y+r) and LAND_HEIGHT_MASK) = 0) and (Land[y+r, x-r] and lfNotCurrentMask <> 0);
-    if b then
-        exit(true);
-    
-    b:= (((x+r) and LAND_WIDTH_MASK) = 0) and (((y-r) and LAND_HEIGHT_MASK) = 0) and (Land[y-r, x+r] and lfNotCurrentMask <> 0);
-    if b then
-        exit(true);
-    
-    b:= (((x+r) and LAND_WIDTH_MASK) = 0) and (((y+r) and LAND_HEIGHT_MASK) = 0) and (Land[y+r, x+r] and lfNotCurrentMask <> 0);
-    if b then
-        exit(true);
-    
-    TestColl:= false;
-end;
+
 
-function TestCollWithLand(x, y, r: LongInt): boolean; inline;
-var b: boolean;
-begin
-    b:= (((x-r) and LAND_WIDTH_MASK) = 0) and (((y-r) and LAND_HEIGHT_MASK) = 0) and (Land[y-r, x-r] > lfAllObjMask);
-    if b then
-        exit(true);
-        
-    b:= (((x-r) and LAND_WIDTH_MASK) = 0) and (((y+r) and LAND_HEIGHT_MASK) = 0) and (Land[y+r, x-r] > lfAllObjMask);
-    if b then
-        exit(true);
-        
-    b:= (((x+r) and LAND_WIDTH_MASK) = 0) and (((y-r) and LAND_HEIGHT_MASK) = 0) and (Land[y-r, x+r] > lfAllObjMask);
-    if b then
-        exit(true);
-        
-    b:= (((x+r) and LAND_WIDTH_MASK) = 0) and (((y+r) and LAND_HEIGHT_MASK) = 0) and (Land[y+r, x+r] > lfAllObjMask);
-    if b then
-        exit(true);
-
-    TestCollWithLand:= false;
-end;
-
-function TraceFall(eX, eY: LongInt; x, y, dX, dY: Real; r: LongWord): LongInt;
+function TraceFall(eX, eY: LongInt; var x, y: Real; dX, dY: Real; r: LongWord; Target: TTarget): LongInt;
 var skipLandCheck: boolean;
-    rCorner: real;
+    rCorner, dxdy, odX, odY: real;
     dmg: LongInt;
 begin
+    odX:= dX;
+    odY:= dY;
     skipLandCheck:= true;
-    if x - eX < 0 then dX:= -dX;
-    if y - eY < 0 then dY:= -dY;
     // ok. attempt approximate search for an unbroken trajectory into water.  if it continues far enough, assume out of map
     rCorner:= r * 0.75;
     while true do
-    begin
+        begin
         x:= x + dX;
         y:= y + dY;
         dY:= dY + cGravityf;
         skipLandCheck:= skipLandCheck and (r <> 0) and (abs(eX-x) + abs(eY-y) < r) and ((abs(eX-x) < rCorner) or (abs(eY-y) < rCorner));
-        if (not skipLandCheck) and TestCollWithLand(trunc(x), trunc(y), cHHRadius) then
-        begin
-            if 0.4 < dY then
-            begin
-                dmg := 1 + trunc((abs(dY) - 0.4) * 70);
-                if dmg >= 1 then
-                    exit(dmg);
+        if not skipLandCheck and TestCollExcludingObjects(trunc(x), trunc(y), Target.Radius) then
+            with Target do
+                begin
+                if (Kind = gtHedgehog) and (0.4 < dY) then
+                    begin
+                    dmg := 1 + trunc((dY - 0.4) * 70);
+                    exit(dmg)
+                    end
+                else 
+                    begin
+                    dxdy:= abs(dX)+abs(dY);
+                    if ((Kind = gtMine) and (dxdy > 0.35)) or 
+                       ((Kind = gtExplosives) and 
+                            (((State and gstTmpFlag <> 0) and (dxdy > 0.35)) or
+                             ((State and gstTmpFlag = 0) and 
+                                ((abs(odX) > 0.15) or ((abs(odY) > 0.15) and 
+                                (abs(odX) > 0.02))) and (dxdy > 0.35)))) then
+                        begin
+                        dmg := trunc(dxdy * 25);
+                        exit(dmg)
+                        end
+                    else if (Kind = gtExplosives) and not((abs(odX) > 0.15) or ((abs(odY) > 0.15) and (abs(odX) > 0.02))) and (dY > 0.2) then
+                        begin
+                        dmg := trunc(dy * 70);
+                        exit(dmg)
+                        end
+                    end;
+            exit(0)
             end;
-            exit(0)
-        end;
-        if (y > cWaterLine) or (x > 4096) or (x < 0) then
-            exit(-1);
-    end;
+        if (y > cWaterLine) or (x > rightX) or (x < leftX) then exit(-1)
+        end
 end;
 
-function TraceShoveFall(x, y, dX, dY: Real): LongInt;
+function TraceShoveFall(var x, y: Real; dX, dY: Real; Target: TTarget): LongInt;
 var dmg: LongInt;
+    dxdy, odX, odY: real;
 begin
+    odX:= dX;
+    odY:= dY;
 //v:= random($FFFFFFFF);
     while true do
-    begin
+        begin
         x:= x + dX;
         y:= y + dY;
         dY:= dY + cGravityf;
 
-{        if ((trunc(y) and LAND_HEIGHT_MASK) = 0) and ((trunc(x) and LAND_WIDTH_MASK) = 0) then 
+{        if ((trunc(y) and LAND_HEIGHT_MASK) = 0) and ((trunc(x) and LAND_WIDTH_MASK) = 0) then
             begin
             LandPixels[trunc(y), trunc(x)]:= v;
             UpdateLandTexture(trunc(X), 1, trunc(Y), 1, true);
             end;}
 
-
-        // consider adding dX/dY calc here for fall damage
-        if TestCollExcludingObjects(trunc(x), trunc(y), cHHRadius) then
-        begin
-            if 0.4 < dY then
-            begin
-                dmg := 1 + trunc((abs(dY) - 0.4) * 70);
-                if dmg >= 1 then
+        if TestCollExcludingObjects(trunc(x), trunc(y), Target.Radius) then
+            with Target do
+                begin
+                if (Kind = gtHedgehog) and (0.4 < dY) then
+                    begin
+                    dmg := trunc((dY - 0.4) * 70);
                     exit(dmg);
-            end;
+                    end
+                else 
+                    begin
+                    dxdy:= abs(dX)+abs(dY);
+                    if ((Kind = gtMine) and (dxdy > 0.4)) or 
+                       ((Kind = gtExplosives) and 
+                            (((State and gstTmpFlag <> 0) and (dxdy > 0.4)) or
+                             ((State and gstTmpFlag = 0) and 
+                                ((abs(odX) > 0.15) or ((abs(odY) > 0.15) and 
+                                (abs(odX) > 0.02))) and (dxdy > 0.35)))) then
+                        begin
+                        dmg := trunc(dxdy * 50);
+                        exit(dmg)
+                        end
+                    else if (Kind = gtExplosives) and not((abs(odX) > 0.15) or ((abs(odY) > 0.15) and (abs(odX) > 0.02))) and (dY > 0.2) then
+                        begin
+                        dmg := trunc(dy * 70);
+                        exit(dmg)
+                        end
+                    end;
             exit(0)
         end;
-        if (y > cWaterLine) or (x > 4096) or (x < 0) then
+        if (y > cWaterLine) or (x > rightX) or (x < leftX) then
             // returning -1 for drowning so it can be considered in the Rate routine
             exit(-1)
     end;
 end;
 
-function RateExplosion(Me: PGear; x, y, r: LongInt): LongInt;
+function RateExplosion(Me: PGear; x, y, r: LongInt): LongInt; inline;
 begin
-    RateExplosion:= RateExplosion(Me, x, y, r, 0);
+    RateExplosion:= RealRateExplosion(Me, x, y, r, 0);
+    ResetTargets;
+end;
+function RateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt; inline;
+begin
+    RateExplosion:= RealRateExplosion(Me, x, y, r, Flags);
+    ResetTargets;
 end;
 
-function RateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt;
-var i, fallDmg, dmg, dmgBase, rate, erasure: LongInt;
-    dX, dY, dmgMod: real;
+function RealRateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt;
+var i, fallDmg, dmg, dmgBase, rate, subrate, erasure: LongInt;
+    pX, pY, dX, dY: real;
     hadSkips: boolean;
 begin
 fallDmg:= 0;
-dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent;
 rate:= 0;
 // add our virtual position
 with Targets.ar[Targets.Count] do
@@ -427,10 +492,12 @@
     Point.y:= hwRound(Me^.Y);
     skip:= false;
     matters:= true;
+    Kind:= gtHedgehog;
+    Density:= 1;
+    Radius:= cHHRadius;
     Score:= - ThinkingHH^.Health
     end;
 // rate explosion
-dmgBase:= r + cHHRadius div 2;
 
 if (Flags and afErasesLand <> 0) and (GameFlags and gfSolidLand = 0) then erasure:= r
 else erasure:= 0;
@@ -438,98 +505,169 @@
 hadSkips:= false;
 
 for i:= 0 to Targets.Count do
-    with Targets.ar[i] do
-      if not matters then hadSkips:= true
-        else
-        begin
-        dmg:= 0;
-        if abs(Point.x - x) + abs(Point.y - y) < dmgBase then
-            dmg:= trunc(dmgMod * min((dmgBase - trunc(sqrt(sqr(Point.x - x)+sqr(Point.y - y)))) div 2, r));
+    if not Targets.ar[i].dead then
+        with Targets.ar[i] do
+          if not matters then hadSkips:= true
+            else
+            begin
+            dmg:= 0;
+            dmgBase:= r + Radius div 2;
+            if abs(Point.x - x) + abs(Point.y - y) < dmgBase then
+                dmg:= trunc(dmgMod * min((dmgBase - trunc(sqrt(sqr(Point.x - x)+sqr(Point.y - y)))) div 2, r));
 
-        if dmg > 0 then
-            begin
-            if (Flags and afTrackFall <> 0) and (dmg < abs(Score)) then
+            if dmg > 0 then
                 begin
-                dX:= 0.005 * dmg + 0.01;
-                dY:= dX;
-                if (x and LAND_WIDTH_MASK = 0) and ((y+cHHRadius+2) and LAND_HEIGHT_MASK = 0) and 
-                   (Land[y+cHHRadius+2, x] and lfIndestructible <> 0) then
-                     fallDmg:= trunc(TraceFall(x, y, Point.x, Point.y, dX, dY, 0) * dmgMod)
-                else fallDmg:= trunc(TraceFall(x, y, Point.x, Point.y, dX, dY, erasure) * dmgMod)
-                end;
-            if fallDmg < 0 then // drowning. score healthier hogs higher, since their death is more likely to benefit the AI
-                if Score > 0 then
-                    inc(rate, (KillScore + Score div 10) * 1024)   // Add a bit of a bonus for bigger hog drownings
-                else
-                    dec(rate, (KillScore * friendlyfactor div 100 - Score div 10) * 1024) // and more of a punishment for drowning bigger friendly hogs
-            else if (dmg+fallDmg) >= abs(Score) then
-                if Score > 0 then
-                    inc(rate, KillScore * 1024 + (dmg + fallDmg)) // tiny bonus for dealing more damage than needed to kill
-                else
-                    dec(rate, KillScore * friendlyfactor div 100 * 1024)
-            else
-                if Score > 0 then
-                    inc(rate, (dmg + fallDmg) * 1024)
-                else dec(rate, (dmg + fallDmg) * friendlyfactor div 100 * 1024)
+                pX:= Point.x;
+                pY:= Point.y;
+                fallDmg:= 0;
+                if (Flags and afTrackFall <> 0) and (Score > 0) and (dmg < Score) then
+                    begin
+                    dX:= (0.005 * dmg + 0.01) / Density;
+                    dY:= dX;
+                    if (Kind = gtExplosives) and (State and gstTmpFlag = 0) and 
+                       (((abs(dY) > 0.15) and (abs(dX) < 0.02)) or
+                        ((abs(dY) < 0.15) and (abs(dX) < 0.15))) then
+                        dX:= 0;
+
+                    if pX - x < 0 then dX:= -dX;
+                    if pY - y < 0 then dY:= -dY;
+
+                    if (x and LAND_WIDTH_MASK = 0) and ((y+cHHRadius+2) and LAND_HEIGHT_MASK = 0) and
+                       (Land[y+cHHRadius+2, x] and lfIndestructible <> 0) then
+                         fallDmg:= trunc(TraceFall(x, y, pX, pY, dX, dY, 0, Targets.ar[i]) * dmgMod)
+                    else fallDmg:= trunc(TraceFall(x, y, pX, pY, dX, dY, erasure, Targets.ar[i]) * dmgMod)
+                    end;
+                if Kind = gtHedgehog then
+                    begin
+                    if fallDmg < 0 then // drowning. score healthier hogs higher, since their death is more likely to benefit the AI
+                        begin
+                        if Score > 0 then
+                            inc(rate, (KillScore + Score div 10) * 1024)   // Add a bit of a bonus for bigger hog drownings
+                        else
+                            dec(rate, (KillScore * friendlyfactor div 100 - Score div 10) * 1024) // and more of a punishment for drowning bigger friendly hogs
+                        end
+                    else if (dmg+fallDmg) >= abs(Score) then
+                        begin
+                        dead:= true;
+                        Targets.reset:= true;
+                        if dX < 0.035 then
+                            begin
+                            subrate:= RealRateExplosion(Me, round(pX), round(pY), 61, afErasesLand or (Flags and afTrackFall));
+                            if abs(subrate) > 2000 then inc(Rate,subrate)
+                            end;
+                        if Score > 0 then
+                             inc(rate, KillScore * 1024 + (dmg + fallDmg)) // tiny bonus for dealing more damage than needed to kill
+                        else dec(rate, KillScore * friendlyfactor div 100 * 1024)
+                        end
+                    else
+                        begin
+                        if Score > 0 then
+                             inc(rate, (dmg + fallDmg) * 1024)
+                        else dec(rate, (dmg + fallDmg) * friendlyfactor div 100 * 1024)
+                        end
+                    end
+                else if (fallDmg >= 0) and ((dmg+fallDmg) >= Score) then
+                    begin
+                    dead:= true;
+                    Targets.reset:= true;
+                    if Kind = gtExplosives then
+                         subrate:= RealRateExplosion(Me, round(pX), round(pY), 151, afErasesLand or (Flags and afTrackFall))
+                    else subrate:= RealRateExplosion(Me, round(pX), round(pY), 101, afErasesLand or (Flags and afTrackFall));
+                    if abs(subrate) > 2000 then inc(Rate,subrate);
+                    end
+                end
             end;
-        end;
 
 if hadSkips and (rate = 0) then
-    RateExplosion:= BadTurn
+    RealRateExplosion:= BadTurn
     else
-    RateExplosion:= rate;
+    RealRateExplosion:= rate;
 end;
 
-function RateShove(x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt;
-var i, fallDmg, dmg, rate: LongInt;
-    dX, dY, dmgMod: real;
+function RateShove(Me: PGear; x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt;
+var i, fallDmg, dmg, rate, subrate: LongInt;
+    dX, dY, pX, pY: real;
 begin
 fallDmg:= 0;
 dX:= gdX * 0.01 * kick;
 dY:= gdY * 0.01 * kick;
-dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent;
 rate:= 0;
 for i:= 0 to Pred(Targets.Count) do
     with Targets.ar[i] do
-      if skip then 
-        if (Flags and afSetSkip = 0) then skip:= false else {still skip}
-      else if matters then
-        begin
-        dmg:= 0;
-        if abs(Point.x - x) + abs(Point.y - y) < r then
-            dmg:= r - trunc(sqrt(sqr(Point.x - x)+sqr(Point.y - y)));
-
-        if dmg > 0 then
+        if skip then
+            begin
+            if Flags and afSetSkip = 0 then skip:= false
+            end
+        else if matters then
             begin
-            if (Flags and afSetSkip <> 0) then skip:= true;
-            if (Flags and afTrackFall <> 0) and (Score > 0) then 
-                fallDmg:= trunc(TraceShoveFall(Point.x, Point.y - 2, dX, dY) * dmgMod);
-            if fallDmg < 0 then // drowning. score healthier hogs higher, since their death is more likely to benefit the AI
-                if Score > 0 then
-                    inc(rate, KillScore + Score div 10)   // Add a bit of a bonus for bigger hog drownings
-                else
-                    dec(rate, KillScore * friendlyfactor div 100 - Score div 10) // and more of a punishment for drowning bigger friendly hogs
-            else if power+fallDmg >= abs(Score) then
-                if Score > 0 then
-                    inc(rate, KillScore)
-                else
-                    dec(rate, KillScore * friendlyfactor div 100)
-            else
-                if Score > 0 then
-                    inc(rate, power+fallDmg)
-                else
-                    dec(rate, (power+fallDmg) * friendlyfactor div 100)
+            dmg:= 0;
+            if abs(Point.x - x) + abs(Point.y - y) < r then
+                dmg:= r - trunc(sqrt(sqr(Point.x - x)+sqr(Point.y - y)));
+
+            if dmg > 0 then
+                begin
+                pX:= Point.x;
+                pY:= Point.y-2;
+                fallDmg:= 0;
+                if (Flags and afSetSkip <> 0) then skip:= true;
+                if not(dead) and (Flags and afTrackFall <> 0) and (Score > 0) and (power < Score) then
+                    if (Kind = gtExplosives) and (State and gstTmpFlag = 0) and 
+                       (((abs(dY) > 0.15) and (abs(dX) < 0.02)) or
+                        ((abs(dY) < 0.15) and (abs(dX) < 0.15))) then
+                        fallDmg:= trunc(TraceShoveFall(pX, pY, 0, dY, Targets.ar[i]) * dmgMod)
+                    else
+                        fallDmg:= trunc(TraceShoveFall(pX, pY, dX, dY, Targets.ar[i]) * dmgMod);
+                if Kind = gtHedgehog then
+                    begin
+                    if fallDmg < 0 then // drowning. score healthier hogs higher, since their death is more likely to benefit the AI
+                        begin
+                        if Score > 0 then
+                            inc(rate, KillScore + Score div 10)   // Add a bit of a bonus for bigger hog drownings
+                        else
+                            dec(rate, KillScore * friendlyfactor div 100 - Score div 10) // and more of a punishment for drowning bigger friendly hogs
+                        end
+                    else if power+fallDmg >= abs(Score) then
+                        begin
+                        dead:= true;
+                        Targets.reset:= true;
+                        if dX < 0.035 then
+                            begin
+                            subrate:= RealRateExplosion(Me, round(pX), round(pY), 61, afErasesLand or afTrackFall);
+                            if abs(subrate) > 2000 then inc(Rate,subrate div 1024)
+                            end;
+                        if Score > 0 then
+                            inc(rate, KillScore)
+                        else
+                            dec(rate, KillScore * friendlyfactor div 100)
+                        end
+                    else
+                        begin
+                        if Score > 0 then
+                            inc(rate, power+fallDmg)
+                        else
+                            dec(rate, (power+fallDmg) * friendlyfactor div 100)
+                        end
+                    end
+                else if (fallDmg >= 0) and ((dmg+fallDmg) >= Score) then
+                    begin
+                    dead:= true;
+                    Targets.reset:= true;
+                    if Kind = gtExplosives then
+                         subrate:= RealRateExplosion(Me, round(pX), round(pY), 151, afErasesLand or (Flags and afTrackFall))
+                    else subrate:= RealRateExplosion(Me, round(pX), round(pY), 101, afErasesLand or (Flags and afTrackFall));
+                    if abs(subrate) > 2000 then inc(Rate,subrate div 1024);
+                    end
+                end
             end;
-        end;
-RateShove:= rate * 1024
+RateShove:= rate * 1024;
+ResetTargets
 end;
 
 function RateShotgun(Me: PGear; gdX, gdY: real; x, y: LongInt): LongInt;
-var i, dmg, fallDmg, baseDmg, rate, erasure: LongInt;
-    dX, dY, dmgMod: real;
+var i, dmg, fallDmg, baseDmg, rate, subrate, erasure: LongInt;
+    pX, pY, dX, dY: real;
     hadSkips: boolean;
 begin
-dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent;
 rate:= 0;
 gdX:= gdX * 0.01;
 gdY:= gdX * 0.01;
@@ -540,6 +678,9 @@
     Point.y:= hwRound(Me^.Y);
     skip:= false;
     matters:= true;
+    Kind:= gtHedgehog;
+    Density:= 1;
+    Radius:= cHHRadius;
     Score:= - ThinkingHH^.Health
     end;
 // rate shot
@@ -551,48 +692,80 @@
 hadSkips:= false;
 
 for i:= 0 to Targets.Count do
-    with Targets.ar[i] do
-      if not matters then hadSkips:= true
-        else
-        begin
-        dmg:= 0;
-        if abs(Point.x - x) + abs(Point.y - y) < baseDmg then
-            begin
-            dmg:= min(baseDmg - trunc(sqrt(sqr(Point.x - x)+sqr(Point.y - y))), 25);
-            dmg:= trunc(dmg * dmgMod);
-            end;
-        if dmg > 0 then
+    if not Targets.ar[i].dead then
+        with Targets.ar[i] do
+          if not matters then hadSkips:= true
+            else
             begin
-            dX:= gdX * dmg;
-            dY:= gdY * dmg;
-            if dX < 0 then dX:= dX - 0.01
-            else dX:= dX + 0.01;
-            if (x and LAND_WIDTH_MASK = 0) and ((y+cHHRadius+2) and LAND_HEIGHT_MASK = 0) and 
-               (Land[y+cHHRadius+2, x] and lfIndestructible <> 0) then
-                 fallDmg:= trunc(TraceFall(x, y, Point.x, Point.y, dX, dY, 0) * dmgMod)
-            else fallDmg:= trunc(TraceFall(x, y, Point.x, Point.y, dX, dY, erasure) * dmgMod);
-            if fallDmg < 0 then // drowning. score healthier hogs higher, since their death is more likely to benefit the AI
-                if Score > 0 then
-                    inc(rate, KillScore + Score div 10)   // Add a bit of a bonus for bigger hog drownings
-                else
-                    dec(rate, KillScore * friendlyfactor div 100 - Score div 10) // and more of a punishment for drowning bigger friendly hogs
-            else if (dmg+fallDmg) >= abs(Score) then
-                if Score > 0 then
-                    inc(rate, KillScore)
-                else
-                    dec(rate, KillScore * friendlyfactor div 100)
-            else
-                if Score > 0 then
-                    inc(rate, dmg+fallDmg)
-            else
-                dec(rate, (dmg+fallDmg) * friendlyfactor div 100)
+            dmg:= 0;
+            if abs(Point.x - x) + abs(Point.y - y) < baseDmg then
+                begin
+                dmg:= min(baseDmg - trunc(sqrt(sqr(Point.x - x)+sqr(Point.y - y))), 25);
+                dmg:= trunc(dmg * dmgMod);
+                end;
+            if dmg > 0 then
+                begin
+                if not(dead) and (Score > 0) and (dmg < Score) then
+                    begin
+                    pX:= Point.x;
+                    pY:= Point.y;
+                    dX:= gdX * dmg / Density;
+                    dY:= gdY * dmg / Density;
+                    if dX < 0 then dX:= dX - 0.01
+                    else dX:= dX + 0.01;
+                    if (Kind = gtExplosives) and (State and gstTmpFlag = 0) and 
+                       (((abs(dY) > 0.15) and (abs(dX) < 0.02)) or
+                        ((abs(dY) < 0.15) and (abs(dX) < 0.15))) then 
+                       dX:= 0;
+                    if (x and LAND_WIDTH_MASK = 0) and ((y+cHHRadius+2) and LAND_HEIGHT_MASK = 0) and
+                       (Land[y+cHHRadius+2, x] and lfIndestructible <> 0) then
+                         fallDmg:= trunc(TraceFall(x, y, pX, pY, dX, dY, 0, Targets.ar[i]) * dmgMod)
+                    else fallDmg:= trunc(TraceFall(x, y, pX, pY, dX, dY, erasure, Targets.ar[i]) * dmgMod)
+                    end;
+                if Kind = gtHedgehog then
+                    begin
+                    if fallDmg < 0 then // drowning. score healthier hogs higher, since their death is more likely to benefit the AI
+                        begin
+                        if Score > 0 then
+                            inc(rate, KillScore + Score div 10)   // Add a bit of a bonus for bigger hog drownings
+                        else
+                            dec(rate, KillScore * friendlyfactor div 100 - Score div 10) // and more of a punishment for drowning bigger friendly hogs
+                        end
+                    else if (dmg+fallDmg) >= abs(Score) then
+                        begin
+                        dead:= true;
+                        Targets.reset:= true;
+                        if dX < 0.035 then
+                            begin
+                            subrate:= RealRateExplosion(Me, round(pX), round(pY), 61, afErasesLand or afTrackFall);
+                            if abs(subrate) > 2000 then inc(Rate,subrate div 1024)
+                            end;
+                        if Score > 0 then
+                            inc(rate, KillScore)
+                        else
+                            dec(rate, KillScore * friendlyfactor div 100)
+                        end
+                    else if Score > 0 then
+                         inc(rate, dmg+fallDmg)
+                    else dec(rate, (dmg+fallDmg) * friendlyfactor div 100)
+                    end
+                else if (fallDmg >= 0) and ((dmg+fallDmg) >= Score) then
+                    begin
+                    dead:= true;
+                    Targets.reset:= true;
+                    if Kind = gtExplosives then
+                         subrate:= RealRateExplosion(Me, round(pX), round(pY), 151, afErasesLand or afTrackFall)
+                    else subrate:= RealRateExplosion(Me, round(pX), round(pY), 101, afErasesLand or afTrackFall);
+                    if abs(subrate) > 2000 then inc(Rate,subrate div 1024);
+                    end
+                end
             end;
-        end;
 
 if hadSkips and (rate = 0) then
     RateShotgun:= BadTurn
     else
     RateShotgun:= rate * 1024;
+    ResetTargets;
 end;
 
 function RateHammer(Me: PGear): LongInt;
@@ -605,14 +778,13 @@
 
 for i:= 0 to Pred(Targets.Count) do
     with Targets.ar[i] do
-      if matters then
          // hammer hit radius is 8, shift is 10
-        if abs(Point.x - x) + abs(Point.y - y) < 18 then
+      if matters and (Kind = gtHedgehog) and (abs(Point.x - x) + abs(Point.y - y) < 18) then
             begin
             r:= trunc(sqrt(sqr(Point.x - x)+sqr(Point.y - y)));
 
             if r <= 18 then
-                if Score > 0 then 
+                if Score > 0 then
                     inc(rate, Score div 3)
                 else
                     inc(rate, Score div 3 * friendlyfactor div 100)
@@ -630,7 +802,7 @@
 bY:= hwRound(Gear^.Y);
 case JumpType of
     jmpNone: exit(false);
-    
+
     jmpHJump:
         if TestCollisionYwithGear(Gear, -1) = 0 then
         begin
@@ -640,7 +812,7 @@
         end
     else
         exit(false);
-        
+
     jmpLJump:
         begin
             if TestCollisionYwithGear(Gear, -1) <> 0 then
@@ -662,12 +834,12 @@
 end;
 
 repeat
-        {if ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) then 
+        {if ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) then
             begin
             LandPixels[hwRound(Gear^.Y), hwRound(Gear^.X)]:= Gear^.Hedgehog^.Team^.Clan^.Color;
             UpdateLandTexture(hwRound(Gear^.X), 1, hwRound(Gear^.Y), 1, true);
             end;}
-            
+
     if not (hwRound(Gear^.Y) + cHHRadius < cWaterLine) then
         exit(false);
     if (Gear^.State and gstMoving) <> 0 then
@@ -725,7 +897,7 @@
 GoInfo.JumpType:= jmpNone;
 tY:= hwRound(Gear^.Y);
 repeat
-        {if ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) then 
+        {if ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) then
             begin
             LandPixels[hwRound(Gear^.Y), hwRound(Gear^.X)]:= random($FFFFFFFF);//Gear^.Hedgehog^.Team^.Clan^.Color;
             UpdateLandTexture(hwRound(Gear^.X), 1, hwRound(Gear^.Y), 1, true);
@@ -739,8 +911,8 @@
             AddWalkBonus(pX, tY, 250, -40);
         exit(false)
         end;
-        
-    // hog is falling    
+
+    // hog is falling
     if (Gear^.State and gstMoving) <> 0 then
         begin
         inc(GoInfo.Ticks);
@@ -749,7 +921,7 @@
             begin
             GoInfo.FallPix:= 0;
             // try ljump instead of fall with damage
-            HHJump(AltGear, jmpLJump, GoInfo); 
+            HHJump(AltGear, jmpLJump, GoInfo);
             if AltGear^.Hedgehog^.BotLevel < 4 then
                 AddWalkBonus(pX, tY, 175, -20);
             exit(false)