hedgewars/HHHandlers.inc
author smxx
Sat, 10 Apr 2010 19:04:39 +0000
changeset 3331 8e05feb871c3
parent 3310 e6e9b811d32f
child 3350 5cd02aafc612
permissions -rw-r--r--
Engine: * Moved "new crate" messages to group AmmoInfo * Show Sudden Death countdown * Fixed Sudden Death round counting (so '15 rounds' will really be 15 rounds, not 16)

(*
 * 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 ChangeAmmo(Gear: PGear);
var slot, i: Longword;
begin
slot:= Gear^.MsgParam;

with PHedgehog(Gear^.Hedgehog)^ do
    begin
    Gear^.Message:= Gear^.Message and not gm_Slot;

    if ((Gear^.State and (gstAttacking or gstAttacked)) <> 0)
        or (MultiShootAttacks > 0)
        or ((Gear^.State and gstHHDriven) = 0) then exit;

    Gear^.Message:= Gear^.Message and not (gm_LJump or gm_HJump);

    if CurSlot = slot then
        begin
        i:= 0;
        repeat
        inc(CurAmmo);
        if (CurAmmo > cMaxSlotAmmoIndex) then
            begin
            CurAmmo:= 0;
            inc(i);
            TryDo(i < 2, 'Engine bug: no ammo in current slot', true)
            end;
        until (Ammo^[slot, CurAmmo].Count > 0) and (Team^.Clan^.TurnNumber > Ammoz[Ammo^[slot, CurAmmo].AmmoType].SkipTurns)
        end else
        begin
        i:= 0;
        // check whether there is ammo in slot
        while (i <= cMaxSlotAmmoIndex)
          and ((Ammo^[slot, i].Count = 0)
               or (Team^.Clan^.TurnNumber <= Ammoz[Ammo^[slot, i].AmmoType].SkipTurns)) do inc(i);

        if i <= cMaxSlotAmmoIndex then
            begin
            CurSlot:= slot;
            CurAmmo:= i
            end
        end
    end
end;

procedure HHSetWeapon(Gear: PGear);
var t: LongInt;
    weap: TAmmoType;
begin
weap:= TAmmoType(Gear^.MsgParam);

if PHedgehog(Gear^.Hedgehog)^.Team^.Clan^.TurnNumber <= Ammoz[weap].SkipTurns then exit; // weapon is not activated yet

Gear^.MsgParam:= Ammoz[weap].Slot;

t:= cMaxSlotAmmoIndex;

Gear^.Message:= Gear^.Message and not gm_Weapon;

with PHedgehog(Gear^.Hedgehog)^ do
    while (Ammo^[CurSlot, CurAmmo].AmmoType <> weap) and (t >= 0) do
        begin
        ChangeAmmo(Gear);
        dec(t)
        end;

ApplyAmmoChanges(PHedgehog(Gear^.Hedgehog)^)
end;

procedure HHSetTimer(Gear: PGear);
begin
Gear^.Message:= Gear^.Message and not gm_Timer;
with PHedgehog(Gear^.Hedgehog)^ do
    if (Ammo^[CurSlot, CurAmmo].Propz and ammoprop_Timerable) <> 0 then
        begin
        Ammo^[CurSlot, CurAmmo].Timer:= 1000 * Gear^.MsgParam;
        with CurrentTeam^ do
            ApplyAmmoChanges(Hedgehogs[CurrHedgehog]);
        end;
end;


procedure Attack(Gear: PGear);
var xx, yy: hwFloat;
    tmpGear: PVisualGear;
begin
bShowFinger:= false;
with Gear^,
     PHedgehog(Gear^.Hedgehog)^ do
     begin
     if ((State and gstHHDriven) <> 0)and
        ((State and (gstAttacked or gstHHChooseTarget)) = 0) and
        (((State and gstMoving) = 0) or
            // Allow attacks while moving on ammo with AltAttack
            ((CurAmmoGear <> nil) and ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0)) or
            ((Ammo^[CurSlot, CurAmmo].Propz and ammoprop_AttackInMove) <> 0)) and
        ((TargetPoint.X <> NoPointX) or ((Ammo^[CurSlot, CurAmmo].Propz and ammoprop_NeedTarget) = 0)) then
        begin
        State:= State or gstAttacking;
        if Power = cMaxPower then Message:= Message and not gm_Attack
        else if (Ammo^[CurSlot, CurAmmo].Propz and ammoprop_Power) = 0 then Message:= Message and not gm_Attack
        else begin
             if Power = 0 then
                begin
                AttackBar:= CurrentTeam^.AttackBar;
                PlaySound(sndThrowPowerUp)
                end;
             inc(Power)
             end;
        if ((Message and gm_Attack) <> 0) then exit;

        if (Ammo^[CurSlot, CurAmmo].Propz and ammoprop_Power) <> 0 then
           begin
           StopSound(sndThrowPowerUp);
           PlaySound(sndThrowRelease);
           end;

        xx:= SignAs(AngleSin(Angle), dX);
        yy:= -AngleCos(Angle);

        if ((Gear^.State and gstHHHJump) <> 0) then xx:= - xx;
        if Ammo^[CurSlot, CurAmmo].AttackVoice <> sndNone then
           PlaySound(Ammo^[CurSlot, CurAmmo].AttackVoice, CurrentTeam^.voicepack);
             case Ammo^[CurSlot, CurAmmo].AmmoType of
                      amGrenade: FollowGear:= AddGear(hwRound(X), hwRound(Y), gtAmmo_Bomb,    0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, Ammo^[CurSlot, CurAmmo].Timer);
                      amMolotov: FollowGear:= AddGear(hwRound(X), hwRound(Y), gtMolotov,      0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0);
                  amClusterBomb: FollowGear:= AddGear(hwRound(X), hwRound(Y), gtClusterBomb,  0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, Ammo^[CurSlot, CurAmmo].Timer);
                      amBazooka: FollowGear:= AddGear(hwRound(X), hwRound(Y), gtAmmo_Grenade, 0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0);
                          amBee: FollowGear:= AddGear(hwRound(X), hwRound(Y), gtBee,          0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0);
                      amShotgun: begin
                                 PlaySound(sndShotgunReload);
                                 CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtShotgunShot,  0, xx * _0_5, yy * _0_5, 0);
                                 end;
                   amPickHammer: CurAmmoGear:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y) + cHHRadius, gtPickHammer, 0, _0, _0, 0);
                         amSkip: ParseCommand('/skip', true);
                         amRope: CurAmmoGear:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtRope, 0, xx, yy, 0);
                         amMine: AddGear(hwRound(X) + hwSign(dX) * 7, hwRound(Y), gtMine, gstWait, SignAs(_0_02, dX), _0, 3000);
                       amDEagle: CurAmmoGear:= AddGear(hwRound(X + xx * cHHRadius), hwRound(Y + yy * cHHRadius), gtDEagleShot, 0, xx * _0_5, yy * _0_5, 0);
                  amSniperRifle: begin
                                 PlaySound(sndSniperReload);
                                 CurAmmoGear:= AddGear(hwRound(X + xx * cHHRadius), hwRound(Y + yy * cHHRadius), gtSniperRifleShot, 0, xx * _0_5, yy * _0_5, 0);
                                 end;
                     amDynamite: AddGear(hwRound(X) + hwSign(dX) * 7, hwRound(Y), gtDynamite, 0, SignAs(_0_03, dX), _0, 5000);
                    amFirePunch: CurAmmoGear:= AddGear(hwRound(X) + hwSign(dX) * 10, hwRound(Y), gtFirePunch, 0, xx, _0, 0);
                         amWhip: begin
                                 CurAmmoGear:= AddGear(hwRound(X) + hwSign(dX) * 10, hwRound(Y), gtWhip, 0, SignAs(_1, dX), - _0_8, 0);
                                 PlaySound(sndWhipCrack)
                                 end;
                  amBaseballBat: begin
                                 CurAmmoGear:= AddGear(hwRound(X) + hwSign(dX) * 10, hwRound(Y), gtShover, gsttmpFlag, xx * _0_5, yy * _0_5, 0);
                                 PlaySound(sndBaseballBat) // TODO: Only play if something is hit?
                                 end;
                    amParachute: CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtParachute, 0, _0, _0, 0);
                    amAirAttack: AddGear(Ammo^[CurSlot, CurAmmo].Pos, 0, gtAirAttack, 0, _0, _0, 0);
                   amMineStrike: AddGear(Ammo^[CurSlot, CurAmmo].Pos, 0, gtAirAttack, 1, _0, _0, 0);
                    amBlowTorch: CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtBlowTorch, 0, SignAs(_0_5, dX), _0, 0);
                       amGirder: CurAmmoGear:= AddGear(0, 0, gtGirder, Ammo^[CurSlot, CurAmmo].Pos, _0, _0, 0);
                     amTeleport: CurAmmoGear:= AddGear(0, 0, gtTeleport, 0, _0, _0, 0);
                       amSwitch: CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtSwitcher, 0, _0, _0, 0);
                       amMortar: begin
                playSound(sndMortar);
                FollowGear:= AddGear(hwRound(X), hwRound(Y), gtMortar,  0, xx*cMaxPower/cPowerDivisor, yy*cMaxPower/cPowerDivisor, 0);
                 end;
                      amRCPlane: begin
                                 CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtRCPlane,  0, xx * cMaxPower / cPowerDivisor / 4, yy * cMaxPower / cPowerDivisor / 4, 0);
                                 CurAmmoGear^.SoundChannel:= LoopSound(sndRCPlane, nil)
                                 end;
                       amKamikaze: CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtKamikaze, 0, xx * _0_5, yy * _0_5, 0);
                         amCake: CurAmmoGear:= AddGear(hwRound(X) + hwSign(dX) * 3, hwRound(Y), gtCake, 0, xx, _0, 0);
                    amSeduction: CurAmmoGear:= AddGear(hwRound(X + xx * cHHRadius * 2), hwRound(Y + yy * cHHRadius * 2), gtSeduction, 0, xx * _0_4, yy * _0_4, 0);
                   amWatermelon: FollowGear:= AddGear(hwRound(X), hwRound(Y), gtWatermelon,  0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, Ammo^[CurSlot, CurAmmo].Timer);
                  amHellishBomb: FollowGear:= AddGear(hwRound(X), hwRound(Y), gtHellishBomb,    0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0);
                       amNapalm: AddGear(Ammo^[CurSlot, CurAmmo].Pos, 0, gtAirAttack, 2, _0, _0, 0);
                        amDrill: FollowGear:= AddGear(hwRound(X), hwRound(Y), gtDrill, 0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0);
                      amBallgun: CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtBallgun,  0, xx * _0_5, yy * _0_5, 0);
                    amJetpack: CurAmmoGear:= AddGear(hwRound(X), hwRound(Y), gtJetpack, 0, _0, _0, 0);
                    amBirdy: begin
                             PlaySound(sndWhistle);
                             CurAmmoGear:= AddGear(hwRound(X), hwRound(Y) - 32, gtBirdy, 0, _0, _0, 0);
                             end;
                      amLowGravity: begin
                                    PlaySound(sndLowGravity);
                                    cGravity:= cMaxWindSpeed / 2
                                    end;
                      amExtraDamage: cDamageModifier:= _1_5;
                      amInvulnerable: Invulnerable:= true;
                      amExtraTime: TurnTimeLeft:= TurnTimeLeft + 30000;
                      amLaserSight: cLaserSighting:= true;
                      amVampiric: cVampiric:= true;
                  end;

        uStats.AmmoUsed(Ammo^[CurSlot, CurAmmo].AmmoType);

        if not (SpeechText = '') then
            begin
            tmpGear:= AddVisualGear(0, 0, vgtSpeechBubble);
            if tmpGear <> nil then
               begin
               tmpGear^.Text:= SpeechText;
               tmpGear^.Hedgehog:= Gear^.Hedgehog;
               tmpGear^.FrameTicks:= SpeechType;
               end;
            SpeechText:= ''
            end;

        Power:= 0;
        if (CurAmmoGear <> nil)
           and (((Ammo^[CurSlot, CurAmmo].Propz) and ammoprop_AltUse) = 0){check for dropping ammo from rope} then
           begin
           CurAmmoGear^.Ammo:= @(Ammo^[CurSlot, CurAmmo]);
           CurAmmoGear^.AmmoType:= CurAmmoGear^.Ammo^.AmmoType;
           Message:= Message or gm_Attack;
           CurAmmoGear^.Message:= Message
           end else begin
           if not CurrentTeam^.ExtDriven and
             ((Ammo^[CurSlot, CurAmmo].Propz and ammoprop_Power) <> 0) then SendIPC('a');
           AfterAttack;
           end
        end else Message:= Message and not gm_Attack;
     end
