merge blowtorch draft
authoralfadur
Sun, 21 Jun 2020 03:08:21 +0300
changeset 15645 6c689729b745
parent 15644 237d691e5069 (current diff)
parent 15642 92ce801d0681 (diff)
child 15646 adbd2bcf7159
merge blowtorch draft
hedgewars/uGearsHandlersMess.pas
--- a/ChangeLog.txt	Sun Jun 21 03:00:39 2020 +0300
+++ b/ChangeLog.txt	Sun Jun 21 03:08:21 2020 +0300
@@ -10,11 +10,19 @@
  + Themes: Add fade-in and fade-out effects
  + Themes: Make Sudden Death flakes in Underwater theme rise
  + New taunt chat commands: /bubble, /happy
+ + Teach computer players how to ...
+ +   - use drill strike, piano strike, air mine, cleaver, seduction
+ +   - use mine strike (0 seconds only)
+ +   - use RC plane (very basic)
+ +   - drop mines from a cliff
+ + Low level computer players are more inaccurate with guns
+ + Various small computer player improvements
  * Racer: Resize waypoints in custom-sized drawn maps
  * Mutant: Fix impossible to become mutant after mutant is gone
  * A Classic Fairytale: Mission 1: Fix possibility of getting stuck in “Leap of Faith” section
  * A Space Adventure: The First Stop: Fix broken victory condition when eliminating minions
  * A Space Adventure: Killing the Specialists: Don't award player health if enemy hurts itself
+ * Fix many projectiles not being affected by Heavy Wind after turn end
  * Fix hog getting stuck when opening parachute right after a shoryuken digging through land
  * Fix weapon schemes sometimes not being saved properly
 
--- a/hedgewars/uAI.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uAI.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -167,11 +167,6 @@
 
                     AddAction(BestActions, aia_Weapon, Longword(a), 300 + random(400), 0, 0);
 
-                    if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
-                        begin
-                        AddAction(BestActions, aia_Put, 0, 8, ap.AttackPutX, ap.AttackPutY)
-                        end;
-
                     if (ap.Angle > 0) then
                         AddAction(BestActions, aia_LookRight, 0, 200, 0, 0)
                     else if (ap.Angle < 0) then
@@ -180,6 +175,18 @@
                     if (Ammoz[a].Ammo.Propz and ammoprop_Timerable) <> 0 then
                         AddAction(BestActions, aia_Timer, ap.Time div 1000, 400, 0, 0);
 
