hedgewars/GSHandlers.inc
author sheepluva
Tue, 04 May 2010 02:46:23 +0000
changeset 3416 310fda7c1dc5
parent 3415 1ca22b3493e9
child 3417 27ff4a6375dd
permissions -rw-r--r--
fix segfault on destroyed portal

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2010 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
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 *)

procedure makeHogsWorry(x, y: hwFloat; r: LongInt);
var gi: PGear;
     d: LongInt;
begin
    gi:= GearsList;
    while gi <> nil do
        begin
        if (gi^.Kind = gtHedgehog) then
            begin
            d:= r - hwRound(Distance(gi^.X - x, gi^.Y - y));
            if (d > 1) and not gi^.Invulnerable and (GetRandom(2) = 0) then
                begin
                if (CurrentHedgehog^.Gear = gi) then
                    PlaySound(sndOops, PHedgehog(gi^.Hedgehog)^.Team^.voicepack)
                else
                    begin
                    if (gi^.State and gstMoving) = 0 then
                        gi^.State:= gi^.State or gstLoser;
                    if d > r div 2 then
                        PlaySound(sndNooo, PHedgehog(gi^.Hedgehog)^.Team^.voicepack)
                    else
                        PlaySound(sndUhOh, PHedgehog(gi^.Hedgehog)^.Team^.voicepack);
                    end;
                end;
            end;
        gi:= gi^.NextGear
        end;
end;
////////////////////////////////////////////////////////////////////////////////
procedure doStepDrowningGear(Gear: PGear); forward;

function CheckGearDrowning(Gear: PGear): boolean;
var skipSpeed, skipAngle, skipDecay: hwFloat;
    i, maxDrops: LongInt;
    particle: PVisualGear;
begin
// probably needs tweaking. might need to be in a case statement based upon gear type
if cWaterLine < hwRound(Gear^.Y) + Gear^.Radius then
    begin
    skipSpeed:= _0_25;
    skipAngle:= _1_9;  
    skipDecay:= _0_87;  // this could perhaps be a tiny bit higher.
    if  (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) > skipSpeed) and
        (hwAbs(Gear^.dX) > skipAngle * hwAbs(Gear^.dY)) then
       begin
       Gear^.dY.isNegative:= true;
       Gear^.dY:= Gear^.dY * skipDecay;
       Gear^.dX:= Gear^.dX * skipDecay;
       CheckGearDrowning:= false;
       PlaySound(sndSkip)
       end
    else
        begin
        CheckGearDrowning:= true;
        Gear^.State:= gstDrowning;
        Gear^.RenderTimer:= false;
        if (Gear^.Kind <> gtSniperRifleShot) and (Gear^.Kind <> gtShotgunShot) and (Gear^.Kind <> gtDEagleShot) and (Gear^.Kind <> gtSineGunShot) then
          Gear^.doStep:= @doStepDrowningGear;
        if Gear^.Kind = gtHedgehog then
            begin
            Gear^.State:= Gear^.State and (not gstHHDriven);
            AddCaption(Format(GetEventString(eidDrowned), PHedgehog(Gear^.Hedgehog)^.Name), cWhiteColor, capgrpMessage);
            end;
        if hwRound(Gear^.Y) < cWaterLine + 64 + Gear^.Radius then // don't play splash if they are already way past the surface
            PlaySound(sndSplash)
        end;
    
    if not cReducedQuality and (hwRound(Gear^.Y) < cWaterLine + 64 + Gear^.Radius) then 
        begin
        AddVisualGear(hwRound(Gear^.X), cWaterLine, vgtSplash);
       
        maxDrops := (Gear^.Radius div 2) + hwRound(Gear^.dX * Gear^.Radius * 2) + hwRound(Gear^.dY * Gear^.Radius * 2);
        for i:= max(maxDrops div 3, min(32, Random(maxDrops))) downto 0 do 
            begin
            particle := AddVisualGear(hwRound(Gear^.X) - 3 + Random(6), cWaterLine, vgtDroplet);
            if particle <> nil then
                begin
                particle^.dX := particle^.dX - (Gear^.dX / 10);
                particle^.dY := particle^.dY - (Gear^.dY / 5)
                end
            end
        end;
    end
else
    CheckGearDrowning:= false
end;

procedure CheckCollision(Gear: PGear);
begin
if TestCollisionXwithGear(Gear, hwSign(Gear^.X)) or TestCollisionYwithGear(Gear, hwSign(Gear^.Y))
    then Gear^.State:= Gear^.State or      gstCollision
    else Gear^.State:= Gear^.State and not gstCollision
end;

procedure CheckHHDamage(Gear: PGear);
var 
    dmg: Longword;
    i: LongInt;
    particle: PVisualGear;
begin
if _0_4 < Gear^.dY then
    begin
    dmg:= ModifyDamage(1 + hwRound((hwAbs(Gear^.dY) - _0_4) * 70), Gear);
    if dmg < 1 then exit;

    for i:= min(12, (3 + dmg div 10)) downto 0 do begin
        particle := AddVisualGear(hwRound(Gear^.X) - 5 + Random(10), hwRound(Gear^.Y) + 12, vgtDust);
        if particle <> nil then particle^.dX := particle^.dX + (Gear^.dX / 5);
        end;

    if(Gear^.Invulnerable) then exit;

    if _0_6 < Gear^.dY then
        PlaySound(sndOw4, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack)
    else
        PlaySound(sndOw1, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack);

    ApplyDamage(Gear, dmg);
    end
end;

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
procedure CalcRotationDirAngle(Gear: PGear);
var dAngle: real;
begin
dAngle:= (Gear^.dX.QWordValue + Gear^.dY.QWordValue) / $80000000;
if not Gear^.dX.isNegative then
    Gear^.DirAngle:= Gear^.DirAngle + dAngle
else
    Gear^.DirAngle:= Gear^.DirAngle - dAngle;

if Gear^.DirAngle < 0 then Gear^.DirAngle:= Gear^.DirAngle + 360
else if 360 < Gear^.DirAngle then Gear^.DirAngle:= Gear^.DirAngle - 360
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepDrowningGear(Gear: PGear);
begin
AllInactive:= false;
Gear^.Y:= Gear^.Y + cDrownSpeed;
Gear^.X:= Gear^.X + Gear^.dX * cDrownSpeed;
if (cWaterOpacity > $FE) or (hwRound(Gear^.Y) > Gear^.Radius + cWaterLine + cVisibleWater) then DeleteGear(Gear);
// Create some bubbles (0.5% might be better but causes too few bubbles sometimes)
if (cWaterOpacity < $FF) and ((GameTicks and $1F) = 0) then
    if (Gear^.Kind = gtHedgehog) and (Random(4) = 0) then
        AddVisualGear(hwRound(Gear^.X) - Gear^.Radius, hwRound(Gear^.Y) - Gear^.Radius, vgtBubble)
    else if Random(12) = 0 then
        AddVisualGear(hwRound(Gear^.X) - Gear^.Radius, hwRound(Gear^.Y) - Gear^.Radius, vgtBubble)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepFallingGear(Gear: PGear);
var isFalling: boolean;
    //tmp: QWord;
    tdX, tdY: hwFloat;
    collV, collH: LongInt;
begin
if Gear^.dX > _0_995 then Gear^.dX:= _0_995;
if Gear^.dY > _0_995 then Gear^.dY:= _0_995;
Gear^.State:= Gear^.State and not gstCollision;
collV:= 0; 
collH:= 0;
tdX:= Gear^.dX;
tdY:= Gear^.dY;

// might need some testing/adjustments - just to avoid projectiles to fly forever (accelerated by wind/skips)
if (hwRound(Gear^.X) < LAND_WIDTH div -2) or (hwRound(Gear^.X) > LAND_WIDTH * 3 div 2) then
    begin
    Gear^.State:= Gear^.State or gstCollision;
    exit
    end;

if Gear^.dY.isNegative then
    begin
    isFalling:= true;
    if TestCollisionYwithGear(Gear, -1) then
        begin
        collV:= -1;
        Gear^.dX:=   Gear^.dX * Gear^.Friction;
        Gear^.dY:= - Gear^.dY * Gear^.Elasticity;
        Gear^.State:= Gear^.State or gstCollision
        end
    else if (Gear^.AdvBounce=1) and TestCollisionYwithGear(Gear, 1) then collV:= 1;
    end 
else if TestCollisionYwithGear(Gear, 1) then
    begin
    collV:= 1;
    isFalling:= false;
    Gear^.dX:=   Gear^.dX * Gear^.Friction;
    Gear^.dY:= - Gear^.dY * Gear^.Elasticity;
    Gear^.State:= Gear^.State or gstCollision
    end
else
    begin
    isFalling:= true;
    if (Gear^.AdvBounce=1) and not Gear^.dY.isNegative and TestCollisionYwithGear(Gear, -1) then collV:= -1;
    end;


if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then
    begin
    collH:= hwSign(Gear^.dX);
    Gear^.dX:= - Gear^.dX * Gear^.Elasticity;
    Gear^.dY:=   Gear^.dY * Gear^.Elasticity;
    Gear^.State:= Gear^.State or gstCollision
    end 
else if (Gear^.AdvBounce=1) and TestCollisionXwithGear(Gear, -hwSign(Gear^.dX)) then collH:= -hwSign(Gear^.dX);

//if Gear^.AdvBounce and (collV <>0) and (collH <> 0) and (hwSqr(tdX) + hwSqr(tdY) > _0_08) then
if (Gear^.AdvBounce=1) and (collV <>0) and (collH <> 0) and ((collV=-1) or ((tdX.QWordValue + tdY.QWordValue) > _0_2.QWordValue)) then
    begin
    Gear^.dX:= tdY*Gear^.Elasticity*Gear^.Friction;
    Gear^.dY:= tdX*Gear^.Elasticity;//*Gear^.Friction;
    Gear^.dY.isNegative:= not tdY.isNegative;
    isFalling:= false;
    Gear^.AdvBounce:= 10;
    end;

if Gear^.AdvBounce > 1 then dec(Gear^.AdvBounce);

if isFalling then Gear^.dY:= Gear^.dY + cGravity;

Gear^.X:= Gear^.X + Gear^.dX;
Gear^.Y:= Gear^.Y + Gear^.dY;
CheckGearDrowning(Gear);
//if (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) < _0_0002) and
if ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) < _0_02.QWordValue) and
    (not isFalling) then
    Gear^.State:= Gear^.State and not gstMoving
else
    Gear^.State:= Gear^.State or      gstMoving;

if (Gear^.nImpactSounds > 0) then
    if ((Gear^.Damage <> 0) or ((Gear^.State and (gstCollision or gstMoving)) = (gstCollision or gstMoving))) and
       ((Gear^.dX.QWordValue > _0_1.QWordValue) or (Gear^.dY.QWordValue > _0_1.QWordValue)) then
        PlaySound(TSound(ord(Gear^.ImpactSound) + LongInt(GetRandom(Gear^.nImpactSounds))), true);
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBomb(Gear: PGear);
var i, x, y: LongInt;
    dX, dY: hwFloat;
    Fire: PGear;
begin
AllInactive:= false;

doStepFallingGear(Gear);

dec(Gear^.Timer);
if Gear^.Timer = 1000 then // might need adjustments
    case Gear^.Kind of
        gtAmmo_Bomb: makeHogsWorry(Gear^.X, Gear^.Y, 50);
        gtClusterBomb: makeHogsWorry(Gear^.X, Gear^.Y, 20);
        gtWatermelon: makeHogsWorry(Gear^.X, Gear^.Y, 75);
        gtHellishBomb: makeHogsWorry(Gear^.X, Gear^.Y, 90);
        gtGasBomb: makeHogsWorry(Gear^.X, Gear^.Y, 50);
    end;

if (Gear^.Kind = gtBall) and ((Gear^.State and gstTmpFlag) <> 0) then
    begin
    CheckCollision(Gear);
    if (Gear^.State and gstCollision) <> 0 then
        doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 20, EXPLDontDraw or EXPLNoGfx);
    end;

if Gear^.Timer = 0 then
    begin
    case Gear^.Kind of
        gtAmmo_Bomb: doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound);
             gtBall: doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 40, EXPLAutoSound);
        gtClusterBomb: begin
                x:= hwRound(Gear^.X);
                y:= hwRound(Gear^.Y);
                doMakeExplosion(x, y, 20, EXPLAutoSound);
                for i:= 0 to 4 do
                    begin
                    dX:= rndSign(GetRandom * _0_1);
                    dY:= (GetRandom - _3) * _0_08;
                    AddGear(x, y, gtCluster, 0, dX, dY, 25);
                    end
                end;
        gtWatermelon: begin
                x:= hwRound(Gear^.X);
                y:= hwRound(Gear^.Y);
                doMakeExplosion(x, y, 75, EXPLAutoSound);
                for i:= 0 to 5 do
                    begin
                    dX:= rndSign(GetRandom * _0_1);
                    dY:= (GetRandom - _1_5) * _0_3;
                    AddGear(x, y, gtMelonPiece, 0, dX, dY, 75)^.DirAngle:= i * 60;
                    end
                end;
        gtHellishBomb: begin
                x:= hwRound(Gear^.X);
                y:= hwRound(Gear^.Y);
                doMakeExplosion(x, y, 90, EXPLAutoSound);
                
                for i:= 0 to 127 do
                    begin
                    dX:= AngleCos(i * 16) * _0_5 * (GetRandom + _1);
                    dY:= AngleSin(i * 16) * _0_5 * (GetRandom + _1);
                    Fire:= AddGear(x, y, gtFlame, 0, dX, dY, 0);
                    if i mod 2 = 0 then Fire^.State:= Fire^.State or gsttmpFlag;
                    Fire:= AddGear(x, y, gtFlame, 0, dX, -dY, 0);
                    if i mod 2 <> 0 then Fire^.State:= Fire^.State or gsttmpFlag;
                    end
                end;
		gtGasBomb: doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound or EXPLPoisoned);
        end;
    DeleteGear(Gear);
    exit
    end;

CalcRotationDirAngle(Gear);

if Gear^.Kind = gtHellishBomb then
    begin

    if Gear^.Timer = 3000 then
        begin
        Gear^.nImpactSounds:= 0;
        PlaySound(sndHellish);
        end;

    if (GameTicks and $3F) = 0 then
        if (Gear^.State and gstCollision) = 0 then
            AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtEvilTrace, 0, _0, _0, 0);
    end;
end;
////////////////////////////////////////////////////////////////////////////////
procedure doStepMolotov(Gear: PGear);
var i, gX, gY: LongInt;
    dX, dY: hwFloat;
    Fire: PGear;
begin
    AllInactive:= false;
    
    doStepFallingGear(Gear);
    CalcRotationDirAngle(Gear);

    if (Gear^.State and gstCollision) <> 0 then begin
        PlaySound(sndMolotov);
        gX:= hwRound(Gear^.X);
        gY:= hwRound(Gear^.Y);
        //doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 5, EXPLAutoSound);
        for i:= 0 to 20 do begin
                dX:= AngleCos(i * 2) * ((_0_1*(i div 5))) * (GetRandom + _1);
                dY:= AngleSin(i * 8) * _0_5 * (GetRandom + _1);
                Fire:= AddGear(gX, gY, gtFlame, 0, dX, dY, 0);
                Fire^.State:= Fire^.State or gsttmpFlag;
                Fire:= AddGear(gX, gY, gtFlame, 0, dX, -dY, 0);
                Fire^.State:= Fire^.State or gsttmpFlag;
                Fire:= AddGear(gX, gY, gtFlame, 0, -dX, dY, 0);
                Fire^.State:= Fire^.State or gsttmpFlag;
                Fire:= AddGear(gX, gY, gtFlame, 0, -dX, -dY, 0);
                Fire^.State:= Fire^.State or gsttmpFlag;
        end;
        DeleteGear(Gear);
        exit
    end;
