hedgewars/uAIMisc.pas
branchhedgeroid
changeset 7855 ddcdedd3330b
parent 7850 fcbb024090a4
child 7996 66e8ac9c2274
--- a/hedgewars/uAIMisc.pas	Thu Nov 24 13:44:30 2011 +0100
+++ b/hedgewars/uAIMisc.pas	Sun Oct 28 13:28:23 2012 +0100
@@ -1,6 +1,6 @@
 (*
  * Hedgewars, a free turn based strategy game
- * Copyright (c) 2005-2011 Andrey Korotaev <unC0Rr@gmail.com>
+ * Copyright (c) 2004-2012 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
@@ -24,59 +24,78 @@
 
 const MAXBONUS = 1024;
 
+      afTrackFall  = $00000001;
+      afErasesLand = $00000002;
+      afSetSkip    = $00000004;
+
+
 type TTarget = record
-               Point: TPoint;
-               Score: LongInt;
-               end;
-     TTargets = record
-                Count: Longword;
-                ar: array[0..Pred(cMaxHHs)] of TTarget;
-                end;
-     TJumpType = (jmpNone, jmpHJump, jmpLJump);
-     TGoInfo = record
-               Ticks: Longword;
-               FallPix: Longword;
-               JumpType: TJumpType;
-               end;
-     TBonus = record
-              X, Y: LongInt;
-              Radius: LongInt;
-              Score: LongInt;
-              end;
+    Point: TPoint;
+    Score: LongInt;
+    skip: boolean;
+    end;
+TTargets = record
+    Count: Longword;
+    ar: array[0..Pred(cMaxHHs)] of TTarget;
+    end;
+TJumpType = (jmpNone, jmpHJump, jmpLJump);
+TGoInfo = record
+    Ticks: Longword;
+    FallPix: Longword;
+    JumpType: TJumpType;
+    end;
+TBonus = record
+    X, Y: LongInt;
+    Radius: LongInt;
+    Score: LongInt;
+    end;
 
 procedure initModule;
 procedure freeModule;
 
 procedure FillTargets;
-procedure FillBonuses(isAfterAttack: boolean; filter: TGearsType = []);
-procedure AwareOfExplosion(x, y, r: LongInt);
-function RatePlace(Gear: PGear): LongInt;
-function TestCollExcludingMe(Me: PGear; x, y, r: LongInt): boolean;
-function TestColl(x, y, r: LongInt): boolean;
-function RateExplosion(Me: PGear; x, y, r: LongInt): LongInt;
-function RateShove(Me: PGear; x, y, r, power: LongInt): LongInt;
-function RateShotgun(Me: PGear; x, y: LongInt): LongInt;
-function RateHammer(Me: PGear): LongInt;
-function HHGo(Gear, AltGear: PGear; var GoInfo: TGoInfo): boolean;
-function AIrndSign(num: LongInt): LongInt;
+procedure AddBonus(x, y: LongInt; r: Longword; s: LongInt); inline;
+procedure FillBonuses(isAfterAttack: boolean);
+procedure AwareOfExplosion(x, y, r: LongInt); inline;
+
+function  RatePlace(Gear: PGear): LongInt;
+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  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  RateShotgun(Me: PGear; gdX, gdY: real; x, y: LongInt): LongInt;
+function  RateHammer(Me: PGear): LongInt;
+
+function  HHGo(Gear, AltGear: PGear; var GoInfo: TGoInfo): boolean;
+function  AIrndSign(num: LongInt): LongInt;
 
 var ThinkingHH: PGear;
     Targets: TTargets;
 
     bonuses: record
-             Count: Longword;
-             ar: array[0..Pred(MAXBONUS)] of TBonus;
-             end;
+        Count: Longword;
+        ar: array[0..Pred(MAXBONUS)] of TBonus;
+        end;
+
+    walkbonuses: record
+        Count: Longword;
+        ar: array[0..Pred(MAXBONUS div 8)] of TBonus;  // don't use too many
+        end;
+
+const KillScore = 200;
+var friendlyfactor: LongInt = 300;
 
 implementation
-uses uCollisions, uVariables, uUtils, uDebug;
-
-const KillScore = 200;
+uses uCollisions, uVariables, uUtils, uLandTexture, uGearsUtils;
 
-var friendlyfactor: LongInt = 300;
+var 
     KnownExplosion: record
-                    X, Y, Radius: LongInt
-                    end = (X: 0; Y: 0; Radius: 0);
+        X, Y, Radius: LongInt
+        end = (X: 0; Y: 0; Radius: 0);
 
 procedure FillTargets;
 var i, t: Longword;
@@ -91,19 +110,22 @@
             begin
             for i:= 0 to cMaxHHIndex do
                 if (Hedgehogs[i].Gear <> nil)
-                and (Hedgehogs[i].Gear <> ThinkingHH) then
+                and (Hedgehogs[i].Gear <> ThinkingHH) 
+                and (Hedgehogs[i].Gear^.Health > Hedgehogs[i].Gear^.Damage) 
+                    then
                     begin
                     with Targets.ar[Targets.Count], Hedgehogs[i] do
                         begin
+                        skip:= false;
                         Point.X:= hwRound(Gear^.X);
                         Point.Y:= hwRound(Gear^.Y);
                         if Clan <> CurrentTeam^.Clan then
                             begin
-                            Score:=  Gear^.Health;
+                            Score:= Gear^.Health - Gear^.Damage;
                             inc(e)
                             end else
                             begin
-                            Score:= -Gear^.Health;
+                            Score:= Gear^.Damage - Gear^.Health;
                             inc(f)
                             end
                         end;
@@ -115,186 +137,427 @@
 else friendlyfactor:= max(30, 300 - f * 80 div max(1,e))
 end;
 
-procedure FillBonuses(isAfterAttack: boolean; filter: TGearsType);
-var Gear: PGear;
-    MyClan: PClan;
-
-    procedure AddBonus(x, y: LongInt; r: Longword; s: LongInt);
+procedure AddBonus(x, y: LongInt; r: Longword; s: LongInt); inline;
+begin
+if(bonuses.Count < MAXBONUS) then
     begin
     bonuses.ar[bonuses.Count].x:= x;
     bonuses.ar[bonuses.Count].y:= y;
     bonuses.ar[bonuses.Count].Radius:= r;
     bonuses.ar[bonuses.Count].Score:= s;
     inc(bonuses.Count);
-    TryDo(bonuses.Count <= MAXBONUS, 'Bonuses overflow', true)
     end;
+end;
 
+procedure AddWalkBonus(x, y: LongInt; r: Longword; s: LongInt); inline;
+begin
+if(walkbonuses.Count < MAXBONUS div 8) then
+    begin
+    walkbonuses.ar[walkbonuses.Count].x:= x;
+    walkbonuses.ar[walkbonuses.Count].y:= y;
+    walkbonuses.ar[walkbonuses.Count].Radius:= r;
+    walkbonuses.ar[walkbonuses.Count].Score:= s;
+    inc(walkbonuses.Count);
+    end;
+end;
+
+procedure FillBonuses(isAfterAttack: boolean);
+var Gear: PGear;
+    MyClan: PClan;
+    i: Longint;
 begin
 bonuses.Count:= 0;
 MyClan:= ThinkingHH^.Hedgehog^.Team^.Clan;
 Gear:= GearsList;
 while Gear <> nil do
     begin
-    if (filter = []) or (Gear^.Kind in filter) then
-      case Gear^.Kind of
-          gtCase: AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 33, 25);
-          gtFlame: if (Gear^.State and gsttmpFlag) <> 0 then
-                  AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 20, -50);
+        case Gear^.Kind of
+            gtCase:
+                AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y) + 3, 37, 25);
+            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 
-          gtMine:  if ((Gear^.State and gstAttacking) = 0) and 
-                      (((cMineDudPercent < 90) and (Gear^.Health <> 0)) or
-                       ((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
-          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
-                          AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 60, -25)
-                      else
-                          if isAfterAttack and (ThinkingHH^.Hedgehog <> Gear^.Hedgehog) then
-                              if (ClansCount > 2) or (MyClan = Gear^.Hedgehog^.Team^.Clan) then
-                                  AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 150, -3) // hedgehog-friend
-                              else
-                                  AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 100, 3)
-                      end;
-          end;
+            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
+                    AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 60, -25)
+                else
+                    if isAfterAttack
+                      and (ThinkingHH^.Hedgehog <> Gear^.Hedgehog)
+                      and ((hwAbs(Gear^.dX) + hwAbs(Gear^.dY)) < _0_1) then
+                        if (ClansCount > 2) or (MyClan = Gear^.Hedgehog^.Team^.Clan) then
+                            AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 150, -3) // hedgehog-friend
+                        else
+                            AddBonus(hwRound(Gear^.X), hwRound(Gear^.Y), 100, 3)
+                end;
+            end;
     Gear:= Gear^.NextGear
     end;
 if isAfterAttack and (KnownExplosion.Radius > 0) then