+                    if ((Ammoz[a].Ammo.Propz and ammoprop_SetBounce) > 0) and (ap.Bounce > 0) then
+                        begin
+                        AddAction(BestActions, aia_Precise, aim_push, 10, 0, 0);
+                        AddAction(BestActions, aia_Timer, ap.Bounce, 200, 0, 0);
+                        AddAction(BestActions, aia_Precise, aim_release, 10, 0, 0);
+                        end;
+
+                    if (Ammoz[a].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
+                        begin
+                        AddAction(BestActions, aia_Put, 0, 8, ap.AttackPutX, ap.AttackPutY)
+                        end;
+
                     if (Ammoz[a].Ammo.Propz and ammoprop_NoCrosshair) = 0 then
                         begin
                         dAngle:= LongInt(Me^.Angle) - Abs(ap.Angle);
@@ -247,7 +254,7 @@
 const FallPixForBranching = cHHRadius;
 var
     maxticks, oldticks, steps, tmp: Longword;
-    BaseRate, BestRate, Rate: LongInt;
+    BaseRate, BestRate, Rate, i: LongInt;
     GoInfo: TGoInfo;
     CanGo: boolean;
     AltMe: TGear;
@@ -321,6 +328,13 @@
                     AddAction(BestActions, aia_Weapon, Longword(amExtraTime), 80, 0, 0);
                     AddAction(BestActions, aia_attack, aim_push, 10, 0, 0);
                     AddAction(BestActions, aia_attack, aim_release, 10, 0, 0);
+                    // Better bot levels know they can spam extra time if infinite
+                    if (BotLevel < 3) and (HHHasAmmo(Me^.Hedgehog^, amExtraTime) = AMMO_INFINITE) then
+                        for i:= 1 to 3 do
+                            begin
+                            AddAction(BestActions, aia_attack, aim_push, 100, 0, 0);
+                            AddAction(BestActions, aia_attack, aim_release, 100, 0, 0);
+                            end;
                 end;
 
                 break;
--- a/hedgewars/uAIActions.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uAIActions.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -31,6 +31,7 @@
     aia_Up         = 5;
     aia_Down       = 6;
     aia_Switch     = 7;
+    aia_Precise    = 8;
 
     aia_Weapon     = $8000;
     aia_WaitXL     = $8001;
@@ -73,7 +74,7 @@
 var PrevX: LongInt = 0;
     timedelta: Longword = 0;
 
-const ActionIdToStr: array[0..7] of string[16] = (
+const ActionIdToStr: array[0..8] of string[16] = (
 {aia_none}           '',
 {aia_Left}           'left',
 {aia_Right}          'right',
@@ -81,7 +82,8 @@
 {aia_attack}         'attack',
 {aia_Up}             'up',
 {aia_Down}           'down',
-{aia_Switch}         'switch'
+{aia_Switch}         'switch',
+{aia_Precise}        'precise'
                      );
 
 {$IFDEF TRACEAIACTIONS}
--- a/hedgewars/uAIAmmoTests.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uAIAmmoTests.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -30,7 +30,7 @@
 var windSpeed: real;
 
 type TAttackParams = record
-        Time, AttacksNum: Longword;
+        Time, Bounce, AttacksNum: Longword;
         Angle, Power: LongInt;
         ExplX, ExplY, ExplR: LongInt;
         AttackPutX, AttackPutY: LongInt;
@@ -44,6 +44,7 @@
 function TestClusterBomb(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestWatermelon(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestDrillRocket(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestRCPlane(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestMortar(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestShotgun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestDesertEagle(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
@@ -53,10 +54,19 @@
 function TestWhip(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestKamikaze(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestAirAttack(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestDrillStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestMineStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestSMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestPiano(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestTeleport(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestHammer(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestCake(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestSeduction(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 function TestDynamite(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestKnife(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestAirMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+function TestMinigun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 
 type TAmmoTestProc = function (Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
     TAmmoTest = record
@@ -75,7 +85,7 @@
             (proc: nil;              flags: 0), // amPickHammer
             (proc: nil;              flags: 0), // amSkip
             (proc: nil;              flags: 0), // amRope
-            (proc: nil;              flags: 0), // amMine
+            (proc: @TestMine;        flags: amtest_NoTarget), // amMine
             (proc: @TestDesertEagle; flags: amtest_MultipleAttacks), // amDEagle
             (proc: @TestDynamite;    flags: amtest_NoTarget), // amDynamite
             (proc: @TestFirePunch;   flags: amtest_NoTarget), // amFirePunch
@@ -83,7 +93,7 @@
             (proc: @TestBaseballBat; flags: amtest_NoTarget), // amBaseballBat
             (proc: nil;              flags: 0), // amParachute
             (proc: @TestAirAttack;   flags: amtest_Rare), // amAirAttack
-            (proc: nil;              flags: 0), // amMineStrike
+            (proc: @TestMineStrike;  flags: amtest_Rare), // amMineStrike
             (proc: nil;              flags: 0), // amBlowTorch
             (proc: nil;              flags: 0), // amGirder
             (proc: nil;              flags: 0), // amTeleport
@@ -92,13 +102,13 @@
             (proc: @TestMortar;      flags: 0), // amMortar
             (proc: @TestKamikaze;    flags: 0), // amKamikaze
             (proc: @TestCake;        flags: amtest_Rare or amtest_NoTarget), // amCake
-            (proc: nil;              flags: 0), // amSeduction
+            (proc: @TestSeduction;   flags: amtest_NoTarget), // amSeduction
             (proc: @TestWatermelon;  flags: 0), // amWatermelon
             (proc: nil;              flags: 0), // amHellishBomb
             (proc: nil;              flags: 0), // amNapalm
             (proc: @TestDrillRocket; flags: 0), // amDrill
             (proc: nil;              flags: 0), // amBallgun
-            (proc: nil;              flags: 0), // amRCPlane
+            (proc: @TestRCPlane;     flags: 0), // amRCPlane
             (proc: nil;              flags: 0), // amLowGravity
             (proc: nil;              flags: 0), // amExtraDamage
             (proc: nil;              flags: 0), // amInvulnerable
@@ -110,23 +120,23 @@
             (proc: @TestMolotov;     flags: 0), // amMolotov
             (proc: nil;              flags: 0), // amBirdy
             (proc: nil;              flags: 0), // amPortalGun
-            (proc: nil;              flags: 0), // amPiano
+            (proc: @TestPiano;       flags: amtest_Rare), // amPiano
             (proc: @TestGrenade;     flags: amtest_NoTrackFall), // amGasBomb
             (proc: @TestShotgun;     flags: 0), // amSineGun
             (proc: nil;              flags: 0), // amFlamethrower
-            (proc: @TestGrenade;     flags: 0), // amSMine
+            (proc: @TestSMine;       flags: 0), // amSMine
             (proc: @TestHammer;      flags: amtest_NoTarget), // amHammer
             (proc: nil;              flags: 0), // amResurrector
-            (proc: nil;              flags: 0), // amDrillStrike
+            (proc: @TestDrillStrike; flags: amtest_Rare), // amDrillStrike
             (proc: nil;              flags: 0), // amSnowball
             (proc: nil;              flags: 0), // amTardis
             (proc: nil;              flags: 0), // amLandGun
             (proc: nil;              flags: 0), // amIceGun
-            (proc: nil;              flags: 0), // amKnife
+            (proc: @TestKnife;       flags: 0), // amKnife
             (proc: nil;              flags: 0), // amRubber
-            (proc: nil;              flags: 0), // amAirMine
+            (proc: @TestAirMine;     flags: 0), // amAirMine
             (proc: nil;              flags: 0), // amCreeper
-            (proc: @TestShotgun;     flags: 0)  // amMinigun
+            (proc: @TestMinigun;     flags: 0)  // amMinigun
             );
 
 implementation
@@ -411,6 +421,87 @@
     TestDrillRocket:= valueResult
 end;
 
+function TestRCPlane(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+const
+    MIN_RANGE =  200;
+var Vx, Vy, meX, meY, x, y: real;
+    rx, ry, valueResult: LongInt;
+    range, maxRange: integer;
+begin
+// This is a very simple test to let a RC plane fly in a straight line, without dropping any bombs
+// TODO: Teach AI how to steer
+// TODO: Teach AI how to drop bombs
+// TODO: Teach AI how to predict fire behavior
+Flags:= Flags; // avoid compiler hint
+if Level = 5 then
+    exit(BadTurn)
+else if Level = 4 then
+    maxRange:= 2200
+else if Level = 3 then
+    maxRange:= 2900
+else if Level = 2 then
+    maxRange:= 3500
+else
+    maxRange:= 3900;
+TestRCPlane:= BadTurn;
+ap.ExplR:= 0;
+ap.Time:= 0;
+ap.Power:= 1;
+meX:= hwFloat2Float(Me^.X);
+meY:= hwFloat2Float(Me^.Y);
+x:= meX;
+y:= meY;
+range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y);
+if ( range < MIN_RANGE ) or ( range > maxRange) then
+    exit(BadTurn);
+
+Vx:= (Targ.Point.X - x) * 1 / 1024;
+Vy:= (Targ.Point.Y - y) * 1 / 1024;
+ap.Angle:= DxDy2AttackAnglef(Vx, -Vy);
+repeat
+    x:= x + vX;
+    y:= y + vY;
+    rx:= trunc(x);
+    ry:= trunc(y);
+    if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 8)) or
+        ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 8)) then
+        begin
+        x:= x + vX * 8;
+        y:= y + vY * 8;
+
+        // Intentionally low rating to discourage use
+        if Level = 1 then
+            valueResult:= RateExplosion(Me, rx, ry, 26, afTrackFall or afErasesLand)
+        else
+            valueResult:= RateExplosion(Me, rx, ry, 26);
+
+        // Check range again in case the plane collided before target
+        range:= Metric(trunc(meX), trunc(meY), rx, ry);
+        if ( range < MIN_RANGE ) or ( range > maxRange) then
+            exit(BadTurn);
+
+        // If impact location is close, above us and wind blows in our direction,
+        // there's a risk of fire flying towards us, so fail in this case.
+        if (Level < 3) and (range <= 600) and (meY >= ry) and
+            (((ap.Angle < 0) and (windSpeed > 0)) or ((ap.Angle > 0) and (windSpeed < 0))) then
+            exit(BadTurn);
+
+        // Apply inaccuracy
+        if (not cLaserSighting) then
+            inc(ap.Angle, + AIrndSign(random((Level - 1) * 9)));
+
+        if (valueResult <= 0) then
+            valueResult:= BadTurn;
+        exit(valueResult)
+        end
+until (Abs(Targ.Point.X - trunc(x)) + Abs(Targ.Point.Y - trunc(y)) < 4)
+    or (x < 0)
+    or (y < 0)
+    or (trunc(x) > LAND_WIDTH)
+    or (trunc(y) > LAND_HEIGHT);
+
+TestRCPlane:= BadTurn
+end;
 
 function TestSnowball(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 var Vx, Vy, r: real;
@@ -479,61 +570,85 @@
 end;
 
 function TestMolotov(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
-var Vx, Vy, r: real;
-    Score, EX, EY, valueResult: LongInt;
-    TestTime: LongInt;
-    targXWrap, x, y, dY, meX, meY: real;
+const timeLimit = 50;
+var Vx, Vy, r, meX, meY: real;
+    rTime: LongInt;
+    EX, EY: LongInt;
+    valueResult: LongInt;
+    targXWrap, x, y, dX, dY: real;
     t: LongInt;
+    value, range: LongInt;
 begin
 Flags:= Flags; // avoid compiler hint
 meX:= hwFloat2Float(Me^.X);
 meY:= hwFloat2Float(Me^.Y);
-valueResult:= BadTurn;
-TestTime:= 0;
+ap.Time:= 0;
+rTime:= 350;
 ap.ExplR:= 0;
 if (WorldEdge = weWrap) then
     if (Targ.Point.X < meX) then
          targXWrap:= Targ.Point.X + (RightX-LeftX)
     else targXWrap:= Targ.Point.X - (RightX-LeftX);
+valueResult:= BadTurn;
 repeat
-    inc(TestTime, 300);
+    rTime:= rTime + 300 + Level * 50 + random(300);
     if (WorldEdge = weWrap) and (random(2)=0) then
-         Vx:= (targXWrap - meX) / TestTime
-    else Vx:= (Targ.Point.X - meX) / TestTime;
-    Vy:= cGravityf * (TestTime div 2) - Targ.Point.Y - meY / TestTime;
+         Vx:= (targXWrap + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime
+    else
+         Vx:= (Targ.Point.X + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime;
+    Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime;
     r:= sqr(Vx) + sqr(Vy);
+
     if not (r > 1) then
         begin
         x:= meX;
         y:= meY;
+        dX:= Vx;
         dY:= -Vy;
-        t:= TestTime;
+        t:= rTime;
         repeat
             x:= CheckWrap(x);
-            x:= x + Vx;
+            x:= x + dX;
+
             y:= y + dY;
             dY:= dY + cGravityf;
             dec(t)
-        until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 6)) or
-               ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 6))) or (t = 0);
+        until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 5)) or
+               ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 5))) or (t < -timeLimit);
+
         EX:= trunc(x);
         EY:= trunc(y);
-        if t < 50 then
-            Score:= RateExplosion(Me, EX, EY, 97)  // average of 17 attempts, most good, but some failing spectacularly
-        else
-            Score:= BadTurn;
+
+        // Sanity check 1: Make sure we're not too close to impact location
+        range:= Metric(trunc(meX), trunc(meY), EX, EY);
+        if (range < 150) and (Level < 5) then
+            exit(BadTurn);
+
+        // Sanity check 2: If impact location is close, above us and wind blows
+        // towards us, there's a risk of fire flying towards us, so fail in this case.
+        if (Level < 3) and (range <= 600) and (trunc(meY) >= EX) and
+            (((ap.Angle < 0) and (windSpeed > 0)) or ((ap.Angle > 0) and (windSpeed < 0))) then
+            exit(BadTurn);
 
-        if valueResult < Score then
+        if t >= -timeLimit then
+            value:= RateExplosion(Me, EX, EY, 97) // average of 17 attempts, most good, but some failing spectacularly
+        else
+            value:= BadTurn;
+
+        if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then
+            value := BadTurn;
+
+        if (valueResult < value) or ((valueResult = value) and (Level < 3)) then
             begin
-            ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random(Level));
-            ap.Power:= trunc(sqrt(r) * cMaxPower) + AIrndSign(random(Level) * 15);
+            ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 9));
+            ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 17 + 1);
             ap.ExplR:= 100;
             ap.ExplX:= EX;
             ap.ExplY:= EY;
-            valueResult:= Score
+            valueResult:= value
             end;
         end
-until (TestTime > 5050 - Level * 800);
+until rTime > 5050 - Level * 800;
 TestMolotov:= valueResult
 end;
 
@@ -547,6 +662,7 @@
 begin
 valueResult:= BadTurn;
 TestTime:= 0;
+ap.Bounce:= 0;
 ap.ExplR:= 0;
 meX:= hwFloat2Float(Me^.X);
 meY:= hwFloat2Float(Me^.Y);
@@ -611,6 +727,7 @@
 Flags:= Flags; // avoid compiler hint
 valueResult:= BadTurn;
 TestTime:= 500;
+ap.Bounce:= 0;
 ap.ExplR:= 0;
 meX:= hwFloat2Float(Me^.X);
 meY:= hwFloat2Float(Me^.Y);
@@ -827,6 +944,9 @@
 Vx:= (Targ.Point.X - x) * 1 / 1024;
 Vy:= (Targ.Point.Y - y) * 1 / 1024;
 ap.Angle:= DxDy2AttackAnglef(Vx, -Vy);
+// Apply inaccuracy
+if (not cLaserSighting) then
+    inc(ap.Angle, + AIrndSign(random((Level - 1) * 10)));
 repeat
     x:= x + vX;
     y:= y + vY;
@@ -881,6 +1001,9 @@
 Vx:= (Targ.Point.X - x) * t;
 Vy:= (Targ.Point.Y - y) * t;
 ap.Angle:= DxDy2AttackAnglef(Vx, -Vy);
+// Apply inaccuracy
+if (not cLaserSighting) then
+    inc(ap.Angle, + AIrndSign(random((Level - 1) * 10)));
 d:= 0;
 
 ix:= trunc(x);
@@ -935,6 +1058,9 @@
 Vx:= (Targ.Point.X - x) * t;
 Vy:= (Targ.Point.Y - y) * t;
 ap.Angle:= DxDy2AttackAnglef(Vx, -Vy);
+// Apply inaccuracy
+inc(ap.Angle, + AIrndSign(random((Level - 1) * 5)));
+
 d:= 0;
 
 repeat
@@ -1160,7 +1286,10 @@
         dx:= (Targ.Point.X - x) * t;
         dy:= (Targ.Point.Y - y) * t;
 
-        ap.Angle:= DxDy2AttackAnglef(dx, -dy)
+        ap.Angle:= DxDy2AttackAnglef(dx, -dy);
+        // Apply inaccuracy
+        if (not cLaserSighting) then
+            inc(ap.Angle, + AIrndSign(random((Level - 1) * 10)));
         end;
 
     if dx >= 0 then cx:= 0.45 else cx:= -0.45;
@@ -1173,7 +1302,7 @@
         valueResult:= valueResult +
             RateShove(Me, trunc(x), trunc(y)
                 , 30, 30, 25
-                , cx, -0.9, trackFall or afSetSkip);
+                , cx, -0.9, trackFall or afSetSkip or afIgnoreMe);
         end;
 
     if (d < 10) and (dx = 0) then
@@ -1183,14 +1312,14 @@
         tx:= trunc(x);
         v:= RateShove(Me, tx, trunc(y)
                 , 30, 30, 25
-                , -cx, -0.9, trackFall);
+                , -cx, -0.9, trackFall or afIgnoreMe);
         for i:= 1 to 512 div step - 2 do
             begin
             y:= y + dy;
             v:= v +
                 RateShove(Me, tx, trunc(y)
                     , 30, 30, 25
-                    , -cx, -0.9, trackFall or afSetSkip);
+                    , -cx, -0.9, trackFall or afSetSkip or afIgnoreMe);
             end
         end;
 
@@ -1203,11 +1332,11 @@
 
     v:= RateShove(Me, trunc(x), trunc(y)
             , 30, 30, 25
-            , cx, -0.9, trackFall);
+            , cx, -0.9, trackFall or afIgnoreMe);
     valueResult:= valueResult + v - KillScore * friendlyfactor div 100 * 1024;
 
     if v < 65536 then