end;

procedure doStepWatermelon(Gear: PGear);
begin
AllInactive:= false;
Gear^.doStep:= @doStepBomb
end;

procedure doStepCluster(Gear: PGear);
begin
AllInactive:= false;
doStepFallingGear(Gear);
if (Gear^.State and gstCollision) <> 0 then
    begin
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Timer, EXPLAutoSound);
    DeleteGear(Gear);
    exit
    end;

if (Gear^.Kind = gtMelonPiece) or (Gear^.Kind = gtBall) then
    CalcRotationDirAngle(Gear)
else
    if (GameTicks and $1F) = 0 then
        AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepGrenade(Gear: PGear);
begin
AllInactive:= false;
Gear^.dX:= Gear^.dX + cWindSpeed;
doStepFallingGear(Gear);
if (Gear^.State and gstCollision) <> 0 then
    begin
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound);
    DeleteGear(Gear);
    exit
    end;
if (GameTicks and $3F) = 0 then
    AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHealthTagWork(Gear: PGear);
begin
if Gear^.Kind = gtHealthTag then
    AllInactive:= false;

dec(Gear^.Timer);
Gear^.Y:= Gear^.Y + Gear^.dY;

if Gear^.Timer = 0 then
    begin
    if (Gear^.Kind = gtHealthTag) and (PHedgehog(Gear^.Hedgehog)^.Gear <> nil) then
        PHedgehog(Gear^.Hedgehog)^.Gear^.Active:= true; // to let current hh die
    DeleteGear(Gear)
    end
end;

procedure doStepHealthTagWorkUnderWater(Gear: PGear);
begin
AllInactive:= false;

Gear^.Y:= Gear^.Y - _0_08;

if hwRound(Gear^.Y) < cWaterLine + 10 then
    DeleteGear(Gear)
end;

procedure doStepHealthTag(Gear: PGear);
var s: shortstring;
begin
AllInactive:= false;
Gear^.dY:= -_0_08;

str(Gear^.State, s);
Gear^.Tex:= RenderStringTex(s, PHedgehog(Gear^.Hedgehog)^.Team^.Clan^.Color, fnt16);

if hwRound(Gear^.Y) < cWaterLine then
    Gear^.doStep:= @doStepHealthTagWork
else
    Gear^.doStep:= @doStepHealthTagWorkUnderWater;

Gear^.Y:= Gear^.Y - int2hwFloat(Gear^.Tex^.h)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepGrave(Gear: PGear);
begin
AllInactive:= false;
if Gear^.dY.isNegative then
   if TestCollisionY(Gear, -1) then Gear^.dY:= _0;

if not Gear^.dY.isNegative then
   if TestCollisionY(Gear, 1) then
      begin
      Gear^.dY:= - Gear^.dY * Gear^.Elasticity;
      if Gear^.dY > - _1div1024 then
         begin
         Gear^.Active:= false;
         exit
         end else if Gear^.dY < - _0_03 then PlaySound(Gear^.ImpactSound)
      end;

Gear^.Y:= Gear^.Y + Gear^.dY;
CheckGearDrowning(Gear);
Gear^.dY:= Gear^.dY + cGravity
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBeeWork(Gear: PGear);
var t: hwFloat;
    gX,gY: LongInt;
    nuw: boolean;
const uw: boolean = false;
begin
AllInactive:= false;
gX:= hwRound(Gear^.X);
gY:= hwRound(Gear^.Y);
nuw:= (cWaterLine < hwRound(Gear^.Y) + Gear^.Radius);
if nuw and not uw then
    begin
    AddVisualGear(gX, cWaterLine, vgtSplash);
    AddVisualGear(gX - 3 + Random(6), cWaterLine, vgtDroplet);
    AddVisualGear(gX - 3 + Random(6), cWaterLine, vgtDroplet);
    AddVisualGear(gX - 3 + Random(6), cWaterLine, vgtDroplet);
    AddVisualGear(gX - 3 + Random(6), cWaterLine, vgtDroplet);
    StopSound(Gear^.SoundChannel);
    Gear^.SoundChannel:= LoopSound(sndBeeWater);
    uw:= nuw
    end
else if not nuw and uw then
    begin
    AddVisualGear(gX, cWaterLine, vgtSplash);
    StopSound(Gear^.SoundChannel);
    Gear^.SoundChannel:= LoopSound(sndBee);
    uw:= nuw
    end;


t:= Distance(Gear^.dX, Gear^.dY);
Gear^.dX:= Gear^.Elasticity * (Gear^.dX + _0_000004 * (TargetPoint.X - gX));
Gear^.dY:= Gear^.Elasticity * (Gear^.dY + _0_000004 * (TargetPoint.Y - gY));

t:= t / Distance(Gear^.dX, Gear^.dY);
Gear^.dX:= Gear^.dX * t;
Gear^.dY:= Gear^.dY * t;
Gear^.X:= Gear^.X + Gear^.dX;
Gear^.Y:= Gear^.Y + Gear^.dY;

if (GameTicks and $3F) = 0 then
   begin
      AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBeeTrace);
   end;

CheckCollision(Gear);
dec(Gear^.Timer);
if ((Gear^.State and gstCollision) <> 0) or (Gear^.Timer = 0) then
   begin
   StopSound(Gear^.SoundChannel);
   doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound);
   DeleteGear(Gear);
   end;
end;

procedure doStepBee(Gear: PGear);
begin
AllInactive:= false;
Gear^.X:= Gear^.X + Gear^.dX;
Gear^.Y:= Gear^.Y + Gear^.dY;
Gear^.dY:= Gear^.dY + cGravity;
CheckCollision(Gear);
if (Gear^.State and gstCollision) <> 0 then
   begin
   doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound);
   DeleteGear(Gear);
   exit
   end;
dec(Gear^.Timer);
if Gear^.Timer = 0 then
   begin
   Gear^.SoundChannel:= LoopSound(sndBee);
   Gear^.Timer:= 5000;
   Gear^.doStep:= @doStepBeeWork
   end;
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepShotIdle(Gear: PGear);
begin
AllInactive:= false;
inc(Gear^.Timer);
if Gear^.Timer > 75 then
    begin
    DeleteGear(Gear);
    AfterAttack
    end
end;

procedure doStepShotgunShot(Gear: PGear);
var i: LongWord;
    shell: PVisualGear;
begin
AllInactive:= false;

if ((Gear^.State and gstAnimation) = 0) then
    begin
    dec(Gear^.Timer);
    if Gear^.Timer = 0 then
        begin
        PlaySound(sndShotgunFire);
        shell:= AddVisualGear(hwRound(Gear^.x), hwRound(Gear^.y), vgtShell);
        if shell <> nil then
        begin 
           shell^.dX:= gear^.dX / -4;
           shell^.dY:= gear^.dY / -4;
           shell^.Frame:= 0
        end;
        Gear^.State:= Gear^.State or gstAnimation
        end;
    exit
    end
    else inc(Gear^.Timer);

i:= 200;
repeat
Gear^.X:= Gear^.X + Gear^.dX;
Gear^.Y:= Gear^.Y + Gear^.dY;
CheckCollision(Gear);
if (Gear^.State and gstCollision) <> 0 then
    begin
    Gear^.X:= Gear^.X + Gear^.dX * 8;
    Gear^.Y:= Gear^.Y + Gear^.dY * 8;
    ShotgunShot(Gear);
    Gear^.doStep:= @doStepShotIdle;
    exit
    end;

CheckGearDrowning(Gear);
if (Gear^.State and gstDrowning) <> 0 then
    begin
    Gear^.doStep:= @doStepShotIdle;
    exit
    end;
dec(i)
until i = 0;
if (hwRound(Gear^.X) and LAND_WIDTH_MASK <> 0) or (hwRound(Gear^.Y) and LAND_HEIGHT_MASK <> 0) then
    Gear^.doStep:= @doStepShotIdle
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBulletWork(Gear: PGear);
var i, x, y: LongWord;
    oX, oY: hwFloat;
begin
AllInactive:= false;
inc(Gear^.Timer);
i:= 80;
oX:= Gear^.X;
oY:= Gear^.Y;
repeat
  Gear^.X:= Gear^.X + Gear^.dX;
  Gear^.Y:= Gear^.Y + Gear^.dY;
  x:= hwRound(Gear^.X);
  y:= hwRound(Gear^.Y);
  if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0)
     and (Land[y, x] <> 0) then inc(Gear^.Damage);
  if Gear^.Damage > 5 then
      if Gear^.Ammo^.AmmoType = amDEagle then
          AmmoShove(Gear, 7, 20)
      else
          AmmoShove(Gear, Gear^.Timer, 20);
  CheckGearDrowning(Gear);
  dec(i)
until (i = 0) or (Gear^.Damage > Gear^.Health) or ((Gear^.State and gstDrowning) <> 0);
if Gear^.Damage > 0 then
   begin
   DrawTunnel(oX, oY, Gear^.dX, Gear^.dY, 82 - i, 1);
   dec(Gear^.Health, Gear^.Damage);
   Gear^.Damage:= 0
   end;
if ((Gear^.State and gstDrowning) <> 0) and (Gear^.Damage < Gear^.Health) and (cWaterOpacity < $FF) then
    begin
    for i:=(Gear^.Health - Gear^.Damage) * 4 downto 0 do
        begin
        if Random(6) = 0 then
            AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtBubble);
        Gear^.X:= Gear^.X + Gear^.dX;
        Gear^.Y:= Gear^.Y + Gear^.dY;
        end;
    end;

if (Gear^.Health <= 0)
    or (hwRound(Gear^.X) and LAND_WIDTH_MASK <> 0)
    or (hwRound(Gear^.Y) and LAND_HEIGHT_MASK <> 0) then
    begin
    if (Gear^.Kind = gtSniperRifleShot) and ((GameFlags and gfLaserSight) = 0) then cLaserSighting:= false;
    if (Gear^.Ammo^.NumPerTurn <= CurrentHedgehog^.MultiShootAttacks) and
       ((GameFlags and gfArtillery) = 0) then cArtillery:= false;
    Gear^.doStep:= @doStepShotIdle
    end;
end;

procedure doStepDEagleShot(Gear: PGear);
begin
PlaySound(sndGun);
Gear^.doStep:= @doStepBulletWork
end;

procedure doStepSniperRifleShot(Gear: PGear);
var HHGear: PGear;
    shell: PVisualGear;
begin
cArtillery:= true;
HHGear:=PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.State:= HHGear^.State or gstNotKickable;
HedgehogChAngle(HHGear);
if not cLaserSighting then // game does not have default laser sight. turn it on and give them a chance to aim
    begin
    cLaserSighting:= true;
    HHGear^.Message:= 0;
    if(HHGear^.Angle - 32 >= 0) then dec(HHGear^.Angle,32)
    end;

if (HHGear^.Message and gm_Attack) <> 0 then
    begin
    shell:= AddVisualGear(hwRound(Gear^.x), hwRound(Gear^.y), vgtShell);
    if shell <> nil then
       begin
       shell^.dX:= gear^.dX / -2;
       shell^.dY:= gear^.dY / -2;
       shell^.Frame:= 1
       end;
    Gear^.State:= Gear^.State or gstAnimation;
    Gear^.dX:= SignAs(AngleSin(HHGear^.Angle), HHGear^.dX) * _0_5;
    Gear^.dY:= -AngleCos(HHGear^.Angle) * _0_5;
    PlaySound(sndGun);
    Gear^.doStep:= @doStepBulletWork;
    end
else
    if (GameTicks mod 32) = 0 then
        if (GameTicks mod 4096) < 2048 then
            begin
            if(HHGear^.Angle + 1 <= cMaxAngle) then inc(HHGear^.Angle)
            end
        else
            if(HHGear^.Angle - 1 >= 0) then dec(HHGear^.Angle);

if (TurnTimeLeft > 0) then
    dec(TurnTimeLeft)
else
    begin
    DeleteGear(Gear);
    AfterAttack
    end;
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepActionTimer(Gear: PGear);
begin
dec(Gear^.Timer);
case Gear^.Kind of
    gtATStartGame: begin
                   AllInactive:= false;
                   if Gear^.Timer = 0 then
                      begin
                      AddCaption(trmsg[sidStartFight], cWhiteColor, capgrpGameState);
                      end
                   end;
 gtATSmoothWindCh: begin
                   if Gear^.Timer = 0 then
                      begin
                      if WindBarWidth < Gear^.Tag then inc(WindBarWidth)
                         else if WindBarWidth > Gear^.Tag then dec(WindBarWidth);
                      if WindBarWidth <> Gear^.Tag then Gear^.Timer:= 10;
                      end
                   end;
   gtATFinishGame: begin
                   AllInactive:= false;
                   if Gear^.Timer = 1000 then
                      begin
                      ScreenFade:= sfToBlack;
                      ScreenFadeValue:= 0;
                      ScreenFadeSpeed:= 1;
                      end;
                   if Gear^.Timer = 0 then
                      begin
                      SendIPC('N');
                      SendIPC('q');
                      GameState:= gsExit
                      end
                   end;
     end;
if Gear^.Timer = 0 then DeleteGear(Gear)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepPickHammerWork(Gear: PGear);
var i, ei: LongInt;
    HHGear: PGear;
begin
AllInactive:= false;
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
dec(Gear^.Timer);
if (Gear^.Timer = 0)or((Gear^.Message and gm_Destroy) <> 0)or((HHGear^.State and gstHHDriven) = 0) then
    begin
    StopSound(Gear^.SoundChannel);
    DeleteGear(Gear);
    AfterAttack;
    exit
    end;

if (Gear^.Timer mod 33) = 0 then
    begin
    HHGear^.State:= HHGear^.State or gstNoDamage;
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y) + 7, 6, EXPLDontDraw);
    HHGear^.State:= HHGear^.State and not gstNoDamage
    end;

if (Gear^.Timer mod 47) = 0 then
    begin
    i:= hwRound(Gear^.X) - Gear^.Radius - LongInt(GetRandom(2));
    ei:= hwRound(Gear^.X) + Gear^.Radius + LongInt(GetRandom(2));
    while i <= ei do
        begin
        DrawExplosion(i, hwRound(Gear^.Y) + 3, 3);
        inc(i, 1)
        end;

    if CheckLandValue(hwRound(Gear^.X + Gear^.dX + SignAs(_6,Gear^.dX)), hwRound(Gear^.Y + _1_9), COLOR_INDESTRUCTIBLE) then
        begin
        Gear^.X:= Gear^.X + Gear^.dX;
        Gear^.Y:= Gear^.Y + _1_9;
        end;
    SetAllHHToActive;
    end;
if TestCollisionYwithGear(Gear, 1) then
    begin
    Gear^.dY:= _0;
    SetLittle(HHGear^.dX);
    HHGear^.dY:= _0;
    end else
    begin
    Gear^.dY:= Gear^.dY + cGravity;
    Gear^.Y:= Gear^.Y + Gear^.dY;
    if hwRound(Gear^.Y) > cWaterLine then Gear^.Timer:= 1
    end;

Gear^.X:= Gear^.X + HHGear^.dX;
HHGear^.X:= Gear^.X;
HHGear^.Y:= Gear^.Y - int2hwFloat(cHHRadius);