-   with KnownExplosion do
+    with KnownExplosion do
         AddBonus(X, Y, Radius + 10, -Radius);
+if isAfterAttack then
+    begin
+    for i:= 0 to Pred(walkbonuses.Count) do
+        with walkbonuses.ar[i] do
+            AddBonus(X, Y, Radius, Score);
+    walkbonuses.Count:= 0
+    end;
 end;
 
-procedure AwareOfExplosion(x, y, r: LongInt);
+procedure AwareOfExplosion(x, y, r: LongInt); inline;
 begin
-KnownExplosion.X:= x;
-KnownExplosion.Y:= y;
-KnownExplosion.Radius:= r
+    KnownExplosion.X:= x;
+    KnownExplosion.Y:= y;
+    KnownExplosion.Radius:= r
 end;
 
 function RatePlace(Gear: PGear): LongInt;
 var i, r: LongInt;
     rate: LongInt;
+    gX, gY: real;
 begin
+gX:= hwFloat2Float(Gear^.X);
+gY:= hwFloat2Float(Gear^.Y);
 rate:= 0;
 for i:= 0 to Pred(bonuses.Count) do
     with bonuses.ar[i] do
         begin
-        r:= hwRound(Distance(Gear^.X - int2hwFloat(X), Gear^.Y - int2hwFloat(Y)));
-        if r < Radius then
-            inc(rate, Score * (Radius - r))
+        r:= Radius;
+        if abs(gX-X)+abs(gY-Y) < Radius then
+            r:= trunc(sqrt(sqr(gX - X)+sqr(gY - Y)));
+        if r < 20 then
+                inc(rate, Score * Radius)
+        else if r < Radius then
+                inc(rate, Score * (Radius - r))
         end;
     RatePlace:= rate;
 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;