end;

procedure AfterAttack;
var s: shortstring;
begin
with CurrentHedgehog^.Gear^,
        CurrentHedgehog^ do
    begin
    State:= State and not gstAttacking;
    if ((Ammo^[CurSlot, CurAmmo].Propz) and ammoprop_Effect) = 0 then
        begin
        Inc(MultiShootAttacks);
        
        if (Ammo^[CurSlot, CurAmmo].NumPerTurn >= MultiShootAttacks) then
            begin
            s:= inttostr(Ammo^[CurSlot, CurAmmo].NumPerTurn + 1 - MultiShootAttacks);
            AddCaption(format(trmsg[sidRemaining], s), cWhiteColor, capgrpAmmostate);
            end;
        
        if (Ammo^[CurSlot, CurAmmo].NumPerTurn >= MultiShootAttacks) or
            ((GameFlags and gfMultiWeapon) <> 0) then
            begin
            isInMultiShoot:= true
            end
        else
            begin
            TurnTimeLeft:= Ammoz[Ammo^[CurSlot, CurAmmo].AmmoType].TimeAfterTurn;
            State:= State or gstAttacked;
            OnUsedAmmo(CurrentHedgehog^);
            end;
        end
    else
        begin
        OnUsedAmmo(CurrentHedgehog^);
        ApplyAmmoChanges(CurrentHedgehog^);
        end;
    AttackBar:= 0;
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehogDead(Gear: PGear);
const frametime = 200;
      timertime = frametime * 6;