if (Gear^.Message and gm_Attack) <> 0 then
   if (Gear^.State and gsttmpFlag) <> 0 then Gear^.Timer:= 1 else else
   if (Gear^.State and gsttmpFlag) = 0 then Gear^.State:= Gear^.State or gsttmpFlag;
if ((Gear^.Message and gm_Left) <> 0) then Gear^.dX:= - _0_3 else
   if ((Gear^.Message and gm_Right) <> 0) then Gear^.dX:= _0_3
                                          else Gear^.dX:= _0;
end;

procedure doStepPickHammer(Gear: PGear);
var i, y: LongInt;
    ar: TRangeArray;
    HHGear: PGear;
begin
i:= 0;
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;

y:= hwRound(Gear^.Y) - cHHRadius * 2;
while y < hwRound(Gear^.Y) do
   begin
   ar[i].Left := hwRound(Gear^.X) - Gear^.Radius - LongInt(GetRandom(2));
   ar[i].Right:= hwRound(Gear^.X) + Gear^.Radius + LongInt(GetRandom(2));
   inc(y, 2);
   inc(i)
   end;

DrawHLinesExplosions(@ar, 3, hwRound(Gear^.Y) - cHHRadius * 2, 2, Pred(i));
Gear^.dY:= HHGear^.dY;
DeleteCI(HHGear);

Gear^.SoundChannel:= LoopSound(sndPickhammer);
doStepPickHammerWork(Gear);
Gear^.doStep:= @doStepPickHammerWork
end;

////////////////////////////////////////////////////////////////////////////////
var BTPrevAngle, BTSteps: LongInt;

procedure doStepBlowTorchWork(Gear: PGear);
var HHGear: PGear;
    b: boolean;
    prevX: LongInt;
begin
AllInactive:= false;
dec(Gear^.Timer);
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;

HedgehogChAngle(HHGear);

b:= false;

if abs(LongInt(HHGear^.Angle) - BTPrevAngle) > 7  then
    begin
    Gear^.dX:= SignAs(AngleSin(HHGear^.Angle) * _0_5, HHGear^.dX);
    Gear^.dY:= AngleCos(HHGear^.Angle) * ( - _0_5);
    BTPrevAngle:= HHGear^.Angle;
    b:= true
    end;

if ((HHGear^.State and gstMoving) <> 0) then
    begin
    doStepHedgehogMoving(HHGear);
    if (HHGear^.State and gstHHDriven) = 0 then Gear^.Timer:= 0
    end;

if Gear^.Timer mod cHHStepTicks = 0 then
    begin
    b:= true;
    if Gear^.dX.isNegative then
        HHGear^.Message:= (HHGear^.Message and (gm_Attack or gm_Up or gm_Down)) or gm_Left
    else
        HHGear^.Message:= (HHGear^.Message and (gm_Attack or gm_Up or gm_Down)) or gm_Right;

    if ((HHGear^.State and gstMoving) = 0) then
        begin
        HHGear^.State:= HHGear^.State and not gstAttacking;
        prevX:= hwRound(HHGear^.X);

// why the call to HedgehogStep then a further increment of X?
        if (prevX = hwRound(HHGear^.X)) and
           CheckLandValue(hwRound(HHGear^.X + SignAs(_6, HHGear^.dX)), hwRound(HHGear^.Y), COLOR_INDESTRUCTIBLE) then HedgehogStep(HHGear);

        if (prevX = hwRound(HHGear^.X)) and
           CheckLandValue(hwRound(HHGear^.X + SignAs(_6, HHGear^.dX)), hwRound(HHGear^.Y), COLOR_INDESTRUCTIBLE) then HHGear^.X:= HHGear^.X + SignAs(_1, HHGear^.dX);
        HHGear^.State:= HHGear^.State or gstAttacking
        end;

    inc(BTSteps);
    if BTSteps = 7 then
        begin
        BTSteps:= 0;
        if CheckLandValue(hwRound(HHGear^.X + Gear^.dX * (cHHRadius + cBlowTorchC) + SignAs(_6,Gear^.dX)), hwRound(HHGear^.Y + Gear^.dY * (cHHRadius + cBlowTorchC)), COLOR_INDESTRUCTIBLE) then
            begin
            Gear^.X:= HHGear^.X + Gear^.dX * (cHHRadius + cBlowTorchC);
            Gear^.Y:= HHGear^.Y + Gear^.dY * (cHHRadius + cBlowTorchC);
            end;
        HHGear^.State:= HHGear^.State or gstNoDamage;
        AmmoShove(Gear, 2, 15);
        HHGear^.State:= HHGear^.State and not gstNoDamage
        end;
    end;

if b then
   DrawTunnel(HHGear^.X - Gear^.dX * cHHRadius, HHGear^.Y - _4 - Gear^.dY * cHHRadius + hwAbs(Gear^.dY) * 7,
              Gear^.dX, Gear^.dY,
              cHHRadius * 5, cHHRadius * 2 + 7);

if (Gear^.Timer = 0) or ((HHGear^.Message and gm_Attack) <> 0) then
    begin
    HHGear^.Message:= 0;
    HHGear^.State:= HHGear^.State and (not gstNotKickable);
    DeleteGear(Gear);
    AfterAttack
    end
end;

procedure doStepBlowTorch(Gear: PGear);
var HHGear: PGear;
begin
BTPrevAngle:= High(LongInt);
BTSteps:= 0;
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.Message:= 0;
HHGear^.State:= HHGear^.State or gstNotKickable;
Gear^.doStep:= @doStepBlowTorchWork
end;

////////////////////////////////////////////////////////////////////////////////

procedure doStepRope(Gear: PGear); forward;

procedure doStepRopeAfterAttack(Gear: PGear);
var HHGear: PGear;
begin
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
if ((HHGear^.State and gstHHDriven) = 0)
    or (CheckGearDrowning(HHGear))
    or TestCollisionYwithGear(HHGear, 1) then
    begin
    DeleteGear(Gear);
    isCursorVisible:= false;
    ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^);
    exit
    end;

HedgehogChAngle(HHGear);

if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then SetLittle(HHGear^.dX);

if HHGear^.dY.isNegative and TestCollisionYwithGear(HHGear, -1) then HHGear^.dY:= _0;
HHGear^.X:= HHGear^.X + HHGear^.dX;
HHGear^.Y:= HHGear^.Y + HHGear^.dY;
HHGear^.dY:= HHGear^.dY + cGravity;

if (Gear^.Message and gm_Attack) <> 0 then
    begin
    Gear^.X:= HHGear^.X;
    Gear^.Y:= HHGear^.Y;

    ApplyAngleBounds(PHedgehog(Gear^.Hedgehog)^, amRope);

    Gear^.dX:= SignAs(AngleSin(HHGear^.Angle), HHGear^.dX);
    Gear^.dY:= -AngleCos(HHGear^.Angle);
    Gear^.Friction:= _450;
    Gear^.Elasticity:= _0;
    Gear^.State:= Gear^.State and not gsttmpflag;
    Gear^.doStep:= @doStepRope;
    end
end;

procedure doStepRopeWork(Gear: PGear);
var HHGear: PGear;
    len, tx, ty, nx, ny, ropeDx, ropeDy, mdX, mdY: hwFloat;
    lx, ly: LongInt;
    haveCollision,
    haveDivided: boolean;

    procedure DeleteMe;
    begin
    with HHGear^ do
        begin
        Message:= Message and not gm_Attack;
        State:= (State or gstMoving) and not gstWinner;
        end;
    DeleteGear(Gear)
    end;

    procedure WaitCollision;
    begin
    with HHGear^ do
        begin
        Message:= Message and not gm_Attack;
        State:= State or gstMoving;
        end;
    RopePoints.Count:= 0;
    Gear^.Elasticity:= _0;
    Gear^.doStep:= @doStepRopeAfterAttack
    end;

begin
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;

if ((HHGear^.State and gstHHDriven) = 0)
    or (CheckGearDrowning(HHGear)) then
    begin
    PlaySound(sndRopeRelease);
    DeleteMe;
    exit
    end;

if (Gear^.Message and gm_Left  <> 0) then HHGear^.dX:= HHGear^.dX - _0_0002 else
if (Gear^.Message and gm_Right <> 0) then HHGear^.dX:= HHGear^.dX + _0_0002;

if not TestCollisionYwithGear(HHGear, 1) then HHGear^.dY:= HHGear^.dY + cGravity;

ropeDx:= HHGear^.X - Gear^.X; // vector between hedgehog and rope attaching point
ropeDy:= HHGear^.Y - Gear^.Y;

mdX:= ropeDx + HHGear^.dX;
mdY:= ropeDy + HHGear^.dY;
len:= _1 / Distance(mdX, mdY);
mdX:= mdX * len; // rope vector plus hedgehog direction vector normalized
mdY:= mdY * len;

Gear^.dX:= mdX; // for visual purposes only
Gear^.dY:= mdY;

/////
    tx:= HHGear^.X;
    ty:= HHGear^.Y;

    if ((Gear^.Message and gm_Down) <> 0) and (Gear^.Elasticity < Gear^.Friction) then
        if not (TestCollisionXwithGear(HHGear, hwSign(ropeDx))
                or TestCollisionYwithGear(HHGear, hwSign(ropeDy))) then
                    Gear^.Elasticity:= Gear^.Elasticity + _0_3;

    if ((Gear^.Message and gm_Up) <> 0) and (Gear^.Elasticity > _30) then
        if not (TestCollisionXwithGear(HHGear, -hwSign(ropeDx))
                or TestCollisionYwithGear(HHGear, -hwSign(ropeDy))) then
                    Gear^.Elasticity:= Gear^.Elasticity - _0_3;

    HHGear^.X:= Gear^.X + mdX * Gear^.Elasticity;
    HHGear^.Y:= Gear^.Y + mdY * Gear^.Elasticity;

    HHGear^.dX:= HHGear^.X - tx;
    HHGear^.dY:= HHGear^.Y - ty;
////


    haveDivided:= false;
    // check whether rope needs dividing
    len:= _1 / Distance(ropeDx, ropeDy); // old rope pos
    nx:= ropeDx * len;
    ny:= ropeDy * len;

    len:= Gear^.Elasticity - _5;
    while len > _3 do
            begin
            lx:= hwRound(Gear^.X + mdX * len);
            ly:= hwRound(Gear^.Y + mdY * len);
            if ((ly and LAND_HEIGHT_MASK) = 0) and ((lx and LAND_WIDTH_MASK) = 0) and (Land[ly, lx] <> 0) then
                begin
                with RopePoints.ar[RopePoints.Count] do
                    begin
                    X:= Gear^.X;
                    Y:= Gear^.Y;
                    if RopePoints.Count = 0 then RopePoints.HookAngle:= DxDy2Angle(Gear^.dY, Gear^.dX);
                    b:= (nx * HHGear^.dY) > (ny * HHGear^.dX);
                    dLen:= len
                    end;
                with RopePoints.rounded[RopePoints.Count] do
                    begin
                    X:= hwRound(Gear^.X);
                    Y:= hwRound(Gear^.Y);
                    end;

                Gear^.X:= Gear^.X + nx * len;
                Gear^.Y:= Gear^.Y + ny * len;
                inc(RopePoints.Count);
                TryDo(RopePoints.Count <= MAXROPEPOINTS, 'Rope points overflow', true);
                Gear^.Elasticity:= Gear^.Elasticity - len;
                Gear^.Friction:= Gear^.Friction - len;
                haveDivided:= true;
                break
                end;
            len:= len - _0_3 // should be the same as increase step
            end;

if not haveDivided then
    if RopePoints.Count > 0 then // check whether the last dividing point could be removed
        begin
        tx:= RopePoints.ar[Pred(RopePoints.Count)].X;
        ty:= RopePoints.ar[Pred(RopePoints.Count)].Y;
        mdX:= tx - Gear^.X;
        mdY:= ty - Gear^.Y;
        if RopePoints.ar[Pred(RopePoints.Count)].b xor (mdX * (ty - HHGear^.Y) > (tx - HHGear^.X) * mdY) then
            begin
            dec(RopePoints.Count);
            Gear^.X:= RopePoints.ar[RopePoints.Count].X;
            Gear^.Y:= RopePoints.ar[RopePoints.Count].Y;
            Gear^.Elasticity:= Gear^.Elasticity + RopePoints.ar[RopePoints.Count].dLen;
            Gear^.Friction:= Gear^.Friction + RopePoints.ar[RopePoints.Count].dLen;

            // restore hog position
            len:= _1 / Distance(mdX, mdY);
            mdX:= mdX * len; 
            mdY:= mdY * len;

            HHGear^.X:= Gear^.X - mdX * Gear^.Elasticity;
            HHGear^.Y:= Gear^.Y - mdY * Gear^.Elasticity;
            end
        end;

haveCollision:= false;
if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then
    begin
    HHGear^.dX:= -_0_6 * HHGear^.dX;
    haveCollision:= true
    end;
if TestCollisionYwithGear(HHGear, hwSign(HHGear^.dY)) then
    begin
    HHGear^.dY:= -_0_6 * HHGear^.dY;
    haveCollision:= true
    end;

if haveCollision
    and (Gear^.Message and (gm_Left or gm_Right) <> 0)
    and (Gear^.Message and (gm_Up or gm_Down) <> 0) then
    begin
    HHGear^.dX:= SignAs(hwAbs(HHGear^.dX) + _0_2, HHGear^.dX);
    HHGear^.dY:= SignAs(hwAbs(HHGear^.dY) + _0_2, HHGear^.dY)
    end;

len:= Distance(HHGear^.dX, HHGear^.dY);
if len > _0_8 then
    begin
    len:= _0_8 / len;
    HHGear^.dX:= HHGear^.dX * len;
    HHGear^.dY:= HHGear^.dY * len;
    end;

if (Gear^.Message and gm_Attack) <> 0 then
    if (Gear^.State and gsttmpFlag) <> 0 then
        with PHedgehog(Gear^.Hedgehog)^ do
            begin
            PlaySound(sndRopeRelease);
            if Ammo^[CurSlot, CurAmmo].AmmoType <> amParachute then
                WaitCollision
            else
                DeleteMe
            end
    else
else
    if (Gear^.State and gsttmpFlag) = 0 then
        Gear^.State:= Gear^.State or gsttmpFlag;
end;

procedure doStepRopeAttach(Gear: PGear);
var HHGear: PGear;
    tx, ty, tt: hwFloat;

    procedure RemoveFromAmmo;
    begin
    if (Gear^.State and gstAttacked) = 0 then
        begin
        OnUsedAmmo(PHedgehog(HHGear^.Hedgehog)^);
        Gear^.State:= Gear^.State or gstAttacked
        end;
    ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^)
    end;

begin
Gear^.X:= Gear^.X - Gear^.dX;
Gear^.Y:= Gear^.Y - Gear^.dY;
Gear^.Elasticity:= Gear^.Elasticity + _1;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
DeleteCI(HHGear);