-        inc(valueResult, RateExplosion(Me, trunc(x), trunc(y), 30));
+        inc(valueResult, RateExplosion(Me, trunc(x), trunc(y), 30, afIgnoreMe));
 
     TestKamikaze:= valueResult;
 end;
@@ -1303,6 +1432,361 @@
 TestAirAttack:= valueResult;
 end;
 
+function TestDrillStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+const cShift = 4;
+var bombsSpeed, X, Y, dX, dY, drillX, drillY: real;
+    t2: real;
+    b: array[0..9] of boolean;
+    dmg: array[0..9] of LongInt;
+    fexit, collided: boolean;
+    i, t, value, valueResult, attackTime, drillTimer, targetX: LongInt;
+begin
+Flags:= Flags; // avoid compiler hint
+ap.ExplR:= 0;
+if (Level > 3) or (cGravityf = 0) then
+    exit(BadTurn);
+
+ap.Angle:= 0;
+targetX:= Targ.Point.X;
+ap.AttackPutY:= Targ.Point.Y;
+
+bombsSpeed:= hwFloat2Float(cBombsSpeed);
+X:= Targ.Point.X - 135 - cShift; // hh center - cShift
+X:= X - bombsSpeed * sqrt(((Targ.Point.Y + 128) * 2) / cGravityf);
+Y:= -128;
+dX:= bombsSpeed;
+dY:= 0;
+
+valueResult:= 0;
+
+attackTime:= 0;
+while attackTime <= 4000 do
+    begin
+    inc(attackTime, 1000);
+    value:= 0;
+    for i:= 0 to 9 do
+        begin
+        b[i]:= true;
+        dmg[i]:= 0
+        end;
+
+    repeat
+        X:= X + dX;
+        Y:= Y + dY;
+        dY:= dY + cGravityf;
+        fexit:= true;
+
+        for i:= 0 to 9 do
+            if b[i] then
+                begin
+                fexit:= false;
+                collided:= false;
+                drillX:= trunc(X) + LongWord(i * 30);
+                drillY:= trunc(Y);
+                // Collided with land ... simulate drilling
+                if TestCollExcludingObjects(trunc(drillX), trunc(drillY), 4) and
+                    (Abs(Targ.Point.X - trunc(X)) + Abs(Targ.Point.Y - trunc(Y)) > 21) then
+                    begin
+                    drillTimer := attackTime;
+                    t2 := 0.5 / sqrt(sqr(dX) + sqr(dY));
+                    dX := dX * t2;
+                    dY := dY * t2;
+                    repeat
+                        drillX:= drillX + dX;
+                        drillY:= drillY + dY;
+                        dec(drillTimer, 10);
+                    until (Abs(Targ.Point.X - drillX) + Abs(Targ.Point.Y - drillY) < 22)
+                        or (drillX < 0)
+                        or (drillY < 0)
+                        or (trunc(drillX) > LAND_WIDTH)
+                        or (trunc(drillY) > LAND_HEIGHT)
+                        // TODO: Simulate falling again when rocket has left terrain again
+                        or (drillTimer <= 0);
+                    collided:= true;
+                    end
+                // Collided with something else ... record collision
+                else if TestColl(trunc(drillX), trunc(drillY), 4) then
+                    collided:= true;
+
+                // Simulate explosion
+                if collided then
+                    begin
+                    b[i]:= false;
+                    dmg[i]:= RateExplosion(Me, trunc(drillX), trunc(drillY), 58);
+                    // 58 (instead of 60) for better prediction (hh moves after explosion of one of the rockets)
+                    end;
+                end;
+    until fexit or (Y > cWaterLine);
+
+    for i:= 0 to 5 do
+        if dmg[i] <> BadTurn then
+            inc(value, dmg[i]);
+    t:= value;
+    targetX:= Targ.Point.X - 60;
+
+    for i:= 0 to 3 do
+        if dmg[i] <> BadTurn then
+            begin
+            dec(t, dmg[i]);
+            inc(t, dmg[i + 6]);
+            if t > value then
+                begin
+                value:= t;
+                targetX:= Targ.Point.X - 30 - cShift + i * 30
+                end
+            end;
+
+    if value > valueResult then
+        begin
+        valueResult:= value;
+        ap.AttackPutX:= targetX;
+        ap.Time:= attackTime;
+        end;
+end;
+
+if valueResult <= 0 then
+    valueResult:= BadTurn
+else
+    begin
+    // Weaker AI has chance to get the time wrong by 1-3 seconds
+    if Level = 5 then
+        // +/- 3 seconds
+        ap.Time:= ap.Time + (3 - random(7)) * 1000
+    else if Level = 4 then
+        // +/- 2 seconds
+        ap.Time:= ap.Time + (2 - random(5)) * 1000
+    else if Level = 3 then
+        // +/- 1 second
+        if (random(2) = 0) then
+            ap.Time:= ap.Time + (1 - random(3)) * 1000
+    else if Level = 2 then
+        // 50% chance for +/- 1 second
+        if (random(2) = 0) then
+            ap.Time:= ap.Time + (1 - random(3)) * 1000;
+    ap.Time:= Min(5000, Max(1000, ap.Time));
+    end;
+
+TestDrillStrike:= valueResult;
+end;
+
+function TestMineStrike(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+const cShift = 4;
+var minesSpeed, X, Y, dY: real;
+    b: array[0..9] of boolean;
+    dmg: array[0..9] of LongInt;
+    fexit: boolean;
+    i, t, valueResult: LongInt;
+begin
+Flags:= Flags; // avoid compiler hint
+ap.ExplR:= 0;
+ap.Time:= 0;
+
+// AI currently only supports cMinesTime = 0 because it's the most
+// predictable.
+// Other cMinesTime values are risky because of bouncy mines;
+// so they are unsupported.
+// TODO: Implement mine strike for other values of MineTime
+// TODO: Teach AI to avoid hitting their own with mines
+if (Level > 3) or (cGravityf = 0) or (cMinesTime <> 0) then
+    exit(BadTurn);
+
+ap.Angle:= 0;
+ap.AttackPutX:= Targ.Point.X;
+ap.AttackPutY:= Targ.Point.Y;
+
+minesSpeed:= hwFloat2Float(cBombsSpeed);
+X:= Targ.Point.X - 135 - cShift; // hh center - cShift
+X:= X - minesSpeed * sqrt(((Targ.Point.Y + 128) * 2) / cGravityf);
+Y:= -128;
+dY:= 0;
+
+for i:= 0 to 9 do
+    begin
+    b[i]:= true;
+    dmg[i]:= 0
+    end;
+valueResult:= 0;
+
+repeat
+    X:= X + minesSpeed;
+    Y:= Y + dY;
+    dY:= dY + cGravityf;
+    fexit:= true;
+
+    for i:= 0 to 9 do
+        if b[i] then
+            begin
+            fexit:= false;
+            if TestColl(trunc(X) + LongWord(i * 30), trunc(Y), 4) then
+                begin
+                b[i]:= false;
+                dmg[i]:= RateExplosion(Me, trunc(X) + LongWord(i * 30), trunc(Y), 96)
+                end
+            end;
+until fexit or (Y > cWaterLine);
+
+for i:= 0 to 5 do
+    if dmg[i] <> BadTurn then
+        inc(valueResult, dmg[i]);
+t:= valueResult;
+ap.AttackPutX:= Targ.Point.X - 60;
+
+for i:= 0 to 3 do
+    if dmg[i] <> BadTurn then
+        begin
+        dec(t, dmg[i]);
+        inc(t, dmg[i + 6]);
+        if t > valueResult then
+            begin
+            valueResult:= t;
+            ap.AttackPutX:= Targ.Point.X - 30 - cShift + i * 30
+            end
+        end;
+
+if valueResult <= 0 then
+    valueResult:= BadTurn;
+TestMineStrike:= valueResult;
+end;
+
+function TestSMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+const timeLimit = 50;
+var Vx, Vy, r, meX, meY: real;
+    rTime: LongInt;
+    EX, EY: LongInt;
+    valueResult: LongInt;
+    targXWrap, x, y, dX, dY: real;
+    t: LongInt;
+    value: LongInt;
+begin
+Flags:= Flags; // avoid compiler hint
+meX:= hwFloat2Float(Me^.X);
+meY:= hwFloat2Float(Me^.Y);
+ap.Time:= 0;
+rTime:= 350;
+ap.ExplR:= 0;
+if (WorldEdge = weWrap) then
+    if (Targ.Point.X < meX) then
+         targXWrap:= Targ.Point.X + (RightX-LeftX)
+    else targXWrap:= Targ.Point.X - (RightX-LeftX);
+valueResult:= BadTurn;
+repeat
+    rTime:= rTime + 300 + Level * 50 + random(300);
+    if (WorldEdge = weWrap) and (random(2)=0) then
+         Vx:= (targXWrap + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime
+    else
+         Vx:= (Targ.Point.X + AIrndSign(2) + AIrndOffset(Targ, Level) - meX) / rTime;
+    Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime;
+    r:= sqr(Vx) + sqr(Vy);
+
+    if not (r > 1) then
+        begin
+        x:= meX;
+        y:= meY;
+        dX:= Vx;
+        dY:= -Vy;
+        t:= rTime;
+        repeat
+            x:= CheckWrap(x);
+            x:= x + dX;
+
+            y:= y + dY;
+            dY:= dY + cGravityf;
+            dec(t)
+        until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 2)) or
+               ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 2))) or (t < -timeLimit);
+
+        EX:= trunc(x);
+        EY:= trunc(y);
+
+        if t >= -timeLimit then
+            if (Level = 1) and (Flags and amtest_NoTrackFall = 0) then
+                value:= RateExplosion(Me, EX, EY, 61, afTrackFall)
+            else
+                value:= RateExplosion(Me, EX, EY, 61);
+
+        if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then
+            value := BadTurn;
+
+        if (valueResult < value) or ((valueResult = value) and (Level < 3)) then
+            begin
+            ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 9));
+            ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 17 + 1);
+            ap.ExplR:= 60;
+            ap.ExplX:= EX;
+            ap.ExplY:= EY;
+            valueResult:= value
+            end;
+        end
+until rTime > 5050 - Level * 800;
+TestSMine:= valueResult
+end;
+
+function TestPiano(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+const BOUNCES = 5;
+var X, Y: real;
+    dmg: array[0..BOUNCES-1] of LongInt;
+    i, e, rate, valueResult: LongInt;
+begin
+Flags:= Flags; // avoid compiler hint
+ap.ExplR:= 0;
+ap.Time:= 0;
+if (cGravityf <= 0) then
+    exit(BadTurn);
+
+if (Level > 2) then
+    exit(BadTurn);
+
+ap.Angle:= 0;
+ap.AttackPutX:= Targ.Point.X;
+ap.AttackPutY:= Targ.Point.Y;
+
+X:= Targ.Point.X;
+Y:= -128;
+
+for i:= 0 to BOUNCES-1 do
+    dmg[i]:= 0;
+
+i:= 1;
+repeat
+    // Piano goes down
+    Y:= Y + 11;
+    if TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(X), trunc(Y), 32) then
+        begin
+        for e:= -1 to 1 do
+            begin
+            rate:= RateExplosion(Me, trunc(X) + 30*e, trunc(Y)+40, 161, afIgnoreMe);
+            if rate <> BadTurn then
+                dmg[i]:= dmg[i] + rate;
+            end;
+
+        if (i > 1) and (dmg[i] > 0) then
+            dmg[i]:= dmg[i] div 2;
+        inc(i);
+        // Skip past the blast hole
+        Y:= Y + 41
+        end;
+until (i > BOUNCES) or (Y > cWaterLine);
+
+if (i = 0) and (Y > cWaterLine) then
+    exit(BadTurn);
+
+valueResult:= 0;
+for i:= 0 to BOUNCES do
+    if dmg[i] <= BadTurn then
+        begin
+        valueResult:= BadTurn;
+        break;
+        end
+    else
+        inc(valueResult, dmg[i]);
+ap.AttackPutX:= Targ.Point.X;
+
+valueResult:= valueResult - KillScore * friendlyfactor div 100 * 1024;
+
+if valueResult <= 0 then
+    valueResult:= BadTurn;
+TestPiano:= valueResult;
+end;
 
 function TestTeleport(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 var
@@ -1426,6 +1910,27 @@
     TestCake:= valueResult;
 end;
 
+function TestSeduction(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+var rate: LongInt;
+begin
+Flags:= Flags; // avoid compiler hint
+Level:= Level; // avoid compiler hint
+Targ:= Targ;
+
+if (Level = 5) then
+    exit(BadTurn);
+
+ap.ExplR:= 0;
+ap.Time:= 0;
+ap.Power:= 1;
+ap.Angle:= 0;
+
+rate:= RateSeduction(Me);
+if rate <= 0 then
+    rate:= BadTurn;
+TestSeduction:= rate;
+end;
+
 function TestDynamite(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
 var valueResult: LongInt;
     x, y, dx, dy: real;
@@ -1471,4 +1976,263 @@
 TestDynamite:= valueResult
 end;
 
+function TestMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+var valueResult: LongInt;
+    x, y, dx, dy: real;
+    EX, EY, t: LongInt;
+begin
+Flags:= Flags; // avoid compiler hint
+Targ:= Targ; // avoid compiler hint
+
+x:= hwFloat2Float(Me^.X) + hwSign(Me^.dX) * 7;
+y:= hwFloat2Float(Me^.Y);
+dx:= hwSign(Me^.dX) * 0.02;
+dy:= 0;
+t:= 10000;
+repeat
+    dec(t);
+    x:= x + dx;
+    dy:= dy + cGravityf;
+    y:= y + dy;
+    if ((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 2)) or
+        ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 2)) then
+        t:= 0;
+until t = 0;
+
+EX:= trunc(x);
+EY:= trunc(y);
+
+if Level = 1 then
+    valueResult:= RateExplosion(Me, EX, EY, 51, afTrackFall or afErasesLand)
+else
+    valueResult:= RateExplosion(Me, EX, EY, 51);
+
+if (valueResult > 0) then
+    begin
+    ap.Angle:= 0;
+    ap.Power:= 1;
+    ap.Time:= 0;
+    if (Level < 5) then
+        // Set minimum mine bounciness for improved aim
+        ap.Bounce:= 1
+    else
+        ap.Bounce:= 0;
+    ap.ExplR:= 100;
+    ap.ExplX:= EX;
+    ap.ExplY:= EY
+    end else
+    valueResult:= BadTurn;
+
+TestMine:= valueResult
+end;
+
+function TestKnife(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+const timeLimit = 300;
+var Vx, Vy, r, meX, meY: real;
+    rTime: LongInt;
+    EX, EY: LongInt;
+    valueResult: LongInt;
+    targXWrap, x, y, dX, dY: real;
+    t: LongInt;
+    value, range: LongInt;
+begin
+Flags:= Flags; // avoid compiler hint
+meX:= hwFloat2Float(Me^.X);
+meY:= hwFloat2Float(Me^.Y);
+ap.Time:= 0;
+rTime:= 350;
+ap.ExplR:= 0;
+if (WorldEdge = weWrap) then
+    if (Targ.Point.X < meX) then
+         targXWrap:= Targ.Point.X + (RightX-LeftX)
+    else
+         targXWrap:= Targ.Point.X - (RightX-LeftX);
+valueResult:= BadTurn;
+repeat
+    rTime:= rTime + 300 + Level * 50 + random(300);
+    if (WorldEdge = weWrap) and (random(2)=0) then
+         Vx:= (targXWrap - meX) / rTime
+    else
+         Vx:= (Targ.Point.X - meX) / rTime;
+    Vy:= cGravityf * rTime * 0.5 - (Targ.Point.Y + 1 - meY) / rTime;
+    r:= sqr(Vx) + sqr(Vy);
+
+    if not (r > 1) then
+        begin
+        x:= meX;
+        y:= meY;
+        dX:= Vx;
+        dY:= -Vy;
+        t:= rTime;
+        repeat
+            x:= CheckWrap(x);
+            x:= x + dX;
+
+            y:= y + dY;
+            dY:= dY + cGravityf;
+            dec(t)
+        until (((Me = CurrentHedgehog^.Gear) and TestColl(trunc(x), trunc(y), 7)) or
+               ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, trunc(x), trunc(y), 7))) or (t < -timeLimit);
+
+        EX:= trunc(x);
+        EY:= trunc(y);
+
+        // Sanity check: Make sure we're not too close to impact location
+        range:= Metric(trunc(meX), trunc(meY), EX, EY);
+        if (range <= 40) then
+            exit(BadTurn);
+
+        if t >= -timeLimit then
+            value:= RateShove(Me, EX, EY, 16, trunc(sqr((abs(dY)+abs(dX))*40000/10000)), 0, dX, dY, 0)
+        else
+            value:= BadTurn;
+
+        if (value = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then
+            value := BadTurn;
+
+        if (valueResult < value) or ((valueResult = value) and (Level = 1)) then
+            begin
+            ap.Angle:= DxDy2AttackAnglef(Vx, Vy) + AIrndSign(random((Level - 1) * 12));
+            ap.Power:= trunc(sqrt(r) * cMaxPower) - random((Level - 1) * 22 + 1);
+            valueResult:= value
+            end;
+        end
+until rTime > 5050 - Level * 800;
+TestKnife:= valueResult
+end;
+
+function TestAirMine(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+const
+    MIN_RANGE = 160;
+    MAX_RANGE = 2612;
+var Vx, Vy, meX, meY, x, y, r: real;
+    rx, ry, valueResult: LongInt;
+    range, maxRange: integer;
+begin
+Flags:= Flags; // avoid compiler hint
+maxRange:= MAX_RANGE - ((Level - 1) * 300);
+TestAirMine:= BadTurn;
+ap.ExplR:= 60;
+ap.Time:= 0;
+meX:= hwFloat2Float(Me^.X);
+meY:= hwFloat2Float(Me^.Y);
+x:= meX;
+y:= meY;
+
+// Rough first range check
+range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y);
+if ( range < MIN_RANGE ) or ( range > maxRange ) then
+    exit(BadTurn);
+
+Vx:= (Targ.Point.X - x) * 1 / 1024;
+Vy:= (Targ.Point.Y - y) * 1 / 1024;
+ap.Angle:= DxDy2AttackAnglef(Vx, -Vy);
+repeat
+    x:= x + vX;
+    y:= y + vY;
+    rx:= trunc(x);
+    ry:= trunc(y);
+    if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 8)) or
+        ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 8)) then
+        begin
+        x:= x + vX * 8;
+        y:= y + vY * 8;
+
+        if Level = 1 then
+            valueResult:= RateExplosion(Me, rx, ry, 61, afTrackFall)
+        else
+            valueResult:= RateExplosion(Me, rx, ry, 61);
+
+        // Precise range calculation required to calculate power;
+        // The air mine is very sensitive to small changes in power.
+        r:= sqr(meX - rx) + sqr(meY - ry);
+        range:= trunc(sqrt(r));
+
+        if ( range < MIN_RANGE ) or ( range > maxRange ) then
+            exit(BadTurn);
+        ap.Power:= ((range + cHHRadius*2) * cMaxPower) div MAX_RANGE;
+
+        // Apply inaccuracy
+        inc(ap.Power, (random(93*(Level-1)) - 31*(Level-1))); // Level 1 spread: -124 .. 248
+        if (not cLaserSighting) then
+            inc(ap.Angle, + AIrndSign(random((Level - 1) * 10)));
+
+        if (valueResult <= 0) then
+            valueResult:= BadTurn;
+        exit(valueResult)
+        end
+until (abs(Targ.Point.X - trunc(x)) + abs(Targ.Point.Y - trunc(y)) < 4)
+    or (x < 0)
+    or (y < 0)
+    or (trunc(x) > LAND_WIDTH)
+    or (trunc(y) > LAND_HEIGHT);
+
+TestAirMine := BadTurn
+end;
+
+function TestMinigun(Me: PGear; Targ: TTarget; Level: LongInt; var ap: TAttackParams; Flags: LongWord): LongInt;
+const
+    MAX_RANGE = 400;
+var Vx, Vy, x, y: real;
+    rx, ry, valueResult: LongInt;
+    range: integer;
+begin
+// This code is still very similar to TestShotgun,
+// but it's a good simple estimate.
+// TODO: Simulate random bullets
+// TODO: Replace RateShotgun with something else
+// TODO: Teach AI to move aim during shooting
+Flags:= Flags; // avoid compiler hint
+TestMinigun:= BadTurn;
+ap.ExplR:= 0;
+ap.Time:= 0;
+ap.Power:= 1;
+x:= hwFloat2Float(Me^.X);
+y:= hwFloat2Float(Me^.Y);
+range:= Metric(trunc(x), trunc(y), Targ.Point.X, Targ.Point.Y);
+if ( range > MAX_RANGE ) then
+    exit(BadTurn);
+
+Vx:= (Targ.Point.X - x) * 1 / 1024;
+Vy:= (Targ.Point.Y - y) * 1 / 1024;
+ap.Angle:= DxDy2AttackAnglef(Vx, -Vy);
+// Minigun angle is limited
+if (ap.Angle < Ammoz[amMinigun].minAngle) or (ap.Angle > Ammoz[amMinigun].maxAngle) then
+    exit(BadTurn);
+
+// Apply inaccuracy
+if (not cLaserSighting) then
+    inc(ap.Angle, + AIrndSign(random((Level - 1) * 10)));
+repeat
+    x:= x + vX;
+    y:= y + vY;
+    rx:= trunc(x);
+    ry:= trunc(y);
+    if ((Me = CurrentHedgehog^.Gear) and TestColl(rx, ry, 1)) or
+        ((Me <> CurrentHedgehog^.Gear) and TestCollExcludingMe(Me^.Hedgehog^.Gear, rx, ry, 1)) then
+    begin
+        x:= x + vX * 8;
+        y:= y + vY * 8;
+        // TODO: Use different rating function
+        valueResult:= RateShotgun(Me, vX, vY, rx, ry);
+
+        if (valueResult = 0) and (Targ.Kind = gtHedgehog) and (Targ.Score > 0) then
+            begin
+            if GameFlags and gfSolidLand = 0 then
+                 valueResult:= 1024 - Metric(Targ.Point.X, Targ.Point.Y, rx, ry) div 64
+            else valueResult := BadTurn
+            end
+        else
+            dec(valueResult, Level * 4000);
+        exit(valueResult)
+    end
+until (Abs(Targ.Point.X - trunc(x)) + Abs(Targ.Point.Y - trunc(y)) < 4)
+    or (x < 0)
+    or (y < 0)
+    or (trunc(x) > LAND_WIDTH)
+    or (trunc(y) > LAND_HEIGHT);
+
+TestMinigun:= BadTurn
+end;
+
 end.
