hedgewars/uGearsHedgehog.pas
author alfadur
Fri, 26 Jun 2020 17:40:22 +0300
changeset 15661 afeffdb4a712
parent 15649 ea189ee9394f
child 15672 85d2afe34116
permissions -rw-r--r--
add sentry 🤖 stub

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2015 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *)

{$INCLUDE "options.inc"}

unit uGearsHedgehog;
interface
uses uTypes, uGearsHandlersMess;

procedure doStepHedgehog(Gear: PGear);
procedure AfterAttack;
procedure HedgehogStep(Gear: PGear);
procedure doStepHedgehogMoving(Gear: PGear);
procedure HedgehogChAngle(HHGear: PGear);
procedure PickUp(HH, Gear: PGear);
procedure AddPickup(HH: THedgehog; ammo: TAmmoType; cnt, X, Y: LongWord);
procedure CheckIce(Gear: PGear); inline;
procedure PlayTaunt(taunt: Longword);
function HHGetTimer(Gear: PGear): LongWord;
function HHGetTimerMsg(Gear: PGear): LongWord;
function HHGetBounciness(Gear: PGear): LongWord;
function HHGetBouncinessMsg(Gear: PGear): LongWord;

implementation
uses uConsts, uVariables, uFloat, uAmmos, uSound, uCaptions,
    uCommands, uLocale, uUtils, uStats, uIO, uScript,
    uGearsList, uCollisions, uRandom, uStore, uTeams,
    uGearsUtils, uVisualGearsList, uChat;

var GHStepTicks: LongWord = 0;

procedure AFKSkip;
var
    t: byte;
begin
    t:= 0;
    while (TeamsArray[t] <> CurrentTeam) do inc(t);

    AddChatString(#2 + Format(shortstring(trmsg[sidAutoSkip]), CurrentTeam^.TeamName));

    ParseCommand('/skip', true)
end;

// Shouldn't more of this ammo switching stuff be moved to uAmmos ?
function ChangeAmmo(HHGear: PGear): boolean;
var slot, i: Longword;
    ammoidx: LongInt;
    prevAmmo: TAmmoType;
begin
ChangeAmmo:= false;
slot:= HHGear^.MsgParam;

with HHGear^.Hedgehog^ do
    begin
    HHGear^.Message:= HHGear^.Message and (not gmSlot);
    prevAmmo:= CurAmmoType;
    ammoidx:= 0;
    if (((HHGear^.State and (gstAttacking or gstAttacked)) <> 0) and (GameFlags and gfInfAttack = 0))
    or ((HHGear^.State and gstHHDriven) = 0) then
        exit;
    ChangeAmmo:= true;

    while (ammoidx < cMaxSlotAmmoIndex) and (Ammo^[slot, ammoidx].AmmoType <> CurAmmoType) do
        inc(ammoidx);

    if (MultiShootAttacks > 0) then
        begin
        if (Effects[heArtillery] = 2) then
            Effects[heArtillery]:= 0;
        if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NoRoundEnd) = 0 then
            begin
            MultiShootAttacks:= Ammoz[CurAmmoType].Ammo.NumPerTurn;
            AfterAttack
            end
        else OnUsedAmmo(HHGear^.Hedgehog^)
        end;

    MultiShootAttacks:= 0;
    HHGear^.Message:= HHGear^.Message and (not (gmLJump or gmHJump));

    if Ammoz[CurAmmoType].Slot = slot then
        begin
        i:= 0;
        repeat
        inc(ammoidx);
        if (ammoidx > cMaxSlotAmmoIndex) then
            begin
            inc(i);
            CurAmmoType:= amNothing;
            ammoidx:= -1;
            //TryDo(i < 2, 'Engine bug: no ammo in current slot', true)
            end;
        until (i = 1) or ((Ammo^[slot, ammoidx].Count > 0)
        and (Team^.Clan^.TurnNumber > Ammoz[Ammo^[slot, ammoidx].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
            ammoidx:= i
        else ammoidx:= -1
        end;
        if ammoidx >= 0 then
            CurAmmoType:= Ammo^[slot, ammoidx].AmmoType;
    // Try again in the next slot
    if (CurAmmoType = prevAmmo) and (slot < cMaxSlotIndex) then
        begin
        inc(slot);
        HHGear^.MsgParam:= slot;
        ChangeAmmo(HHGear)
        end
    end
end;

procedure HHSetWeapon(HHGear: PGear);
var t: LongInt;
    weap: TAmmoType;
    Hedgehog: PHedgehog;
    s: boolean;
    prevState, newState: LongWord;
begin
s:= false;

weap:= TAmmoType(HHGear^.MsgParam);
Hedgehog:= HHGear^.Hedgehog;

HHGear^.Message:= HHGear^.Message and (not gmWeapon);

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

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

t:= cMaxSlotAmmoIndex;

prevState:= HHGear^.State;
newState:= prevState;
with Hedgehog^ do
    while (CurAmmoType <> weap) and (t >= 0) do
        begin
        s:= ChangeAmmo(HHGear);
        if HHGear^.State <> prevState then // so we can keep gstAttacked out of consideration when looping
            newState:= HHGear^.State;
        HHGear^.State:= prevState;
        dec(t)
        end;
HHGear^.State:= newState;

if s then
    ApplyAmmoChanges(HHGear^.Hedgehog^)
end;

procedure HHSetTimer(Gear: PGear);
var CurWeapon: PAmmo;
    color: LongWord;
begin
Gear^.Message:= Gear^.Message and (not gmTimer);
CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
with Gear^.Hedgehog^ do
    if (((Gear^.State and gstAttacked) <> 0) and (GameFlags and gfInfAttack = 0))
    or ((Gear^.State and gstHHDriven) = 0) then
        exit;
    if ((Gear^.Message and gmPrecise) <> 0) and ((CurWeapon^.Propz and ammoprop_SetBounce) <> 0) then
        begin
        color:= Gear^.Hedgehog^.Team^.Clan^.Color;

        case Gear^.MsgParam of
            1: begin
               AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce1]), color, capgrpAmmostate);
               end;
            2: begin
               AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce2]), color, capgrpAmmostate);
               end;
            3: begin
               AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce3]), color, capgrpAmmostate);
               end;
            4: begin
               AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce4]), color, capgrpAmmostate);
               end;
            5: begin
               AddCaption(FormatA(trmsg[sidBounce], trmsg[sidBounce5]), color, capgrpAmmostate);
               end
            end;
        CurWeapon^.Bounciness:= bouncinessLevels[Gear^.MsgParam - 1];
        end
    else if (CurWeapon^.Propz and ammoprop_Timerable) <> 0 then
        begin
        CurWeapon^.Timer:= 1000 * Gear^.MsgParam;
        with CurrentTeam^ do
            ApplyAmmoChanges(Hedgehogs[CurrHedgehog]);
        end;
end;

// Return timer (in ticks) of hogs current ammo or MSGPARAM_INVALID
// if not timerable
function HHGetTimer(Gear: PGear): LongWord;
var CurWeapon: PAmmo;
begin
CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
with Gear^.Hedgehog^ do
    if ((CurWeapon^.Propz and ammoprop_Timerable) <> 0) then
        HHGetTimer:= CurWeapon^.Timer
    else
        HHGetTimer:= MSGPARAM_INVALID;
end;

// Returns timer as a corresponding msgParam for /timer command
function HHGetTimerMsg(Gear: PGear): LongWord;
var timer: LongInt;
begin
timer:= HHGetTimer(Gear);
if timer > -1 then
    HHGetTimerMsg:= timer div 1000
else
    HHGetTimerMsg:= MSGPARAM_INVALID
end;

// Returns the selected bounciness value for the hog gear's current ammo
// or MSGPARAM_INVALID if current ammo has no settable bounciness
function HHGetBounciness(Gear: PGear): LongWord;
var CurWeapon: PAmmo;
begin
CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
with Gear^.Hedgehog^ do
    if ((CurWeapon^.Propz and ammoprop_SetBounce) <> 0) then
        HHGetBounciness:= CurWeapon^.Bounciness
    else
        HHGetBounciness:= MSGPARAM_INVALID