if (HHGear^.State and gstMoving) <> 0 then
    begin
    if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then SetLittle(HHGear^.dX);
    if HHGear^.dY.isNegative and TestCollisionYwithGear(HHGear, -1) then HHGear^.dY:= _0;

    HHGear^.X:= HHGear^.X + HHGear^.dX;
    Gear^.X:= Gear^.X + HHGear^.dX;

    if TestCollisionYwithGear(HHGear, 1) then
        begin
        CheckHHDamage(HHGear);
        HHGear^.dY:= _0;
        //HHGear^.State:= HHGear^.State and not (gstHHJumping or gstHHHJump);
        end else
        begin
        HHGear^.Y:= HHGear^.Y + HHGear^.dY;
        Gear^.Y:= Gear^.Y + HHGear^.dY;
        HHGear^.dY:= HHGear^.dY + cGravity;
        end;
        
    tt:= Gear^.Elasticity;
    tx:= _0;
    ty:= _0;
    while tt > _20 do
        begin
        if  TestCollisionXwithXYShift(Gear, tx, hwRound(ty), -hwSign(Gear^.dX))
        or TestCollisionYwithXYShift(Gear, hwRound(tx), hwRound(ty), -hwSign(Gear^.dY)) then
            begin
            Gear^.X:= Gear^.X + tx;
            Gear^.Y:= Gear^.Y + ty;
            Gear^.Elasticity:= tt;
            Gear^.doStep:= @doStepRopeWork;
            PlaySound(sndRopeAttach);
            with HHGear^ do State:= State and not (gstAttacking or gstHHJumping or gstHHHJump);

            RemoveFromAmmo;

            tt:= _0;
            exit
            end;
        tx:= tx + Gear^.dX + Gear^.dX;
        ty:= ty + Gear^.dY + Gear^.dY;
        tt:= tt - _2;
        end;
    end;

CheckCollision(Gear);

if (Gear^.State and gstCollision) <> 0 then
    if Gear^.Elasticity < _10 then
        Gear^.Elasticity:= _10000
    else
        begin
        Gear^.doStep:= @doStepRopeWork;
        PlaySound(sndRopeAttach);
        with HHGear^ do State:= State and not (gstAttacking or gstHHJumping or gstHHHJump);

        RemoveFromAmmo;

        exit
        end;

if (Gear^.Elasticity > Gear^.Friction)
or ((Gear^.Message and gm_Attack) = 0)
or ((HHGear^.State and gstHHDriven) = 0)
or (HHGear^.Damage > 0) then
    begin
    with PHedgehog(Gear^.Hedgehog)^.Gear^ do
        begin
        State:= State and not gstAttacking;
        Message:= Message and not gm_Attack
        end;
    DeleteGear(Gear)
    end
end;

procedure doStepRope(Gear: PGear);
begin
Gear^.dX:= - Gear^.dX;
Gear^.dY:= - Gear^.dY;
Gear^.doStep:= @doStepRopeAttach;
PlaySound(sndRopeShot)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepSmokeTrace(Gear: PGear);
begin
inc(Gear^.Timer);
if Gear^.Timer > 64 then
    begin
    Gear^.Timer:= 0;
    dec(Gear^.State)
    end;
Gear^.dX:= Gear^.dX + cWindSpeed;
Gear^.X:= Gear^.X + Gear^.dX;
if Gear^.State = 0 then DeleteGear(Gear)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepExplosionWork(Gear: PGear);
begin
inc(Gear^.Timer);
if Gear^.Timer > 75 then
    begin
    inc(Gear^.State);
    Gear^.Timer:= 0;
    if Gear^.State > 5 then DeleteGear(Gear)
    end;
end;

procedure doStepExplosion(Gear: PGear);
var i: LongWord;
begin
for i:= 0 to 31 do AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtFire);
for i:= 0 to 8 do AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtExplPart);
for i:= 0 to 8 do AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtExplPart2);
Gear^.doStep:= @doStepExplosionWork
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepMine(Gear: PGear);
begin
if (Gear^.State and gstMoving) <> 0 then
    begin
    DeleteCI(Gear);
    doStepFallingGear(Gear);
    if (Gear^.State and gstMoving) = 0 then
        begin
        AddGearCI(Gear);
        Gear^.dX:= _0;
        Gear^.dY:= _0
        end;
    CalcRotationDirAngle(Gear);
    AllInactive:= false
    end else
    if ((GameTicks and $3F) = 25) then
        doStepFallingGear(Gear);

if ((Gear^.State and gsttmpFlag) <> 0) and (Gear^.Health <> 0) then
    if ((Gear^.State and gstAttacking) = 0) then
        begin
        if ((GameTicks and $1F) = 0) then
            if CheckGearNear(Gear, gtHedgehog, 46, 32) <> nil then Gear^.State:= Gear^.State or gstAttacking
        end else // gstAttacking <> 0
        begin
        AllInactive:= false;
        if (Gear^.Timer and $FF) = 0 then PlaySound(sndMineTick);
        if Gear^.Timer = 0 then
            begin
            if ((Gear^.State and gstWait) <> 0) or 
               (cMineDudPercent = 0) or
               (getRandom(100) > cMineDudPercent) then
               begin
               doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound);
               DeleteGear(Gear)
               end
            else
               begin
               AddVisualGear(hwRound(Gear^.X) - 4  + Random(8), hwRound(Gear^.Y) - 4 - Random(4), vgtSmoke);
               PlaySound(sndVaporize);
               Gear^.Health:= 0;
               end;
            exit
            end;
        dec(Gear^.Timer);
        end else // gsttmpFlag = 0
    if TurnTimeLeft = 0 then Gear^.State:= Gear^.State or gsttmpFlag;
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepDynamite(Gear: PGear);
begin
doStepFallingGear(Gear);
AllInactive:= false;
if Gear^.Timer mod 166 = 0 then inc(Gear^.Tag);
if Gear^.Timer = 1000 then // might need better timing
    makeHogsWorry(Gear^.X, Gear^.Y, 75);
if Gear^.Timer = 0 then
    begin
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 75, EXPLAutoSound);
    DeleteGear(Gear);
    exit
    end;
dec(Gear^.Timer);
end;

///////////////////////////////////////////////////////////////////////////////

(*
TODO
Increase damage as barrel smokes?
Try tweaking friction some more
*)
procedure doStepRollingBarrel(Gear: PGear);
var i: LongInt;
    particle: PVisualGear;
begin
Gear^.State:= Gear^.State or gstAnimation;
if ((Gear^.dX.QWordValue <> 0) or (Gear^.dY.QWordValue <> 0))  then
    begin
    DeleteCI(Gear);
    AllInactive:= false;
    if not Gear^.dY.isNegative and (Gear^.dY > _0_03) and TestCollisionYwithGear(Gear, 1) then
        begin
        Gear^.State:= Gear^.State or gsttmpFlag;
        inc(Gear^.Damage, hwRound(Gear^.dY * _50));
        for i:= min(12, hwRound(Gear^.dY*_10)) downto 0 do 
            begin
            particle:= AddVisualGear(hwRound(Gear^.X) - 5 + Random(10), hwRound(Gear^.Y) + 12, vgtDust);
            if particle <> nil then particle^.dX := particle^.dX + (Gear^.dX / 5)
            end
        end
    else if not Gear^.dX.isNegative and (Gear^.dX > _0_03) and TestCollisionXwithGear(Gear, 1) then
        inc(Gear^.Damage, hwRound(Gear^.dX * _50))
    else if Gear^.dY.isNegative and (Gear^.dY < -_0_03) and TestCollisionYwithGear(Gear, -1) then
        inc(Gear^.Damage, hwRound(Gear^.dY * -_50))
    else if Gear^.dX.isNegative and (Gear^.dX < -_0_03) and TestCollisionXwithGear(Gear, -1) then
        inc(Gear^.Damage, hwRound(Gear^.dX * -_50));
    
    doStepFallingGear(Gear);
    CalcRotationDirAngle(Gear);
    //CheckGearDrowning(Gear)
    end
else 
    begin
    Gear^.State:= Gear^.State or gsttmpFlag;
    AddGearCI(Gear)
    end;
(*
Attempt to make a barrel knock itself over an edge.  Would need more checks to avoid issues like burn damage
    begin
    x:= hwRound(Gear^.X);
    y:= hwRound(Gear^.Y);
    if (((y+1) and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) then
        if (Land[y+1, x] = 0) then
            begin
            if (((y+1) and LAND_HEIGHT_MASK) = 0) and (((x+Gear^.Radius-2) and LAND_WIDTH_MASK) = 0) and (Land[y+1, x+Gear^.Radius-2] = 0) then
                Gear^.dX:= -_0_08
            else if (((y+1 and LAND_HEIGHT_MASK)) = 0) and (((x-(Gear^.Radius-2)) and LAND_WIDTH_MASK) = 0) and (Land[y+1, x-(Gear^.Radius-2)] = 0) then
                Gear^.dX:= _0_08;
            end;
    if Gear^.dX.QWordValue = 0 then AddGearCI(Gear)
    end; *)

if not Gear^.dY.isNegative and (Gear^.dY < _0_001) and TestCollisionYwithGear(Gear, 1) then Gear^.dY:= _0;
if hwAbs(Gear^.dX) < _0_001 then Gear^.dX:= _0;
    
if ((Gear^.Health * 100 div cBarrelHealth) < random(90)) and ((GameTicks and $FF) = 0) then
    if (cBarrelHealth div Gear^.Health) > 2 then 
        AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmoke)
    else
        AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmokeWhite);
dec(Gear^.Health, Gear^.Damage);
Gear^.Damage:= 0;
if Gear^.Health <= 0 then Gear^.doStep:= @doStepCase; // Hand off to doStepCase for the explosion

end;

procedure doStepCase(Gear: PGear);
var i, x, y: LongInt;
    k: TGearType;
    exBoom: boolean;
    dX, dY: HWFloat;
begin
k:= Gear^.Kind;
exBoom:= false;

if (Gear^.Message and gm_Destroy) > 0 then
    begin
    DeleteGear(Gear);
    FreeActionsList;
    SetAllToActive; // something (hh, mine, etc...) could be on top of the case
    with CurrentHedgehog^ do
        if Gear <> nil then Gear^.Message:= Gear^.Message and not (gm_LJump or gm_HJump);
    exit
    end;

if k = gtExplosives then
    begin
    //if V > _0_03 then Gear^.State:= Gear^.State or gstAnimation;
    if (hwAbs(Gear^.dX) > _0_15) or ((hwAbs(Gear^.dY) > _0_15) and (hwAbs(Gear^.dX) > _0_02)) then Gear^.doStep:= @doStepRollingBarrel;
    
    if ((Gear^.Health * 100 div cBarrelHealth) < random(90)) and ((GameTicks and $FF) = 0) then
        if (cBarrelHealth div Gear^.Health) > 2 then 
            AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmoke)
        else
            AddVisualGear(hwRound(Gear^.X) - 16 + Random(32), hwRound(Gear^.Y) - 2, vgtSmokeWhite);
    dec(Gear^.Health, Gear^.Damage);
    Gear^.Damage:= 0;
    if Gear^.Health <= 0 then
        exBoom:= true;
    end;

if (Gear^.Damage > 0) or exBoom then
    begin
    x:= hwRound(Gear^.X);
    y:= hwRound(Gear^.Y);
    DeleteGear(Gear); // <-- delete gear!

    if k = gtCase then
        begin
        doMakeExplosion(x, y, 25, EXPLAutoSound);
        for i:= 0 to 63 do
            AddGear(x, y, gtFlame, 0, _0, _0, 0);
        end
    else if k = gtExplosives then
        begin
        doMakeExplosion(x, y, 75, EXPLAutoSound);
        for i:= 0 to 31 do
            begin
            dX:= AngleCos(i * 64) * _0_5 * (getrandom + _1);
            dY:= AngleSin(i * 64) * _0_5 * (getrandom + _1);
            AddGear(x, y, gtFlame, 0, dX, dY, 0);
            AddGear(x, y, gtFlame, 0, -dX, -dY, 0)^.State:= gsttmpFlag;
            end
        end;
    exit
    end;

if (Gear^.dY.QWordValue <> 0) or (not TestCollisionYwithGear(Gear, 1)) then
    begin
    AllInactive:= false;
    Gear^.dY:= Gear^.dY + cGravity;
    Gear^.Y:= Gear^.Y + Gear^.dY;
    if (not Gear^.dY.isNegative) and (Gear^.dY > _0_001) then SetAllHHToActive;
    if (Gear^.dY.isNegative) and TestCollisionYwithGear(Gear, -1) then Gear^.dY:= _0;
    if (not Gear^.dY.isNegative) and TestCollisionYwithGear(Gear, 1) then
        begin
        if (Gear^.dY > _0_02) and (k = gtExplosives) then
            inc(Gear^.Damage, hwRound(Gear^.dY * _40));

        if Gear^.dY > _0_2 then
            for i:= min(12, hwRound(Gear^.dY*_10)) downto 0 do 
                AddVisualGear(hwRound(Gear^.X) - 5 + Random(10), hwRound(Gear^.Y) + 12, vgtDust);
        Gear^.dY:= - Gear^.dY * Gear^.Elasticity;
        if Gear^.dY > - _0_001 then Gear^.dY:= _0
            else if Gear^.dY < - _0_03 then
                PlaySound(Gear^.ImpactSound);
        end;
    //if Gear^.dY > - _0_001 then Gear^.dY:= _0
    CheckGearDrowning(Gear);
    end;

if (Gear^.dY.QWordValue = 0) then AddGearCI(Gear)
       else if (Gear^.dY.QWordValue <> 0) then DeleteCI(Gear)
end;

////////////////////////////////////////////////////////////////////////////////

procedure doStepTarget(Gear: PGear);
begin
if (Gear^.Timer = 0) and (Gear^.Tag = 0) then
    PlaySound(sndWarp);

if (Gear^.Tag = 0) and (Gear^.Timer < 1000) then
    inc(Gear^.Timer)
else if Gear^.Tag = 1 then
    begin
        Gear^.Tag:= 2;
        if (TrainingFlags and tfTimeTrial) <> 0 then
            begin
            inc(TurnTimeLeft, TrainingTimeInc);
            
            if TrainingTimeInc > TrainingTimeInM then
                dec(TrainingTimeInc, TrainingTimeInD);
            if TurnTimeLeft > TrainingTimeMax then
                TurnTimeLeft:= TrainingTimeMax;
            end;
    end
else if Gear^.Tag = 2 then
    if Gear^.Timer > 0 then
        dec(Gear^.Timer)
    else
        begin
            if (TrainingFlags and tfTargetRespawn) <> 0 then
                begin
                TrainingTargetGear:= AddGear(0, 0, gtTarget, 0, _0, _0, 0);
                FindPlace(TrainingTargetGear, false, 0, LAND_WIDTH);
                end;
            DeleteGear(Gear);
            exit;
        end;

doStepCase(Gear)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepIdle(Gear: PGear);
begin
AllInactive:= false;
dec(Gear^.Timer);
if Gear^.Timer = 0 then
    begin
    DeleteGear(Gear);
    AfterAttack
    end
end;

procedure doStepShover(Gear: PGear);
var HHGear: PGear;
begin
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.State:= HHGear^.State or gstNoDamage;
DeleteCI(HHGear);

AmmoShove(Gear, 30, 115);

HHGear^.State:= HHGear^.State and not gstNoDamage;
Gear^.Timer:= 250;
Gear^.doStep:= @doStepIdle
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepWhip(Gear: PGear);
var HHGear: PGear;
    i: LongInt;
begin
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.State:= HHGear^.State or gstNoDamage;
DeleteCI(HHGear);

for i:= 0 to 3 do
    begin
    AmmoShove(Gear, 30, 25);
    Gear^.X:= Gear^.X + Gear^.dX * 5
    end;

HHGear^.State:= HHGear^.State and not gstNoDamage;
Gear^.Timer:= 250;
Gear^.doStep:= @doStepIdle
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepFlame(Gear: PGear);
var gX,gY,i: LongInt;
begin
    if (Gear^.State and gsttmpFlag) = 0 then AllInactive:= false;