begin
if PHedgehog(Gear^.Hedgehog)^.Unplaced then exit;
if Gear^.Timer > 1 then
    begin
    AllInactive:= false;
    dec(Gear^.Timer);
    if (Gear^.Timer mod frametime) = 0 then inc(Gear^.Pos)
    end else
if Gear^.Timer = 1 then
    begin
    Gear^.State:= Gear^.State or gstNoDamage;
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 30, EXPLAutoSound);
    AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtGrave, 0, _0, _0, 0)^.Hedgehog:= Gear^.Hedgehog;
    DeleteGear(Gear);
    SetAllToActive
    end else // Gear^.Timer = 0
    begin
    AllInactive:= false;
    Gear^.Z:= cCurrHHZ;
    RemoveGearFromList(Gear);
    InsertGearToList(Gear);
    PlaySound(sndByeBye, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack);
    Gear^.Pos:= 0;
    Gear^.Timer:= timertime
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehogGone(Gear: PGear);
const frametime = 65;
      timertime = frametime * 11;
begin
if PHedgehog(Gear^.Hedgehog)^.Unplaced then exit;
if Gear^.Timer > 1 then
    begin
    AllInactive:= false;
    dec(Gear^.Timer);
    if (Gear^.Timer mod frametime) = 0 then inc(Gear^.Pos)
    end else