end;

// Returns bounciness as a corresponding msgParam for /timer command
function HHGetBouncinessMsg(Gear: PGear): LongWord;
var bounciness, i: LongInt;
begin
    bounciness:= HHGetBounciness(Gear);
    if bounciness > -1 then
        for i:=0 to High(bouncinessLevels) do
            if bounciness = bouncinessLevels[i] then
                begin
                HHGetBouncinessMsg:= i+1;
                exit
                end;
    HHGetBouncinessMsg:= MSGPARAM_INVALID
end;

procedure Attack(Gear: PGear);
var xx, yy, newDx, newDy, lx, ly: hwFloat;
    speech: PVisualGear;
    newGear:  PGear;
    CurWeapon: PAmmo;
    usedAmmoType: TAmmoType;
    altUse, faceLeft: boolean;
    elastic: hwFloat;
begin
newGear:= nil;
bShowFinger:= false;
CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
with Gear^,
    Gear^.Hedgehog^ do
        begin
        usedAmmoType:= CurAmmoType;
        if ((State and gstHHDriven) <> 0) and ((State and (gstAttacked or gstChooseTarget)) = 0) and (((State and gstMoving) = 0)
        or (Power > 0)
        or (CurAmmoType = amTeleport)
        or
        // Allow attacks while moving on ammo with AltAttack
        ((CurAmmoGear <> nil) and ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0))
        or ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AttackInMove) <> 0))
        and ((TargetPoint.X <> NoPointX) or ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NeedTarget) = 0)) then
            begin
            State:= State or gstAttacking;
            if (Power = cMaxPower) or ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_Power) = 0) then
                Message:= Message and (not gmAttack)
            else
                begin
                if Power = 0 then
                    begin
                    AttackBar:= CurrentTeam^.AttackBar;
                    PlaySound(sndThrowPowerUp)
                    end;
                inc(Power)
                end;
            if ((Message and gmAttack) <> 0) then
                exit;

            if (Ammoz[CurAmmoType].Ammo.Propz and ammoprop_Power) <> 0 then
                begin
                StopSound(sndThrowPowerUp);
                PlaySound(sndThrowRelease);
                end;

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

            lx:= X + int2hwfloat(round(GetLaunchX(CurAmmoType, hwSign(dX), Angle)));
            ly:= Y + int2hwfloat(round(GetLaunchY(CurAmmoType, Angle)));

            if ((Gear^.State and gstHHHJump) <> 0) and (Effects[heArtillery] = 0) then
                xx:= - xx;
            if Ammoz[CurAmmoType].Ammo.AttackVoice <> sndNone then
                AddVoice(Ammoz[CurAmmoType].Ammo.AttackVoice, CurrentTeam^.voicepack);