--- a/hedgewars/uAIMisc.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uAIMisc.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -27,6 +27,7 @@
       afTrackFall  = $00000001;
       afErasesLand = $00000002;
       afSetSkip    = $00000004;
+      afIgnoreMe   = $00000008;
 
       BadTurn = Low(LongInt) div 4;
 
@@ -86,6 +87,7 @@
 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  RateSeduction(Me: PGear): LongInt;
 function  RateHammer(Me: PGear): LongInt;
 
 function  HHGo(Gear, AltGear: PGear; var GoInfo: TGoInfo): boolean;
@@ -539,20 +541,22 @@
 x:= round(CheckWrap(real(x)));
 fallDmg:= 0;
 rate:= 0;
-// add our virtual position
-with Targets.ar[Targets.Count] do
-    begin
-    Point.x:= hwRound(Me^.X);
-    Point.y:= hwRound(Me^.Y);
-    skip:= false;
-    matters:= true;
-    Kind:= gtHedgehog;
-    Density:= 1;
-    Radius:= cHHRadius;
-    Score:= - ThinkingHH^.Health
-    end;
+
+if (Flags and afIgnoreMe) = 0 then
+    // add our virtual position
+    with Targets.ar[Targets.Count] do
+        begin
+        Point.x:= hwRound(Me^.X);
+        Point.y:= hwRound(Me^.Y);
+        skip:= false;
+        matters:= true;
+        Kind:= gtHedgehog;
+        Density:= 1;
+        Radius:= cHHRadius;
+        Score:= - ThinkingHH^.Health
+        end;
+
 // rate explosion