if Gear^.Timer = 1 then
    begin
    DeleteGear(Gear);
    SetAllToActive
    end else // Gear^.Timer = 0
    begin
    AllInactive:= false;
    Gear^.Z:= cCurrHHZ;
    RemoveGearFromList(Gear);
    InsertGearToList(Gear);
    PlaySound(sndByeBye, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack);
    PlaySound(sndWarp);
    Gear^.Pos:= 0;
    Gear^.Timer:= timertime
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure PickUp(HH, Gear: PGear);
var s: shortstring;
    a: TAmmoType;
    i: LongInt;
    vga: PVisualGear;
begin
Gear^.Message:= gm_Destroy;
PlaySound(sndShotgunReload);
case Gear^.Pos of
       posCaseUtility,
       posCaseAmmo: begin
                    a:= TAmmoType(Gear^.State);
                    AddAmmo(PHedgehog(HH^.Hedgehog)^, a);
// Possibly needs to check shared clan ammo game flag once added.
// On the other hand, no obvious reason that clan members shouldn't know what ammo another clan member picked up
                    if (not (PHedgehog(HH^.Hedgehog)^.Team^.ExtDriven 
                      or (PHedgehog(HH^.Hedgehog)^.BotLevel > 0)))
                      or (PHedgehog(HH^.Hedgehog)^.Team^.Clan^.ClanIndex = LocalClan)
                      or (GameType = gmtDemo)  then
                        begin
                        s:= trammo[Ammoz[a].NameId] + ' (+' + IntToStr(Ammoz[a].NumberInCase) + ')';
                        AddCaption(s, PHedgehog(HH^.Hedgehog)^.Team^.Clan^.Color, capgrpAmmoinfo);

                        // show ammo icon
                        vga:= AddVisualGear(hwRound(Gear^.X), hwRound(Gear^.Y), vgtAmmo);
                        if vga <> nil then
                            vga^.Frame:= Longword(a);
                        end;

                    end;
     posCaseHealth: begin
                    inc(HH^.Health, Gear^.Health);
                    PHedgehog(HH^.Hedgehog)^.Effects[hePoisoned] := false;
                    str(Gear^.Health, s);
                    s:= '+' + s;
                    AddCaption(s, PHedgehog(HH^.Hedgehog)^.Team^.Clan^.Color, capgrpAmmoinfo);
                    RenderHealth(PHedgehog(HH^.Hedgehog)^);
                    RecountTeamHealth(PHedgehog(HH^.Hedgehog)^.Team);

                    i:= 0;
                    while i < Gear^.Health do
                        begin
                        AddVisualGear(hwRound(HH^.X), hwRound(HH^.Y), vgtHealth);
                        inc(i, 5);
                        end;
                    end;
     end