// Initiating alt attack
            if  (CurAmmoGear <> nil)
            and ((Ammoz[CurAmmoGear^.AmmoType].Ammo.Propz and ammoprop_AltAttack) <> 0)
            and ((Gear^.Message and gmLJump) <> 0)
            and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) then
                begin
                if (CurAmmoGear^.AmmoType = amJetpack) and (Gear^.Message and gmPrecise <> 0) then
                    begin
                    newDx:= xx*cMaxPower/cPowerDivisor;
                    newDy:= yy*cMaxPower/cPowerDivisor
                    end
                else
                    begin
                    newDx:= dX;
                    newDy:= dY
                    end;
                altUse:= true
                end
            else
                begin
                newDx:= xx*Power/cPowerDivisor;
                newDy:= yy*Power/cPowerDivisor;
                altUse:= false
                end;

            case CurAmmoType of
                      amGrenade: newGear:= AddGear(hwRound(lx), hwRound(ly), gtGrenade,         0, newDx, newDy, CurWeapon^.Timer);
                      amAirMine: newGear:= AddGear(hwRound(lx), hwRound(ly), gtAirMine,         0, newDx, newDy, 0);
                      amMolotov: newGear:= AddGear(hwRound(lx), hwRound(ly), gtMolotov,      0, newDx, newDy, 0);
                  amClusterBomb: newGear:= AddGear(hwRound(lx), hwRound(ly), gtClusterBomb,  0, newDx, newDy, CurWeapon^.Timer);
                      amGasBomb: newGear:= AddGear(hwRound(lx), hwRound(ly), gtGasBomb,      0, newDx, newDy, CurWeapon^.Timer);
                      amBazooka: newGear:= AddGear(hwRound(lx), hwRound(ly), gtShell,        0, newDx, newDy, 0);
                     amSnowball: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSnowball,     0, newDx, newDy, 0);
                          amBee: newGear:= AddGear(hwRound(lx), hwRound(ly), gtBee,          0, newDx, newDy, 0);
                      amShotgun: begin
                                 PlaySound(sndShotgunReload);
                                 newGear:= AddGear(hwRound(lx), hwRound(ly), gtShotgunShot,  0, xx * _0_5, yy * _0_5, 0);
                                 end;
                   amPickHammer: newGear:= AddGear(hwRound(lx), hwRound(ly) + cHHRadius, gtPickHammer, 0, _0, _0, 0);
                         amSkip: ParseCommand('/skip', true);
                         amRope: newGear:= AddGear(hwRound(lx), hwRound(ly), gtRope, 0, xx, yy, 0);
                         amMine: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtMine, gstWait, SignAs(_0_02, dX), _0, 0);
                        amSMine: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSMine,    0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0);
                        amKnife: begin
                                 newGear:= AddGear(hwRound(lx), hwRound(ly), gtKnife,    0, xx*Power/cPowerDivisor, yy*Power/cPowerDivisor, 0);
                                 newGear^.State:= newGear^.State or gstMoving;
                                 newGear^.Radius:= 4 // temporarily shrink so it doesn't instantly embed in the ground
                                 end;
                       amDEagle: newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtDEagleShot, 0, xx * _0_5, yy * _0_5, 0);
                      amMinigun: begin
                                 PlaySound(sndMinigun);
                                 newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtMinigun, 0, xx * _0_5, yy * _0_5, 0);
                                 end;
                      amSineGun: newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtSineGunShot, 0, xx * _0_5, yy * _0_5, 0);
                    amPortalGun: begin
                                 newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtPortal, 0, xx * _0_6, yy * _0_6,
                                 // set selected color
                                 CurWeapon^.Pos);
                                 end;
                  amSniperRifle: begin
                                 PlaySound(sndSniperReload);
                                 newGear:= AddGear(hwRound(lx + xx * cHHRadius), hwRound(ly + yy * cHHRadius), gtSniperRifleShot, 0, xx * _0_5, yy * _0_5, 0);
                                 end;
                     amDynamite: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtDynamite, 0, SignAs(_0_03, dX), _0, 5000);
                      amCreeper: begin
                                 // TODO: Implement proper creeper spawning code. This is still the old rubber duck code.

                                 // Does it spawn inside water?
                                 if (LeftX > hwRound(Gear^.X) - Gear^.Karma) or (RightX < hwRound(Gear^.X) + Gear^.Karma) or (cWaterLine < hwRound(Gear^.Y) + Gear^.Karma) then
                                     PlaySound(sndDroplet2)
                                 else
                                     // spawned in air, normal drop sound
                                     PlaySound(sndCreeperDrop);
                                 newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtCreeper, 0, SignAs(_0_03, dX), _0, 0);
                                 if not ((not dX.isNegative) xor ((State and gstHHHJump) <> 0)) then
                                     newGear^.Tag:= -1
                                 else
                                     newGear^.Tag:= 1;
                                 end;
                    amSentry: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 7, hwRound(ly), gtSentry, 0, SignAs(_0_03, dX), _0, 0);
                    amFirePunch: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtFirePunch, 0, xx, _0, 0);
                         amWhip: begin
                                 newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtWhip, 0, SignAs(_1, dX), - _0_8, 0);
                                 PlaySound(sndWhipCrack)
                                 end;
                       amHammer: begin
                                 newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtHammer, 0, SignAs(_1, dX), - _0_8, 0);
                                 PlaySound(sndWhack)
                                 end;
                  amBaseballBat: begin
                                 newGear:= AddGear(hwRound(lx) + hwSign(dX) * 10, hwRound(ly), gtShover, gsttmpFlag, xx * _0_5, yy * _0_5, 0);
                                 PlaySound(sndBaseballBat) // TODO: Only play if something is hit?
                                 end;
                    amParachute: begin
                                 faceLeft:= IsHogFacingLeft(Gear);
                                 newGear:= AddGear(hwRound(lx), hwRound(ly), gtParachute, 0, _0, _0, 0);
                                 if faceLeft then
                                     newGear^.Tag:= -1;
                                 PlaySound(sndParachute)
                                 end;
                    // we save CurWeapon^.Pos (in this case: cursor direction) by using it as (otherwise irrelevant) X value of the new gear.
                    amAirAttack: newGear:= AddGear(CurWeapon^.Pos, 0, gtAirAttack, 0, _0, _0, 0);
                   amMineStrike: newGear:= AddGear(CurWeapon^.Pos, 0, gtAirAttack, 1, _0, _0, 0);
                  amDrillStrike: newGear:= AddGear(CurWeapon^.Pos, 0, gtAirAttack, 3, _0, _0, CurWeapon^.Timer);
                       amNapalm: newGear:= AddGear(CurWeapon^.Pos, 0, gtAirAttack, 2, _0, _0, 0);
                    amBlowTorch: newGear:= AddGear(hwRound(lx), hwRound(ly), gtBlowTorch, 0, SignAs(_0_5, dX), _0, 0);
                       amGirder: newGear:= AddGear(0, 0, gtGirder, CurWeapon^.Pos, _0, _0, 0);
                       amRubber: begin
                                 newGear:= AddGear(0, 0, gtGirder, CurWeapon^.Pos, _0, _0, 0);
                                 newGear^.AmmoType:= amRubber
                                 end;
                     amTeleport: newGear:= AddGear(CurWeapon^.Pos, 0, gtTeleport, 0, _0, _0, 0);
                       amSwitch: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSwitcher, 0, _0, _0, 0);
                       amMortar: begin
                                 playSound(sndMortar);
                                 newGear:= AddGear(hwRound(lx), hwRound(ly), gtMortar,  0, xx*cMaxPower/cPowerDivisor, yy*cMaxPower/cPowerDivisor, 0);
                                 end;
                      amRCPlane: begin
                                 newGear:= AddGear(hwRound(lx), hwRound(ly), gtRCPlane,  0, xx * cMaxPower / cPowerDivisor / 4, yy * cMaxPower / cPowerDivisor / 4, 0);
                                 newGear^.SoundChannel:= LoopSound(sndRCPlane)
                                 end;
                     amKamikaze: newGear:= AddGear(hwRound(lx), hwRound(ly), gtKamikaze, 0, xx * _0_5, yy * _0_5, 0);
                         amCake: newGear:= AddGear(hwRound(lx) + hwSign(dX) * 3, hwRound(ly), gtCake, 0, SignAs(cLittle, xx), _0, 0);
                    amSeduction: newGear:= AddGear(hwRound(lx), hwRound(ly), gtSeduction, 0, _0, _0, 0);
                   amWatermelon: newGear:= AddGear(hwRound(lx), hwRound(ly), gtWatermelon,  0, newDx, newDy, CurWeapon^.Timer);
                  amHellishBomb: newGear:= AddGear(hwRound(lx), hwRound(ly), gtHellishBomb,    0, newDx, newDy, 0);
                        amDrill: newGear:= AddGear(hwRound(lx), hwRound(ly), gtDrill, 0, newDx, newDy, 0);
                      amBallgun: newGear:= AddGear(hwRound(X), hwRound(Y), gtBallgun,  0, xx * _0_5, yy * _0_5, 0);
                      amJetpack: newGear:= AddGear(hwRound(lx), hwRound(ly), gtJetpack, 0, _0, _0, 0);
                        amBirdy: begin
                             PlaySound(sndWhistle);
                             newGear:= AddGear(hwRound(lx), hwRound(ly) - 32, gtBirdy, 0, _0, _0, 0);
                             end;
                   amLowGravity: begin
                                 PlaySound(sndLowGravity);
                                 cGravity:= cMaxWindSpeed;
                                 cGravityf:= 0.00025;
                                 cLowGravity := true
                                 end;
                  amExtraDamage: begin
                                 PlaySound(sndExtraDamage);
                                 cDamageModifier:= _1_5
                                 end;
                 amInvulnerable: begin
                                 PlaySound(sndInvulnerable);
                                 Effects[heInvulnerable]:= 1
                                 end;
                    amExtraTime: begin
                                 PlaySound(sndExtraTime);
                                 if TurnTimeLeft <= (cMaxTurnTime - ExtraTime) then
                                     TurnTimeLeft:= TurnTimeLeft + ExtraTime
                                 else
                                     TurnTimeLeft:= cMaxTurnTime;
                                 end;
                   amLaserSight: begin
                                 PlaySound(sndLaserSight);
                                 cLaserSighting:= true
                                 end;
                     amVampiric: begin
                                 PlaySoundV(sndOw1, Team^.voicepack);
                                 cVampiric:= true;
                                 end;
                        amPiano: newGear:= AddGear(TargetPoint.X, -1024, gtPiano, 0, _0, _0, 0);
                 amFlamethrower: newGear:= AddGear(hwRound(X), hwRound(Y), gtFlamethrower,  0, xx * _0_5, yy * _0_5, 0);
                      amLandGun: newGear:= AddGear(hwRound(X), hwRound(Y), gtLandGun,  0, xx * _0_5, yy * _0_5, 0);
                  amResurrector: begin
                                 newGear:= AddGear(hwRound(lx), hwRound(ly), gtResurrector, 0, _0, _0, 0);
                                 newGear^.SoundChannel := LoopSound(sndResurrector);
                                 end;
                       amTardis: newGear:= AddGear(hwRound(X), hwRound(Y), gtTardis, 0, _0, _0, 0);
                       amIceGun: newGear:= AddGear(hwRound(X), hwRound(Y), gtIceGun, 0, _0, _0, 0);
            end;
            if altUse and (newGear <> nil) and
               ((CurAmmoGear = nil) or (CurAmmoGear^.AmmoType <> amJetpack) or (Gear^.Message and gmPrecise = 0)) then
               begin
               newGear^.dX:= newDx / newGear^.Density;
               newGear^.dY:= newDY / newGear^.Density
               end;
            if (CurAmmoGear <> nil) and (CurAmmoGear^.AmmoType = amJetpack) and
               (Gear^.Message and gmPrecise <> 0) and CheckCoordInWater(hwRound(X), hwRound(Y)) then
                newGear^.State:= newGear^.State or gstSubmersible;

            case CurAmmoType of
                     amGrenade, amMolotov,
                 amClusterBomb, amGasBomb,
                     amBazooka, amSnowball,
                         amBee, amSMine,
                      amMortar, amWatermelon,
                 amHellishBomb, amDrill,
                     amAirMine: FollowGear:= newGear;

                     amShotgun, amPickHammer,
                        amRope, amDEagle,
                     amSineGun, amSniperRifle,
                   amFirePunch, amWhip,
                      amHammer, amBaseballBat,
                   amParachute, amBlowTorch,
                      amGirder, amTeleport,
                      amSwitch, amRCPlane,
                    amKamikaze, amCake,
                   amSeduction, amBallgun,
                     amJetpack, amBirdy,
                amFlamethrower, amLandGun,
                 amResurrector, amTardis,
                       amPiano, amIceGun,
                   amAirAttack, amNapalm,
                  amMineStrike, amDrillStrike,
                      amRubber, amMinigun: CurAmmoGear:= newGear;
            end;
            if (CurAmmoType = amCake) or (CurAmmoType = amPiano) then
                FollowGear:= newGear;

            if ((CurAmmoType = amMine) or (CurAmmoType = amSMine) or (CurAmmoType = amAirMine)) and (GameFlags and gfInfAttack <> 0) then
                newGear^.FlightTime:= GameTicks + min(TurnTimeLeft,1000)
            else if CurAmmoType = amDrill then
                newGear^.FlightTime:= GameTicks + 250;
            if Ammoz[CurAmmoType].Ammo.Propz and ammoprop_NeedTarget <> 0 then
                begin
                newGear^.Target.X:= TargetPoint.X;
                newGear^.Target.Y:= TargetPoint.Y
                end;
            if (newGear <> nil) and (newGear^.CollisionMask and lfCurHogCrate <> 0) then newGear^.CollisionMask:= newGear^.CollisionMask and (not lfCurHogCrate);

            // Clear FollowGear if using on a rope/parachute/saucer etc so focus stays with the hog's movement
            if altUse then
                FollowGear:= nil;

            if (newGear <> nil) and ((Ammoz[newGear^.AmmoType].Ammo.Propz and ammoprop_SetBounce) <> 0) then
                begin
                elastic:=  int2hwfloat(CurWeapon^.Bounciness) / _1000;

            if elastic < _1 then
                newGear^.Elasticity:= newGear^.Elasticity * elastic
            else if elastic > _1 then
                newGear^.Elasticity:= _1 - ((_1-newGear^.Elasticity) / elastic);