if not TestCollisionYwithGear(Gear, 1) then
    begin
    AllInactive:= false;
    if Gear^.dX.QWordValue > _0_01.QWordValue then
        Gear^.dX:= Gear^.dX * _0_995;
    Gear^.dY:= Gear^.dY + cGravity;
    if (Gear^.State and gsttmpFlag) <> 0 then Gear^.dY:= Gear^.dY + cGravity;
    if Gear^.dY.QWordValue > _0_2.QWordValue then Gear^.dY:= Gear^.dY * _0_995;

    if (Gear^.State and gsttmpFlag) <> 0 then Gear^.X:= Gear^.X + Gear^.dX else
    Gear^.X:= Gear^.X + Gear^.dX + cWindSpeed * 640;
    Gear^.Y:= Gear^.Y + Gear^.dY;

    if (hwRound(Gear^.Y) > cWaterLine) then
        begin
        gX:= hwRound(Gear^.X);
        for i:= 0 to 3 do
            AddVisualGear(gX - 16 + Random(32), cWaterLine - 16 + Random(16), vgtSteam);
        PlaySound(sndVaporize);
        DeleteGear(Gear);
        exit
        end
    end else begin
        if (Gear^.State and gsttmpFlag) <> 0 then 
            begin
            Gear^.Radius:= 9;
            AmmoShove(Gear, 2, 30);
            Gear^.Radius:= 1
            end;
        if Gear^.Timer > 0 then
            begin
            dec(Gear^.Timer);
            inc(Gear^.Damage)
            end
        else begin
// Standard fire
            if (Gear^.State and gsttmpFlag) = 0 then
                begin
                Gear^.Radius:= 9;
                AmmoShove(Gear, 4, 100);
                gX:= hwRound(Gear^.X);
                gY:= hwRound(Gear^.Y);
                Gear^.Radius:= 1;
                doMakeExplosion(gX, gY, 4, EXPLNoDamage);
                if ((GameTicks and $7) = 0) and (Random(2) = 0) then
                  for i:= 1 to Random(2)+1 do
                    AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke);
                if Gear^.Health > 0 then dec(Gear^.Health);
                Gear^.Timer:= 450 - Gear^.Tag * 8
                end
                else begin
// Modified fire
                if ((GameTicks and $7FF) = 0) and ((GameFlags and gfSolidLand) = 0) then begin
                    DrawExplosion(gX, gY, 4);
                    
                    for i:= 0 to Random(3) do
                      AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke);
                end;
                // This one is interesting.  I think I understand the purpose, but I wonder if a bit more fuzzy of kicking could be done with getrandom.
                Gear^.Timer:= 100 - Gear^.Tag * 3;
                if (Gear^.Damage > 3000+Gear^.Tag*1500) then Gear^.Health:= 0
                end
            end
        end;
if Gear^.Health = 0 then begin
  gX:= hwRound(Gear^.X);
  gY:= hwRound(Gear^.Y);
  if (Gear^.State and gsttmpFlag) = 0 then begin
    if ((GameTicks and $3) = 0) and (Random(1) = 0) then begin
      for i:= 1 to Random(2)+1 do begin
        AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke);
      end;
    end;
  end else begin
    for i:= 0 to Random(3) do begin
      AddVisualGear(gX - 3 + Random(6), gY - 2, vgtSmoke);
    end;
  end;
  
  DeleteGear(Gear)
  end;
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepFirePunchWork(Gear: PGear);
var HHGear: PGear;
begin
AllInactive:= false;
if ((Gear^.Message and gm_Destroy) <> 0) then
    begin
    DeleteGear(Gear);
    AfterAttack;
    exit
    end;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
if hwRound(HHGear^.Y) <= Gear^.Tag - 2 then
    begin
    Gear^.Tag:= hwRound(HHGear^.Y);
    DrawTunnel(HHGear^.X - int2hwFloat(cHHRadius), HHGear^.Y - _1, _0_5, _0, cHHRadius * 4, 2);
    HHGear^.State:= HHGear^.State or gstNoDamage;
    Gear^.Y:= HHGear^.Y;
    AmmoShove(Gear, 30, 40);
    HHGear^.State:= HHGear^.State and not gstNoDamage
    end;

HHGear^.dY:= HHGear^.dY + cGravity;
if not (HHGear^.dY.isNegative) then
    begin
    HHGear^.State:= HHGear^.State or gstMoving;
    DeleteGear(Gear);
    AfterAttack;
    exit
    end;

if CheckLandValue(hwRound(HHGear^.X), hwRound(HHGear^.Y + HHGear^.dY + SignAs(_6,Gear^.dY)), COLOR_INDESTRUCTIBLE) then
   HHGear^.Y:= HHGear^.Y + HHGear^.dY
end;

procedure doStepFirePunch(Gear: PGear);
var HHGear: PGear;
begin
AllInactive:= false;
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
DeleteCI(HHGear);
HHGear^.X:= int2hwFloat(hwRound(HHGear^.X)) - _0_5;
HHGear^.dX:= SignAs(cLittle, Gear^.dX);

HHGear^.dY:= - _0_3;

Gear^.X:= HHGear^.X;
Gear^.dX:= SignAs(_0_45, Gear^.dX);
Gear^.dY:= - _0_9;
Gear^.doStep:= @doStepFirePunchWork;
DrawTunnel(HHGear^.X - int2hwFloat(cHHRadius), HHGear^.Y + _1, _0_5, _0, cHHRadius * 4, 5);

PlaySound(TSound(ord(sndFirePunch1) + GetRandom(6)), PHedgehog(HHGear^.Hedgehog)^.Team^.voicepack)
end;

////////////////////////////////////////////////////////////////////////////////

procedure doStepParachuteWork(Gear: PGear);
var HHGear: PGear;
begin
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;

inc(Gear^.Timer);

if TestCollisionYwithGear(HHGear, 1)
    or ((HHGear^.State and gstHHDriven) = 0)
    or CheckGearDrowning(HHGear)
    or ((Gear^.Message and gm_Attack) <> 0) then
    begin
    with HHGear^ do
        begin
        Message:= 0;
        SetLittle(dX);
        dY:= _0;
        State:= State or gstMoving;
        end;
    DeleteGear(Gear);
    isCursorVisible:= false;
    ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^);
    exit
    end;

if not TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then
    HHGear^.X:= HHGear^.X + cWindSpeed * 200;

if (Gear^.Message and gm_Left) <> 0 then HHGear^.X:= HHGear^.X - cMaxWindSpeed * 80
else if (Gear^.Message and gm_Right) <> 0 then HHGear^.X:= HHGear^.X + cMaxWindSpeed * 80;
if (Gear^.Message and gm_Up) <> 0 then HHGear^.Y:= HHGear^.Y - cGravity * 40
else if (Gear^.Message and gm_Down) <> 0 then HHGear^.Y:= HHGear^.Y + cGravity * 40;

HHGear^.Y:= HHGear^.Y + cGravity * 100;
Gear^.X:= HHGear^.X;
Gear^.Y:= HHGear^.Y
end;

procedure doStepParachute(Gear: PGear);
var HHGear: PGear;
begin
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;

DeleteCI(HHGear);

AfterAttack;

HHGear^.State:= HHGear^.State and not (gstAttacking or gstAttacked or gstMoving);
HHGear^.Message:= HHGear^.Message and not gm_Attack;

Gear^.doStep:= @doStepParachuteWork;

Gear^.Message:= HHGear^.Message;
doStepParachuteWork(Gear)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepAirAttackWork(Gear: PGear);
var i: Longint;
begin
AllInactive:= false;
Gear^.X:= Gear^.X + cAirPlaneSpeed * Gear^.Tag;

if (Gear^.Health > 0)and(not (Gear^.X < Gear^.dX))and(Gear^.X < Gear^.dX + cAirPlaneSpeed) then
    begin
    dec(Gear^.Health);
    case Gear^.State of
            0: FollowGear:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, cBombsSpeed * Gear^.Tag, _0, 0);
            1: FollowGear:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtMine,    0, cBombsSpeed * Gear^.Tag, _0, 0);
            2: for i:= -19 to 19 do
                FollowGear:= AddGear(hwRound(Gear^.X) + i div 3, hwRound(Gear^.Y), gtFlame, 0, _0_001 * i, _0, 0);
            end;
    Gear^.dX:= Gear^.dX + int2hwFloat(30 * Gear^.Tag)
    end;

if (GameTicks and $3F) = 0 then
    AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0);

if (hwRound(Gear^.X) > (LAND_WIDTH+1024)) or (hwRound(Gear^.X) < -1024) then DeleteGear(Gear)
end;

procedure doStepAirAttack(Gear: PGear);
begin
AllInactive:= false;

if Gear^.X.QWordValue = 0 then
    begin
    Gear^.Tag:=  1;
    Gear^.X:= -_1024;
    end
else
    begin
    Gear^.Tag:= -1;
    Gear^.X:= int2hwFloat(LAND_WIDTH + 1024);
    end;

Gear^.Y:= int2hwFloat(topY-300);
Gear^.dX:= int2hwFloat(TargetPoint.X - 5 * Gear^.Tag * 15);

if (int2hwFloat(TargetPoint.Y) - Gear^.Y > _0) and (Gear^.State <> 2) then
        Gear^.dX:= Gear^.dX - cBombsSpeed * hwSqrt((int2hwFloat(TargetPoint.Y) - Gear^.Y) * 2 / cGravity) * Gear^.Tag;

Gear^.Health:= 6;
Gear^.doStep:= @doStepAirAttackWork;
end;

////////////////////////////////////////////////////////////////////////////////

procedure doStepAirBomb(Gear: PGear);
begin
AllInactive:= false;
doStepFallingGear(Gear);
if (Gear^.State and gstCollision) <> 0 then
    begin
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 30, EXPLAutoSound);
    DeleteGear(Gear);
    exit
    end;
if (GameTicks and $3F) = 0 then
    AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0)
end;

////////////////////////////////////////////////////////////////////////////////

procedure doStepGirder(Gear: PGear);
var HHGear: PGear;
    x, y, tx, ty: hwFloat;
begin
AllInactive:= false;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
tx:= int2hwFloat(TargetPoint.X);
ty:= int2hwFloat(TargetPoint.Y);
x:= HHGear^.X;
y:= HHGear^.Y;

if (Distance(tx - x, ty - y) > _256) or
   not TryPlaceOnLand(TargetPoint.X - SpritesData[sprAmGirder].Width div 2,
                      TargetPoint.Y - SpritesData[sprAmGirder].Height div 2,
                      sprAmGirder, Gear^.State, true) then
    begin
    PlaySound(sndDenied);
    HHGear^.Message:= HHGear^.Message and not gm_Attack;
    HHGear^.State:= HHGear^.State and not gstAttacking;
    HHGear^.State:= HHGear^.State or gstHHChooseTarget;
    isCursorVisible:= true;
    DeleteGear(Gear)
    end
else begin
    PlaySound(sndPlaced);
    DeleteGear(Gear);
    AfterAttack;
    end;

HHGear^.State:= HHGear^.State and not (gstAttacking or gstAttacked);
HHGear^.Message:= HHGear^.Message and not gm_Attack;
TargetPoint.X:= NoPointX
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepTeleportAfter(Gear: PGear);
var HHGear: PGear;
begin
PHedgehog(Gear^.Hedgehog)^.Unplaced:= false;
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.Y:= HHGear^.Y + HHGear^.dY; // hedgehog falling to collect cases
HHGear^.dY:= HHGear^.dY + cGravity;
if TestCollisionYwithGear(HHGear, 1)
    or CheckGearDrowning(HHGear) then
    begin
    DeleteGear(Gear);
    AfterAttack
    end
end;

procedure doStepTeleportAnim(Gear: PGear);
begin
inc(Gear^.Timer);
if Gear^.Timer = 65 then
    begin
    Gear^.Timer:= 0;
    inc(Gear^.Pos);
    if Gear^.Pos = 11 then
        Gear^.doStep:= @doStepTeleportAfter
    end;
end;

procedure doStepTeleport(Gear: PGear);
var HHGear: PGear;
begin
AllInactive:= false;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
if not TryPlaceOnLand(TargetPoint.X - SpritesData[sprHHTelepMask].Width div 2,
                      TargetPoint.Y - SpritesData[sprHHTelepMask].Height div 2,
                      sprHHTelepMask, 0, false) then
        begin
        HHGear^.Message:= HHGear^.Message and not gm_Attack;
        HHGear^.State:= HHGear^.State and not gstAttacking;
        HHGear^.State:= HHGear^.State or gstHHChooseTarget;
        DeleteGear(Gear);
        isCursorVisible:= true;
        PlaySound(sndDenied)
        end
    else begin
        DeleteCI(HHGear);
        SetAllHHToActive;
        Gear^.doStep:= @doStepTeleportAnim;
        // copy old HH position and direction to Gear (because we need them for drawing the vanishing hog)
        Gear^.dX:= HHGear^.dX;
         // retrieve the cursor direction (it was previously copied to X so it doesn't get lost)
         HHGear^.dX.isNegative := (Gear^.X.QWordValue <> 0);
        Gear^.X:= HHGear^.X;
        Gear^.Y:= HHGear^.Y;
        HHGear^.X:= int2hwFloat(TargetPoint.X);
        HHGear^.Y:= int2hwFloat(TargetPoint.Y);
        HHGear^.State:= HHGear^.State or gstMoving;
        playSound(sndWarp)
        end;
TargetPoint.X:= NoPointX;
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepSwitcherWork(Gear: PGear);
var HHGear: PGear;
    Msg, State: Longword;
begin
AllInactive:= false;

if ((Gear^.Message and not gm_Switch) <> 0) or (TurnTimeLeft = 0) then
    begin
    HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
    Msg:= Gear^.Message and not gm_Switch;
    DeleteGear(Gear);
    OnUsedAmmo(PHedgehog(HHGear^.Hedgehog)^);
    ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^);

    HHGear:= CurrentHedgehog^.Gear;
    ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^);
    HHGear^.Message:= Msg;
    exit
    end;

if (Gear^.Message and gm_Switch) <> 0 then
    begin
    HHGear:= CurrentHedgehog^.Gear;
    HHGear^.Message:= HHGear^.Message and not gm_Switch;
    Gear^.Message:= Gear^.Message and not gm_Switch;
    State:= HHGear^.State;
    HHGear^.State:= 0;
    HHGear^.Active:= false;
    HHGear^.Z:= cHHZ;
    RemoveGearFromList(HHGear);
    InsertGearToList(HHGear);

    PlaySound(sndSwitchHog);
    
    repeat
        CurrentTeam^.CurrHedgehog:= Succ(CurrentTeam^.CurrHedgehog) mod (CurrentTeam^.HedgehogsNumber);
    until (CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog].Gear <> nil);

    CurrentHedgehog:= @CurrentTeam^.Hedgehogs[CurrentTeam^.CurrHedgehog];

    HHGear:= CurrentHedgehog^.Gear;
    HHGear^.State:= State;
    HHGear^.Active:= true;
    FollowGear:= HHGear;
    HHGear^.Z:= cCurrHHZ;
    RemoveGearFromList(HHGear);
    InsertGearToList(HHGear);
    Gear^.X:= HHGear^.X;
    Gear^.Y:= HHGear^.Y
    end;
end;

procedure doStepSwitcher(Gear: PGear);
var HHGear: PGear;
begin
Gear^.doStep:= @doStepSwitcherWork;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
with HHGear^ do
    begin
    State:= State and not gstAttacking;
    Message:= Message and not gm_Attack
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepMortar(Gear: PGear);
var dX, dY: hwFloat;
    i: LongInt;
    dxn, dyn: boolean;