+function TestCollExcludingMe(Me: PGear; x, y, r: LongInt): boolean; inline;
 var MeX, MeY: LongInt;
 begin
     if ((x and LAND_WIDTH_MASK) = 0) and ((y and LAND_HEIGHT_MASK) = 0) then
-        begin
+    begin
         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] and $FF00) = 0) then exit(false);
-        end;
-    exit(TestColl(x, y, r))
+        if ((((x-MeX)*(x-MeX)) + ((y-MeY)*(y-MeY))) < 256) and ((Land[y, x] and $FF00) = 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] and $FF00 <> 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 $FF00 <> 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 $FF00 <> 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 $FF00 <> 0);
+    if b then
+        exit(true);
+    
+    TestCollExcludingObjects:= false;
+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 $FF7F <> 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 $FF7F <> 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 $FF7F <> 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 $FF7F <> 0);
+    if b then
+        exit(true);
+    
+    TestColl:= false;
 end;
 
-function TestColl(x, y, r: LongInt): boolean;
+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] <> 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] <> 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] <> 0);
-if b then exit(true);
-TestColl:=(((x+r) and LAND_WIDTH_MASK) = 0)and(((y+r) and LAND_HEIGHT_MASK) = 0) and (Land[y+r, x+r] <> 0)
+    b:= (((x-r) and LAND_WIDTH_MASK) = 0) and (((y-r) and LAND_HEIGHT_MASK) = 0) and (Land[y-r, x-r] > 255);
+    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] > 255);
+    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] > 255);
+    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] > 255);
+    if b then
+        exit(true);
+
+    TestCollWithLand:= false;
+end;
+
+function TraceFall(eX, eY: LongInt; x, y, dX, dY: Real; r: LongWord): LongInt;
+var skipLandCheck: boolean;
+    rCorner: real;
+    dmg: LongInt;
+begin
+    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
+        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);
+            end;
+            exit(0)
+        end;
+        if (y > cWaterLine) or (x > 4096) or (x < 0) then
+            exit(-1);
+    end;
+end;
+
+function TraceShoveFall(x, y, dX, dY: Real): LongInt;
+var dmg: LongInt;
+begin
+//v:= random($FFFFFFFF);
+    while true do
+    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 
+            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
+                    exit(dmg);
+            end;
+            exit(0)
+        end;
+        if (y > cWaterLine) or (x > 4096) or (x < 0) 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;
-var i, dmg, rate: LongInt;
 begin