(* Experimented with friction modifier. Didn't seem helpful
            fric:= int2hwfloat(CurWeapon^.Bounciness) / _250;
            if fric < _1 then newGear^.Friction:= newGear^.Friction * fric
            else if fric > _1 then newGear^.Friction:= _1 - ((_1-newGear^.Friction) / fric)*)
            end;


            uStats.AmmoUsed(CurAmmoType);

            if not (SpeechText = '') then
                begin
                speech:= AddVisualGear(0, 0, vgtSpeechBubble);
                if speech <> nil then
                    begin
                    speech^.Text:= SpeechText;
                    speech^.Hedgehog:= Gear^.Hedgehog;
                    speech^.FrameTicks:= SpeechType;
                    AddChatString(#9+Format(shortstring(trmsg[sidChatHog]), Gear^.Hedgehog^.Name, SpeechText));
                    end;
                SpeechText:= ''
                end;

            Power:= 0;
            if (CurAmmoGear <> nil) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_AltUse) = 0){check for dropping ammo from rope} then
                begin
                if CurAmmoType in [amRope,amResurrector] then
                    Message:= Message or gmAttack;
                CurAmmoGear^.Message:= Message
                end
            else
                begin
                if (not CurrentTeam^.ExtDriven) and ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_Power) <> 0) then
                    SendIPC(_S'a');
                AfterAttack;
                end;
            TargetPoint.X := NoPointX;
            end
        else
            Message:= Message and (not gmAttack);

    ScriptCall('onHogAttack', ord(usedAmmoType));
    end; // of with Gear^, Gear^.Hedgehog^ do
end;

procedure AfterAttack;
var s: ansistring;
    a: TAmmoType;
    HHGear: PGear;
begin
with CurrentHedgehog^ do
    begin
    HHGear:= Gear;
    a:= CurAmmoType;
    if HHGear <> nil then HHGear^.State:= HHGear^.State and (not gstAttacking);
    if (Ammoz[a].Ammo.Propz and ammoprop_Effect) = 0 then
        begin
        Inc(MultiShootAttacks);

        if (Ammoz[a].Ammo.NumPerTurn > 0) and ((GameFlags and gfMultiWeapon) = 0) then
            begin
            s:= ansistring(inttostr(Ammoz[a].Ammo.NumPerTurn - MultiShootAttacks + 1));
            AddCaption(formatA(trmsg[sidRemaining], s), capcolDefault, capgrpAmmostate);
            end;

        if (Ammoz[a].Ammo.NumPerTurn >= MultiShootAttacks)
        or ((GameFlags and gfMultiWeapon) <> 0) then
            begin
            isInMultiShoot:= true
            end
        else
            begin
            OnUsedAmmo(CurrentHedgehog^);
            if ((Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) = 0) and (((GameFlags and gfInfAttack) = 0) or PlacingHogs) then
                begin
                if TagTurnTimeLeft = 0 then
                    TagTurnTimeLeft:= TurnTimeLeft;
                if (HHGear <> nil) and ((HHGear^.State and gstHHDriven) <> 0) then
                    begin
                    if (CurAmmoGear <> nil) and (CurAmmoGear^.State and gstSubmersible <> 0) and CheckCoordInWater(hwRound(CurAmmoGear^.X), hwRound(CurAmmoGear^.Y)) then
                         TurnTimeLeft:=(Ammoz[a].TimeAfterTurn * cGetAwayTime) div 25
                    else TurnTimeLeft:=(Ammoz[a].TimeAfterTurn * cGetAwayTime) div 100;
                    IsGetAwayTime := true;
                    end;
                end;
            if ((Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) = 0) and (HHGear <> nil) then
                HHGear^.State:= HHGear^.State or gstAttacked;
            if (a = amNothing) or ((Ammoz[a].Ammo.Propz and ammoprop_NoRoundEnd) <> 0) or
               (((GameFlags and gfInfAttack) <> 0) and ((Ammoz[a].Ammo.Propz and ammoprop_ForceTurnEnd) = 0)) then
                ApplyAmmoChanges(CurrentHedgehog^)
            end;
        end
    else
        begin
        OnUsedAmmo(CurrentHedgehog^);
        ApplyAmmoChanges(CurrentHedgehog^);
        end;
    AttackBar:= 0
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehogDead(Gear: PGear);
const frametime = 200;
      timertime = frametime * 6;
var grave:  PGear;
begin
if 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^.Hedgehog^.Effects[heFrozen]:= 0;
    Gear^.State:= Gear^.State or gstNoDamage;
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), Gear^.Boom, CurrentHedgehog, EXPLAutoSound);
    grave:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtGrave, 0, _0, _0, 0);
    grave^.Hedgehog:= Gear^.Hedgehog;
    grave^.Pos:= Gear^.uid;
    
    DeleteGear(Gear);
    SetAllToActive
    end
else // Gear^.Timer = 0
    begin
    AllInactive:= false;
    Gear^.Z:= cCurrHHZ;
    RemoveGearFromList(Gear);
    InsertGearToList(Gear);
    case random(3) of
    0: PlaySoundV(sndByeBye, Gear^.Hedgehog^.Team^.voicepack);
    1: PlaySoundV(sndSoLong, Gear^.Hedgehog^.Team^.voicepack);
    2: PlaySoundV(sndOhDear, Gear^.Hedgehog^.Team^.voicepack);
    end;
    Gear^.Pos:= 0;
    Gear^.Timer:= timertime
    end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehogGone(Gear: PGear);
const frametime = 65;
      timertime = frametime * 11;
var i: LongInt;
begin
if 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);
    // only play sound for one alive hedgehog
    with Gear^.Hedgehog^.Team^ do
        for i:= 0 to cMaxHHIndex do
            begin
            if (Hedgehogs[i].Gear <> nil) then
                begin
                if (Hedgehogs[i].Gear = Gear) then
                    begin
                    PlaySoundV(sndByeBye, Gear^.Hedgehog^.Team^.voicepack);
                    PlaySound(sndWarp);
                    end;
                break;
                end;
            end;
    Gear^.Pos:= 0;
    Gear^.Timer:= timertime
    end
end;

procedure AddPickup(HH: THedgehog; ammo: TAmmoType; cnt, X, Y: LongWord);
var s: ansistring;
    name: ansistring;
    vga: PVisualGear;
begin
    if cnt <> 0 then AddAmmo(HH, ammo, cnt)
    else AddAmmo(HH, ammo);

    if IsHogLocal(@HH) then
        begin
        if length(trluaammo[Ammoz[ammo].NameId]) > 0 then
            name:= trluaammo[Ammoz[ammo].NameId]
        else
            name:= trammo[Ammoz[ammo].NameId];

        if cnt = 0 then
            cnt:= Ammoz[ammo].NumberInCase;

        if (ammo = amNothing) or (cnt = 0) then
            s:= trmsg[sidEmptyCrate]
        else if cnt >= AMMO_INFINITE then
            // infinity symbol
            s:= name + ansistring(' (+' + char($E2) + char($88) + char($9E) +')')
        else
            s:= name + ansistring(' (+' + IntToStr(cnt) + ')');

        AddCaption(s, HH.Team^.Clan^.Color, capgrpAmmoinfo);

        // show ammo icon (if not empty)
        if (ammo <> amNothing) and (cnt <> 0) then
            begin
            vga:= AddVisualGear(X, Y, vgtAmmo);
            if vga <> nil then
                vga^.Frame:= Longword(ammo);
            end

        end;