end;

const StepTicks: LongWord = 0;

procedure HedgehogStep(Gear: PGear);
var PrevdX: LongInt;
begin
if ((Gear^.State and (gstAttacking or gstMoving)) = 0) then
   begin
   if isCursorVisible then
      with PHedgehog(Gear^.Hedgehog)^ do
        with Ammo^[CurSlot, CurAmmo] do
          begin
          if (Gear^.Message and gm_Left  ) <> 0 then
             Pos:= (Pos + Ammoz[AmmoType].PosCount - 1) mod Ammoz[AmmoType].PosCount
          else
          if (Gear^.Message and gm_Right ) <> 0 then
             Pos:= (Pos + 1) mod Ammoz[AmmoType].PosCount
          else exit;
          StepTicks:= 200;
          exit
          end;

    if ((Gear^.Message and gm_Animate) <> 0) then
        begin
        Gear^.Message:= 0;
        Gear^.State:= Gear^.State or gstAnimation;
        Gear^.Tag:= Gear^.MsgParam;
        Gear^.Timer:= 0;
        Gear^.Pos:= 0
        end;

   if ((Gear^.Message and gm_LJump ) <> 0) then
      begin
      Gear^.Message:= Gear^.Message and not gm_LJump;
      DeleteCI(Gear);
      if not TestCollisionYwithGear(Gear, -1) then
         if not TestCollisionXwithXYShift(Gear, _0, -2, hwSign(Gear^.dX)) then Gear^.Y:= Gear^.Y - _2 else
         if not TestCollisionXwithXYShift(Gear, _0, -1, hwSign(Gear^.dX)) then Gear^.Y:= Gear^.Y - _1;
      if not (TestCollisionXwithGear(Gear, hwSign(Gear^.dX))
         or   TestCollisionYwithGear(Gear, -1)) then
         begin
         Gear^.dY:= -_0_15;
         if not cArtillery then Gear^.dX:= SignAs(_0_15, Gear^.dX);
         Gear^.State:= Gear^.State or gstMoving or gstHHJumping;
         PlaySound(sndJump1, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack);
         exit
         end;
      end;

   if ((Gear^.Message and gm_HJump ) <> 0) then
      begin
      DeleteCI(Gear);
      Gear^.Message:= Gear^.Message and not gm_HJump;

      Gear^.dY:= -_0_2;
      SetLittle(Gear^.dX);
      Gear^.State:= Gear^.State or gstMoving or gstHHJumping;
      PlaySound(sndJump3, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack);
      exit
      end;

   PrevdX:= hwSign(Gear^.dX);
   if (Gear^.Message and gm_Left  )<>0 then Gear^.dX:= -cLittle else
   if (Gear^.Message and gm_Right )<>0 then Gear^.dX:=  cLittle else exit;

   if (Gear^.Message and (gm_Left or gm_Right)) <> 0 then
      begin
      StepSoundTimer:= cHHStepTicks;
      end;
   
   StepTicks:= cHHStepTicks;
   if PrevdX <> hwSign(Gear^.dX) then
      begin
      FollowGear:= Gear;
      exit
      end;
   DeleteCI(Gear); // must be after exit!! (see previous line)

   PHedgehog(Gear^.Hedgehog)^.visStepPos:= (PHedgehog(Gear^.Hedgehog)^.visStepPos + 1) and 7;
   if TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then
      begin
      if not (TestCollisionXwithXYShift(Gear, _0, -6, hwSign(Gear^.dX))
         or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1;
      if not (TestCollisionXwithXYShift(Gear, _0, -5, hwSign(Gear^.dX))
         or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1;
      if not (TestCollisionXwithXYShift(Gear, _0, -4, hwSign(Gear^.dX))
         or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1;
      if not (TestCollisionXwithXYShift(Gear, _0, -3, hwSign(Gear^.dX))
         or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1;
      if not (TestCollisionXwithXYShift(Gear, _0, -2, hwSign(Gear^.dX))
         or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1;
      if not (TestCollisionXwithXYShift(Gear, _0, -1, hwSign(Gear^.dX))
         or TestCollisionYwithGear(Gear, -1)) then Gear^.Y:= Gear^.Y - _1;
      end;

   if (not cArtillery) and ((Gear^.Message and gm_Precise) = 0) and (not TestCollisionXwithGear(Gear, hwSign(Gear^.dX))) then
      Gear^.X:= Gear^.X + SignAs(_1, Gear^.dX);

   SetAllHHToActive;

   if not TestCollisionYwithGear(Gear, 1) then
   begin
   Gear^.Y:= Gear^.Y + _1;
   if not TestCollisionYwithGear(Gear, 1) then
   begin
   Gear^.Y:= Gear^.Y + _1;
   if not TestCollisionYwithGear(Gear, 1) then
   begin
   Gear^.Y:= Gear^.Y + _1;
   if not TestCollisionYwithGear(Gear, 1) then
   begin
   Gear^.Y:= Gear^.Y + _1;
   if not TestCollisionYwithGear(Gear, 1) then
   begin
   Gear^.Y:= Gear^.Y + _1;
   if not TestCollisionYwithGear(Gear, 1) then
   begin
   Gear^.Y:= Gear^.Y + _1;
   if not TestCollisionYwithGear(Gear, 1) then
      begin
      Gear^.Y:= Gear^.Y - _6;
      Gear^.dY:= _0;
      Gear^.State:= Gear^.State or gstMoving;
      exit
      end;
   end
   end
   end
   end
   end
   end;
   AddGearCI(Gear)
   end
end;

procedure HedgehogChAngle(Gear: PGear);
var da: LongWord;
begin
with PHedgehog(Gear^.Hedgehog)^ do
    if (Ammo^[CurSlot, CurAmmo].AmmoType = amRope)
    and ((Gear^.State and (gstMoving or gstHHJumping)) = gstMoving) then da:= 2 else da:= 1;

if (((Gear^.Message and gm_Precise) = 0) or ((GameTicks mod 5) = 1)) then
    if ((Gear^.Message and gm_Up) <> 0) and (Gear^.Angle >= CurMinAngle + da) then dec(Gear^.Angle, da)
    else
    if ((Gear^.Message and gm_Down) <> 0) and (Gear^.Angle + da <= CurMaxAngle) then inc(Gear^.Angle, da)
end;

procedure doStepHedgehog(Gear: PGear); forward;
////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehogMoving(Gear: PGear);
var isFalling: boolean;
begin
if PHedgehog(Gear^.Hedgehog)^.Unplaced then
   begin
   Gear^.dY:= _0;
   Gear^.dX:= _0;
   Gear^.State:= Gear^.State and not gstMoving;
   exit
   end;
isFalling:= (Gear^.dY.isNegative) or not TestCollisionYKick(Gear, 1);
if isFalling then
   begin
   if (Gear^.dY.isNegative) and TestCollisionYKick(Gear, -1) then Gear^.dY:= _0;
   Gear^.State:= Gear^.State or gstMoving;
   Gear^.dY:= Gear^.dY + cGravity
   end else
   begin
   if ((hwAbs(Gear^.dX) + hwAbs(Gear^.dY)) < _0_55)
      and ((Gear^.State and gstHHJumping) <> 0) then SetLittle(Gear^.dX);

   if not Gear^.dY.isNegative then
      begin
      CheckHHDamage(Gear);

      if ((Gear^.State and gstHHHJump) <> 0) and (not cArtillery) and
         (Gear^.dX.QWordValue < _0_02.QWordValue) then Gear^.dX.isNegative:= not Gear^.dX.isNegative; // landing after high jump

      Gear^.State:= Gear^.State and not (gstHHJumping or gstHHHJump);
      Gear^.dY:= _0;
      end else Gear^.dY:= Gear^.dY + cGravity;

   if ((Gear^.State and gstMoving) <> 0) then Gear^.dX:= Gear^.dX * Gear^.Friction
   end;

if (Gear^.State <> 0) then DeleteCI(Gear);

if (Gear^.State and gstMoving) <> 0 then
   if TestCollisionXKick(Gear, hwSign(Gear^.dX)) then
      if not isFalling then
         if hwAbs(Gear^.dX) > _0_01 then
            if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -1, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_96; Gear^.Y:= Gear^.Y - _1 end else
            if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -2, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_93; Gear^.Y:= Gear^.Y - _2 end else
            if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -3, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_9 ; Gear^.Y:= Gear^.Y - _3 end else
            if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -4, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_87; Gear^.Y:= Gear^.Y - _4 end else
            if not TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -5, hwSign(Gear^.dX)) then begin Gear^.X:= Gear^.X + Gear^.dX; Gear^.dX:= Gear^.dX * _0_84; Gear^.Y:= Gear^.Y - _5 end else
            if hwAbs(Gear^.dX) > _0_02 then Gear^.dX:= -Gear^.Elasticity * Gear^.dX
                                   else begin
                                        Gear^.State:= Gear^.State and not gstMoving;
                                        SetLittle(Gear^.dX)
                                        end
            else begin
                 Gear^.State:= Gear^.State and not gstMoving;
                 SetLittle(Gear^.dX)
                 end
         else if (hwAbs(Gear^.dX) > cLittle)
                and ((Gear^.State and gstHHJumping) = 0)
                then Gear^.dX:= -Gear^.Elasticity * Gear^.dX
                else SetLittle(Gear^.dX);

if (not isFalling) and
   (hwAbs(Gear^.dX) + hwAbs(Gear^.dY) < _0_03) then
   begin
   Gear^.State:= Gear^.State and not gstWinner;
   Gear^.State:= Gear^.State and not gstMoving;
   SetLittle(Gear^.dX);
   Gear^.dY:= _0
   end else Gear^.State:= Gear^.State or gstMoving;

if (Gear^.State and gstMoving) <> 0 then
   begin
   Gear^.State:= Gear^.State and not gstAnimation;
// ARTILLERY but not being moved by explosions
   Gear^.X:= Gear^.X + Gear^.dX;
   Gear^.Y:= Gear^.Y + Gear^.dY;
   if (not Gear^.dY.isNegative) and
      (not TestCollisionYKick(Gear, 1)) and
       TestCollisionYwithXYShift(Gear, 0, 1, 1) then
      begin
      CheckHHDamage(Gear);
      Gear^.dY:= _0;
      Gear^.Y:= Gear^.Y + _1
      end;
   CheckGearDrowning(Gear);
   if (Gear^.State and gstDrowning) <> 0 then isCursorVisible:= false
   end;

if (hwAbs(Gear^.dY) > _0) and (Gear^.FlightTime > 0) and ((GameFlags and gfLowGravity) = 0) then
    begin
    inc(Gear^.FlightTime, 1);
    if Gear^.FlightTime = 3000 then
        begin
        AddCaption(GetEventString(eidHomerun), cWhiteColor, capgrpMessage);
        PlaySound(sndHomerun)
        end;
    end
else
    begin
    Gear^.FlightTime:= 0;
    end;

end;

procedure doStepHedgehogDriven(Gear: PGear);
var t: PGear;
    wasJumping: boolean;
begin
if not isInMultiShoot then
   AllInactive:= false
else
   Gear^.Message:= 0;

if (TurnTimeLeft = 0) or (Gear^.Damage > 0) then
    begin
    TurnTimeLeft:= 0;
    isCursorVisible:= false;
    Gear^.State:= Gear^.State and not (gstHHDriven or gstAnimation or gstAttacking);
    AttackBar:= 0;
    if Gear^.Damage > 0 then
        Gear^.State:= Gear^.State and not (gstHHJumping or gstHHHJump);
    exit
    end;

if (Gear^.State and gstAnimation) <> 0 then
    begin
    Gear^.Message:= 0;
    if (Gear^.Pos = Wavez[TWave(Gear^.Tag)].VoiceDelay) and (Gear^.Timer = 0) then PlaySound(Wavez[TWave(Gear^.Tag)].Voice, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack);
    inc(Gear^.Timer);
    if Gear^.Timer = Wavez[TWave(Gear^.Tag)].Interval then
        begin
        Gear^.Timer:= 0;
        inc(Gear^.Pos);
        if Gear^.Pos = Wavez[TWave(Gear^.Tag)].FramesCount then
            Gear^.State:= Gear^.State and not gstAnimation
        end;
    exit
    end;

if ((Gear^.State and gstMoving) <> 0)
    or (StepTicks = cHHStepTicks)
    or (CurAmmoGear <> nil) then // we are moving
    begin
    with PHedgehog(Gear^.Hedgehog)^ do
        if (CurAmmoGear = nil)
        and (Gear^.dY > _0_39)
        and (Ammo^[CurSlot, CurAmmo].AmmoType = amParachute) then Gear^.Message:= Gear^.Message or gm_Attack;
    // check for case with ammo
    t:= CheckGearNear(Gear, gtCase, 36, 36);
    if t <> nil then
        PickUp(Gear, t)
    end;

if (CurAmmoGear = nil) then
    if (((Gear^.Message and gm_Attack) <> 0)
        or ((Gear^.State and gstAttacking) <> 0)) then
        Attack(Gear) // should be before others to avoid desync with '/put' msg and changing weapon msgs
    else
else with PHedgehog(Gear^.Hedgehog)^ do
     if ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0)
        and ((Gear^.Message and gm_LJump) <> 0)
        and (((Ammo^[CurSlot, CurAmmo].Propz) and ammoprop_AltUse) <> 0) then
        begin
        Gear^.Message:= Gear^.Message and not gm_LJump;
        Attack(Gear)
        end;

if (CurAmmoGear = nil)
    or ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0) then
    begin
    if ((Gear^.Message and gm_Slot) <> 0) then
        begin
        ChangeAmmo(Gear);
        ApplyAmmoChanges(PHedgehog(Gear^.Hedgehog)^)
        end;

    if ((Gear^.Message and gm_Weapon) <> 0) then HHSetWeapon(Gear);

    if ((Gear^.Message and gm_Timer) <> 0) then HHSetTimer(Gear);
    end;

if CurAmmoGear <> nil then
   begin
   CurAmmoGear^.Message:= Gear^.Message;
   exit
   end;

if not isInMultiShoot then
   HedgehogChAngle(Gear);

if (Gear^.State and gstMoving) <> 0 then
    begin
    wasJumping:= ((Gear^.State and gstHHJumping) <> 0);

    if ((Gear^.Message and gm_HJump) <> 0) and
        wasJumping and
        ((Gear^.State and gstHHHJump) = 0) then
        if (not (hwAbs(Gear^.dX) > cLittle)) and (Gear^.dY < -_0_02) then
            begin
            Gear^.State:= Gear^.State or gstHHHJump;
            Gear^.dY:= -_0_25;
            if not cArtillery then Gear^.dX:= -SignAs(_0_02, Gear^.dX);
            PlaySound(sndJump2, PHedgehog(Gear^.Hedgehog)^.Team^.voicepack)
            end;

    Gear^.Message:= Gear^.Message and not (gm_LJump or gm_HJump);

    if (not cArtillery) and wasJumping and
        TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) then SetLittle(Gear^.dX);

    doStepHedgehogMoving(Gear);

    if ((Gear^.State and (gstMoving or gstDrowning)) = 0) then
        begin
        AddGearCI(Gear);
        if wasJumping then
            StepTicks:= 410
        else
            StepTicks:= 95
        end;
    exit
    end;

    if not isInMultiShoot then
        begin
        if StepTicks > 0 then dec(StepTicks);
        if (StepTicks = 0) then HedgehogStep(Gear)
        end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehogFree(Gear: PGear);
var prevState: Longword;
begin
prevState:= Gear^.State;

doStepHedgehogMoving(Gear);

if (Gear^.State and (gstMoving or gstDrowning)) <> 0 then
    begin
    if Gear^.Damage > 0 then CalcRotationDirAngle(Gear);
    AllInactive:= false;
    exit
    end;

if (Gear^.Health = 0) then
    begin
    if PrvInactive then
        begin
        Gear^.Timer:= 0;
        FollowGear:= Gear;
        PrvInactive:= false;
        AllInactive:= false;

        if not PHedgehog(Gear^.Hedgehog)^.Team^.hasGone then
            begin
            Gear^.State:= Gear^.State or gstHHDeath;
            Gear^.doStep:= @doStepHedgehogDead;
            // Death message
            AddCaption(Format(GetEventString(eidDied), PHedgehog(Gear^.Hedgehog)^.Name), cWhiteColor, capgrpMessage);
            end
        else
            begin
            Gear^.State:= Gear^.State or gstHHGone;
            Gear^.doStep:= @doStepHedgehogGone;
            // Gone message
            AddCaption(Format(GetEventString(eidGone), PHedgehog(Gear^.Hedgehog)^.Name), cWhiteColor, capgrpMessage);
            end
        end;
    exit
    end;

if ((Gear^.State and gstWait) = 0) and
    (prevState <> Gear^.State) then
    begin
    Gear^.State:= gstWait;
    Gear^.Timer:= 150
    end else
    begin
    if Gear^.Timer = 0 then
        begin
        Gear^.State:= 0;
        Gear^.Active:= false;
        AddGearCI(Gear);
        exit
        end else dec(Gear^.Timer)
    end;

AllInactive:= false
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehog(Gear: PGear);
begin
if (Gear^.Message and gm_Destroy) <> 0 then
    begin
    DeleteGear(Gear);
    exit
    end;

if (Gear^.State and gstHHDriven) = 0 then
    doStepHedgehogFree(Gear)
else
    begin
    with PHedgehog(Gear^.Hedgehog)^ do
        if Team^.hasGone then TeamGoneEffect(Team^);
    doStepHedgehogDriven(Gear)
    end;
end;