+    RateExplosion:= RateExplosion(Me, x, y, r, 0);
+end;
+
+function RateExplosion(Me: PGear; x, y, r: LongInt; Flags: LongWord): LongInt;
+var i, fallDmg, dmg, dmgBase, rate, erasure: LongInt;
+    dX, dY, dmgMod: real;
+begin
+fallDmg:= 0;
+dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent;
 rate:= 0;
 // add our virtual position
 with Targets.ar[Targets.Count] do
-     begin
-     Point.x:= hwRound(Me^.X);
-     Point.y:= hwRound(Me^.Y);
-     Score:= - ThinkingHH^.Health
-     end;
+    begin
+    Point.x:= hwRound(Me^.X);
+    Point.y:= hwRound(Me^.Y);
+    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;
 for i:= 0 to Targets.Count do
     with Targets.ar[i] do
-         begin
-         dmg:= hwRound(_0_01 * cDamageModifier
-            * min((r + cHHRadius div 2 - LongInt(DistanceI(Point.x - x, Point.y - y).Round)) div 2, r) * cDamagePercent);
+        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 dmg > 0 then
+        if dmg > 0 then
             begin
-            if dmg >= abs(Score) then
-               if Score > 0 then inc(rate, KillScore)
-                            else dec(rate, KillScore * friendlyfactor div 100)
+            if (Flags and afTrackFall <> 0) and (dmg < abs(Score)) 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)
-                            else dec(rate, dmg * friendlyfactor div 100)
+                if Score > 0 then
+                    inc(rate, (dmg + fallDmg) * 1024)
+                else dec(rate, (dmg + fallDmg) * friendlyfactor div 100 * 1024)
             end;
-         end;
-RateExplosion:= rate * 1024;
+        end;
+RateExplosion:= rate;
 end;
 
-function RateShove(Me: PGear; x, y, r, power: LongInt): LongInt;
-var i, dmg, rate: LongInt;
+function RateShove(x, y, r, power, kick: LongInt; gdX, gdY: real; Flags: LongWord): LongInt;
+var i, fallDmg, dmg, rate: LongInt;
+    dX, dY, dmgMod: real;
 begin
-Me:= Me; // avoid compiler hint
+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
-         begin
-         dmg:= r - hwRound(DistanceI(Point.x - x, Point.y - y));
-         dmg:= hwRound(_0_01 * cDamageModifier * dmg * cDamagePercent);
-         if dmg > 0 then
+      if skip then 
+        if (Flags and afSetSkip = 0) then skip:= false else {still skip}
+      else  
+        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
             begin
-            if power >= abs(Score) then
-               if Score > 0 then inc(rate, KillScore)
-                            else dec(rate, KillScore * friendlyfactor div 100)
+            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)
-                            else dec(rate, power * friendlyfactor div 100)
+                if Score > 0 then
+                    inc(rate, power+fallDmg)
+                else
+                    dec(rate, (power+fallDmg) * friendlyfactor div 100)
             end;
-         end;
+        end;
 RateShove:= rate * 1024
 end;
 