begin
AllInactive:= false;
dxn:= Gear^.dX.isNegative;
dyn:= Gear^.dY.isNegative;

doStepFallingGear(Gear);
if (Gear^.State and gstCollision) <> 0 then
    begin
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 20, EXPLAutoSound);

    Gear^.dX.isNegative:= not dxn;
    Gear^.dY.isNegative:= not dyn;
    for i:= 0 to 4 do
        begin
        dX:= Gear^.dX + (GetRandom - _0_5) * _0_03;
        dY:= Gear^.dY + (GetRandom - _0_5) * _0_03;
        AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtCluster, 0, dX, dY, 25);
        end;

    DeleteGear(Gear);
    exit
    end;

if (GameTicks and $3F) = 0 then
    AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepKamikazeWork(Gear: PGear);
const upd: Longword = 0;
var i: LongWord;
    HHGear: PGear;
begin
AllInactive:= false;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.State:= HHGear^.State or gstNoDamage;
DeleteCI(HHGear);

i:= 2;
repeat
    Gear^.X:= Gear^.X + HHGear^.dX;
    Gear^.Y:= Gear^.Y + HHGear^.dY;
    HHGear^.X:= Gear^.X;
    HHGear^.Y:= Gear^.Y;

    inc(Gear^.Damage, 2);

//  if TestCollisionXwithGear(HHGear, hwSign(Gear^.dX))
//      or TestCollisionYwithGear(HHGear, hwSign(Gear^.dY)) then inc(Gear^.Damage, 3);

    dec(i)
until (i = 0) or (Gear^.Damage > Gear^.Health);

inc(upd);
if upd > 3 then
    begin
    if Gear^.Health < 1500 then Gear^.Pos:= 2;

    AmmoShove(Gear, 30, 40);

    DrawTunnel(HHGear^.X - HHGear^.dX * 10,
            HHGear^.Y - _2 - HHGear^.dY * 10 + hwAbs(HHGear^.dY) * 2,
            HHGear^.dX,
            HHGear^.dY,
            20 + cHHRadius * 2,
            cHHRadius * 2 + 6);

    upd:= 0
    end;

if Gear^.Health < Gear^.Damage then
    begin
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 30, EXPLAutoSound);
    AfterAttack;
    DeleteGear(Gear);
    DeleteGear(HHGear);
    end else
    begin
    dec(Gear^.Health, Gear^.Damage);
    Gear^.Damage:= 0
    end
end;

procedure doStepKamikazeIdle(Gear: PGear);
begin
AllInactive:= false;
dec(Gear^.Timer);
if Gear^.Timer = 0 then
    begin
    Gear^.Pos:= 1;
    PlaySound(sndKamikaze, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack);
    Gear^.doStep:= @doStepKamikazeWork
    end
end;

procedure doStepKamikaze(Gear: PGear);
var HHGear: PGear;
begin
AllInactive:= false;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;

HHGear^.dX:= Gear^.dX;
HHGear^.dY:= Gear^.dY;

Gear^.dX:= SignAs(_0_45, Gear^.dX);
Gear^.dY:= - _0_9;

Gear^.Timer:= 550;

Gear^.doStep:= @doStepKamikazeIdle
end;

////////////////////////////////////////////////////////////////////////////////
const cakeh = 27;
      cakeDmg = 75;
var CakePoints: array[0..Pred(cakeh)] of record x, y: hwFloat; end;
    CakeI: Longword;

procedure doStepCakeExpl(Gear: PGear);
begin
AllInactive:= false;

inc(Gear^.Tag);
if Gear^.Tag < 2250 then exit;

doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), cakeDmg, EXPLAutoSound);
AfterAttack;
DeleteGear(Gear)
end;

procedure doStepCakeDown(Gear: PGear);
var gi: PGear;
    dmg: LongInt;
begin
AllInactive:= false;

inc(Gear^.Tag);
if Gear^.Tag < 100 then exit;
Gear^.Tag:= 0;

if Gear^.Pos = 0 then
    begin
    gi:= GearsList;
    while gi <> nil do
        begin
        dmg:= cakeDmg * 2 - hwRound(Distance(gi^.X - Gear^.X, gi^.Y - Gear^.Y));
        if (dmg > 1) and (gi^.Kind = gtHedgehog) then
            if (CurrentHedgehog^.Gear = gi) and (not gi^.Invulnerable) then
                gi^.State:= gi^.State or gstLoser
            else
                gi^.State:= gi^.State or gstWinner;
        gi:= gi^.NextGear
        end;
    Gear^.doStep:= @doStepCakeExpl;
    PlaySound(sndCake)
    end else dec(Gear^.Pos)
end;


procedure doStepCakeWork(Gear: PGear);
const dirs: array[0..3] of TPoint = ((x: 0; y: -1), (x: 1; y: 0),(x: 0; y: 1),(x: -1; y: 0));
var xx, yy, xxn, yyn: LongInt;
    da: LongInt;
    tdx, tdy: hwFloat;

    procedure PrevAngle;
    begin
    Gear^.Angle:= (LongInt(Gear^.Angle) + 4 - dA) mod 4
    end;

    procedure NextAngle;
    begin
    Gear^.Angle:= (LongInt(Gear^.Angle) + 4 + dA) mod 4
    end;

begin
AllInactive:= false;

inc(Gear^.Tag);
if Gear^.Tag < 7 then exit;

dA:= hwSign(Gear^.dX);
xx:= dirs[Gear^.Angle].x;
yy:= dirs[Gear^.Angle].y;
xxn:= dirs[(LongInt(Gear^.Angle) + 4 + dA) mod 4].x;
yyn:= dirs[(LongInt(Gear^.Angle) + 4 + dA) mod 4].y;

if (xx = 0) then
    if TestCollisionYwithGear(Gear, yy) then
        PrevAngle
    else begin
        Gear^.Tag:= 0;
        Gear^.Y:= Gear^.Y + int2hwFloat(yy);
        if not TestCollisionXwithGear(Gear, xxn) then
            begin
            Gear^.X:= Gear^.X + int2hwFloat(xxn);
            NextAngle
            end;
        end;

if (yy = 0) then
    if TestCollisionXwithGear(Gear, xx) then
        PrevAngle
    else begin
        Gear^.Tag:= 0;
        Gear^.X:= Gear^.X + int2hwFloat(xx);
        if not TestCollisionYwithGear(Gear, yyn) then
            begin
            Gear^.Y:= Gear^.Y + int2hwFloat(yyn);
            NextAngle
            end;
        end;

if Gear^.Tag = 0 then
    begin
    CakeI:= (CakeI + 1) mod cakeh;
    tdx:= CakePoints[CakeI].x - Gear^.X;
    tdy:= - CakePoints[CakeI].y + Gear^.Y;
    CakePoints[CakeI].x:= Gear^.X;
    CakePoints[CakeI].y:= Gear^.Y;
    Gear^.DirAngle:= DxDy2Angle(tdx, tdy);
    end;

dec(Gear^.Health);
Gear^.Timer:= Gear^.Health*10; // This is not seconds, but at least it is *some* feedback
if (Gear^.Health = 0) or ((Gear^.Message and gm_Attack) <> 0) then
    begin
    FollowGear:= Gear;
    Gear^.RenderTimer:= false;
    Gear^.doStep:= @doStepCakeDown
    end
end;

procedure doStepCakeUp(Gear: PGear);
var i: Longword;
begin
AllInactive:= false;

inc(Gear^.Tag);
if Gear^.Tag < 100 then exit;
Gear^.Tag:= 0;

if Gear^.Pos = 6 then
    begin
    for i:= 0 to Pred(cakeh) do
        begin
        CakePoints[i].x:= Gear^.X;
        CakePoints[i].y:= Gear^.Y
        end;
    CakeI:= 0;
    Gear^.doStep:= @doStepCakeWork
    end else inc(Gear^.Pos)
end;

procedure doStepCakeFall(Gear: PGear);
begin
AllInactive:= false;

Gear^.dY:= Gear^.dY + cGravity;
if TestCollisionYwithGear(Gear, 1) then
    Gear^.doStep:= @doStepCakeUp
else
    begin
    Gear^.Y:= Gear^.Y + Gear^.dY;
    if CheckGearDrowning(Gear) then AfterAttack
    end
end;

procedure doStepCake(Gear: PGear);
var HHGear: PGear;
begin
AllInactive:= false;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.Message:= HHGear^.Message and (not gm_Attack);
DeleteCI(HHGear);

FollowGear:= Gear;

Gear^.doStep:= @doStepCakeFall
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepSeductionWork(Gear: PGear);
var x, y: LongInt;
begin
AllInactive:= false;

Gear^.X:= Gear^.X + Gear^.dX;
Gear^.Y:= Gear^.Y + Gear^.dY;
x:= hwRound(Gear^.X);
y:= hwRound(Gear^.Y);

if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) then
    if (Land[y, x] <> 0) then
        begin
        Gear^.dX.isNegative:= not Gear^.dX.isNegative;
        Gear^.dY.isNegative:= not Gear^.dY.isNegative;
        Gear^.dX:= Gear^.dX * _1_5;
        Gear^.dY:= Gear^.dY * _1_5 - _0_3;
        AmmoShove(Gear, 0, 40);
        AfterAttack;
        DeleteGear(Gear)
        end
    else
else
    begin
    AfterAttack;
    DeleteGear(Gear)
    end
end;

procedure doStepSeductionWear(Gear: PGear);
begin
AllInactive:= false;
inc(Gear^.Timer);
if Gear^.Timer > 250 then
    begin
    Gear^.Timer:= 0;
    inc(Gear^.Pos);
    if Gear^.Pos = 5 then
        PlaySound(sndYoohoo, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack)
    end;

if Gear^.Pos = 14 then
    Gear^.doStep:= @doStepSeductionWork
end;

procedure doStepSeduction(Gear: PGear);
begin
AllInactive:= false;
DeleteCI(PHedgehog(Gear^.Hedgehog)^.Gear);
Gear^.doStep:= @doStepSeductionWear
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepWaterUp(Gear: PGear);
var i: LongWord;
begin
AllInactive:= false;

inc(Gear^.Timer);
if Gear^.Timer = 17 then
    Gear^.Timer:= 0
else
    exit;

if cWaterLine > 0 then
    begin
    dec(cWaterLine);
    for i:= 0 to LAND_WIDTH - 1 do
        Land[cWaterLine, i]:= 0;
    SetAllToActive
    end;

inc(Gear^.Tag);
if (Gear^.Tag = 47) or (cWaterLine = 0) then
    DeleteGear(Gear)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepDrillDrilling(Gear: PGear);
var t: PGearArray;
    ox, oy: hwFloat;
begin
AllInactive:= false;

if (Gear^.Timer > 0) and ((Gear^.Timer mod 10) = 0) then
    begin
    ox:= Gear^.X;
    oy:= Gear^.Y;
    Gear^.X:= Gear^.X + Gear^.dX;
    Gear^.Y:= Gear^.Y + Gear^.dY;
    DrawTunnel(oX, oY, Gear^.dX, Gear^.dY, 2, 6);
    if(CheckGearDrowning(Gear)) then
        begin
        StopSound(Gear^.SoundChannel);
        exit
        end
    end;

t:= CheckGearsCollision(Gear); //fixes drill not exploding when touching HH bug
if (Gear^.Timer = 0)
or (t^.Count <> 0)
or (not TestCollisionYWithGear(Gear, hwSign(Gear^.dY))
and not TestCollisionXWithGear(Gear, hwSign(Gear^.dX)))
or (Land[hwRound(Gear^.Y), hwRound(Gear^.X)] = COLOR_INDESTRUCTIBLE) then
    begin //out of time or exited ground
    StopSound(Gear^.SoundChannel);
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound);
    DeleteGear(Gear);
    exit
    end;

dec(Gear^.Timer);
end;

procedure doStepDrill(Gear: PGear);
var t: PGearArray;
    oldDx, oldDy: hwFloat;
    t2: hwFloat;
begin
AllInactive:= false;

Gear^.dX:= Gear^.dX + cWindSpeed;
oldDx:= Gear^.dX;
oldDy:= Gear^.dY;

doStepFallingGear(Gear);

if (GameTicks and $3F) = 0 then
    AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0);

if ((Gear^.State and gstCollision) <> 0) then begin //hit
    Gear^.dX:= oldDx;
    Gear^.dY:= oldDy;

    t:= CheckGearsCollision(Gear);
    if (t^.Count = 0) then begin //hit the ground not the HH
        t2 := _0_5 / Distance(Gear^.dX, Gear^.dY);
        Gear^.dX:= Gear^.dX * t2;
        Gear^.dY:= Gear^.dY * t2;
    end else begin //explode right on contact with HH
        doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 50, EXPLAutoSound);
        DeleteGear(Gear);
        exit;
        end;

    Gear^.SoundChannel:= LoopSound(sndDrillRocket);
    Gear^.doStep:= @doStepDrillDrilling;
    dec(Gear^.Timer)
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBallgunWork(Gear: PGear);
var HHGear: PGear;
    rx, ry: hwFloat;
    gX, gY: LongInt;
begin
    AllInactive:= false;
    dec(Gear^.Timer);
    HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
    HedgehogChAngle(HHGear);
    gX:= hwRound(Gear^.X);
    gY:= hwRound(Gear^.Y);
    if (Gear^.Timer mod 100) = 0 then
        begin
        rx:= rndSign(getRandom * _0_1);
        ry:= rndSign(getRandom * _0_1);

        AddGear(gx, gy, gtBall, 0,
                SignAs(AngleSin(HHGear^.Angle) * _0_8, HHGear^.dX) + rx,
                AngleCos(HHGear^.Angle) * ( - _0_8) + ry,
                0);

        PlaySound(sndGun);
        end;

    if (Gear^.Timer = 0) or (HHGear^.Damage <> 0) then
        begin
        DeleteGear(Gear);
        AfterAttack
        end
end;

procedure doStepBallgun(Gear: PGear);
var HHGear: PGear;
begin
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.Message:= HHGear^.Message and not (gm_Up or gm_Down);
HHGear^.State:= HHGear^.State or gstNotKickable;
Gear^.doStep:= @doStepBallgunWork
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepRCPlaneWork(Gear: PGear);
const cAngleSpeed = 3;
var HHGear: PGear;
    i: LongInt;
    dX, dY: hwFloat;
    fChanged: boolean;
    trueAngle: Longword;
    t: PGear;
begin
AllInactive:= false;

if ((TrainingFlags and tfRCPlane) = 0) and (Gear^.Timer > 0) then dec(Gear^.Timer);

if ((TrainingFlags and tfRCPlane) <> 0) and ((TrainingFlags and tfTimeTrial) <> 0 ) and (TimeTrialStartTime = 0) then TimeTrialStartTime:= RealTicks;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
FollowGear:= Gear;

fChanged:= false;
if ((HHGear^.State and gstHHDriven) = 0) or (Gear^.Timer = 0) then
    begin
    fChanged:= true;
    if Gear^.Angle > 2048 then dec(Gear^.Angle) else
        if Gear^.Angle < 2048 then inc(Gear^.Angle) else fChanged:= false
    end
else
    begin
    if ((Gear^.Message and gm_Left) <> 0) then
        begin
        fChanged:= true;
        Gear^.Angle:= (Gear^.Angle + (4096 - cAngleSpeed)) mod 4096
        end;

    if ((Gear^.Message and gm_Right) <> 0) then
        begin
        fChanged:= true;
        Gear^.Angle:= (Gear^.Angle + cAngleSpeed) mod 4096
        end
    end;