-
 if (Flags and afErasesLand <> 0) and (GameFlags and gfSolidLand = 0) then erasure:= r
 else erasure:= 0;
 
@@ -561,8 +565,9 @@
 for i:= 0 to Targets.Count do
     if not Targets.ar[i].dead then
         with Targets.ar[i] do
-          if not matters then hadSkips:= true
-            else
+          if not matters then
+            hadSkips:= true
+          else
             begin
             dmg:= 0;
             dmgBase:= r + Radius div 2;
@@ -832,6 +837,92 @@
 ResetTargets;
 end;
 
+function RateSeduction(Me: PGear): LongInt;
+var pX, pY, i, r, rate, subrate, fallDmg: LongInt;
+    diffX, diffY: LongInt;
+    meX, meY, dX, dY: hwFloat;
+    pXr, pYr: real;
+    hadSkips: boolean;
+begin
+meX:= Me^.X;
+meY:= Me^.Y;
+rate:= 0;
+for i:= 0 to Targets.Count do
+    if not Targets.ar[i].dead then
+        with Targets.ar[i] do
+            begin
+            pX:= Point.X;
+            pY:= Point.Y;
+            diffX:= pX - hwRound(meX);
+            diffY:= pY - hwRound(meY);
+            if (Me^.Hedgehog^.BotLevel < 4) and (abs(diffX) <= cHHRadius*2) and (diffY >= 0) and (diffY <= cHHRadius*2) then
+                // Don't use seduction if too close to other hog. We could be
+                // standing on it, so using seduction would remove the ground on
+                // which we stand on, which is dangerous
+                exit(BadTurn);
+
+            if (not matters) then
+                hadSkips:= true
+            else if matters and (Kind = gtHedgehog) and (abs(pX - hwRound(meX)) + abs(pY - hwRound(meY)) < cSeductionDist) then
+                begin
+                r:= trunc(sqrt(sqr(abs(pX - hwRound(meX)))+sqr(abs(pY - hwRound(meY)))));
+                if r < cSeductionDist then
+                    begin
+
+                    if (WorldEdge <> weWrap) or (not (hwAbs(meX - int2hwFloat(pX)) > int2hwFloat(cSeductionDist))) then
+                        dX:= _50 * cGravity * (meX - int2hwFloat(pX)) / _25
+                    else if (not (hwAbs(meX + int2hwFloat((RightX-LeftX) - pX)) > int2hwFloat(cSeductionDist))) then
+                        dX:= _50 * cGravity * (meX + (int2hwFloat((RightX-LeftX) - pX))) / _25
+                    else
+                        dX:= _50 * cGravity * (meX - (int2hwFloat((RightX-LeftX) - pX))) / _25;
+                    dY:= -_450 * cMaxWindSpeed * 2;
+
+
+                    pXr:= pX;
+                    pYr:= pY;
+                    fallDmg:= trunc(TraceShoveFall(pXr, pYr, hwFloat2Float(dX), hwFloat2Float(dY), Targets.ar[i]) * dmgMod);
+
+                    // rate damage
+                    if fallDmg < 0 then // drowning
+                        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 (fallDmg) >= abs(Score) then // deadly fall damage
+                        begin
+                        dead:= true;
+                        Targets.reset:= true;
+                        if (hwFloat2Float(dX) < 0.035) then
+                            begin
+                            subrate:= RealRateExplosion(Me, round(pX), round(pY), 61, afErasesLand or afTrackFall); // hog explodes
+                            if abs(subrate) > 2000 then
+                                inc(rate, subrate)
+                            end;
+                        if Score > 0 then
+                             inc(rate, KillScore * 1024 + (fallDmg)) // tiny bonus for dealing more damage than needed to kill
+                        else
+                             dec(rate, KillScore * friendlyfactor div 100 * 1024)
+                        end
+                    else if (fallDmg <> 0) then // non-deadly fall damage
+                        if Score > 0 then
+                             inc(rate, fallDmg * 1024)
+                        else
+                             dec(rate, fallDmg * friendlyfactor div 100 * 1024)
+                    else // no damage, just shoved
+                        if (Score < 0) then
+                            dec(rate, 100); // small penalty for shoving friendly hogs as it might be dangerous
+                    end;
+                end;
+            end;
+
+if hadSkips and (rate <= 0) then
+    RateSeduction:= BadTurn
+else
+    RateSeduction:= rate * 1024;
+end;
+
 function RateHammer(Me: PGear): LongInt;
 var x, y, i, r, rate: LongInt;
     hadSkips: boolean;