-function RateShotgun(Me: PGear; x, y: LongInt): LongInt;
-var i, dmg, rate: LongInt;
+function RateShotgun(Me: PGear; gdX, gdY: real; x, y: LongInt): LongInt;
+var i, dmg, fallDmg, baseDmg, rate, erasure: LongInt;
+    dX, dY, dmgMod: real;
 begin
+dmgMod:= 0.01 * hwFloat2Float(cDamageModifier) * cDamagePercent;
 rate:= 0;
+gdX:= gdX * 0.01;
+gdY:= gdX * 0.01;
 // add our virtual position
 with Targets.ar[Targets.Count] do
-     begin
-     Point.x:= hwRound(Me^.X);
-     Point.y:= hwRound(Me^.Y);
-     Score:= - ThinkingHH^.Health
-     end;
+    begin
+    Point.x:= hwRound(Me^.X);
+    Point.y:= hwRound(Me^.Y);
+    Score:= - ThinkingHH^.Health
+    end;
 // rate shot
+baseDmg:= cHHRadius + cShotgunRadius + 4;
+if GameFlags and gfSolidLand = 0 then erasure:= cShotgunRadius
+else erasure:= 0;
 for i:= 0 to Targets.Count do
     with Targets.ar[i] do
-         begin
-         dmg:= min(cHHRadius + cShotgunRadius + 4 - hwRound(DistanceI(Point.x - x, Point.y - y)), 25);
-         dmg:= hwRound(_0_01 * cDamageModifier * dmg * cDamagePercent);
-         if dmg > 0 then
+        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
             begin
-                if dmg >= abs(Score) then dmg := KillScore;
-                if Score > 0 then inc(rate, dmg)
-                else dec(rate, dmg * friendlyfactor div 100);
+            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)
             end;
-         end;        
+        end;        
 RateShotgun:= rate * 1024;
 end;
 
@@ -308,189 +571,195 @@
 
 for i:= 0 to Pred(Targets.Count) do
     with Targets.ar[i] do
-         begin
          // hammer hit radius is 8, shift is 10
-         r:= hwRound(DistanceI(Point.x - x, Point.y - y));
+        if 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 
-                inc(rate, Score div 3)
-                else 
-                inc(rate, Score div 3 * friendlyfactor div 100)
-         end;
+            if r <= 18 then
+                if Score > 0 then 
+                    inc(rate, Score div 3)
+                else
+                    inc(rate, Score div 3 * friendlyfactor div 100)
+            end;
 RateHammer:= rate * 1024;
 end;
 
 function HHJump(Gear: PGear; JumpType: TJumpType; var GoInfo: TGoInfo): boolean;
 var bX, bY: LongInt;
-    bRes: boolean;
 begin
-bRes:= false;
+HHJump:= false;
 GoInfo.Ticks:= 0;
 GoInfo.JumpType:= jmpNone;
 bX:= hwRound(Gear^.X);
 bY:= hwRound(Gear^.Y);
 case JumpType of