end;

////////////////////////////////////////////////////////////////////////////////
procedure PickUp(HH, Gear: PGear);
var ag, gi: PGear;
    healthBoost: LongInt;
begin
if Gear^.State and gstFrozen <> 0 then exit;
if Gear^.Message and gmDestroy <> 0 then exit;

Gear^.Message:= gmDestroy;
if (Gear^.Pos and posCaseExplode) <> 0 then
    if (Gear^.Pos and posCasePoison) <> 0 then
        doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, HH^.Hedgehog, EXPLAutoSound + EXPLPoisoned)
    else
        doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, HH^.Hedgehog, EXPLAutoSound)
else if (Gear^.Pos and posCasePoison) <> 0 then
    doMakeExplosion(hwRound(Gear^.X), hwRound(Gear^.Y), 25, HH^.Hedgehog, EXPLAutoSound + EXPLPoisoned + EXPLNoDamage)
else
case Gear^.Pos of
       posCaseUtility,
       posCaseAmmo: begin
                    PlaySound(sndShotgunReload);
                    if Gear^.AmmoType <> amNothing then
                        begin
                        AddPickup(HH^.Hedgehog^, Gear^.AmmoType, Gear^.Power, hwRound(Gear^.X), hwRound(Gear^.Y));
                        end
                    else
                        begin
// Add spawning here...
                        AddRandomness(GameTicks);

                        gi := GearsList;
                        while gi <> nil do
                            begin
                            if (gi^.Kind = gtGenericFaller) and (gi^.Tag = 1) then
                                begin
                                gi^.Active:= true;
                                gi^.State:= gi^.State or gstTmpFlag;
                                gi^.X:= int2hwFloat(GetRandom(rightX-leftX)+leftX);
                                gi^.Y:= int2hwFloat(GetRandom(LAND_HEIGHT-topY)+topY);
                                gi^.dX:= _90-(GetRandomf*_360);
                                gi^.dY:= _90-(GetRandomf*_360)
                                end;
                            gi := gi^.NextGear
                            end;
                        ag:= AddGear(hwRound(Gear^.X), hwRound(Gear^.Y), gtAddAmmo, gstInvisible, _0, _0, GetRandom(125)+25);
                        ag^.Pos:= Gear^.Pos;
                        ag^.Power:= Gear^.Power
                        end;
                    end;
     posCaseHealth: begin
                    PlaySound(sndShotgunReload);
                    healthBoost:= IncHogHealth(HH^.Hedgehog, Gear^.Health);
                    HH^.Hedgehog^.Effects[hePoisoned] := 0;
                    RenderHealth(HH^.Hedgehog^);
                    RecountTeamHealth(HH^.Hedgehog^.Team);
                    HHHeal(HH^.Hedgehog, healthBoost, true);
                    end;
     end
end;

procedure HedgehogStep(Gear: PGear);
var PrevdX: LongInt;
    CurWeapon: PAmmo;
begin
CurWeapon:= GetCurAmmoEntry(Gear^.Hedgehog^);
if ((Gear^.State and (gstAttacking or gstMoving)) = 0) then
    begin
    if isCursorVisible then
        with Gear^.Hedgehog^ do
            with CurWeapon^ do
                begin
                if Ammoz[AmmoType].PosCount < 2 then
                    exit
                else if (Gear^.Message and gmLeft  ) <> 0 then
                    Pos:= (Pos - 1 + Ammoz[AmmoType].PosCount) mod Ammoz[AmmoType].PosCount
                else if (Gear^.Message and gmRight ) <> 0 then
                    Pos:= (Pos + 1) mod Ammoz[AmmoType].PosCount
                else
                    exit;
                GHStepTicks:= 200;
                exit
                end;

    if (Gear^.Hedgehog^.Unplaced) then
        exit;

    if ((Gear^.Message and gmAnimate) <> 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 gmLJump ) <> 0) then
        begin
        Gear^.Message:= Gear^.Message and (not gmLJump);
        DeleteCI(Gear);
        if TestCollisionYwithGear(Gear, -1) = 0 then
            if TestCollisionXwithXYShift(Gear, _0, -2, hwSign(Gear^.dX)) = 0 then
                Gear^.Y:= Gear^.Y - _2
            else
                if TestCollisionXwithXYShift(Gear, _0, -1, hwSign(Gear^.dX)) = 0 then
                    Gear^.Y:= Gear^.Y - _1;
            if (TestCollisionXwithGear(Gear, hwSign(Gear^.dX)) = 0) and
               (TestCollisionYwithGear(Gear, -1) = 0) then
                begin
                Gear^.dY:= -_0_15;
                if Gear^.Hedgehog^.Effects[heArtillery] = 0 then
                    Gear^.dX:= SignAs(_0_15, Gear^.dX);
                Gear^.State:= Gear^.State or gstMoving or gstHHJumping;
                PlaySoundV(sndJump1, Gear^.Hedgehog^.Team^.voicepack);
        exit
        end;
    end;

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

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

    if (Gear^.Message and (gmLeft or gmRight) <> 0) and (Gear^.State and gstMoving = 0) and 
		(CheckGearNear(Gear, gtPortal, 26, 26) = nil) then
		Gear^.PortalCounter:= 0;
    PrevdX:= hwSign(Gear^.dX);
    if (Gear^.Message and gmLeft  )<>0 then
        Gear^.dX:= -_0 else
    if (Gear^.Message and gmRight )<>0 then
        Gear^.dX:=  _0
    else exit;

    StepSoundTimer:= cHHStepTicks;

    GHStepTicks:= cHHStepTicks;
    if PrevdX <> hwSign(Gear^.dX) then
        begin
        FollowGear:= Gear;
        exit
        end;
    DeleteCI(Gear); // must be after exit!! (see previous line)

    Gear^.Hedgehog^.visStepPos:= (Gear^.Hedgehog^.visStepPos + 1) and 7;

    if ((Gear^.Hedgehog^.Effects[heArtillery] = 0) or
           ((CurAmmoGear <> nil) and (CurAmmoGear^.Kind = gtBlowTorch))) and
       ((Gear^.Message and gmPrecise) = 0) then
        MakeHedgehogsStep(Gear);

    SetAllHHToActive(false);
    AddCI(Gear)
    end
end;

procedure HedgehogChAngle(HHGear: PGear);
var da: LongWord;
begin
with HHGear^.Hedgehog^ do
    if (((CurAmmoType = amRope) or ((CurAmmoGear <> nil) and (CurAmmoGear^.AmmoType = amRope))) and
            ((HHGear^.State and (gstMoving or gstHHJumping)) = gstMoving))
    or ((CurAmmoType = amPortalGun) and ((HHGear^.State and gstMoving) <> 0)) then
        da:= 2
    else da:= 1;

if ((HHGear^.Message and gmPrecise = 0) or ((CurAmmoGear <> nil) and (CurAmmoGear^.AmmoType = amJetpack))) or (GameTicks mod 5 = 1) then
    if ((HHGear^.Message and gmUp) <> 0) and (HHGear^.Angle >= CurMinAngle + da) then
        dec(HHGear^.Angle, da)
    else
        if ((HHGear^.Message and gmDown) <> 0) and (HHGear^.Angle + da <= CurMaxAngle) then
            inc(HHGear^.Angle, da)
end;


////////////////////////////////////////////////////////////////////////////////
procedure moveHedgehogOutOfWall(Gear: PGear);
var
    hx: hwFloat;
    colly, collxl, collxr: boolean;
begin
colly:= (TestCollisionYwithGear(Gear, 1) <> 0);