--- a/hedgewars/uConsts.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uConsts.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -228,6 +228,7 @@
     cSeductionDist = 250; // effect distance of seduction
 
     ExtraTime = 30000; // amount of time (ms) given for using Extra Time
+    MaxMoreWindTime = 5000; // amount of time (ms) for land objects like gfMine to be affected after end of turn
 
     // do not change this value
     cDefaultZoomLevel = 2.0; // 100% zoom
--- a/hedgewars/uGears.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uGears.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -595,6 +595,11 @@
             dec(TurnTimeLeft)
         end;
 
+if (TurnTimeLeft = 0) and (ReadyTimeLeft = 0) then
+    inc(TimeNotInTurn)
+else
+    TimeNotInTurn:= 0;
+
 if skipFlag then
     begin
     if TagTurnTimeLeft = 0 then
--- a/hedgewars/uGearsHandlersMess.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uGearsHandlersMess.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -535,8 +535,15 @@
 
     if isFalling and (Gear^.State and gstNoGravity = 0) then
         begin
+        // Apply gravity and wind
         Gear^.dY := Gear^.dY + cGravity;
-        if (GameFlags and gfMoreWind <> 0) and (TurnTimeLeft > 0) and
+        if ((GameFlags and gfMoreWind) <> 0) and
+           // Disable gfMoreWind for land objects on turn end to prevent bouncing them forever
+           // This solution is rather ugly, in that it will occassionally suddenly wind physics
+           // while a gear is moving, this can be rather confusing.
+           // TODO: Find a way to make gfMoreWind-affected land objects settle more reliably
+           // and quickler without touching wind itselvs
+           ((not (Gear^.Kind in [gtMine, gtAirMine, gtSMine, gtKnife, gtExplosives])) or (TimeNotInTurn < MaxMoreWindTime)) and
            ((xland or land) = 0) and
            ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) > _0_02.QWordValue) then
             Gear^.dX := Gear^.dX + cWindSpeed / Gear^.Density
--- a/hedgewars/uGearsHedgehog.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uGearsHedgehog.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -529,7 +529,8 @@
                   amMineStrike, amDrillStrike,
                       amRubber, amMinigun: CurAmmoGear:= newGear;
             end;
-            if CurAmmoType = amCake then FollowGear:= newGear;
+            if (CurAmmoType = amCake) or (CurAmmoType = amPiano) then
+                FollowGear:= newGear;
 
             if ((CurAmmoType = amMine) or (CurAmmoType = amSMine) or (CurAmmoType = amAirMine)) and (GameFlags and gfInfAttack <> 0) then
                 newGear^.FlightTime:= GameTicks + min(TurnTimeLeft,1000)
--- a/hedgewars/uVariables.pas	Sun Jun 21 03:00:39 2020 +0300
+++ b/hedgewars/uVariables.pas	Sun Jun 21 03:08:21 2020 +0300
@@ -105,6 +105,7 @@
     TurnClockActive : boolean;
     TagTurnTimeLeft : Longword;
     ReadyTimeLeft   : Longword;
+    TimeNotInTurn   : Longword; // Milliseconds that passed while no turn is active
     IsGetAwayTime   : boolean;
     GameOver        : boolean;
     cSuddenDTurns   : LongInt;
@@ -2898,6 +2899,7 @@
     GameOver            := false;
     TurnClockActive     := true;
     TagTurnTimeLeft     := 0;
+    TimeNotInTurn       := 0;
     cSuddenDTurns       := 15;
     LastSuddenDWarn     := -2;
     cInitHealth         := 100;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/flags_js.xhtml	Sun Jun 21 03:08:21 2020 +0300