if fChanged then
    begin
    Gear^.dX.isNegative:= (Gear^.Angle > 2048);
    if Gear^.dX.isNegative then
        trueAngle:= 4096 - Gear^.Angle
    else
        trueAngle:= Gear^.Angle;

    Gear^.dX:= SignAs(AngleSin(trueAngle), Gear^.dX) * _0_25;
    Gear^.dY:= AngleCos(trueAngle) * -_0_25;
    end;

Gear^.X:= Gear^.X + Gear^.dX;
Gear^.Y:= Gear^.Y + Gear^.dY;

if (TrainingFlags and tfRCPlane) = 0 then
    begin
    if (GameTicks and $FF) = 0 then
        if Gear^.Timer < 3500 then
            AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtEvilTrace, 0, _0, _0, 0)
        else
            AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0);

    if ((HHGear^.Message and gm_Attack) <> 0) and (Gear^.Health <> 0) then
        begin
        HHGear^.Message := HHGear^.Message and not gm_Attack;
        AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAirBomb, 0, Gear^.dX * _0_5, Gear^.dY * _0_5, 0);
        dec(Gear^.Health)
        end;

    if ((HHGear^.Message and gm_LJump) <> 0)
        and ((Gear^.State and gsttmpFlag) = 0) then
        begin
        Gear^.State:= Gear^.State or gsttmpFlag;
        PauseMusic;
        playSound(sndRideOfTheValkyries);
        end;

    // pickup bonuses
    t:= CheckGearNear(Gear, gtCase, 36, 36);
    if t <> nil then
        PickUp(HHGear, t);
    end
else
    begin
    if (GameTicks and $FF) = 0 then
        AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtSmokeTrace, 0, _0, _0, 0);

    // pickup targets
    t:= CheckGearNear(Gear, gtTarget, 36, 36);
    if t <> nil then
        begin
        if t^.Tag <> 0 then // collect it only once
            exit;
        PlaySound(sndShotgunReload);
        t^.Tag:= 1;
        TrainingTargetGear:= nil; // remove target cursor
        exit;
        end;

    if (TurnTimeLeft > 0) then 
        dec(TurnTimeLeft)
    end;
        
CheckCollision(Gear);

if ((Gear^.State and gstCollision) <> 0) or (((TrainingFlags and tfRCPlane) <> 0) and (TurnTimeLeft = 0))
    or CheckGearDrowning(Gear) then
    begin
    if ((TrainingFlags and tfRCPlane) <> 0) and ((TrainingFlags and tfTimeTrial) <> 0 ) and (TimeTrialStopTime = 0) then TimeTrialStopTime:= RealTicks;
    StopSound(Gear^.SoundChannel);
    StopSound(sndRideOfTheValkyries);
    ResumeMusic;

    if ((Gear^.State and gstCollision) <> 0) or (((TrainingFlags and tfRCPlane) <> 0) and (TurnTimeLeft = 0)) then
        begin
        doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, EXPLAutoSound);
        for i:= 0 to 32 do
            begin
            dX:= AngleCos(i * 64) * _0_5 * (GetRandom + _1);
            dY:= AngleSin(i * 64) * _0_5 * (GetRandom + _1);
            AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtFlame, 0, dX, dY, 0);
            AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtFlame, 0, dX, -dY, 0);
            end;
        DeleteGear(Gear)
        end;

    AfterAttack;
    CurAmmoGear:= nil;
    TurnTimeLeft:= 14 * 125;
    
    if (TrainingFlags and tfRCPlane) <> 0 then
        TurnTimeLeft:= 0; // HACK: RCPlane training allows unlimited plane starts in last 2 seconds

    HHGear^.Message:= 0;
    ParseCommand('/taunt '#1, true)
    end
end;

procedure doStepRCPlane(Gear: PGear);
var HHGear: PGear;
begin
HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
HHGear^.Message:= 0;
HHGear^.State:= HHGear^.State or gstNotKickable;
Gear^.Angle:= HHGear^.Angle;
Gear^.Tag:= hwSign(HHGear^.dX);
if HHGear^.dX.isNegative then Gear^.Angle:= 4096 - Gear^.Angle;
Gear^.doStep:= @doStepRCPlaneWork
end;

procedure doStepJetpackWork(Gear: PGear);
var HHGear: PGear;
    fuel: LongInt;
    move: hwFloat;
begin
AllInactive:= false;
HHGear:=PHedgehog(Gear^.Hedgehog)^.Gear;
//dec(Gear^.Timer);
move:= _0_1;
fuel:= 50;
(*if (HHGear^.Message and gm_Precise) <> 0 then
    begin
    move:= _0_02;
    fuel:= 5;
    end;*)

if (HHGear^.Message and gm_Up) <> 0 then
    begin
    if (not HHGear^.dY.isNegative) or (HHGear^.Y > -_256) then
        HHGear^.dY:= HHGear^.dY - move;
    HHGear^.dY:= HHGear^.dY - move;
    dec(Gear^.Health, fuel);
    Gear^.MsgParam:= Gear^.MsgParam or gm_Up;
    Gear^.Timer:= GameTicks
    end;
if (HHGear^.Message and gm_Left) <> 0 then move.isNegative:= true;
if (HHGear^.Message and (gm_Left or gm_Right)) <> 0 then
    begin
    HHGear^.dX:= HHGear^.dX + (move * _0_2);
    dec(Gear^.Health, fuel div 5);
    Gear^.MsgParam:= Gear^.MsgParam or (HHGear^.Message and (gm_Left or gm_Right));
    Gear^.Timer:= GameTicks
    end;

// erases them all at once :-/
if (Gear^.Timer <> 0) and (GameTicks - Gear^.Timer > 250) then
    begin
    Gear^.Timer:= 0;
    Gear^.MsgParam:= 0
    end;

if Gear^.Health < 0 then Gear^.Health:= 0;
if (GameTicks and $3F) = 0 then
       begin
//AddCaption('Fuel: '+inttostr(round(Gear^.Health/20))+'%', cWhiteColor, capgrpAmmostate);
       if Gear^.Tex <> nil then FreeTexture(Gear^.Tex);
       Gear^.Tex:= RenderStringTex(trmsg[sidFuel] + ': ' + inttostr(round(Gear^.Health / 20)) + '%', cWhiteColor, fntSmall)
       end;

if HHGear^.Message and (gm_Attack or gm_Up or gm_Precise or gm_Left or gm_Right) <> 0 then Gear^.State:= Gear^.State and not gsttmpFlag;
HHGear^.Message:= HHGear^.Message and not (gm_Up or gm_Precise or gm_Left or gm_Right);
HHGear^.State:= HHGear^.State or gstMoving;

Gear^.X:= HHGear^.X;
Gear^.Y:= HHGear^.Y;
// For some reason I need to reapply followgear here, something else grabs it otherwise.
if not bShowAmmoMenu then FollowGear:= HHGear;

if ((Gear^.State and gsttmpFlag) = 0) or (HHGear^.dY < _0) then doStepHedgehogMoving(HHGear);

if  (Gear^.Health = 0)
    or (HHGear^.Damage <> 0)
    or CheckGearDrowning(HHGear)
    or (TurnTimeLeft = 0)
    // allow brief ground touches - to be fair on this, might need another counter
    or (((GameTicks and $1FF) = 0) and (not HHGear^.dY.isNegative) and TestCollisionYwithGear(HHGear, 1))
    or ((Gear^.Message and gm_Attack) <> 0) then
    begin
    with HHGear^ do
        begin
        Message:= 0;
        Active:= true;
        State:= State or gstMoving
        end;
    DeleteGear(Gear);
    isCursorVisible:= false;
    ApplyAmmoChanges(PHedgehog(HHGear^.Hedgehog)^);
//    if Gear^.Tex <> nil then FreeTexture(Gear^.Tex);
//    Gear^.Tex:= RenderStringTex(trmsg[sidFuel] + ': ' + inttostr(round(Gear^.Health / 20)) + '%', cWhiteColor, fntSmall)
    //AddCaption(trmsg[sidFuel]+': '+inttostr(round(Gear^.Health/20))+'%', cWhiteColor, capgrpAmmostate);
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepJetpack(Gear: PGear);
var HHGear: PGear;
begin
Gear^.doStep:= @doStepJetpackWork;

HHGear:= PHedgehog(Gear^.Hedgehog)^.Gear;
FollowGear:= HHGear;
AfterAttack;
with HHGear^ do
    begin
    State:= State and not gstAttacking;
    Message:= Message and not (gm_Attack or gm_Up or gm_Precise or gm_Left or gm_Right);
    if (dY < _0_1) and (dY > -_0_1) then
        begin
        Gear^.State:= Gear^.State or gsttmpFlag;
        dY:= dY - _0_2
        end
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBirdyDisappear(Gear: PGear);
begin
AllInactive:= false;
Gear^.Pos:= 0;
if Gear^.Timer < 2000 then
    inc(Gear^.Timer, 1)
else
    begin
    DeleteGear(Gear);
    end;
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBirdyFly(Gear: PGear);
var HHGear: PGear;
    fuel, i: LongInt;
    move: hwFloat;
begin
HHGear:= CurrentHedgehog^.Gear;

move:= _0_1;
fuel:= 50;

if Gear^.Pos > 0 then
    dec(Gear^.Pos, 1)
else if (HHGear^.Message and (gm_Left or gm_Right or gm_Up)) <> 0 then
    Gear^.Pos:= 500;

if HHGear^.dX.isNegative then
    Gear^.Tag:= -1
else
    Gear^.Tag:= 1;

if (HHGear^.Message and gm_Up) <> 0 then
    begin
    if (not HHGear^.dY.isNegative) or (HHGear^.Y > -_256) then
        HHGear^.dY:= HHGear^.dY - move;
    HHGear^.dY:= HHGear^.dY - move;
    dec(Gear^.Health, fuel);
    Gear^.MsgParam:= Gear^.MsgParam or gm_Up;
    end;
if (HHGear^.Message and gm_Left) <> 0 then move.isNegative:= true;
if (HHGear^.Message and (gm_Left or gm_Right)) <> 0 then
    begin
    HHGear^.dX:= HHGear^.dX + (move * _0_2);
    dec(Gear^.Health, fuel div 5);
    Gear^.MsgParam:= Gear^.MsgParam or (HHGear^.Message and (gm_Left or gm_Right));
    end;

if Gear^.Health < 0 then Gear^.Health:= 0;
if ((GameTicks and $FF) = 0) and (Gear^.Health < 500) then
    for i:= ((500-Gear^.Health) div 250) downto 0 do
        AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtFeather);

if (HHGear^.Message and gm_Attack <> 0) then begin
        HHGear^.Message := HHGear^.Message and not gm_Attack;
        if Gear^.FlightTime > 0 then begin
            AddGear(hwRound(Gear^.X), hwRound(Gear^.Y) + 32, gtEgg, 0, Gear^.dX * _0_5, Gear^.dY, 0);
            PlaySound(sndBirdyLay);
            dec(Gear^.FlightTime)
        end;
end;

if HHGear^.Message and (gm_Up or gm_Precise or gm_Left or gm_Right) <> 0 then Gear^.State:= Gear^.State and not gsttmpFlag;
HHGear^.Message:= HHGear^.Message and not (gm_Up or gm_Precise or gm_Left or gm_Right);
HHGear^.State:= HHGear^.State or gstMoving;

Gear^.X:= HHGear^.X;
Gear^.Y:= HHGear^.Y - int2hwFloat(32);
// For some reason I need to reapply followgear here, something else grabs it otherwise.
if not bShowAmmoMenu then FollowGear:= HHGear;

if ((Gear^.State and gsttmpFlag) = 0) or (HHGear^.dY < _0) then doStepHedgehogMoving(HHGear);

if  (Gear^.Health = 0)
    or (HHGear^.Damage <> 0)
    or CheckGearDrowning(HHGear)
    or (TurnTimeLeft = 0)
    // allow brief ground touches - to be fair on this, might need another counter
    or (((GameTicks and $1FF) = 0) and (not HHGear^.dY.isNegative) and TestCollisionYwithGear(HHGear, 1))
    or ((Gear^.Message and gm_Attack) <> 0) then
    begin
    with HHGear^ do
        begin
        Message:= 0;
        Active:= true;
        State:= State or gstMoving
        end;
    Gear^.State:= Gear^.State or gstAnimation or gstTmpFlag;
    if HHGear^.dY < _0 then
        begin
        Gear^.dX:= HHGear^.dX;
        Gear^.dY:= HHGear^.dY;
        end;
    Gear^.Timer:= 0;
    Gear^.doStep:= @doStepBirdyDisappear;
    CurAmmoGear:= nil;
    isCursorVisible:= false;
    AfterAttack;
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBirdyDescend(Gear: PGear);
var HHGear: PGear;
begin
if Gear^.Timer > 0 then
    dec(Gear^.Timer, 1)
else if CurrentHedgehog = nil then
    begin
    DeleteGear(Gear);
    AfterAttack;
    exit
    end;
HHGear:= CurrentHedgehog^.Gear;
HHGear^.Message:= HHGear^.Message and not (gm_Up or gm_Precise or gm_Left or gm_Right);
if abs(hwRound(HHGear^.Y - Gear^.Y)) > 32 then
    begin
    if Gear^.Timer = 0 then
        Gear^.Y:= Gear^.Y + _0_1
    end
else if Gear^.Timer = 0 then
    begin
    Gear^.doStep:= @doStepBirdyFly;
    HHGear^.dY:= -_0_2
    end
end;

procedure doStepBirdyAppear(Gear: PGear);
begin
Gear^.Pos:= 0;
if Gear^.Timer < 2000 then
    inc(Gear^.Timer, 1)
else
    begin
    Gear^.Timer:= 500;
    Gear^.dX:= _0;
    Gear^.dY:= _0;
    Gear^.State:=  Gear^.State and not gstAnimation;
    Gear^.doStep:= @doStepBirdyDescend;
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBirdy(Gear: PGear);
var HHGear: PGear;
begin
gear^.State:=  gear^.State or gstAnimation and not gstTmpFlag;
Gear^.doStep:= @doStepBirdyAppear;
if CurrentHedgehog = nil then
    begin
    DeleteGear(Gear);
    exit
    end;

HHGear:= CurrentHedgehog^.Gear;

if HHGear^.dX.isNegative then
    Gear^.Tag:= -1
else
    Gear^.Tag:= 1;
Gear^.Pos:= 0;
AllInactive:= false;
FollowGear:= HHGear;
with HHGear^ do
    begin
    State:= State and not gstAttacking;
    Message:= Message and not (gm_Attack or gm_Up or gm_Precise or gm_Left or gm_Right)
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepBigExplosionWork(Gear: PGear);
var maxMovement: LongInt;
begin
inc(Gear^.Timer);
if (Gear^.Timer and 5) = 0 then
    begin
    maxMovement := max(1, 13 - ((Gear^.Timer * 15) div 250));
    ShakeCamera(maxMovement);
    end;
if Gear^.Timer > 250 then DeleteGear(Gear);
end;

procedure doStepBigExplosion(Gear: PGear);
var i: LongWord;
gX,gY: LongInt;
begin
gX:= hwRound(Gear^.X);
gY:= hwRound(Gear^.Y);
AddVisualGear(gX, gY, vgtSmokeRing);
for i:= 0 to 46 do AddVisualGear(gX, gY, vgtFire);
for i:= 0 to 15 do AddVisualGear(gX, gY, vgtExplPart);
for i:= 0 to 15 do AddVisualGear(gX, gY, vgtExplPart2);
Gear^.doStep:= @doStepBigExplosionWork
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepEggWork(Gear: PGear);
var vg: PVisualGear;
     i: LongInt;