while colly do
    begin
    // don't use TestCollisionXwithXYShift, because it checks for gears
    // save real x-position
    hx:= Gear^.X;

    Gear^.X:= hx + _1;
    collxl:= (TestCollisionX(Gear, -1) <> 0);

    Gear^.X:= hx - _1;
    collxr:= (TestCollisionX(Gear,  1) <> 0);

    // restore original value
    Gear^.X:= hx;

    // do nothing if trapped between 2 walls
    if collxl = collxr then
        break;

    // if we in a wall - fix that
    if collxl then
        begin
        // check for obstacle
        if (TestCollisionX(Gear,  1) <> 0) then
            break;
        Gear^.X:= Gear^.X + _1;
        end
    else
        begin
        // check for obstacle
        if (TestCollisionX(Gear, -1) <> 0) then
            break;
        Gear^.X:= Gear^.X - _1;
        end;

        colly:= (TestCollisionYwithGear(Gear, 1) <> 0);
        end;


end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehogMoving(Gear: PGear);
var isFalling, isUnderwater: boolean;
    land: Word;
    cnt: LongWord;
    s: ansistring;
begin
if Gear^.Hedgehog^.Unplaced then
    begin
    Gear^.dY:= _0;
    Gear^.dX:= _0;
    Gear^.State:= Gear^.State and (not gstMoving);
    exit
    end;

land:= 0;
isUnderwater:= CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y) + Gear^.Radius);
if Gear^.dX.QWordValue > 8160437862 then
    Gear^.dX.QWordValue:= 8160437862;
if Gear^.dY.QWordValue > 8160437862 then
    Gear^.dY.QWordValue:= 8160437862;

isFalling:= (Gear^.dY.isNegative);
if (not isFalling) then
    begin
    // make sure we're not just stuck in wall
    moveHedgehogOutOfWall(Gear);
    // test for floor/obstacle below
    isFalling:= (TestCollisionYKick(Gear, 1) = 0);
    end;

if isFalling then
    begin
    land:= TestCollisionYKick(Gear, -1);
    if (Gear^.dY.isNegative) and (land <> 0) then
        begin
        if land and lfBouncy <> 0 then
            begin
            doStepFallingGear(Gear);
            Gear^.AdvBounce:= 1;
            Gear^.dX:= Gear^.dX * _0_8
            end;
        if (land and lfBouncy = 0) or (Gear^.State and gstCollision <> 0) then
            Gear^.dY:= _0;
        Gear^.State:= Gear^.State and (not gstCollision)
        end;
    Gear^.State:= Gear^.State or gstMoving;
    if (Gear^.State and gstHHDriven <> 0) and
       (FollowGear <> nil) and
       (not CurrentTeam^.ExtDriven) and (hwSqr(Gear^.dX) + hwSqr(Gear^.dY) > _0_003) then
        begin
        // TODO: why so aggressive at setting FollowGear when falling?
        // because hog was being yanked out of frame by other stuff when doing a complicated jump/chute/saucer/roping.
        // added a couple more conditions to make it a bit less aggressive, at cost of possibly spectator failing to follow a maneuver
        FollowGear:= Gear;
        end;
    if isUnderwater then
       Gear^.dY:= Gear^.dY + cGravity / _2
    else
        begin
        Gear^.dY:= Gear^.dY + cGravity;
// this set of circumstances could be less complex if jumping was more clearly identified
        if ((GameFlags and gfMoreWind) <> 0) and (((Gear^.Damage <> 0)
        or ((CurAmmoGear <> nil) and ((CurAmmoGear^.AmmoType = amJetpack) or (CurAmmoGear^.AmmoType = amBirdy)))
        or ((Gear^.dY.QWordValue + Gear^.dX.QWordValue) > _0_55.QWordValue))) then
            Gear^.dX := Gear^.dX + cWindSpeed / Gear^.Density
        end
    end
else
    begin
    land:= TestCollisionYwithGear(Gear, 1);
    if ((Gear^.dX.QWordValue + Gear^.dY.QWordValue) < _0_55.QWordValue) and ((land and lfIce) = 0)
    and ((land and lfBouncy = 0) or (Gear^.State and gstCollision <> 0))
    and (Gear^.Damage = 0)
    and ((Gear^.State and gstHHJumping) <> 0) then
        SetLittle(Gear^.dX);

    if not Gear^.dY.isNegative then
        begin
        if land and lfBouncy <> 0 then
            begin
            doStepFallingGear(Gear);
            Gear^.AdvBounce:= 1;
            // hogs for some reason have very low friction. slippery little buggers
            Gear^.dX:= Gear^.dX * _0_8
            end;

        CheckHHDamage(Gear);

        if (land and lfBouncy = 0) or (Gear^.State and gstCollision <> 0) then
            begin
            if ((Gear^.State and gstHHHJump) <> 0) and (Gear^.Hedgehog^.Effects[heArtillery] = 0)
            and (Gear^.dX.QWordValue < _0_02.QWordValue) then
                begin
                if land and lfBouncy <> 0 then
                    Gear^.dY:= _0;
                Gear^.dX.isNegative:= not Gear^.dX.isNegative // landing after high jump
                end;
            Gear^.State:= Gear^.State and (not (gstHHJumping or gstHHHJump));
            if (land and lfBouncy = 0) or (Gear^.dX.QWordValue < _0_02.QWordValue) then
                Gear^.dY:= _0
            end;
        Gear^.State:= Gear^.State and (not gstCollision)
        end
    else
        Gear^.dY:= Gear^.dY + cGravity;

    if ((Gear^.State and gstMoving) <> 0) then
        begin
        if land and lfIce <> 0 then
            begin
            Gear^.dX:= Gear^.dX * (_1 - (_1 - Gear^.Friction) / _2)
            end
        else
            Gear^.dX:= Gear^.dX * Gear^.Friction;
        end
    end;

if (Gear^.State and (gstMoving or gstHHJumping or gstHHHJump)) <> 0 then
    DeleteCI(Gear);

if isUnderwater then
   begin
   Gear^.dY:= Gear^.dY * _0_999;
   Gear^.dX:= Gear^.dX * _0_999;
   end;