@@ -0,0 +1,228 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<!-- There is, at present, no official xsd for (X)HTML5. A pity. Usefulness would depend on the parser and extensions made by the site.  -->
+    <title>Hedgewars Flags</title>
+
+    <style type="text/css">
+* {padding: 0; margin: 0; }
+body
+{
+    background-color: #0B203D;
+    color: #FFD902;
+    background-size: 100% 100%;
+    font-family: sans-serif;
+}
+form, p
+{
+    background-color: #0B203D;
+    padding: 1em;
+    margin: 1em;
+    border-style: solid;
+    border-radius: 5px;
+    border-width: 2px;
+    border-color: #FFD902;
+}
+h1 {
+    margin:10px;
+}
+a {
+    color: #BFBED0;
+    text-decoration: none;
+}
+.flag
+{
+    margin-top: 12px;
+    margin-left: 20px;
+    float: left;
+    border-radius: 3px;
+    border-color: white;
+    border-width: 1px;
+    border-style: solid;
+    height: 17px;
+    width: 24px;
+    color: transparent;
+}
+a div
+{
+    height: 15px;
+    width: 22px;
+    border-radius: 3px;
+    border-color: black;
+    border-width: 1px;
+    border-style: solid;
+}
+    </style>
+    <script type="application/ecmascript">
+//<![CDATA[
+"use strict";
+var IS_LOCAL=false; // set to true to fetch flags locally. Useful for testing.
+var flags;
+if (IS_LOCAL) {
+/* JavaScript version of a sprite sheet - this could be pretty trivially done in pure HTML, but maintenance
+would be easier with a server-side portion. list of sprites could be gotten from server, but would require XSS whitelisting */
+// Last updated: 1.0.0
+flags=["afghanistan","albania","algeria","american_samoa","andorra","angola","anguilla","antigua_and_barbuda","arabemirates",
+"argentina","armenia","aruba","australia","austria","azerbaijan","bahamas","bahrain","bangladesh","barbados","belarus","belgium",
+"belize","benin","bhutan","bolivia","bosnia_and_herzegovina","botswana","brazil","brunei","bulgaria","burkina_faso","burundi",
+"cambodia","cameroon","canada","cape_verde","central_african_republic","chad","chile","china","christmas_island","cm_42",
+"cm_anarchy","cm_balls","cm_balrog","cm_bars","cm_belarus","cm_binary","cm_birdy","cm_bloodyblade","cm_brittany","cm_bubbles",
+"cm_bustamove","cm_cheese","cm_cog","cm_crossedswords","cm_crosshair","cm_cyborg","cm_danger_fire","cm_danger_stripes",
+"cm_dragonrb","cm_duckhead","cm_earth2","cm_earth","cm_eyeofhorus","cm_eyes","cm_face","cm_fcw","cm_female","cm_firstaid",
+"cm_flames","cm_flower","cm_galaxy","cm_girder","cm_grenade","cm_hax0r","cm_heart","cm_hellish","cm_hurrah","cm_hw2",
+"cm_hw","cm_iluvu","cm_kiwi","cm_lips","cm_magicskull","cm_male","cm_mog","cm_music","cm_pacman2","cm_pacman","cm_pentagram",
+"cm_piet","cm_pirate","cm_pokemon","cm_scout","cm_shoppa","cm_sine","cm_skull","cm_sonic","cm_soviet","cm_spider","cm_star",
+"cm_swordshield2","cm_swordshield","cm_test","cm_vampire","cm_waves","cm_yinyang","colombia","comoros",
+"congo-brazzaville","congo_kinshasa","cook_islands","costa_rica","cote_divoire","croatia","cuba","cyprus",
+"czech_republic","denmark","djibouti","dominican_republic","dominica","easttimor","ecuador","egypt","el_salvador",
+"equatorial_guinea","eritrea","esperanto","estonia","ethiopia","europeanunion","fiji","finland","france","gabon","gambia",
+"georgia","germany","ghana","greece","grenada","guam","guatemala","guinea","guyana","haiti","hedgewars","honduras","hungary",
+"iceland","india","indonesia","iran","iraq","ireland","israel","italy","jamaica","japan","jordan","kazakhstan","kenya","kiribati",
+"kuwait","kyrgyzstan","laos","latvia","lebanon","lesotho","liberia","libya","liechtenstein","lithuania","luxembourg","macau",
+"macedonia","madagascar","malawi","malaysia","maldives","mali","malta","marshall_islands","mauritania","mauritius",
+"mexico","micronesia","moldova","monaco","mongolia","montenegro","montserrat","morocco","mozambique","myanmar","namibia","nauru",
+"nepal","netherlands","new_zealand","nicaragua","nigeria","niger","niue","northern_mariana","northkorea","norway","oman",
+"pakistan","palau","palestine","panama","papua_new_guinea","paraguay","peru","philippines","poland","portugal","puerto_rico",
+"qatar","quebec","romania","russian_federation","rwanda","saint_kitts_and_nevis","saint_lucia","saint_vincent_and_the_grenadines",
+"samoa","san_marino","saotomeandprincipe","saudiarabia","senegal","serbia_and_montenegro","serbia","seychelles","sicily",
+"sierra_leone","singapore","slovakia","slovenia","solomon_islands","somalia","south_africa","south_korea","south_sudan","spain",
+"sri_lanka","sudan","suisse","suriname","swaziland","sweden","syrian_arab_republic","taiwan","tajikistan","tanzania","thailand",
+"tibet","timor_leste","togo","tokelau","tonga","trinidad_and_tobago","tunisia","turkey","turkmenistan","tuvalu","uganda","ukraine",
+"uk_scotland","united_arab_emirates","united_kingdom","united_states","uruguay","uzbekistan","vanuatu","vatican","venezuela","vietnam",
+"western_sahara","yemen","yugoslavia","zambia","zimbabwe"
+//,"cpu","cpu_plain"
+];
+}
+else
+{
+flags = [];
+}
+
+var on_xml_loaded = function(ex)
+{
+    var resp = this.responseText;
+    var r = />([^<]*).png</g;
+    var x;
+    while(x = r.exec(resp))
+    {
+        flags.push(x[1]);
+    }
+    on_flags_loaded();
+}
+
+var on_xml_error = function()
+{
+    var p = document.createElement("p");
+    p.appendChild(document.createTextNode("ERROR: List of flags could not be fetched from the server!"));
+    document.body.appendChild(p);
+}
+
+var on_flags_loaded;
+
+window.onload = function()
+{
+    // Load list of flags
+    if (!IS_LOCAL) {
+        // Request list of flags from repository URL
+        var xml=new XMLHttpRequest();
+        xml.open("GET", "//hg.hedgewars.org/hedgewars/file/tip/share/hedgewars/Data/Graphics/Flags/");
+        xml.addEventListener("error", on_xml_error);
+        xml.onload = on_xml_loaded;
+        xml.send();
+    }
+    else
+    {
+        on_flags_loaded();
+    }
+}
+
+on_flags_loaded = function()
+{
+    // Sort flags
+    var flag_compare = function(a, b)
+    {
+        if (a === "hedgewars")
+            return false;
+        else if (b === "hedgewars")
+            return true;
+        else if (a.startsWith("cm_") && !b.startsWith("cm_"))
+            return true;
+        else if (!a.startsWith("cm_") && b.startsWith("cm_"))
+            return false;
+        else
+            return a > b;
+    }
+
+    flags.sort(flag_compare);
+
+    // Render flags
+    var img;
+    var j = 0;
+    var toDelete = [];
+    var a = document.createElement("a");
+    a.className="flag";
+    a.appendChild(document.createElement("div"));
+    a.lastChild.appendChild(document.createTextNode(""));
+
+    var flagState = 0; // 0 = hedgewars, 1 = country flag, 2 = community flag ("cm_")
+
+    for (var i=0;i<flags.length;i++)
+    {
+        var flag = flags[i];
+
+        var oldFlagState = flagState;
+        if (flagState === 0 && flag !== "hedgewars")
+            flagState++;
+        else if (flagState === 1 && flag.startsWith("cm_"))
+            flagState++;
+        if (flagState !== oldFlagState)
+        {
+            j = 0;
+            document.body.appendChild(document.createElement("br"));
+            document.body.appendChild(document.createElement("br"));
+            document.body.appendChild(document.createElement("br"));
+        }
+
+        var h = document.body.appendChild(a.cloneNode(true));
+        if (IS_LOCAL)
+            h.href = "../share/hedgewars/Data/Graphics/Flags/"+flag+".png";
+        else
+            h.href = "//hg.hedgewars.org/hedgewars/raw-file/tip/share/hedgewars/Data/Graphics/Flags/"+flag+".png";
+
+        img = new Image();
+        img.onload = function() {
+            var name = this.id.substr(7);
+            if (this.height === 32) {
+                staticMasks[name] = true;
+            }
+            this.remove();
+        }
+        img.src = h.href;
+        img.id = "__flag_"+flag;
+
+        h.lastChild.style.backgroundImage = 'url("'+h.href+'")';
+        h.lastChild.lastChild.data = flag;
+        h.title = flag;
+
+        if (j%17 === 16 || i === flags.length-1)
+        {
+            document.body.appendChild(document.createElement("br"));
+            document.body.appendChild(document.createElement("br"));
+            j = 0;
+        } else
+            j++;
+    }
+
+}
+
+//]]>
+    </script>
+</head>
+<body>
+<h1>List of Hedgewars flags</h1>
+<noscript>
+<p><strong>ERROR</strong>: We're so sorry, but this webpage only works with JavaScript enabled. It seems JavaScript is disabled or not supported in your browser.<br/>
+Normally, this webpage would display a preview of the flags in Hedgewars.</p>
+</noscript>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/graves_js_anim.xhtml	Sun Jun 21 03:08:21 2020 +0300
@@ -0,0 +1,319 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<!-- There is, at present, no official xsd for (X)HTML5. A pity. Usefulness would depend on the parser and extensions made by the site.  -->
+    <title>Hedgewars Graves</title>
+
+    <style type="text/css">
+* {padding: 0; margin: 0; }
+body
+{
+    background: url('//hg.hedgewars.org/hedgewars/raw-file/tip/share/hedgewars/Data/Themes/Nature/Sky.png') fixed no-repeat bottom left;
+    background-color: #0B203D;
+    color: #FFD902;
+    -moz-background-size: 200%;
+    background-size: 100% 100%;
+    font-family: sans-serif;
+}
+form, p
+{
+    background-color: #0B203D;
+    padding: 1em;
+    margin: 1em;
+    border-style: solid;
+    border-radius: 5px;
+    border-width: 2px;
+    border-color: #FFD902;
+}
+h1 {
+    text-shadow: 0 0 2px white;
+    color: black;
+    margin:10px;
+}
+a {
+    color: #BFBED0;
+    text-decoration: none;
+}
+.grave
+{
+    margin-top: 12px;
+    margin-left: 20px;
+    float: left;
+    height: 32px;
+    width: 32px;
+    color: transparent;
+}
+.girder
+{
+    width: 100%;
+    height: 30px;
+    clear: left;
+    background-image: url('//hg.hedgewars.org/hedgewars/raw-file/tip/share/hedgewars/Data/Themes/Nature/Girder.png');
+    background-repeat: repeat-x;
+}
+.hide { visibility: hidden; }
+a div
+{
+    margin-top: 6px;
+    height: 32px;
+    width: 32px;
+}
+    </style>
+    <script type="application/ecmascript">
+//<![CDATA[
+var IS_LOCAL=false; // set to true to fetch graves locally. Useful for testing.
+var graves;
+if (IS_LOCAL) {
+/* JavaScript version of a sprite sheet - this could be pretty trivially done in pure HTML, but maintenance
+would be easier with a server-side portion. list of sprites could be gotten from server, but would require XSS whitelisting */
+// Last updated: 1.0.0
+graves=["Badger","Bone","bp2","bubble","Cherry","chest","Clover","coffin",
+"deadhog","dragonball","Duck2","Earth","Egg","eyecross","Flower","Ghost",
+"Grave","heart","money","mouton1","octopus","Old_Apple","pi","plant2",
+"plant3","Plinko","pokeball","pyramid","ring","Rip","Rubberduck","Simple",
+"Simple_reversed","skull","star","Statue","TV","Whisky","Yin_and_Yang"];
+}
+else
+{
+graves = [];
+}
+
+var themes = {
+// Last updated: 1.0.0
+"Art":1,
+"Beach":1,
+"Bamboo":1,
+"Bath":1,
+//"Blox":0, //unused, has no Sky.png or Border.png
+"Brick":0,
+"Cake":0,
+"Castle":1,
+"Cave":1,
+"City":1,
+"Cheese":0,
+"Christmas":1,
+"Compost":1,
+"CrazyMission":0,
+"Deepspace":0,
+"Desert":1,
+"EarthRise":0,
+"Eyes":0,
+"Freeway":0,
+"Fruit":1,
+"Halloween":1,
+"Hell":0,
+"Hoggywood":1,
+"Island":0,
+"Jungle":1,
+"Golf":1,
+"Nature":1,
+"Olympics":1,
+"Planes":0,
+"Sheep":1,
+"Snow":1,
+"Stage":1,
+"Underwater":1};
+var girder;
+var animationInterval;
+
+on_xml_loaded = function(ex)
+{
+    var resp = this.responseText;
+    var r = />([^<]*).png</g;
+    var x;
+    while(x = r.exec(resp))
+    {
+        graves.push(x[1]);
+    }
+    on_graves_loaded();
+}
+
+on_xml_error = function()
+{
+    var p = document.createElement("p");
+    p.appendChild(document.createTextNode("ERROR: List of graves could not be fetched from the server!"));
+    document.body.appendChild(p);
+}
+
+window.onload = function()
+{
+    // Load list of graves
+    if (!IS_LOCAL) {
+        // Request list of graves from repository URL
+        var xml=new XMLHttpRequest();
+        xml.open("GET", "//hg.hedgewars.org/hedgewars/file/tip/share/hedgewars/Data/Graphics/Graves/");
+        xml.addEventListener("error", on_xml_error);
+        xml.onload = on_xml_loaded;
+        xml.send();
+    }
+    else
+    {
+        on_graves_loaded();
+    }
+}
+
+on_graves_loaded = function()
+{
+    // Render girders
+    var s = document.styleSheets[0].cssRules;
+    for(var i=0;i<s.length;i++)
+    {
+        if (s[i].selectorText.toLowerCase() === ".girder")
+            girder = s[i];
+    }
+
+    var a = document.createElement("a");
+    var g = document.createElement("div");
+    g.className="girder";
+    a.className="grave";
+    a.appendChild(document.createElement("div"));
+    a.lastChild.appendChild(document.createTextNode(""));
+
+    // Render graves
+    var missingGraves = [];
+    var img;
+    var j = 0;
+    var toDelete = [];
+    for (var i=0;i<graves.length;i++)
+    {
+        var h = document.body.appendChild(a.cloneNode(true));
+        if (IS_LOCAL)
+            h.href = "../share/hedgewars/Data/Graphics/Graves/"+graves[i]+".png";
+        else
+            h.href = "//hg.hedgewars.org/hedgewars/raw-file/tip/share/hedgewars/Data/Graphics/Graves/"+graves[i]+".png";
+
+        h.lastChild.style.backgroundImage = 'url("'+h.href+'")';
+        h.lastChild.lastChild.data = graves[i];
+        h.title = graves[i];
+        h.idle = Math.floor(Math.random()*16);
+        if (j%8 === 7 || i === graves.length-1)
+            document.body.appendChild(g.cloneNode(false));
+        j++;
+    }
+
+    // Quick and dirty animation
+    animationInterval = setInterval(animateGraves, 128);
+
+    // Theme selection drop-down list
+    var form = document.body.appendChild(document.createElement("form"));
+
+    var opt = document.createElement("option");
+    opt.appendChild(document.createTextNode(""));
+
+    var label = document.createElement("label");
+    label.htmlFor = "theme_select";
+    label.appendChild(document.createTextNode("Theme: "));
+    form.appendChild(label);
+
+    var sel = form.appendChild(document.createElement("select"));
+    sel.id = "theme_select";
+    sel.onchange = switchTheme;
+    for(var theme in themes)
+    {
+        sel.appendChild(opt.cloneNode(true));
+        sel.lastChild.value = theme;
+        sel.lastChild.lastChild.data = theme;
+        if(theme === "Nature")
+            sel.lastChild.selected = true;
+    }
+    form.appendChild(document.createElement("br"));
+
+    // Checkbox: Switch animation
+    var chk = document.createElement("input");
+    chk.id = "anim";
+    chk.type = "checkbox";
+    chk.onclick = switchAnim;
+    chk.checked = true;
+    form.appendChild(chk);
+    label = document.createElement("label");
+    label.htmlFor = "anim";
+    label.appendChild(document.createTextNode("Animate graves"));
+    form.appendChild(label);
+
+    form.appendChild(document.createElement("br"));
+
+    // Checkbox: Hide girders
+    chk = document.createElement("input");
+    chk.id = "hide_girders";
+    chk.type = "checkbox";
+    chk.onclick = hideGirders;
+    chk.checked = true;
+    form.appendChild(chk);
+    label = document.createElement("label");
+    label.htmlFor = "hide_girders";
+    label.appendChild(document.createTextNode("Show girders"));
+    form.appendChild(label);
+
+    document.body.appendChild(form);
+
+
+}
+
+function animateGraves()
+{
+    var a = document.getElementsByTagName("a");
+    for (var i=0;i<a.length;i++)
+    {
+        if (a[i].className !== "grave")
+            continue;
+        // Cycle thru animation frames
+
+        var maskName = a[i].title;
+        // Grave
+        a[i].firstChild.style.backgroundPosition=Math.floor(a[i].idle/16)*-32+"px "+(a[i].idle%16)*-32+"px";
+
+        // Next frame
+        a[i].idle++;
+        if (a[i].idle > 15)
+            a[i].idle = 0;
+    }
+}
+
+// Turn on or off grave animation
+function switchAnim()
+{
+    if (animationInterval)
+    {
+        clearInterval(animationInterval);
+        animationInterval = null;
+    }
+    else animationInterval = setInterval(animateGraves, 128);
+}
+
+// Turn on or off girders
+function hideGirders()
+{
+    var g = document.getElementsByClassName("girder");
+    for(var i=0;i<g.length;i++)
+        if (this.checked)
+            g[i].className = "girder";
+        else
+            g[i].className = "girder hide";
+
+}
+
+// Select theme according to drop-down list value
+function switchTheme()
+{
+    var prefix;
+    if (!IS_LOCAL)
+        prefix = "//hg.hedgewars.org/hedgewars/raw-file/tip";
+    else
+        prefix = "..";
+    document.body.style.backgroundImage='url("'+prefix+'/share/hedgewars/Data/Themes/'+this.value+'/Sky.png")';
+    if (themes[this.value])
+        girder.style.backgroundImage='url("'+prefix+'/share/hedgewars/Data/Themes/'+this.value+'/Girder.png")';
+    else
+        girder.style.backgroundImage='url("'+prefix+'/share/hedgewars/Data/Graphics/Girder.png")';
+}
+//]]>
+    </script>
+</head>
+<body>
+<h1>List of Hedgewars graves</h1>
+<noscript>
+<p><strong>ERROR</strong>: We're so sorry, but this webpage only works with JavaScript enabled. It seems JavaScript is disabled or not supported in your browser.<br/>
+Normally, this webpage would display an animated preview of the graves in Hedgewars.</p>
+</noscript>
+</body>
+</html>