begin
    AllInactive:= false;
    Gear^.dX:= Gear^.dX;
    doStepFallingGear(Gear);
//    CheckGearDrowning(Gear); // already checked for in doStepFallingGear
    CalcRotationDirAngle(Gear);

    if (Gear^.State and gstCollision) <> 0 then
    begin
        doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 10, EXPLPoisoned or EXPLNoGfx);
        doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 10, EXPLPoisoned or EXPLNoGfx);
        PlaySound(sndEggBreak);
        AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtEgg);
        vg:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtEgg);
        if vg <> nil then vg^.Frame:= 2;

    for i:= 10 downto 0 do begin
        vg := AddVisualGear(hwRound(Gear^.X) - 3 + Random(6), hwRound(Gear^.Y) - 3 + Random(6), vgtDust);
        if vg <> nil then vg^.dX := vg^.dX + (Gear^.dX / 5);
        end;

        DeleteGear(Gear);
        exit
    end;
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepPortal(Gear: PGear);
var iterator: PGear;
    s: hwFloat;
begin
    if (Land[hwRound(Gear^.Y), hwRound(Gear^.X)] and $FF00) = 0 then
        begin
        DeleteGear(Gear);
        EXIT;
        end;
        
    if Gear^.IntersectGear <> nil then
    //if (Gear^.IntersectGear <> nil) then
        begin
        iterator:= GearsList;
        while iterator <> nil do
            begin
            if (iterator^.Kind <> gtPortal) and (iterator^.PortedCounter < 20) then
                if (((iterator^.State and gstMoving) <> 0) or (Gear^.IntersectGear^.dY.isNegative and not Gear^.dY.isNegative))
                    and (hwRound(Distance(Gear^.X-iterator^.X,Gear^.Y-iterator^.Y)) < iterator^.Radius+Gear^.Radius) then // Let's check this one more closely
                        if (Gear^.dX*iterator^.dX + Gear^.dY*iterator^.dY).isNegative then // make sure object moves towards the portal
                            begin
                            inc(iterator^.PortedCounter);
                            s:= (_1+(Int2hwFloat(Gear^.Radius))) / Distance(Gear^.IntersectGear^.dX, Gear^.IntersectGear^.dY);
                            iterator^.X:= Gear^.IntersectGear^.X + s * Gear^.IntersectGear^.dX;
                            iterator^.Y:= Gear^.IntersectGear^.Y + s * Gear^.IntersectGear^.dY;
                            s:= Distance(iterator^.dX, iterator^.dY) / Distance(Gear^.IntersectGear^.dX, Gear^.IntersectGear^.dY);
                            iterator^.dX:= s * Gear^.IntersectGear^.dX;
                            iterator^.dY:= s * Gear^.IntersectGear^.dY;
                            end;    

            iterator:= iterator^.NextGear;
            end;
// do portal stuff
        end

(*

2) From then on, if doStepPortal is called and a gear of a radius less than or equal to the portal is within X pixels of the portal (we could also check on moving toward the portal I guess, depends how accurate this needs to be) the portal will then locate the first other portal of the opposite type (there should only be one other one), and move that gear's X/Y to that other portal's location, and modify dX/dY to be relative to that other portal's orientation relative to this portal's orientation.  This might require some tweaking with offsets of a few pixels to avoid getting gears stuck in land.

*)
end;

procedure doStepMovingPortal(Gear: PGear);
var x, y, tx, ty: LongInt;//, bx, by, tangle: LongInt;
    iterator: PGear;
    s, dx, dy: hwFloat;
begin
Gear^.X:= Gear^.X + Gear^.dX;
Gear^.Y:= Gear^.Y + Gear^.dY;
x:= hwRound(Gear^.X);
y:= hwRound(Gear^.Y);
tx:= 0; ty:= 0; // avoid compiler hints

if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0) and (Land[y, x] > 255) then
    begin
    if not calcSlopeTangent(Gear, x, y, tx, ty, 255) then
        begin
        DeleteGear(Gear);
        EXIT;
        end;

    // reject shots at too irregular terrain
    if DistanceI(tx,ty) < _10 then
        begin
        DeleteGear(Gear);
        EXIT;
    end;
    
    // making a normal, normalized vector
    s:= _1/DistanceI(tx,ty);
    dx:= -s * ty;
    dy:=  s * tx;

    // make sure the vector is pointing outwards
    if not (Gear^.dX*dx + Gear^.dY*dy).isNegative then
    begin
        dx:= -dx;
        dy:= -dy;
    end;

    Gear^.dX:= dx;
    Gear^.dY:= dy;

    Gear^.DirAngle:= DxDy2Angle(-dy,dx);
    if not Gear^.dX.isNegative then Gear^.DirAngle:= 180-Gear^.DirAngle;
(* 
This is not quite doing what I want, but basically hoping to avoid portals just sitting out in midair
Works ok for right angles, aaaand that's about it.
The opposite approach could be taken, we could determine the angle of the land using sheepluva's code and snap the Angle/DirAngle to it.
tangle:= Gear^.Angle+1024;
if tangle > 2048 then dec(tangle,2048);
tx:= hwRound(Gear^.X+SignAs(AngleSin(tangle), Gear^.dX)*_6);
ty:= hwRound(Gear^.Y-AngleCos(tangle)*_6);
bx:= hwRound(Gear^.X-SignAs(AngleSin(tangle), Gear^.dX)*_6);
by:= hwRound(Gear^.Y+AngleCos(tangle)*_6);
*)



    if ((Gear^.IntersectGear <> nil) and (hwRound(Distance(Gear^.X - Gear^.IntersectGear^.X,Gear^.Y-Gear^.IntersectGear^.Y)) < Gear^.Radius*2)) 
(*or
(((ty and LAND_HEIGHT_MASK) = 0) and ((tx and LAND_WIDTH_MASK) = 0) and ((Land[ty, tx] and $FF00) = 0)) or
(((by and LAND_HEIGHT_MASK) = 0) and ((bx and LAND_WIDTH_MASK) = 0) and ((Land[by, bx] and $FF00) = 0))*)
    then
        begin
        if CurrentHedgehog <> nil then
            if Gear^.IntersectGear = nil then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1
            else if Gear^.Tag = 2 then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 2
            else CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1;
        DeleteGear(Gear)
        end
    else
        begin
        if CurrentHedgehog <> nil then
            if Gear^.Tag = 2 then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1
            else CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 2;
        inc(Gear^.Tag);
        Gear^.doStep:= @doStepPortal;
        if Gear^.IntersectGear <> nil then
            begin
            Gear^.IntersectGear^.IntersectGear:= Gear;
            AllInactive:= false;
            // This jiggles gears, to ensure a portal connection just placed under a gear takes effect.
            iterator:= GearsList;
            while iterator <> nil do
                begin
                if iterator^.Kind <> gtPortal then
                    begin
                    iterator^.Active:= true;
                    if iterator^.dY.QWordValue = _0.QWordValue then iterator^.dY.isNegative:= false;
                    iterator^.State:= iterator^.State or gstMoving;
                    DeleteCI(iterator);
                    inc(iterator^.dY.QWordValue,10);
                    end;
                iterator:= iterator^.NextGear
                end;
            doStepPortal(Gear);
            if Gear^.IntersectGear <> nil then Gear^.IntersectGear^.doStep(Gear^.IntersectGear);
            end
        end
    end
else if (y > cWaterLine + cVisibleWater + Gear^.Radius) or (y < -LAND_WIDTH) or (x > LAND_WIDTH + LAND_WIDTH) or (x < -LAND_WIDTH) then
    begin
    if CurrentHedgehog <> nil then
        if Gear^.IntersectGear = nil then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1
        else if Gear^.Tag = 2 then CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 2
        else CurrentHedgehog^.Ammo^[CurrentHedgehog^.CurSlot, CurrentHedgehog^.CurAmmo].Timer:= 1;
    DeleteGear(Gear);
    end;
end;

procedure doStepPiano(Gear: PGear);
var r0, r1: LongInt;
begin
AllInactive:= false;
if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) and ((CurrentHedgehog^.Gear^.Message and gm_Slot) <> 0) then
    begin
    case CurrentHedgehog^.Gear^.MsgParam of
        0: PlaySound(sndPiano0);
        1: PlaySound(sndPiano1);
        2: PlaySound(sndPiano2);
        3: PlaySound(sndPiano3);
        4: PlaySound(sndPiano4);
        5: PlaySound(sndPiano5);
        6: PlaySound(sndPiano6);
        7: PlaySound(sndPiano7);
        else PlaySound(sndPiano8);
        end;
    CurrentHedgehog^.Gear^.MsgParam:= 0;
    CurrentHedgehog^.Gear^.Message:= CurrentHedgehog^.Gear^.Message and not gm_Slot;
    end;

if ((Gear^.Pos = 3) and ((GameFlags and gfSolidLand) <> 0)) or (Gear^.Pos = 20) then // bounce up to 20 times (3 times on gameflagged solid land) before dropping past landscape
    begin
    Gear^.dY:= Gear^.dY + cGravity * 3;
    Gear^.Y:= Gear^.Y + Gear^.dY;
    CheckGearDrowning(Gear);
    ResumeMusic;
    exit
    end;

doStepFallingGear(Gear);

if (Gear^.State and gstDrowning) <> 0 then
    begin
    if CurrentHedgehog^.Gear <> nil then
        begin
        // Drown the hedgehog.  Could also just delete it, but hey, this gets a caption
        CurrentHedgehog^.Gear^.Active:= true;
        CurrentHedgehog^.Gear^.X:= Gear^.X;
        CurrentHedgehog^.Gear^.Y:=int2hwFloat(cWaterLine+cVisibleWater)+_128;
        CurrentHedgehog^.Unplaced:= false
        end;
    ResumeMusic
    end
else if (Gear^.State and gstCollision) <> 0 then
    begin
    r0:= GetRandom(21);
    r1:= GetRandom(21);
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 80 + r0, EXPLAutoSound);
    doMakeExplosion(hwRound(Gear^.X) - 30 - r0, hwRound(Gear^.Y) + 40, 40 + r1, EXPLAutoSound);
    doMakeExplosion(hwRound(Gear^.X) + 30 + r1, hwRound(Gear^.Y) + 40, 40 + r0, EXPLAutoSound);
    Gear^.dY:= -_1;
    Gear^.Pos:= Gear^.Pos + 1;
    end
else
    Gear^.dY:= Gear^.dY + cGravity * 2; // let it fall faster so itdoesn't take too long for the whole attack
end;


////////////////////////////////////////////////////////////////////////////////
procedure doStepSineGunShotWork(Gear: PGear);
var x, y, rX, rY, t, tmp, initHealth: LongInt;
    oX, oY, ldX, ldY, sdX, sdY, sine, lx, ly, amp: hwFloat;
    justCollided: boolean;
begin
AllInactive:= false;
initHealth:= Gear^.Health;
lX:= Gear^.X;
lY:= Gear^.Y;
ldX:= Gear^.dX;
ldY:= Gear^.dY;
sdy:= _0_5/Distance(Gear^.dX,Gear^.dY);
ldX:= ldX * sdy;
ldY:= ldY * sdy;
sdY:= hwAbs(ldX) + hwAbs(ldY);
sdX:= _1 - hwAbs(ldX/sdY);
sdY:= _1 - hwAbs(ldY/sdY);
if (ldX.isNegative = ldY.isNegative) then sdY:= -sdY;

// initial angle depends on current GameTicks
t:= GameTicks mod 4096;


// used for a work-around detection of area that is within land array, but outside borders
justCollided:= false;

repeat
    lX:= lX + ldX;
    lY:= lY + ldY;
    oX:= Gear^.X;
    oY:= Gear^.Y;
    rX:= hwRound(oX);
    rY:= hwRound(oY);
    tmp:= t mod 4096;
    amp:= _128 * (_1 - hwSqr(int2hwFloat(Gear^.Health)/initHealth));
    sine:= amp * AngleSin(tmp mod 2048);
    sine.isNegative:= (tmp < 2048);
    inc(t,Gear^.Health div 313);
    Gear^.X:= lX + (sine * sdX);
    Gear^.Y:= ly + (sine * sdY);
    Gear^.dX:= Gear^.X - oX;
    Gear^.dY:= Gear^.Y - oY;
    
    x:= hwRound(Gear^.X);
    y:= hwRound(Gear^.Y);
    
    // if borders are on, stop outside land array
    if hasBorder and (((x and LAND_WIDTH_MASK) <> 0) or ((y and LAND_HEIGHT_MASK) <> 0)) then
            begin
            Gear^.Damage:= 0;
            Gear^.Health:= 0;
            end
    else
        begin
        if (rY <= cWaterLine) or (y <= cWaterLine) then
            begin
            if ((y and LAND_HEIGHT_MASK) = 0) and ((x and LAND_WIDTH_MASK) = 0)
                and (Land[y, x] <> 0) then
                    begin
                    if justCollided then
                        begin
                        Gear^.Damage:= 0;
                        Gear^.Health:= 0;
                        end
                    else
                        begin
                        inc(Gear^.Damage,3);
                        justCollided:= true;
                        end;
                    end
                else
                    justCollided:= false;

        // kick nearby hogs, dig tunnel and add some fire
        // if at least 5 collisions occured
        if Gear^.Damage > 0 then
            begin
            DrawExplosion(rX,rY,Gear^.Radius);
            
            // kick nearby hogs
            AmmoShove(Gear, 35, 50);
            
            dec(Gear^.Health, Gear^.Damage);
            Gear^.Damage:= 0;
            
            // add some fire to the tunnel
            if getRandom(6) = 0 then
                AddGear(x - Gear^.Radius + LongInt(getRandom(2 * Gear^.Radius)), y - getRandom(Gear^.Radius + 1), gtFlame, gsttmpFlag, _0, _0, 0);
            end;

        if getRandom(100) = 0 then
            AddGear(x, y, gtSmokeTrace, 0, _0, _0, 0);
        
        end
        // if underwater get additional damage
        else dec(Gear^.Health, 5);
    end;

    dec(Gear^.Health);
    
    // decrease bullet size towards the end
    if (Gear^.Radius > 4) then begin 
        if (Gear^.Health <= (initHealth div 3)) then dec(Gear^.Radius) end
    else if (Gear^.Radius > 3) then begin
        if (Gear^.Health <= (initHealth div 4)) then dec(Gear^.Radius) end
    else if (Gear^.Radius > 2) then begin
        if (Gear^.Health <= (initHealth div 5)) then dec(Gear^.Radius) end
    else if (Gear^.Radius > 1) then begin
        if (Gear^.Health <= (initHealth div 6)) then dec(Gear^.Radius) end;

until (Gear^.Health <= 0);

DeleteGear(Gear);
AfterAttack;
end;

procedure doStepSineGunShot(Gear: PGear);
var HHGear: PGear;
begin
PlaySound(sndSineGun);


// push the shooting Hedgehog back
HHGear:= CurrentHedgehog^.Gear;
Gear^.dX.isNegative:= not Gear^.dX.isNegative;
Gear^.dY.isNegative:= not Gear^.dY.isNegative;
HHGear^.dX:= Gear^.dX;
HHGear^.dY:= Gear^.dY;
AmmoShove(Gear, 0, 80);
Gear^.dX.isNegative:= not Gear^.dX.isNegative;
Gear^.dY.isNegative:= not Gear^.dY.isNegative;

Gear^.doStep:= @doStepSineGunShotWork

end;