-     jmpNone: exit(bRes);
-    jmpHJump: if TestCollisionYwithGear(Gear, -1) = 0 then
-                 begin
-                 Gear^.dY:= -_0_2;
-                 SetLittle(Gear^.dX);
-                 Gear^.State:= Gear^.State or gstMoving or gstHHJumping;
-                 end else exit(bRes);
-    jmpLJump: begin
-              if TestCollisionYwithGear(Gear, -1) <> 0 then
-                 if not TestCollisionXwithXYShift(Gear, _0, -2, hwSign(Gear^.dX)) then Gear^.Y:= Gear^.Y - int2hwFloat(2) else
-                 if not TestCollisionXwithXYShift(Gear, _0, -1, hwSign(Gear^.dX)) then Gear^.Y:= Gear^.Y - _1;
-              if not (TestCollisionXwithGear(Gear, hwSign(Gear^.dX))
-                 or   (TestCollisionYwithGear(Gear, -1) <> 0)) then
-                 begin
-                 Gear^.dY:= -_0_15;
-                 Gear^.dX:= SignAs(_0_15, Gear^.dX);
-                 Gear^.State:= Gear^.State or gstMoving or gstHHJumping
-                 end else exit(bRes)
-              end
-    end;
+    jmpNone: exit(false);
+    
+    jmpHJump:
+        if TestCollisionYwithGear(Gear, -1) = 0 then
+        begin
+            Gear^.dY:= -_0_2;
+            SetLittle(Gear^.dX);
+            Gear^.State:= Gear^.State or gstMoving or gstHHJumping;
+        end
+    else
+        exit(false);
+        
+    jmpLJump:
+        begin
+            if TestCollisionYwithGear(Gear, -1) <> 0 then
+                if not TestCollisionXwithXYShift(Gear, _0, -2, hwSign(Gear^.dX)) then
+                    Gear^.Y:= Gear^.Y - int2hwFloat(2)
+                else
+                    if not TestCollisionXwithXYShift(Gear, _0, -1, hwSign(Gear^.dX)) then
+                        Gear^.Y:= Gear^.Y - _1;
+            if not (TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) or
+               (TestCollisionYwithGear(Gear, -1) <> 0)) then
+            begin
+                Gear^.dY:= -_0_15;
+                Gear^.dX:= SignAs(_0_15, Gear^.dX);
+                Gear^.State:= Gear^.State or gstMoving or gstHHJumping
+            end
+        else
+            exit(false)
+        end
+end;
 
 repeat
-if not (hwRound(Gear^.Y) + cHHRadius < cWaterLine) then exit(bRes);
-if (Gear^.State and gstMoving) <> 0 then
-   begin
-   if (GoInfo.Ticks = 350) then
-      if (not (hwAbs(Gear^.dX) > cLittle)) and (Gear^.dY < -_0_02) then
-         begin
-         Gear^.dY:= -_0_25;
-         Gear^.dX:= SignAs(_0_02, Gear^.dX)
-         end;
-   if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then SetLittle(Gear^.dX);
-   Gear^.X:= Gear^.X + Gear^.dX;
-   inc(GoInfo.Ticks);
-   Gear^.dY:= Gear^.dY + cGravity;
-   if Gear^.dY > _0_4 then exit(bRes);
-   if (Gear^.dY.isNegative)and (TestCollisionYwithGear(Gear, -1) <> 0) then Gear^.dY:= _0;
-   Gear^.Y:= Gear^.Y + Gear^.dY;
-   if (not Gear^.dY.isNegative)and (TestCollisionYwithGear(Gear, 1) <> 0) then
-      begin
-      Gear^.State:= Gear^.State and not (gstMoving or gstHHJumping);
-      Gear^.dY:= _0;
-      case JumpType of
-           jmpHJump: if bY - hwRound(Gear^.Y) > 5 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
+    begin
+        if (GoInfo.Ticks = 350) then
+            if (not (hwAbs(Gear^.dX) > cLittle)) and (Gear^.dY < -_0_02) then
+            begin
+                Gear^.dY:= -_0_25;
+                Gear^.dX:= SignAs(_0_02, Gear^.dX)
+            end;
+        if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then SetLittle(Gear^.dX);
+            Gear^.X:= Gear^.X + Gear^.dX;
+        inc(GoInfo.Ticks);
+        Gear^.dY:= Gear^.dY + cGravity;
+        if Gear^.dY > _0_4 then
+            exit(false);
+        if (Gear^.dY.isNegative) and (TestCollisionYwithGear(Gear, -1) <> 0) then
+            Gear^.dY:= _0;
+        Gear^.Y:= Gear^.Y + Gear^.dY;
+        if (not Gear^.dY.isNegative) and (TestCollisionYwithGear(Gear, 1) <> 0) then
+            begin
+            Gear^.State:= Gear^.State and (not (gstMoving or gstHHJumping));
+            Gear^.dY:= _0;
+            case JumpType of
+                jmpHJump:
+                    if bY - hwRound(Gear^.Y) > 5 then
                         begin
-                        bRes:= true;
                         GoInfo.JumpType:= jmpHJump;
-                        inc(GoInfo.Ticks, 300 + 300) // 300 before jump, 300 after
+                        inc(GoInfo.Ticks, 300 + 300); // 300 before jump, 300 after
+                        exit(true)
                         end;