if (Gear^.State and gstMoving) <> 0 then
    if TestCollisionXKick(Gear, hwSign(Gear^.dX)) <> 0 then
        if not isFalling then
            if hwAbs(Gear^.dX) > _0_01 then
                if  (TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -1, hwSign(Gear^.dX)) = 0) and
                    (TestCollisionYwithXYShift(Gear, hwSign(Gear^.dX) - hwRound(Gear^.dX), -1, -1) = 0) then
                    begin
                    Gear^.X:= Gear^.X + Gear^.dX;
                    Gear^.dX:= Gear^.dX * _0_96;
                    Gear^.Y:= Gear^.Y - _1
                    end
                else
                    if  (TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -2, hwSign(Gear^.dX)) = 0) and
                        (TestCollisionYwithXYShift(Gear, hwSign(Gear^.dX) - hwRound(Gear^.dX), -1, -1) = 0) then
                        begin
                        Gear^.X:= Gear^.X + Gear^.dX;
                        Gear^.dX:= Gear^.dX * _0_93;
                        Gear^.Y:= Gear^.Y - _2
                        end
                    else
                    if  (TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -3, hwSign(Gear^.dX)) = 0) and
                        (TestCollisionYwithXYShift(Gear, hwSign(Gear^.dX) - hwRound(Gear^.dX), -1, -1) = 0) then
                        begin
                        Gear^.X:= Gear^.X + Gear^.dX;
                        Gear^.dX:= Gear^.dX * _0_9 ;
                        Gear^.Y:= Gear^.Y - _3
                        end
                    else
                        if (TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -4, hwSign(Gear^.dX)) = 0) and
                           (TestCollisionYwithXYShift(Gear, hwSign(Gear^.dX) - hwRound(Gear^.dX), -1, -1) = 0) then
                            begin
                            Gear^.X:= Gear^.X + Gear^.dX;
                            Gear^.dX:= Gear^.dX * _0_87;
                            Gear^.Y:= Gear^.Y - _4
                            end
                    else
                        if (TestCollisionXwithXYShift(Gear, int2hwFloat(hwSign(Gear^.dX)) - Gear^.dX, -5, hwSign(Gear^.dX)) = 0) and
                           (TestCollisionYwithXYShift(Gear, hwSign(Gear^.dX) - hwRound(Gear^.dX), -1, -1) = 0) 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);
                            cnt:= 0;
                            while (cnt < 6) and (not CheckGearDrowning(Gear)) and (Gear <> nil) and (TestCollisionYWithGear(Gear,1) = 0) do
                                begin
                                Gear^.Y:= Gear^.Y + _1;
                                inc(cnt)
                                end;

                            SetLittle(Gear^.dX)
                            end
            else
                begin
                Gear^.State:= Gear^.State and (not gstMoving);
                cnt:= 0;
                while (cnt < 6) and (not CheckGearDrowning(Gear)) and (Gear <> nil) and (TestCollisionYWithGear(Gear,1) = 0) do
                    begin
                    Gear^.Y:= Gear^.Y + _1;
                    inc(cnt)
                    end;
                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
    if (not GameOver) then
        Gear^.State:= Gear^.State and (not gstWinner);
    Gear^.State:= Gear^.State and (not gstMoving);
    cnt:= 0;
    while (cnt < 6) and (not CheckGearDrowning(Gear)) and (Gear <> nil) and (TestCollisionYWithGear(Gear,1) = 0) do
        begin
        Gear^.Y:= Gear^.Y + _1;
        inc(cnt)
        end;

    // could become nil in CheckGearDrowning if ai's hog fails to respawn in ai survival
    if Gear = nil then exit;
    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 (TestCollisionYKick(Gear, 1) = 0) then
        begin
        land:= TestCollisionYwithXYShift(Gear, 0, 1, 1);
        if land and lfBouncy <> 0 then
            doStepFallingGear(Gear);
            Gear^.AdvBounce:= 1;

        if (land <> 0) and ((land and lfBouncy = 0) or (Gear^.State and gstCollision <> 0)) then
            begin
            CheckHHDamage(Gear);
            Gear^.dY:= _0;
            Gear^.Y:= Gear^.Y + _1
            end;
        Gear^.State:= Gear^.State and (not gstCollision)
        end;

    // could become nil if ai's hog fails to respawn in ai survival
    if Gear = nil then exit;
    // hide target cursor if current hog is drowning
    if (Gear^.State and gstDrowning) <> 0 then
        if (CurrentHedgehog^.Gear = Gear) then
            isCursorVisible:= false
    end;
if (not isZero(Gear^.dY)) and (Gear^.FlightTime > 0) and ((GameFlags and gfLowGravity) = 0) then
    begin
    inc(Gear^.FlightTime);
    if (Gear^.FlightTime > 1500) and ((hwRound(Gear^.X) < leftX-250) or (hwRound(Gear^.X) > rightX+250))  then
        begin
        Gear^.FlightTime:= 0;
        s:= ansistring(CurrentHedgehog^.Name);
        AddCaption(FormatA(GetEventString(eidHomerun), s), capcolDefault, capgrpMessage2);
        PlaySound(sndHomerun)
        end;
    end
else
    begin
    uStats.hedgehogFlight(Gear, Gear^.FlightTime);
    Gear^.FlightTime:= 0;
    end;
if (WorldEdge = weNone) and (not Gear^.Hedgehog^.FlownOffMap) and (not isZero(Gear^.dX)) and (not isUnderwater) and ((Gear^.State and gstHHDriven) = 0) and (hwRound(Gear^.Y) < cWaterLine-300) and ((hwRound(Gear^.X) < leftX-2048) or (hwRound(Gear^.X) > rightX+2048)) then
    begin
    PlaySoundV(sndFlyAway, Gear^.Hedgehog^.Team^.voicepack);
    Gear^.Hedgehog^.FlownOffMap:= true;
    end;

end;

procedure doStepHedgehogDriven(HHGear: PGear);
var t: PGear;
    wasJumping: boolean;
    Hedgehog: PHedgehog;
    s: ansistring;
begin
Hedgehog:= HHGear^.Hedgehog;
if not isInMultiShoot then
    AllInactive:= false
else if Hedgehog^.CurAmmoType in [amShotgun, amDEagle, amSniperRifle] then
    HHGear^.Message:= HHGear^.Message and gmPrecise;

if ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_Utility) <> 0) and isInMultiShoot then
    AllInactive:= true
else if not isInMultiShoot then
    AllInactive:= false;

if (TurnTimeLeft = 0) or (HHGear^.Damage > 0) or (HHGear^.Health = 0) or (((GameFlags and gfKing) <> 0) and (not Hedgehog^.Team^.hasKing)) or (LuaEndTurnRequested = true) then
    begin
    if (Hedgehog^.CurAmmoType = amKnife) then
       LoadHedgehogHat(Hedgehog^, Hedgehog^.Hat);
    if TagTurnTimeLeft = 0 then
        TagTurnTimeLeft:= TurnTimeLeft;
    TurnTimeLeft:= 0;
    if (GameOver = false) and ((GameFlags and gfInfAttack) = 0) and ((HHGear^.State and gstAttacked) = 0) and (HHGear^.Damage = 0) and (HHGear^.Health > 0) and (LuaNoEndTurnTaunts = false) and (uStats.getIsTurnSkipped() = false) then
        begin
        AddVoice(sndBoring, Hedgehog^.Team^.voicepack);
        if (GameFlags and gfInfAttack = 0) then
            begin
            s:= Hedgehog^.Name;
            AddCaption(FormatA(GetEventString(eidTimeout), s), capcolDefault, capgrpMessage);
            end;
        end;
    isCursorVisible:= false;
    HHGear^.State:= HHGear^.State and (not (gstHHDriven or gstAnimation or gstAttacking));
    AttackBar:= 0;
    StopSound(sndThrowPowerUp);
    LuaEndTurnRequested:= false;
    LuaNoEndTurnTaunts:= false;
    if (HHGear^.Damage > 0) or (HHGear^.Health = 0) then
        HHGear^.State:= HHGear^.State and (not (gstHHJumping or gstHHHJump));
    exit
    end;

if isAFK and (not CurrentTeam^.ExtDriven) and (CurrentHedgehog^.BotLevel = 0) then
    begin
    AFKSkip;
    exit
    end;

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