-           jmpLJump: if abs(bX - hwRound(Gear^.X)) > 30 then
+                jmpLJump:
+                    if abs(bX - hwRound(Gear^.X)) > 30 then
                         begin
-                        bRes:= true;
                         GoInfo.JumpType:= jmpLJump;
-                        inc(GoInfo.Ticks, 300 + 300) // 300 before jump, 300 after
-                        end;
-           end;
-      exit(bRes)
-      end;
-   end;
+                        inc(GoInfo.Ticks, 300 + 300); // 300 before jump, 300 after
+                        exit(true)
+                        end
+                end;
+            exit(false)
+            end;
+    end;
 until false
 end;
 
 function HHGo(Gear, AltGear: PGear; var GoInfo: TGoInfo): boolean;
-var pX, pY: LongInt;
+var pX, pY, tY: LongInt;
 begin
+HHGo:= false;
+Gear^.CollisionMask:= $FF7F;
 AltGear^:= Gear^;
 
 GoInfo.Ticks:= 0;
 GoInfo.FallPix:= 0;
 GoInfo.JumpType:= jmpNone;
+tY:= hwRound(Gear^.Y);
 repeat
-pX:= hwRound(Gear^.X);
-pY:= hwRound(Gear^.Y);
-if pY + cHHRadius >= cWaterLine then exit(false);
-if (Gear^.State and gstMoving) <> 0 then
-   begin
-   inc(GoInfo.Ticks);
-   Gear^.dY:= Gear^.dY + cGravity;
-   if Gear^.dY > _0_4 then
-      begin
-      Goinfo.FallPix:= 0;
-      HHJump(AltGear, jmpLJump, GoInfo); // try ljump instead of fall with damage
-      exit(false)
-      end;
-   Gear^.Y:= Gear^.Y + Gear^.dY;
-   if hwRound(Gear^.Y) > pY then inc(GoInfo.FallPix);
-   if TestCollisionYwithGear(Gear, 1) <> 0 then
-      begin
-      inc(GoInfo.Ticks, 410);
-      Gear^.State:= Gear^.State and not (gstMoving or gstHHJumping);
-      Gear^.dY:= _0;
-      HHJump(AltGear, jmpLJump, GoInfo); // try ljump instead of fall
-      exit(true)
-      end;
-   continue
-   end;
-   if (Gear^.Message and gmLeft  )<>0 then Gear^.dX:= -cLittle else
-   if (Gear^.Message and gmRight )<>0 then Gear^.dX:=  cLittle else exit(false);
-   if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then
-      begin
-      if not (TestCollisionXwithXYShift(Gear, _0, -6, hwSign(Gear^.dX))
-         or (TestCollisionYwithGear(Gear, -1) <> 0)) then Gear^.Y:= Gear^.Y - _1;
-      if not (TestCollisionXwithXYShift(Gear, _0, -5, hwSign(Gear^.dX))
-         or (TestCollisionYwithGear(Gear, -1) <> 0)) then Gear^.Y:= Gear^.Y - _1;
-      if not (TestCollisionXwithXYShift(Gear, _0, -4, hwSign(Gear^.dX))
-         or (TestCollisionYwithGear(Gear, -1) <> 0)) then Gear^.Y:= Gear^.Y - _1;
-      if not (TestCollisionXwithXYShift(Gear, _0, -3, hwSign(Gear^.dX))
-         or (TestCollisionYwithGear(Gear, -1) <> 0)) then Gear^.Y:= Gear^.Y - _1;
-      if not (TestCollisionXwithXYShift(Gear, _0, -2, hwSign(Gear^.dX))
-         or (TestCollisionYwithGear(Gear, -1) <> 0)) then Gear^.Y:= Gear^.Y - _1;
-      if not (TestCollisionXwithXYShift(Gear, _0, -1, hwSign(Gear^.dX))
-         or (TestCollisionYwithGear(Gear, -1) <> 0)) then Gear^.Y:= Gear^.Y - _1;
-      end;
+        {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);
+            end;}
 