if ((HHGear^.State and gstMoving) <> 0)
or (GHStepTicks = cHHStepTicks)
or (CurAmmoGear <> nil) then // we are moving
    begin
    with Hedgehog^ do
        if (CurAmmoGear = nil)
        and (HHGear^.dY > _0_39)
        and (CurAmmoType = amParachute) then
            HHGear^.Message:= HHGear^.Message or gmAttack;
    // check for case with ammo
    t:= CheckGearNear(HHGear, gtCase, 36, 36);
    if (t <> nil) then
        PickUp(HHGear, t)
    end;

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

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

    if ((HHGear^.Message and gmWeapon) <> 0) then
        HHSetWeapon(HHGear);

    if ((HHGear^.Message and gmTimer) <> 0) then
        HHSetTimer(HHGear);
    end;

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

    HedgehogChAngle(HHGear);

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

    if ((HHGear^.Message and gmHJump) <> 0) and wasJumping and ((HHGear^.State and gstHHHJump) = 0) then
        if (not (hwAbs(HHGear^.dX) > cLittle)) and (HHGear^.dY < _0_05) then
            begin
            HHGear^.State:= HHGear^.State or gstHHHJump;
            HHGear^.dY:= -_0_25;
            if (Hedgehog^.Effects[heArtillery] = 0) then
                HHGear^.dX:= -SignAs(_0_02, HHGear^.dX);
            PlaySoundV(sndJump2, Hedgehog^.Team^.voicepack)
            end;

    HHGear^.Message:= HHGear^.Message and (not (gmLJump or gmHJump));

    if (Hedgehog^.Effects[heArtillery] = 0) and wasJumping and (TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) <> 0) then
        SetLittle(HHGear^.dX);

    if Hedgehog^.Gear <> nil then
        doStepHedgehogMoving(HHGear);

    if ((HHGear^.State and (gstMoving or gstDrowning)) = 0) then
        begin
        AddCI(HHGear);
        if wasJumping then
            GHStepTicks:= 410
        else
            GHStepTicks:= 95
        end;
    exit
    end;

    if not(isInMultiShoot and (Hedgehog^.CurAmmoType in [amShotgun, amDEagle, amSniperRifle])) and (Hedgehog^.Gear <> nil) then
        begin
        if GHStepTicks > 0 then
            dec(GHStepTicks);
        if (GHStepTicks = 0) then
            HedgehogStep(HHGear)
        end
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehogFree(Gear: PGear);
var prevState: Longword;
    s: ansistring;
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 or ((GameFlags and gfInfAttack) <> 0) then
        begin
        Gear^.Timer:= 0;
        FollowGear:= Gear;
        PrvInactive:= false;
        AllInactive:= false;

        if (Gear^.State and gstHHGone) = 0 then
            begin
            Gear^.Hedgehog^.Effects[hePoisoned] := 0;
            if Gear^.Hedgehog^.Effects[heResurrectable] <> 0 then
                begin
                ResurrectHedgehog(Gear);
                end
            else
                begin
                Gear^.State:= (Gear^.State or gstHHDeath) and (not gstAnimation);
                Gear^.doStep:= @doStepHedgehogDead;
                // Death message
                s:= ansistring(Gear^.Hedgehog^.Name);
                if Gear^.Hedgehog^.King then
                    AddCaption(FormatA(GetEventString(eidKingDied), s), capcolDefault, capgrpMessage)
                else
                    AddCaption(FormatA(GetEventString(eidDied), s), capcolDefault, capgrpMessage);
                end;
            end
        else
            begin
            Gear^.State:= Gear^.State and (not gstAnimation);
            Gear^.doStep:= @doStepHedgehogGone;

            // Gone message
            s:= ansistring(Gear^.Hedgehog^.Name);
            AddCaption(FormatA(GetEventString(eidGone), s), capcolDefault, capgrpMessage);
            end
        end;
    exit
    end;

if ((Gear^.State and gstWait) = 0) and
    (prevState <> Gear^.State) then
    begin
    Gear^.State:= Gear^.State or gstWait;
    Gear^.Timer:= 150
    end
else
    begin
    if Gear^.Timer = 0 then
        begin
        Gear^.State:= Gear^.State and (not (gstWait or gstLoser or gstAttacked or gstNotKickable or gstChooseTarget));
        if (not GameOver) then
            Gear^.State:= Gear^.State and (not gstWinner);
        if Gear^.Hedgehog^.Effects[heFrozen] = 0 then Gear^.Active:= false;
        AddCI(Gear);
        exit
        end
    else dec(Gear^.Timer)
    end;

AllInactive:= false
end;

procedure CheckIce(Gear: PGear); inline;
(*
var x,y,tx,ty: LongInt;
    tdX, tdY, slope: hwFloat;
    land: Word; *)
var slope: hwFloat;
begin
    if (Gear^.Message and (gmAllStoppable or gmLJump or gmHJump) = 0)
    and (Gear^.State and (gstHHJumping or gstHHHJump or gstAttacking or gstAnimation) = 0)
    and ((Gear^.Hedgehog = nil) or ((Gear^.Hedgehog^.Effects[heFrozen] = 0) or (Gear^.Hedgehog^.Effects[heFrozen] > 255)))
    and (not Gear^.dY.isNegative) and TurnClockActive and (TestCollisionYwithGear(Gear, 1) and lfIce <> 0) then
        begin
        slope:= CalcSlopeBelowGear(Gear);
        if slope.QWordValue > 730144440 then // ignore mild slopes
            begin
            Gear^.dX:=Gear^.dX+slope*cGravity*_256;
            Gear^.State:= Gear^.State or gstMoving
            end
        end;
(*
    x:= hwRound(Gear^.X);
    y:= hwRound(Gear^.Y);
    AddVisualGear(x, y, vgtSmokeTrace);
    AddVisualGear(x - hwRound(_5*slope), y + hwRound(_5*slope), vgtSmokeTrace);
    AddVisualGear(x + hwRound(_5*slope), y - hwRound(_5*slope), vgtSmokeTrace);
    AddVisualGear(x - hwRound(_20 * slope), y + hwRound(_20 * slope), vgtSmokeTrace);
    AddVisualGear(x + hwRound(_20 * slope), y - hwRound(_20 * slope), vgtSmokeTrace);
    AddVisualGear(x - hwRound(_30 * slope), y + hwRound(_30 * slope), vgtSmokeTrace);
    AddVisualGear(x + hwRound(_30 * slope), y - hwRound(_30 * slope), vgtSmokeTrace);
    AddVisualGear(x - hwRound(_40 * slope), y + hwRound(_40 * slope), vgtSmokeTrace);
    AddVisualGear(x + hwRound(_40 * slope), y - hwRound(_40 * slope), vgtSmokeTrace);
    AddVisualGear(x - hwRound(_50 * slope), y + hwRound(_50 * slope), vgtSmokeTrace);
    AddVisualGear(x + hwRound(_50 * slope), y - hwRound(_50 * slope), vgtSmokeTrace); *)
end;

////////////////////////////////////////////////////////////////////////////////
procedure doStepHedgehog(Gear: PGear);
var tX: hwFloat;
begin
// it might make sense to skip more than just drowning check here
if (not Gear^.Hedgehog^.Unplaced) then
    CheckGearDrowning(Gear);
if Gear = nil then exit;
tX:= Gear^.X;
if WorldWrap(Gear) then
    begin
    if (WorldEdge <> weBounce) and (Gear = CurrentHedgehog^.Gear) and
       (CurAmmoGear <> nil) and (CurAmmoGear^.Kind =gtRope) and (CurAmmoGear^.Elasticity <> _0) then
       CurAmmoGear^.PortalCounter:= 1;
    if (WorldEdge = weWrap) and ((TestCollisionXwithGear(Gear, 1) <> 0) or (TestCollisionXwithGear(Gear, -1) <> 0))  then
        // Stop hedgehog if it collides with land *just* behind other side of world wrap edge
        if ((Gear^.State and gstNotKickable) = 0) then
            begin
            if (hwRound(tX) > leftX + Gear^.Radius * 2) then
                Gear^.X:= int2HwFloat(RightX)
            else
                Gear^.X:= int2HwFloat(LeftX);
            Gear^.dX.QWordValue:= 0;
            Gear^.State := Gear^.State or gstCollision;
            end;
    end;

CheckSum:= CheckSum xor Gear^.Hedgehog^.BotLevel;
if (Gear^.Message and gmDestroy) <> 0 then
    begin
    DeleteGear(Gear);
    exit
    end;
if GameTicks mod 128 = 0 then CheckIce(Gear);
(*
if Gear^.Hedgehog^.Effects[heFrozen] > 0 then
    begin
    if (Gear^.Hedgehog^.Effects[heFrozen] > 256) and (CurrentHedgehog^.Team^.Clan <> Gear^.Hedgehog^.Team^.Clan) then
        dec(Gear^.Hedgehog^.Effects[heFrozen])
    else if GameTicks mod 10 = 0 then
        dec(Gear^.Hedgehog^.Effects[heFrozen])
    end;
*)
if (GameTicks mod 10 = 0) and (Gear^.Hedgehog^.Effects[heFrozen] > 0) and (Gear^.Hedgehog^.Effects[heFrozen] < 256) then
    dec(Gear^.Hedgehog^.Effects[heFrozen]);
if (Gear^.State and gstHHDriven) = 0 then
    doStepHedgehogFree(Gear)
else
    begin
    with Gear^.Hedgehog^ do
        if Team^.hasGone then
            TeamGoneEffect(Team^)
        else
            doStepHedgehogDriven(Gear)
    end;
end;

procedure PlayTaunt(taunt: Longword);
begin
    if CurrentHedgehog^.Gear <> nil then
        with CurrentHedgehog^.Gear^ do
            begin
            Message:= Message or (gmAnimate and InputMask);
            MsgParam:= taunt;
            ScriptCall('onTaunt', MsgParam);
            end
end;

end.