-   if not TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then
-      begin
-      Gear^.X:= Gear^.X + int2hwFloat(hwSign(Gear^.dX));
-      inc(GoInfo.Ticks, cHHStepTicks)
-      end;
-   if TestCollisionYwithGear(Gear, 1) = 0 then
-   begin
-   Gear^.Y:= Gear^.Y + _1;
-   if TestCollisionYwithGear(Gear, 1) = 0 then
-   begin
-   Gear^.Y:= Gear^.Y + _1;
-   if TestCollisionYwithGear(Gear, 1) = 0 then
-   begin
-   Gear^.Y:= Gear^.Y + _1;
-   if TestCollisionYwithGear(Gear, 1) = 0 then
-   begin
-   Gear^.Y:= Gear^.Y + _1;
-   if TestCollisionYwithGear(Gear, 1) = 0 then
-   begin
-   Gear^.Y:= Gear^.Y + _1;
-   if TestCollisionYwithGear(Gear, 1) = 0 then
-   begin
-   Gear^.Y:= Gear^.Y + _1;
-   if TestCollisionYwithGear(Gear, 1) = 0 then
-      begin
-      Gear^.Y:= Gear^.Y - _6;
-      Gear^.dY:= _0;
-      Gear^.State:= Gear^.State or gstMoving
-      end
-   end
-   end
-   end
-   end
-   end
-   end;
-if (pX <> hwRound(Gear^.X)) and ((Gear^.State and gstMoving) = 0) then
-   exit(true);
+    pX:= hwRound(Gear^.X);
+    pY:= hwRound(Gear^.Y);
+    if pY + cHHRadius >= cWaterLine then
+        begin
+        if AltGear^.Hedgehog^.BotLevel < 4 then
+            AddWalkBonus(pX, tY, 250, -40);
+        exit(false)
+        end;
+        
+    // hog is falling    
+    if (Gear^.State and gstMoving) <> 0 then
+        begin
+        inc(GoInfo.Ticks);
+        Gear^.dY:= Gear^.dY + cGravity;
+        if Gear^.dY > _0_4 then
+            begin
+            GoInfo.FallPix:= 0;
+            // try ljump instead of fall with damage
+            HHJump(AltGear, jmpLJump, GoInfo); 
+            if AltGear^.Hedgehog^.BotLevel < 4 then
+                AddWalkBonus(pX, tY, 175, -20);
+            exit(false)
+            end;
+        Gear^.Y:= Gear^.Y + Gear^.dY;
+        if hwRound(Gear^.Y) > pY then
+            inc(GoInfo.FallPix);
+        if TestCollisionYwithGear(Gear, 1) <> 0 then
+            begin
+            inc(GoInfo.Ticks, 410);
+            Gear^.State:= Gear^.State and (not (gstMoving or gstHHJumping));
+            Gear^.dY:= _0;
+            // try ljump instead of fall
+            HHJump(AltGear, jmpLJump, GoInfo);
+            exit(true)
+            end;
+        continue
+        end;
+
+        // usual walk
+        if (Gear^.Message and gmLeft) <> 0 then
+            Gear^.dX:= -cLittle
+        else
+            if (Gear^.Message and gmRight) <> 0 then
+                Gear^.dX:=  cLittle
+            else
+                exit(false);
+
+        if MakeHedgehogsStep(Gear) then
+            inc(GoInfo.Ticks, cHHStepTicks);
+
+        // we have moved for 1 px
+        if (pX <> hwRound(Gear^.X)) and ((Gear^.State and gstMoving) = 0) then
+            exit(true)
 until (pX = hwRound(Gear^.X)) and (pY = hwRound(Gear^.Y)) and ((Gear^.State and gstMoving) = 0);
+
 HHJump(AltGear, jmpHJump, GoInfo);
-HHGo:= false;
 end;
 
 function AIrndSign(num: LongInt): LongInt;
 begin
-if random(2) = 0 then AIrndSign:=   num
-                 else AIrndSign:= - num
+if random(2) = 0 then
+    AIrndSign:=   num
+else
+    AIrndSign:= - num
 end;
 
 procedure initModule;
@@ -503,7 +772,6 @@
 
 procedure freeModule;
 begin
-
 end;
 
 end.