hedgewars/uWorld.pas
author Wuzzy <Wuzzy2@mail.ru>
Mon, 29 Jun 2020 14:25:23 +0200
changeset 15676 c1d0ada72cc8
parent 15675 d738b2b1249e
child 15717 eeab5b90c082
permissions -rw-r--r--
Fix airplane line not being drawn in full for very large maps

(*
 * 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 uWorld;
interface
uses SDLh, uGears, uConsts, uFloat, uRandom, uTypes, uRenderUtils;

procedure initModule;
procedure freeModule;

procedure InitWorld;
procedure ResetWorldTex;

procedure DrawWorld(Lag: LongInt);
procedure DrawWorldStereo(Lag: LongInt; RM: TRenderMode);
procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt);
procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt; forceDisplay : boolean);
procedure HideMission;
procedure SetAmmoTexts(ammoType: TAmmoType; name: ansistring; caption: ansistring; description: ansistring; autoLabels: boolean);
procedure ShakeCamera(amount: LongInt);
procedure InitCameraBorders;
procedure InitTouchInterface;
procedure SetUtilityWidgetState(ammoType: TAmmoType);
procedure animateWidget(widget: POnScreenWidget; fade, showWidget: boolean);
procedure MoveCamera;
procedure onFocusStateChanged;
procedure updateCursorVisibility;
procedure updateTouchWidgets(ammoType: TAmmoType);

implementation
uses
    uStore
    , uMisc
    , uIO
    , uLocale
    , uSound
    , uAmmos
    , uVisualGears
    , uChat
    , uLandTexture
    , uVariables
    , uUtils
    , uTextures
    , uRender
    , uCaptions
    , uCursor
    , uCommands
    , uTeams
    , uDebug
    , uInputHandler
{$IFDEF USE_VIDEO_RECORDING}
    , uVideoRec
{$ENDIF}
    ;

var AMShiftTargetX, AMShiftTargetY, AMShiftX, AMShiftY, SlotsNum: LongInt;
    AMAnimStartTime, AMState : LongInt;
    AMAnimState: Single;
    tmpSurface: PSDL_Surface;
    fpsTexture: PTexture;
    timeTexture: PTexture;
    FPS: Longword;
    CountTicks: Longword;
    prevPoint: TPoint;
    amSel: TAmmoType = amNothing;
    missionTex: PTexture;
    missionTimer: LongInt;
    isFirstFrame: boolean;
    AMAnimType: LongInt;
    recTexture: PTexture;
    AmmoMenuTex     : PTexture;
    HorizontOffset: LongInt;
    cOffsetY: LongInt;
    WorldEnd, WorldFade : array[0..3] of HwColor4f;

const cStereo_Sky           = 0.0500;
      cStereo_Horizon       = 0.0250;
      cStereo_MidDistance   = 0.0175;
      cStereo_Water_distant = 0.0125;
      cStereo_Land          = 0.0075;
      cStereo_Water_near    = 0.0025;
      cStereo_Outside       = -0.0400;

      AMAnimDuration = 200;
      AMHidden    = 0;//AMState values
      AMShowingUp = 1;
      AMShowing   = 2;
      AMHiding    = 3;

      AMTypeMaskX     = $00000001;
      AMTypeMaskY     = $00000002;
      AMTypeMaskAlpha = $00000004;

{$IFDEF MOBILE}
      AMSlotSize = 48;
{$ELSE}
      AMSlotSize = 32;
{$ENDIF}
      AMSlotPadding = (AMSlotSize - 32) shr 1;

{$IFDEF USE_LANDSCAPE_AMMOMENU}
      amNumOffsetX = 0;
      {$IFDEF USE_AM_NUMCOLUMN}
      amNumOffsetY = AMSlotSize;
      {$ELSE}
      amNumOffsetY = 0;
      {$ENDIF}
{$ELSE}
      amNumOffsetY = 0;
      {$IFDEF USE_AM_NUMCOLUMN}
      amNumOffsetX = AMSlotSize;
      {$ELSE}
      amNumOffsetX = 0;
      {$ENDIF}
{$ENDIF}

      cSendCursorPosTime = 50;
      cCursorEdgesDist   = 100;

// helper functions to create the goal/game mode string
function AddGoal(s: ansistring; gf: longword; si: TGoalStrId; i: LongInt): ansistring;
var t: ansistring;
begin
    if (GameFlags and gf) <> 0 then
        begin
        t:= inttostr(i);
        s:= s + FormatA(trgoal[si], t) + '|'
        end;
    AddGoal:= s;
end;

function AddGoal(s: ansistring; gf: longword; si: TGoalStrId): ansistring;
begin
    if (GameFlags and gf) <> 0 then
        s:= s + trgoal[si] + '|';
    AddGoal:= s;
end;

procedure InitWorld;
var i, t: LongInt;
    cp: PClan;
    g: ansistring;
begin
missionTimer:= 0;

if (GameFlags and gfRandomOrder) <> 0 then  // shuffle them up a bit
    begin
    for i:= 0 to ClansCount * 4 do
        begin
        t:= GetRandom(ClansCount);
        if t <> 0 then
            begin
            cp:= ClansArray[0];
            ClansArray[0]:= ClansArray[t];
            ClansArray[t]:= cp;
            ClansArray[t]^.ClanIndex:= t;
            ClansArray[0]^.ClanIndex:= 0;
            end;
        end;
    CurrentTeam:= ClansArray[0]^.Teams[0];
    end;

if (GameFlags and gfInvulnerable) <> 0 then
    cTagsMask:= cTagsMask and (not htHealth);

// if special game flags/settings are changed, add them to the game mode notice window and then show it
g:= ''; // no text/things to note yet

// add custom goals from lua script if there are any
if LuaGoals <> ansistring('') then
    g:= LuaGoals + '|';

// check different game flags
g:= AddGoal(g, gfKing, gidKing); // king?
if ((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0) then
    g:= AddGoal(g, gfAny, gidPlaceKing);
g:= AddGoal(g, gfPlaceHog, gidPlaceHog); // placement?
g:= AddGoal(g, gfTagTeam, gidTagTeam); // tag team mode?
g:= AddGoal(g, gfSharedAmmo, gidSharedAmmo); // shared ammo?
g:= AddGoal(g, gfPerHogAmmo, gidPerHogAmmo);
g:= AddGoal(g, gfMoreWind, gidMoreWind);
g:= AddGoal(g, gfLowGravity, gidLowGravity); // low gravity?
g:= AddGoal(g, gfSolidLand, gidSolidLand); // solid land?
g:= AddGoal(g, gfArtillery, gidArtillery); // artillery?
g:= AddGoal(g, gfInfAttack, gidInfAttack);
g:= AddGoal(g, gfResetWeps, gidResetWeps);
g:= AddGoal(g, gfResetHealth, gidResetHealth);
g:= AddGoal(g, gfKarma, gidKarma); // karma?
g:= AddGoal(g, gfVampiric, gidVampiric); // vampirism?
g:= AddGoal(g, gfInvulnerable, gidInvulnerable); // invulnerability?
g:= AddGoal(g, gfAISurvival, gidAISurvival);

// modified damage modificator?
if cDamagePercent <> 100 then
    g:= AddGoal(g, gfAny, gidDamageModifier, cDamagePercent);

// fade in
ScreenFade:= sfFromBlack;
ScreenFadeValue:= sfMax;
ScreenFadeSpeed:= 1;

// modified mine timers?
if cMinesTime <> 3000 then
    begin
    if cMinesTime = 0 then
        g:= AddGoal(g, gfAny, gidNoMineTimer)
    else if cMinesTime < 0 then
        g:= AddGoal(g, gfAny, gidRandomMineTimer)
    else
        g:= AddGoal(g, gfAny, gidMineTimer, cMinesTime div 1000);
    end;

// if the string has been set, show it for (default timeframe) seconds
if length(g) > 0 then
    // choose icon
    if ((GameFlags and gfKing) <> 0) then
        // crown icon for King Mode
        ShowMission(trgoal[gidCaption], trgoal[gidSubCaption], g, 0, 0)
    else
        // target icon for anything else
        ShowMission(trgoal[gidCaption], trgoal[gidSubCaption], g, 1, 0);

cWaveHeight:= 32;

InitCameraBorders();
uCursor.init();
prevPoint.X:= 0;
prevPoint.Y:= cScreenHeight div 2;
WorldDx:=  -(LongInt(leftX + (playWidth div 2)));
WorldDy:=  -(LAND_HEIGHT - (playHeight div 2)) + (cScreenHeight div 2);

//aligns it to the bottom of the screen, minus the border
SkyOffset:= 0;
HorizontOffset:= 0;

InitTouchInterface();
AMAnimType:= AMTypeMaskX or AMTypeMaskAlpha;
end;

procedure InitCameraBorders;
begin
cGearScrEdgesDist:= min(2 * cScreenHeight div 5, 2 * cScreenWidth div 5);
end;

procedure InitTouchInterface;
begin
{$IFDEF USE_TOUCH_INTERFACE}

//positioning of the buttons
buttonScale:= 1 / cDefaultZoomLevel;


with JumpWidget do
    begin
    show:= true;
    sprite:= sprJumpWidget;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= (cScreenWidth shr 1) - Round(frame.w * 1.2);
    frame.y:= cScreenHeight - frame.h * 2;
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    end;

with AMWidget do
    begin
    show:= true;
    sprite:= sprAMWidget;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= (cScreenWidth shr 1) - frame.w * 2;
    frame.y:= cScreenHeight - Round(frame.h * 1.2);
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    end;

with arrowLeft do
    begin
    show:= true;
    sprite:= sprArrowLeft;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= -(cScreenWidth shr 1) + Round(frame.w * 0.25);
    frame.y:= cScreenHeight - Round(frame.h * 1.5);
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    end;

with arrowRight do
    begin
    show:= true;
    sprite:= sprArrowRight;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= -(cScreenWidth shr 1) + Round(frame.w * 1.5);
    frame.y:= cScreenHeight - Round(frame.h * 1.5);
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    end;

with firebutton do
    begin
    show:= true;
    sprite:= sprFireButton;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= arrowRight.frame.x + arrowRight.frame.w;
    frame.y:= arrowRight.frame.y + (arrowRight.frame.w shr 1) - (frame.w shr 1);
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    end;

with arrowUp do
    begin
    show:= false;
    sprite:= sprArrowUp;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= (cScreenWidth shr 1) - frame.w * 2;
    frame.y:= jumpWidget.frame.y - Round(frame.h * 1.25);
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    with moveAnim do
         begin
         target.x:= frame.x;
         target.y:= frame.y;
         source.x:= frame.x - Round(frame.w * 0.75);
         source.y:= frame.y;
         end;
    end;

with arrowDown do
    begin
    show:= false;
    sprite:= sprArrowDown;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= (cScreenWidth shr 1) - frame.w * 2;
    frame.y:= jumpWidget.frame.y - Round(frame.h * 1.25);
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    with moveAnim do
        begin
        target.x:= frame.x;
        target.y:= frame.y;
        source.x:= frame.x + Round(frame.w * 0.75);
        source.y:= frame.y;
        end;
    end;

with pauseButton do
    begin
    show:= true;
    sprite:= sprPauseButton;
    frame.w:= Round(spritesData[sprPauseButton].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprPauseButton].Texture^.h * buttonScale);
    frame.x:= cScreenWidth div 2 - frame.w;
    frame.y:= 0;
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    end;

with utilityWidget do
    begin
    show:= false;
    sprite:= sprTimerButton;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= arrowLeft.frame.x;
    frame.y:= arrowLeft.frame.y - Round(frame.h * 1.25);
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    with moveAnim do
        begin
        target.x:= frame.x;
        target.y:= frame.y;
        source.x:= frame.x;
        source.y:= frame.y;
        end;
    end;

with utilityWidget2 do
    begin
    show:= false;
    sprite:= sprBounceButton;
    frame.w:= Round(spritesData[sprite].Texture^.w * buttonScale);
    frame.h:= Round(spritesData[sprite].Texture^.h * buttonScale);
    frame.x:= utilityWidget.frame.x + Round(frame.w * 1.25);
    frame.y:= arrowLeft.frame.y - Round(frame.h * 1.25);
    active.x:= frame.x;
    active.y:= frame.y;
    active.w:= frame.w;
    active.h:= frame.h;
    with moveAnim do
        begin
        target.x:= frame.x;
        target.y:= frame.y;
        source.x:= frame.x;
        source.y:= frame.y;
        end;
    end;

{$ENDIF}
end;

// for uStore texture resetting
procedure ResetWorldTex;
begin
    FreeAndNilTexture(fpsTexture);
    FreeAndNilTexture(timeTexture);
    FreeAndNilTexture(missionTex);
    FreeAndNilTexture(recTexture);
    FreeAndNilTexture(AmmoMenuTex);
    AmmoMenuInvalidated:= true;
end;

function GetAmmoMenuTexture(Ammo: PHHAmmo): PTexture;
const BORDERSIZE = 2;
var x, y, i, t, SlotsNumY, SlotsNumX, AMFrame: LongInt;
    STurns: LongInt;
    amSurface: PSDL_Surface;
    AMRect: TSDL_Rect;
{$IFDEF USE_AM_NUMCOLUMN}
    tmpsurf: PSDL_Surface;
    usesDefaultSlotKeys: boolean;
{$ENDIF}
begin
    if cOnlyStats then exit(nil);

    SlotsNum:= 0;
    for i:= 0 to cMaxSlotIndex do
        if (i <> cHiddenSlotIndex) and (Ammo^[i, 0].Count > 0) then
            inc(SlotsNum);
{$IFDEF USE_LANDSCAPE_AMMOMENU}
    SlotsNumX:= SlotsNum;
    SlotsNumY:= cMaxSlotAmmoIndex + 2;
    {$IFDEF USE_AM_NUMCOLUMN}
    inc(SlotsNumY);
    {$ENDIF}
{$ELSE}
    SlotsNumX:= cMaxSlotAmmoIndex + 1;
    SlotsNumY:= SlotsNum + 1;
    {$IFDEF USE_AM_NUMCOLUMN}
    inc(SlotsNumX);
    {$ENDIF}
{$ENDIF}


    AmmoRect.w:= (BORDERSIZE*2) + (SlotsNumX * AMSlotSize) + (SlotsNumX-1);
    AmmoRect.h:= (BORDERSIZE*2) + (SlotsNumY * AMSlotSize) + (SlotsNumY-1);
    amSurface := SDL_CreateRGBSurface(SDL_SWSURFACE, AmmoRect.w, AmmoRect.h, 32, RMask, GMask, BMask, AMask);

    AMRect.x:= BORDERSIZE;
    AMRect.y:= BORDERSIZE;
    AMRect.w:= AmmoRect.w - (BORDERSIZE*2);
    AMRect.h:= AmmoRect.h - (BORDERSIZE*2);

    SDL_FillRect(amSurface, @AMRect, SDL_MapRGB(amSurface^.format, 0,0,0));

    x:= AMRect.x;
    y:= AMRect.y;
{$IFDEF USE_AM_NUMCOLUMN}
    usesDefaultSlotKeys:= CheckDefaultSlotKeys;
{$ENDIF USE_AM_NUMCOLUMN}
    for i:= 0 to cMaxSlotIndex do
        if (i <> cHiddenSlotIndex) and (Ammo^[i, 0].Count > 0) then
            begin
{$IFDEF USE_LANDSCAPE_AMMOMENU}
            y:= AMRect.y;
{$ELSE}
            x:= AMRect.x;
{$ENDIF}
{$IFDEF USE_AM_NUMCOLUMN}
            // Ammo slot number column
            if usesDefaultSlotKeys then
                // F1, F2, F3, F4, ...
                tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar('F'+IntToStr(i+1)), cWhiteColorChannels)
            else
                // 1, 2, 3, 4, ...
                tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(IntToStr(i+1)), cWhiteColorChannels);
            copyToXY(tmpsurf, amSurface,
                     x + AMSlotPadding + (AMSlotSize shr 1) - (tmpsurf^.w shr 1),
                     y + AMSlotPadding + (AMSlotSize shr 1) - (tmpsurf^.h shr 1));

            SDL_FreeSurface(tmpsurf);
    {$IFDEF USE_LANDSCAPE_AMMOMENU}
            y:= AMRect.y + AMSlotSize + 1;
    {$ELSE}
            x:= AMRect.x + AMSlotSize + 1;
    {$ENDIF}
{$ENDIF}


            for t:=0 to cMaxSlotAmmoIndex do
                begin
                if (Ammo^[i, t].Count > 0)  and (Ammo^[i, t].AmmoType <> amNothing) then
                    begin
                    STurns:= Ammoz[Ammo^[i, t].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber;
                    AMFrame:= LongInt(Ammo^[i,t].AmmoType) - 1;
                    if STurns >= 0 then //weapon not usable yet, draw grayed out with turns remaining
                        begin
                        DrawSpriteFrame2Surf(sprAMAmmosBW, amSurface, x + AMSlotPadding,
                                                                 y + AMSlotPadding, AMFrame);
                        if STurns < 100 then
                            DrawSpriteFrame2Surf(sprTurnsLeft, amSurface,
                                x + AMSlotSize-16,
                                y + AMSlotSize + 1 - 16, STurns);
                        end
                    else //draw colored version
                        begin
                        DrawSpriteFrame2Surf(sprAMAmmos, amSurface, x + AMSlotPadding,
                                                               y + AMSlotPadding, AMFrame);
                        end;
{$IFDEF USE_LANDSCAPE_AMMOMENU}
        inc(y, AMSlotSize + 1); //the plus one is for the border
{$ELSE}
        inc(x, AMSlotSize + 1);
{$ENDIF}
        end;
    end;
{$IFDEF USE_LANDSCAPE_AMMOMENU}
    inc(x, AMSlotSize + 1);
{$ELSE}
    inc(y, AMSlotSize + 1);
{$ENDIF}
    end;

for i:= 1 to SlotsNumX -1 do
DrawLine2Surf(amSurface, i * (AMSlotSize+1)+1, BORDERSIZE, i * (AMSlotSize+1)+1, AMRect.h + BORDERSIZE - AMSlotSize - 2,160,160,160);
for i:= 1 to SlotsNumY -1 do
DrawLine2Surf(amSurface, BORDERSIZE, i * (AMSlotSize+1)+1, AMRect.w + BORDERSIZE, i * (AMSlotSize+1)+1,160,160,160);

//draw outer border
DrawSpriteFrame2Surf(sprAMCorners, amSurface, 0                    , 0                    , 0);
DrawSpriteFrame2Surf(sprAMCorners, amSurface, AMRect.w + BORDERSIZE, AMRect.y             , 1);
DrawSpriteFrame2Surf(sprAMCorners, amSurface, AMRect.x             , AMRect.h + BORDERSIZE, 2);
DrawSpriteFrame2Surf(sprAMCorners, amSurface, AMRect.w + BORDERSIZE, AMRect.h + BORDERSIZE, 3);

for i:=0 to BORDERSIZE-1 do
begin
DrawLine2Surf(amSurface, BORDERSIZE, i, AMRect.w + BORDERSIZE, i,160,160,160);//top
DrawLine2Surf(amSurface, BORDERSIZE, AMRect.h+BORDERSIZE+i, AMRect.w + BORDERSIZE, AMRect.h+BORDERSIZE+i,160,160,160);//bottom
DrawLine2Surf(amSurface, i, BORDERSIZE, i, AMRect.h + BORDERSIZE,160,160,160);//left
DrawLine2Surf(amSurface, AMRect.w+BORDERSIZE+i, BORDERSIZE, AMRect.w + BORDERSIZE+i, AMRect.h + BORDERSIZE, 160,160,160);//right
end;

GetAmmoMenuTexture:= Surface2Tex(amSurface, false);
if amSurface <> nil then SDL_FreeSurface(amSurface);
end;

procedure ShowAmmoMenu;
const BORDERSIZE = 2;
var Slot, Pos: LongInt;
    Ammo: PHHAmmo;
    c,i,g,t,STurns: LongInt;
begin
if TurnTimeLeft = 0 then bShowAmmoMenu:= false;

// give the assigned ammo to hedgehog
Ammo:= nil;
if (CurrentTeam <> nil) and (CurrentHedgehog <> nil)
and (not CurrentTeam^.ExtDriven) and (CurrentHedgehog^.BotLevel = 0) then
    Ammo:= CurrentHedgehog^.Ammo
else if (LocalAmmo <> -1) then
    Ammo:= GetAmmoByNum(LocalAmmo);
Pos:= -1;
if Ammo = nil then
    begin
    bShowAmmoMenu:= false;
    AMState:= AMHidden;
    exit
    end;

//Init the menu
if(AmmoMenuInvalidated) then
    begin
    AmmoMenuInvalidated:= false;
    FreeAndNilTexture(AmmoMenuTex);
    AmmoMenuTex:= GetAmmoMenuTexture(Ammo);

{$IFDEF USE_LANDSCAPE_AMMOMENU}
    if isPhone() then
        begin
        AmmoRect.x:= -(AmmoRect.w shr 1);
        AmmoRect.y:= (cScreenHeight shr 1) - (AmmoRect.h shr 1);
        end
    else
        begin
        AmmoRect.x:= -(AmmoRect.w shr 1);
        AmmoRect.y:= cScreenHeight - (AmmoRect.h + AMSlotSize);
        end;
{$ELSE}
        AmmoRect.x:= (cScreenWidth shr 1) - AmmoRect.w - AMSlotSize;
        AmmoRect.y:= cScreenHeight - (AmmoRect.h + AMSlotSize);
{$ENDIF}
    if AMState <> AMShowing then
        begin
        AMShiftTargetX:= (cScreenWidth shr 1) - AmmoRect.x;
        AMShiftTargetY:= cScreenHeight        - AmmoRect.y;

        if (AMAnimType and AMTypeMaskX) <> 0 then AMShiftTargetX:= (cScreenWidth shr 1) - AmmoRect.x
        else AMShiftTargetX:= 0;
        if (AMAnimType and AMTypeMaskY) <> 0 then AMShiftTargetY:= cScreenHeight        - AmmoRect.y
        else AMShiftTargetY:= 0;

        AMShiftX:= AMShiftTargetX;
        AMShiftY:= AMShiftTargetY
        end
end;

AMAnimState:= (RealTicks - AMAnimStartTime) / AMAnimDuration;

if AMState = AMShowing then
    begin
    FollowGear:=nil;
    end;

if AMState = AMShowingUp then // show ammo menu
    begin
    // No "appear" animation in low quality or playing with very short turn time.
    if ((cReducedQuality and rqSlowMenu) <> 0) or (cHedgehogTurnTime <= 10000) then
        begin
        AMShiftX:= 0;
        AMShiftY:= 0;
        CursorPoint.X:= AmmoRect.x + AmmoRect.w - 3;
        CursorPoint.Y:= cScreenHeight - AmmoRect.y - amNumOffsetY - 1;
        AMState:= AMShowing;
        end
    // "Appear" animation
    else
        if AMAnimState < 1 then
            begin
            AMShiftX:= Round(AMShiftTargetX * (1 - AMAnimState));
            AMShiftY:= Round(AMShiftTargetY * (1 - AMAnimState));
            if (AMAnimType and AMTypeMaskAlpha) <> 0 then
                Tint($FF, $ff, $ff, Round($ff * AMAnimState));
            end
        else
            begin
            AMShiftX:= 0;
            AMShiftY:= 0;
            CursorPoint.X:= AmmoRect.x + AmmoRect.w - 3;
            CursorPoint.Y:= cScreenHeight - AmmoRect.y - amNumOffsetY - 1;
            AMState:= AMShowing;
            end;
    end;
if AMState = AMHiding then // hide ammo menu
    begin
    // No "disappear" animation (see above)
    if ((cReducedQuality and rqSlowMenu) <> 0) or (cHedgehogTurnTime <= 10000) then
        begin
        AMShiftX:= AMShiftTargetX;
        AMShiftY:= AMShiftTargetY;
        prevPoint:= CursorPoint;
        AMState:= AMHidden;
        end
    // "Disappear" animation
    else
        if AMAnimState < 1 then
            begin
            AMShiftX:= Round(AMShiftTargetX * AMAnimState);
            AMShiftY:= Round(AMShiftTargetY * AMAnimState);
            if (AMAnimType and AMTypeMaskAlpha) <> 0 then
                Tint($FF, $ff, $ff, Round($ff * (1-AMAnimState)));
            end
         else
            begin
            AMShiftX:= AMShiftTargetX;
            AMShiftY:= AMShiftTargetY;
            prevPoint:= CursorPoint;
            AMState:= AMHidden;
            end;
    end;

DrawTexture(AmmoRect.x + AMShiftX, AmmoRect.y + AMShiftY, AmmoMenuTex);

if ((AMState = AMHiding) or (AMState = AMShowingUp)) and ((AMAnimType and AMTypeMaskAlpha) <> 0 )then
    untint;

Pos:= -1;
Slot:= -1;
{$IFDEF USE_LANDSCAPE_AMMOMENU}
c:= -1;
    for i:= 0 to cMaxSlotIndex do
        if (i <> cHiddenSlotIndex) and (Ammo^[i, 0].Count > 0) then
            begin
            inc(c);
    {$IFDEF USE_AM_NUMCOLUMN}
            g:= 1;
    {$ELSE}
            g:= 0;
    {$ENDIF}
            for t:=0 to cMaxSlotAmmoIndex do
                if (Ammo^[i, t].Count > 0) and (Ammo^[i, t].AmmoType <> amNothing) then
                    begin
                    if (CursorPoint.Y <= (cScreenHeight - AmmoRect.y) - ( g    * (AMSlotSize+1))) and
                       (CursorPoint.Y >  (cScreenHeight - AmmoRect.y) - ((g+1) * (AMSlotSize+1))) and
                       (CursorPoint.X >  AmmoRect.x                   + ( c    * (AMSlotSize+1))) and
                       (CursorPoint.X <= AmmoRect.x                   + ((c+1) * (AMSlotSize+1))) then
                        begin
                        Slot:= i;
                        Pos:= t;
                        STurns:= Ammoz[Ammo^[i, t].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber;
                        if (STurns < 0) and (AMShiftX = 0) and (AMShiftY = 0) then
                            DrawSprite(sprAMSlot,
                                       AmmoRect.x + BORDERSIZE + (c * (AMSlotSize+1)) + AMSlotPadding,
                                       AmmoRect.y + BORDERSIZE + (g  * (AMSlotSize+1)) + AMSlotPadding -1, 0);
                        end;
                        inc(g);
                   end;
            end;
{$ELSE}
c:= -1;
    for i:= 0 to cMaxSlotIndex do
        if (i <> cHiddenSlotIndex) and (Ammo^[i, 0].Count > 0) then
            begin
            inc(c);
    {$IFDEF USE_AM_NUMCOLUMN}
            g:= 1;
    {$ELSE}
            g:= 0;
    {$ENDIF}
            for t:=0 to cMaxSlotAmmoIndex do
                if (Ammo^[i, t].Count > 0) and (Ammo^[i, t].AmmoType <> amNothing) then
                    begin
                    if (CursorPoint.Y <= (cScreenHeight - AmmoRect.y) - ( c    * (AMSlotSize+1))) and
                       (CursorPoint.Y >  (cScreenHeight - AmmoRect.y) - ((c+1) * (AMSlotSize+1))) and
                       (CursorPoint.X >  AmmoRect.x                   + ( g    * (AMSlotSize+1))) and
                       (CursorPoint.X <= AmmoRect.x                   + ((g+1) * (AMSlotSize+1))) then
                        begin
                        Slot:= i;
                        Pos:= t;
                        STurns:= Ammoz[Ammo^[i, t].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber;
                        if (STurns < 0) and (AMShiftX = 0) and (AMShiftY = 0) then
                            DrawSprite(sprAMSlot,
                                       AmmoRect.x + BORDERSIZE + (g * (AMSlotSize+1)) + AMSlotPadding,
                                       AmmoRect.y + BORDERSIZE + (c  * (AMSlotSize+1)) + AMSlotPadding -1, 0);
                        end;
                        inc(g);
                   end;
            end;
{$ENDIF}
    if (Pos >= 0) and (Pos <= cMaxSlotAmmoIndex) and (Slot >= 0) and (Slot <= cMaxSlotIndex) and (Slot <> cHiddenSlotIndex) then
        begin
        if (AMShiftX = 0) and (AMShiftY = 0) then
        if (Ammo^[Slot, Pos].Count > 0) and (Ammo^[Slot, Pos].AmmoType <> amNothing) then
            begin
            if (amSel <> Ammo^[Slot, Pos].AmmoType) or (WeaponTooltipTex = nil) then
                begin
                amSel:= Ammo^[Slot, Pos].AmmoType;
                RenderWeaponTooltip(amSel)
                end;

            DrawTexture(AmmoRect.x + (AMSlotSize shr 1),
                        AmmoRect.y + AmmoRect.h - BORDERSIZE - (AMSlotSize shr 1) - (Ammoz[Ammo^[Slot, Pos].AmmoType].NameTex^.h shr 1),
                        Ammoz[Ammo^[Slot, Pos].AmmoType].NameTex);
            if Ammo^[Slot, Pos].Count < AMMO_INFINITE then
                DrawTexture(AmmoRect.x + AmmoRect.w - 20 - (CountTexz[Ammo^[Slot, Pos].Count]^.w),
                            AmmoRect.y + AmmoRect.h - BORDERSIZE - (AMslotSize shr 1) - (CountTexz[Ammo^[Slot, Pos].Count]^.h shr 1),
                            CountTexz[Ammo^[Slot, Pos].Count]);

            if bSelected and (Ammoz[Ammo^[Slot, Pos].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber < 0) then
                begin
                bShowAmmoMenu:= false;
                SetWeapon(Ammo^[Slot, Pos].AmmoType);
                bSelected:= false;
                FreeAndNilTexture(WeaponTooltipTex);
                updateTouchWidgets(Ammo^[Slot, Pos].AmmoType);
                exit
                end;
            end
        end
    else
        FreeAndNilTexture(WeaponTooltipTex);

    if (WeaponTooltipTex <> nil) and (AMShiftX = 0) and (AMShiftY = 0) then
{$IFDEF USE_LANDSCAPE_AMMOMENU}
        if (not isPhone()) then
            ShowWeaponTooltip(-WeaponTooltipTex^.w div 2, AmmoRect.y - WeaponTooltipTex^.h - AMSlotSize);
{$ELSE}
        ShowWeaponTooltip(AmmoRect.x - WeaponTooltipTex^.w - 3, Min(AmmoRect.y + 1, cScreenHeight - WeaponTooltipTex^.h - 40));
{$ENDIF}

    bSelected:= false;
{$IFNDEF USE_TOUCH_INTERFACE}
   if (AMShiftX = 0) and (AMShiftY = 0) then
        DrawSprite(sprArrow, CursorPoint.X, cScreenHeight - CursorPoint.Y, (RealTicks shr 6) mod 8);
{$ENDIF}
end;

procedure DrawRepeated(spr, sprL, sprR: TSprite; Shift, OffsetY: LongInt);
var i, w, h, lw, lh, rw, rh, sw: LongInt;
begin
sw:= round(cScreenWidth / cScaleFactor);
if (SpritesData[sprL].Texture = nil) and (SpritesData[spr].Texture <> nil) then
    begin
    w:= SpritesData[spr].Width * SpritesData[spr].Texture^.Scale;
    h:= SpritesData[spr].Height * SpritesData[spr].Texture^.Scale;
    i:= Shift mod w;
    if i > 0 then
        dec(i, w);
    dec(i, w * (sw div w + 1));
    repeat
    DrawTexture(i, WorldDy + LAND_HEIGHT + OffsetY - h, SpritesData[spr].Texture, SpritesData[spr].Texture^.Scale);
    inc(i, w)
    until i > sw
    end
else if SpritesData[spr].Texture <> nil then
    begin
    w:= SpritesData[spr].Width * SpritesData[spr].Texture^.Scale;
    h:= SpritesData[spr].Height * SpritesData[spr].Texture^.Scale;
    lw:= SpritesData[sprL].Width * SpritesData[spr].Texture^.Scale;
    lh:= SpritesData[sprL].Height * SpritesData[spr].Texture^.Scale;
    if SpritesData[sprR].Texture <> nil then
        begin
        rw:= SpritesData[sprR].Width * SpritesData[spr].Texture^.Scale;
        rh:= SpritesData[sprR].Height * SpritesData[spr].Texture^.Scale
        end;
    dec(Shift, w div 2);
    DrawTexture(Shift, WorldDy + LAND_HEIGHT + OffsetY - h, SpritesData[spr].Texture, SpritesData[spr].Texture^.Scale);

    i:= Shift - lw;
    while i >= -sw - lw do
        begin
        DrawTexture(i, WorldDy + LAND_HEIGHT + OffsetY - lh, SpritesData[sprL].Texture, SpritesData[sprL].Texture^.Scale);
        dec(i, lw);
        end;

    i:= Shift + w;
    if SpritesData[sprR].Texture <> nil then
        while i <= sw do
            begin
            DrawTexture(i, WorldDy + LAND_HEIGHT + OffsetY - rh, SpritesData[sprR].Texture, SpritesData[sprR].Texture^.Scale);
            inc(i, rw)
            end
    else
        while i <= sw do
            begin
            DrawTexture(i, WorldDy + LAND_HEIGHT + OffsetY - lh, SpritesData[sprL].Texture, SpritesData[sprL].Texture^.Scale);
            inc(i, lw)
            end
    end
end;

// Force camera to stay within a certain area
procedure CameraBounds;
var lowBound: LongInt;
begin
if (not hasBorder) then
    begin
    if WorldDy > (-(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - TopY + cCamLimitY) then
        WorldDy:= (-trunc(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - TopY + cCamLimitY);
    if (RightX - LeftX + cCamLimitX * 2) div 2 < cScreenWidth / cScaleFactor then
        WorldDx:= -((LeftX + RightX) div 2)
    else
        begin
        if WorldDx < -LAND_WIDTH - cCamLimitX + (cScreenWidth / cScaleFactor) then
            WorldDx:= -LAND_WIDTH - cCamLimitX + trunc(cScreenWidth / cScaleFactor);
        if WorldDx > cCamLimitX - (cScreenWidth / cScaleFactor) then
            WorldDx:= cCamLimitX - trunc(cScreenWidth / cScaleFactor);
        end;
    end
else
    begin
    if WorldDy > (-(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - TopY + cCamLimitBorderY) then
        WorldDy:= (-trunc(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - TopY + cCamLimitBorderY);
    if (RightX - LeftX + cCamLimitBorderX * 2) div 2 < cScreenWidth / cScaleFactor then
        WorldDx:= -((LeftX + RightX) div 2)
    else
        begin
        if WorldDx > -LeftX + cCamLimitBorderX - (cScreenWidth / cScaleFactor) then
            WorldDx:= -LeftX + cCamLimitBorderX - trunc(cScreenWidth / cScaleFactor);
        if WorldDx < -RightX - cCamLimitBorderX + (cScreenWidth / cScaleFactor) then
            WorldDx:= -RightX - cCamLimitBorderX  + trunc(cScreenWidth / cScaleFactor);
        end;
    end;

lowBound:= trunc(cScreenHeight / cScaleFactor) + cScreenHeight div 2 - cWaterLine - (cVisibleWater + trunc(CinematicBarH / (cScaleFactor / 2.0)));
if WorldDy < lowBound then
    WorldDy:= lowBound;
end;

procedure DrawWorld(Lag: LongInt);
begin
    if ZoomValue < zoom then
    begin
        zoom:= zoom - 0.002 * Lag;
        if ZoomValue > zoom then
            zoom:= ZoomValue
    end
    else
        if ZoomValue > zoom then
        begin
        zoom:= zoom + 0.002 * Lag;
        if ZoomValue < zoom then
            zoom:= ZoomValue
        end
    else
        ZoomValue:= zoom;

    if (not isPaused) and (not isAFK) and (GameType <> gmtRecord) then
        MoveCamera
    else if (isPaused) then
        CameraBounds;

    if cStereoMode = smNone then
        begin
        RenderClear();
        DrawWorldStereo(Lag, rmDefault)
{$IFDEF USE_S3D_RENDERING}
        end
    else
        begin
        // draw frame for left eye
        RenderClear(rmLeftEye);
        DrawWorldStereo(Lag, rmLeftEye);

        // draw frame for right eye
        RenderClear(rmRightEye);
        DrawWorldStereo(0, rmRightEye);
{$ENDIF}
        end;

FinishRender();
end;

procedure RenderWorldEdge;
var
    tmp, w: LongInt;
    rect: TSDL_Rect;
begin
if (WorldEdge <> weNone) and (WorldEdge <> weSea) then
    begin
(* I think for a bounded world, will fill the left and right areas with black or something. Also will probably want various border effects/animations based on border type.  Prob also, say, trigger a border animation timer on an impact. *)

    rect.y:= ViewTopY;
    rect.h:= ViewHeight;
    tmp:= leftX + WorldDx;
    w:= tmp - ViewLeftX;

    if w > 0 then
        begin
        rect.w:= w;
        rect.x:= ViewLeftX;
        DrawRect(rect, $10, $10, $10, $80, true);
        if WorldEdge = weBounce then
            DrawLineOnScreen(tmp - 1, ViewTopY, tmp - 1, ViewBottomY, 2, $54, $54, $FF, $FF);
        end;

    tmp:= rightX + WorldDx;
    w:= ViewRightX - tmp;

    if w > 0 then
        begin
        rect.w:= w;
        rect.x:= tmp;
        DrawRect(rect, $10, $10, $10, $80, true);
        if WorldEdge = weBounce then
            DrawLineOnScreen(tmp - 1, ViewTopY, tmp - 1, ViewBottomY, 2, $54, $54, $FF, $FF);
        end;

    end;
end;


procedure RenderTeamsHealth;
var t, i, h, v, smallScreenOffset, TeamHealthBarWidth : LongInt;
    r: TSDL_Rect;
    highlight: boolean;
    hasVisibleHog: boolean;
    htex: PTexture;
begin
if VisibleTeamsCount * 20 > Longword(cScreenHeight) div 7 then  // take up less screen on small displays
    begin
    SetScale(1.5);
    smallScreenOffset:= cScreenHeight div 6;
    if VisibleTeamsCount * 100 > Longword(cScreenHeight) then
        Tint($FF,$FF,$FF,$80);
    end
else smallScreenOffset:= 0;
v:= 0; // for updating VisibleTeamsCount
for t:= 0 to Pred(TeamsCount) do
    with TeamsArray[t]^ do
      begin
      hasVisibleHog:= false;
      for i:= 0 to cMaxHHIndex do
          if (Hedgehogs[i].Gear <> nil) then
              hasVisibleHog:= true;
      if (TeamHealth > 0) and hasVisibleHog then
        begin
        // count visible teams
        inc(v);
        highlight:= bShowFinger and (CurrentTeam = TeamsArray[t]) and ((RealTicks mod 1000) < 500);

        if highlight then
            begin
            Tint(Clan^.Color shl 8 or $FF);
            htex:= GenericHealthTexture
            end
        else
            htex:= Clan^.HealthTex;

        // draw owner
        if OwnerTex <> nil then
            DrawTexture(-OwnerTex^.w - NameTagTex^.w - 18, cScreenHeight + DrawHealthY + smallScreenOffset, OwnerTex);

        // draw name
        DrawTexture(-NameTagTex^.w - 16, cScreenHeight + DrawHealthY + smallScreenOffset, NameTagTex);

        // draw flag
        DrawTexture(-14, cScreenHeight + DrawHealthY + smallScreenOffset, FlagTex);

        TeamHealthBarWidth:= cTeamHealthWidth * TeamHealthBarHealth div MaxTeamHealth;

        // draw team health bar
        r.x:= 0;
        r.y:= 0;
        r.w:= 2 + TeamHealthBarWidth;
        r.h:= htex^.h;
        DrawTextureFromRect(14, cScreenHeight + DrawHealthY + smallScreenOffset, @r, htex);

        // draw health bar's right border
        inc(r.x, cTeamHealthWidth + 2);
        r.w:= 3;
        DrawTextureFromRect(TeamHealthBarWidth + 15, cScreenHeight + DrawHealthY + smallScreenOffset, @r, htex);

        // draw hedgehog health separators in team health bar
        h:= 0;
        if not hasGone then
            for i:= 0 to cMaxHHIndex do
                begin
                inc(h, Hedgehogs[i].HealthBarHealth);
                if (h < TeamHealthBarHealth) and (Hedgehogs[i].HealthBarHealth > 0) then
                    if (IsTooDarkToRead(Clan^.Color)) then
                        DrawTexture(15 + h * TeamHealthBarWidth div TeamHealthBarHealth, cScreenHeight + DrawHealthY + smallScreenOffset + 1, SpritesData[sprSlider].Texture)
                    else
                        DrawTexture(15 + h * TeamHealthBarWidth div TeamHealthBarHealth, cScreenHeight + DrawHealthY + smallScreenOffset + 1, SpritesData[sprSliderInverted].Texture);
                end;

        // draw Lua value, if set
        if (hasLuaTeamValue) then
            DrawTexture(TeamHealthBarWidth + 22, cScreenHeight + DrawHealthY + smallScreenOffset, LuaTeamValueTex)
        // otherwise, draw AI kill counter for gfAISurvival
        else if (GameFlags and gfAISurvival) <> 0 then
            DrawTexture(TeamHealthBarWidth + 22, cScreenHeight + DrawHealthY + smallScreenOffset, AIKillsTex);

        // if highlighted, draw flag and other contents again to keep their colors
        // this approach should be faster than drawing all borders one by one tinted or not
        if highlight then
            begin
            if VisibleTeamsCount * 100 > Longword(cScreenHeight) then
                Tint($FF,$FF,$FF,$80)
            else untint;

            // draw name
            r.x:= 2;
            r.y:= 2;
            r.w:= NameTagTex^.w - 4;
            r.h:= NameTagTex^.h - 4;
            DrawTextureFromRect(-NameTagTex^.w - 14, cScreenHeight + DrawHealthY + smallScreenOffset + 2, @r, NameTagTex);

            if OwnerTex <> nil then
                begin
                r.w:= OwnerTex^.w - 4;
                r.h:= OwnerTex^.h - 4;
                DrawTextureFromRect(-OwnerTex^.w - NameTagTex^.w - 16, cScreenHeight + DrawHealthY + smallScreenOffset + 2, @r, OwnerTex)
                end;

            if (hasLuaTeamValue) then
                begin
                r.w:= LuaTeamValueTex^.w - 4;
                r.h:= LuaTeamValueTex^.h - 4;
                DrawTextureFromRect(TeamHealthBarWidth + 24, cScreenHeight + DrawHealthY + smallScreenOffset + 2, @r, LuaTeamValueTex);
                end
            else if (GameFlags and gfAISurvival) <> 0 then
                begin
                r.w:= AIKillsTex^.w - 4;
                r.h:= AIKillsTex^.h - 4;
                DrawTextureFromRect(TeamHealthBarWidth + 24, cScreenHeight + DrawHealthY + smallScreenOffset + 2, @r, AIKillsTex);
                end;

            // draw flag
            r.w:= 22;
            r.h:= 15;
            DrawTextureFromRect(-12, cScreenHeight + DrawHealthY + smallScreenOffset + 2, @r, FlagTex);
            end
        // draw an arrow next to active team
        else if (CurrentTeam = TeamsArray[t]) and (TurnTimeLeft > 0) then
            begin
            h:= -NameTagTex^.w - 24;
            if OwnerTex <> nil then
                h:= h - OwnerTex^.w - 4;
            if (IsTooDarkToRead(TeamsArray[t]^.Clan^.Color)) then
                DrawSpriteRotatedF(sprFingerBackInv, h, cScreenHeight + DrawHealthY + smallScreenOffset + 2 + SpritesData[sprFingerBackInv].Width div 4, 0, 1, -90)
            else
                DrawSpriteRotatedF(sprFingerBack, h, cScreenHeight + DrawHealthY + smallScreenOffset + 2 + SpritesData[sprFingerBack].Width div 4, 0, 1, -90);
            Tint(TeamsArray[t]^.Clan^.Color shl 8 or $FF);
            DrawSpriteRotatedF(sprFinger, h, cScreenHeight + DrawHealthY + smallScreenOffset + 2 + SpritesData[sprFinger].Width div 4, 0, 1, -90);
            untint;
            end;
        end;
      end;
if smallScreenOffset <> 0 then
    begin
    SetScale(cDefaultZoomLevel);
    if VisibleTeamsCount * 20 > Longword(cScreenHeight) div 5 then
        untint;
    end;
VisibleTeamsCount:= v;
end;

procedure RenderAttackBar();
var i: LongInt;
    tdx, tdy: Double;
begin
    if CurrentTeam <> nil then
        case AttackBar of
        2: with CurrentHedgehog^ do
                begin
                tdx:= hwSign(Gear^.dX) * Sin(Gear^.Angle * Pi / cMaxAngle);
                tdy:= - Cos(Gear^.Angle * Pi / cMaxAngle);
                for i:= (Gear^.Power * 24) div cPowerDivisor downto 0 do
                    DrawSprite(sprPower,
                            hwRound(Gear^.X) + GetLaunchX(CurAmmoType, hwSign(Gear^.dX), Gear^.Angle) + LongInt(round(WorldDx + tdx * (24 + i * 2))) - 16,
                            hwRound(Gear^.Y) + GetLaunchY(CurAmmoType, Gear^.Angle) + LongInt(round(WorldDy + tdy * (24 + i * 2))) - 16,
                            i)
                end;
        end;
end;

var preShiftWorldDx: LongInt;

procedure ShiftWorld(Dir: LongInt); inline;
begin
    preShiftWorldDx:= WorldDx;
    WorldDx:= WorldDx + LongInt(Dir * LongInt(playWidth));

end;

procedure UnshiftWorld(); inline;
begin
    WorldDx:= preShiftWorldDx;
end;

procedure DrawWorldStereo(Lag: LongInt; RM: TRenderMode);
var i, t: LongInt;
    spr: TSprite;
    r: TSDL_Rect;
    s: shortstring;
    offsetX, offsetY, screenBottom: LongInt;
    replicateToLeft, replicateToRight, isNotHiddenByCinematic: boolean;
{$IFDEF USE_VIDEO_RECORDING}
    a: Byte;
{$ENDIF}
begin
if WorldEdge <> weWrap then
    begin
    replicateToLeft := false;
    replicateToRight:= false;
    end
else
    begin
    replicateToLeft := (leftX  + WorldDx > ViewLeftX);
    replicateToRight:= (rightX + WorldDx < ViewRightX);
    end;

ScreenBottom:= (WorldDy - trunc(cScreenHeight/cScaleFactor) - (cScreenHeight div 2) + cWaterLine);

// note: offsetY is negative!
offsetY:= 10 *  Min(0, -145 - ScreenBottom); // TODO limit this in the other direction too

// Sky and horizont
if (cReducedQuality and rqNoBackground) = 0 then
    begin
        // Offsets relative to camera - spare them to wimpier cpus, no bg or flakes for them anyway
        SkyOffset:= offsetY div 35 + cWaveHeight;
        HorizontOffset:= SkyOffset;
        if ScreenBottom > SkyOffset then
            HorizontOffset:= HorizontOffset + ((ScreenBottom-SkyOffset) div 20);

        // background
        ChangeDepth(RM, cStereo_Sky);
        if SuddenDeathDmg then
            Tint(SDTint.r, SDTint.g, SDTint.b, SDTint.a);
        DrawRepeated(sprSky, sprSkyL, sprSkyR, (WorldDx + LAND_WIDTH div 2) * 3 div 8, SkyOffset);
        ChangeDepth(RM, -cStereo_Horizon);
        DrawRepeated(sprHorizont, sprHorizontL, sprHorizontR, (WorldDx + LAND_WIDTH div 2) * 3 div 5, HorizontOffset);
        if SuddenDeathDmg then
            untint;
    end;

DrawVisualGears(0, false);
ChangeDepth(RM, -cStereo_MidDistance);
DrawVisualGears(4, false);

if (cReducedQuality and rq2DWater) = 0 then
    begin
        // Waves
        DrawWater(255, SkyOffset, 0);
        ChangeDepth(RM, -cStereo_Water_distant);
        DrawWaves( 1,  0 - WorldDx div 32, offsetY div 35, -49, 64);
        ChangeDepth(RM, -cStereo_Water_distant);
        DrawWaves( -1,  25 + WorldDx div 25, offsetY div 38, -37, 48);
        ChangeDepth(RM, -cStereo_Water_distant);
        DrawWaves( 1,  75 - WorldDx div 19, offsetY div 45, -23, 32);
        ChangeDepth(RM, -cStereo_Water_distant);
        DrawWaves(-1, 100 + WorldDx div 14, offsetY div 70, -7, 24);
    end
else
    DrawWaves(-1, 100, - cWaveHeight div 2, - cWaveHeight div 2, 0);

ChangeDepth(RM, cStereo_Land);
DrawVisualGears(5, false);
DrawLand(WorldDx, WorldDy);

if replicateToLeft then
    begin
    ShiftWorld(-1);
    DrawLand(WorldDx, WorldDy);
    UnshiftWorld();
    end;

if replicateToRight then
    begin
    ShiftWorld(1);
    DrawLand(WorldDx, WorldDy);
    UnshiftWorld();
    end;

DrawWater(255, 0, 0);

if replicateToLeft then
    begin
    ShiftWorld(-1);
    DrawVisualGears(1, true);
    DrawGears();
    DrawVisualGears(6, true);
    UnshiftWorld();
    end;

if replicateToRight then
    begin
    ShiftWorld(1);
    DrawVisualGears(1, true);
    DrawGears();
    DrawVisualGears(6, true);
    UnshiftWorld();
    end;

DrawVisualGears(1, false);
DrawGears;
DrawVisualGears(6, false);


if SuddenDeathDmg then
    DrawWater(SDWaterOpacity, 0, 0)
else
    DrawWater(WaterOpacity, 0, 0);

// Waves
ChangeDepth(RM, cStereo_Water_near);
DrawWaves( 1, 25 - WorldDx div 9, 0, 0, 12);

if (cReducedQuality and rq2DWater) = 0 then
    begin
    ChangeDepth(RM, cStereo_Water_near);
    DrawWaves(-1, 50 + WorldDx div 6, - offsetY div 40, 23, 8);
    if SuddenDeathDmg then
        DrawWater(SDWaterOpacity, - offsetY div 20, 23)
    else
        DrawWater(WaterOpacity, - offsetY div 20, 23);
    ChangeDepth(RM, cStereo_Water_near);
    DrawWaves( 1, 75 - WorldDx div 4, - offsetY div 20, 37, 2);
        if SuddenDeathDmg then
            DrawWater(SDWaterOpacity, - offsetY div 10, 47)
        else
            DrawWater(WaterOpacity, - offsetY div 10, 47);
        ChangeDepth(RM, cStereo_Water_near);
        DrawWaves( -1, 25 + WorldDx div 3, - offsetY div 10, 59, 0);
        end
    else
        DrawWaves(-1, 50, cWaveHeight div 2, cWaveHeight div 2, 0);

// line at airplane height for certain airstrike types (when spawning height is important)
with CurrentHedgehog^ do
    if (isCursorVisible) and ((CurAmmoType = amNapalm) or (CurAmmoType = amMineStrike) or (((GameFlags and gfMoreWind) <> 0) and ((CurAmmoType = amDrillStrike) or (CurAmmoType = amAirAttack)))) then
        DrawLine(-cCamLimitX, topY-300, LAND_WIDTH + cCamLimitX, topY-300, 3.0, (Team^.Clan^.Color shr 16), (Team^.Clan^.Color shr 8) and $FF, Team^.Clan^.Color and $FF, $FF);

// gear HUD extras (fuel indicator, secondary ammo, etc.)
if replicateToLeft then
    begin
    ShiftWorld(-1);
    DrawGearsGui();
    UnshiftWorld();
    end;

if replicateToRight then
    begin
    ShiftWorld(1);
    DrawGearsGui();
    UnshiftWorld();
    end;

DrawGearsGui();

// Finger (arrow pointing to hedgehog).
// NOT wrapped like the other stuff because it might be confusing.
DrawFinger();

// everything after this ChangeDepth will be drawn outside the screen
// note: negative parallax gears should last very little for a smooth stereo effect
    ChangeDepth(RM, cStereo_Outside);

    if replicateToLeft then
        begin
        ShiftWorld(-1);
        DrawVisualGears(2, true);
        UnshiftWorld();
        end;

    if replicateToRight then
        begin
        ShiftWorld(1);
        DrawVisualGears(2, true);
        UnshiftWorld();
        end;

    DrawVisualGears(2, false);

// everything after this ResetDepth will be drawn at screen level (depth = 0)
// note: everything that needs to be readable should be on this level
    ResetDepth(RM);

    if replicateToLeft then
        begin
        ShiftWorld(-1);
        DrawVisualGears(3, true);
        UnshiftWorld();
        end;

    if replicateToRight then
        begin
        ShiftWorld(1);
        DrawVisualGears(3, true);
        UnshiftWorld();
        end;

    DrawVisualGears(3, false);

// Target (e.g. air attack, bee, ...)
if (TargetPoint.X <> NoPointX) and (CurrentTeam <> nil) and (CurrentHedgehog <> nil) then
    begin
    with PHedgehog(CurrentHedgehog)^ do
        begin
        if CurAmmoType = amBee then
            spr:= sprTargetBee
        else
            spr:= sprTargetP;
        if replicateToLeft then
            begin
            ShiftWorld(-1);
            if spr = sprTargetP then
                begin
                if IsTooDarkToRead(Team^.Clan^.Color) then
                    DrawSpriteRotatedF(sprTargetPBackInv, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
                else
                    DrawSpriteRotatedF(sprTargetPBack, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
                Tint(Team^.Clan^.Color shl 8 or $FF);
                end;
            DrawSpriteRotatedF(spr, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
            if spr = sprTargetP then
                untint;
            UnshiftWorld();
            end;

        if replicateToRight then
            begin
            ShiftWorld(1);
            if spr = sprTargetP then
                begin
                if IsTooDarkToRead(Team^.Clan^.Color) then
                    DrawSpriteRotatedF(sprTargetPBackInv, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
                else
                    DrawSpriteRotatedF(sprTargetPBack, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
                Tint(Team^.Clan^.Color shl 8 or $FF);
                end;
            DrawSpriteRotatedF(spr, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
            if spr = sprTargetP then
                untint;
            UnshiftWorld();
            end;

        if spr = sprTargetP then
            begin
            if IsTooDarkToRead(Team^.Clan^.Color) then
                DrawSpriteRotatedF(sprTargetPBackInv, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360)
            else
                DrawSpriteRotatedF(sprTargetPBack, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
            Tint(Team^.Clan^.Color shl 8 or $FF);
            end;
        DrawSpriteRotatedF(spr, TargetPoint.X + WorldDx, TargetPoint.Y + WorldDy, 0, 0, (RealTicks shr 3) mod 360);
        if spr = sprTargetP then
            untint;
        end;
    end;

// Attack bar
if replicateToLeft then
    begin
    ShiftWorld(-1);
    RenderAttackBar();
    UnshiftWorld();
    end;

if replicateToRight then
    begin
    ShiftWorld(1);
    RenderAttackBar();
    UnshiftWorld();
    end;

RenderAttackBar();

// World edge
RenderWorldEdge();

// This scale is used to keep the various widgets at the same dimension at all zoom levels
SetScale(cDefaultZoomLevel);

isNotHiddenByCinematic:= true;
// Cinematic Mode: Determine effects and state
if CinematicScript or (InCinematicMode and autoCameraOn
    and ((CurrentHedgehog = nil) or CurrentHedgehog^.Team^.ExtDriven
    or (CurrentHedgehog^.BotLevel <> 0) or (GameType = gmtDemo))) then
    begin
    if CinematicSteps < 300 then
        begin
        inc(CinematicSteps, Lag);
        if CinematicSteps > 300 then
            begin
            CinematicSteps:= 300;
            isNotHiddenByCinematic:= false;
            end;
        end;
    end
else if CinematicSteps > 0 then
    begin
    dec(CinematicSteps, Lag);
    if CinematicSteps < 0 then
        CinematicSteps:= 0;
    end;

// Turn time
if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
    begin
{$IFDEF USE_TOUCH_INTERFACE}
    offsetX:= cScreenHeight - 13;
{$ELSE}
    offsetX:= 48;
{$ENDIF}
    offsetY:= cOffsetY;
    if ((TurnTimeLeft <> 0) and (TurnTimeLeft < 999000)) or (ReadyTimeLeft <> 0) then
        begin
        if ReadyTimeLeft <> 0 then
            i:= Succ(Pred(ReadyTimeLeft) div 1000)
        else
            i:= Succ(Pred(TurnTimeLeft) div 1000);

        if i>99 then
            t:= 112
        else if i>9 then
            t:= 96
        else
            t:= 80;
        DrawSprite(sprFrame, -(cScreenWidth shr 1) + t + offsetY, cScreenHeight - offsetX, 1);
        while i > 0 do
            begin
            dec(t, 32);
            if isPaused or (not IsClockRunning()) then
                spr := sprBigDigitGray
            else if (ReadyTimeLeft <> 0) then
                spr := sprBigDigitGreen
            else if IsGetAwayTime then
                spr := sprBigDigitRed
            else
                spr := sprBigDigit;
            DrawSprite(spr, -(cScreenWidth shr 1) + t + offsetY, cScreenHeight - offsetX, i mod 10);
            i:= i div 10
            end;
        DrawSprite(sprFrame, -(cScreenWidth shr 1) + t - 4 + offsetY, cScreenHeight - offsetX, 0);
        end;

    end;

// Team bars
if (UIDisplay = uiAll) and (isNotHiddenByCinematic) then
    RenderTeamsHealth;

// Current hedgehog health in top left corner
if ((UIDisplay = uiAll) or (UIDisplay = uiNoTeams)) and (isNotHiddenByCinematic) and
        (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear <> nil) and
        (CurrentHedgehog^.HealthTagTex <> nil) and
        ((CurrentHedgehog^.Gear^.State and gstHHDriven) <> 0) then
    begin
    t:= 11;
    i:= t;
{$IFDEF USE_TOUCH_INTERFACE}
    i:= t + pauseButton.frame.y + pauseButton.frame.h;
{$ENDIF}

    // Hide health and healh icons in gfInvulnerable mode (except heResurrectable)
    if ((GameFlags and gfInvulnerable) = 0) then
    begin
    // Health tag
    DrawTexture(cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - 16, i, CurrentHedgehog^.HealthTagTex);
    inc(t, CurrentHedgehog^.HealthTagTex^.h);
    cDemoClockFPSOffsetY:= t;

    t:= SpritesData[sprHealthHud].Width + 18;
    // Main health icon. Appearance depends on game mode and poisoning state
    if ((GameFlags and gfResetHealth) = 0) then
        if (CurrentHedgehog^.Effects[hePoisoned] <> 0) then
            DrawSprite(sprHealthPoisonHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
        else
            DrawSprite(sprHealthHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
    else
        if (CurrentHedgehog^.Effects[hePoisoned] <> 0) then
            DrawSprite(sprMedicPoisonHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
        else
            DrawSprite(sprMedicHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0);
    // Put halo above health icon for resurrectable hog
    if (CurrentHedgehog^.Effects[heResurrectable] <> 0) then
        DrawSprite(sprHaloHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t - 2), i - SpritesData[sprHaloHud].Height + 1, 0);

    // Additional health-related states
    inc(t, 2);
    // Invulnerable
    if (CurrentHedgehog^.Effects[heInvulnerable] <> 0) then
        begin
        inc(t, SpritesData[sprInvulnHud].Width + 2);
        DrawSprite(sprInvulnHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
        end
    // Karma
    else if ((GameFlags and gfKarma) <> 0) then
        begin
        inc(t, SpritesData[sprKarmaHud].Width + 2);
        DrawSprite(sprKarmaHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0)
        end;
    // Vampirism
    if cVampiric then
        begin
        inc(t, SpritesData[sprVampHud].Width + 2);
        DrawSprite(sprVampHud, (cScreenWidth div 2 - CurrentHedgehog^.HealthTagTex^.w - t), i, 0);
        end;
    end
    // in gfInvulnerable mode ...
    else
        begin
        DrawSprite(sprInvulnHud, cScreenWidth div 2 - 28, i, 0);
        if (CurrentHedgehog^.Effects[heResurrectable] <> 0) then
            // show halo for resurrectable hog
            DrawSprite(sprHaloHud, cScreenWidth div 2 - 30, i - SpritesData[sprHaloHud].Height + 1, 0);
        end;
    end
else
    cDemoClockFPSOffsetY:= 0;

// Wind bar
if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
    begin
{$IFDEF USE_TOUCH_INTERFACE}
    offsetX:= cScreenHeight - 13;
    offsetY:= (cScreenWidth shr 1) + 74;
{$ELSE}
    offsetX:= 30;
    offsetY:= 180;
{$ENDIF}
    DrawSprite(sprWindBar, (cScreenWidth shr 1) - offsetY, cScreenHeight - offsetX, 0);
    if WindBarWidth > 0 then
        begin
        {$WARNINGS OFF}
        r.x:= 8 - (RealTicks shr 6) mod 9;
        {$WARNINGS ON}
        r.y:= 0;
        r.w:= WindBarWidth;
        r.h:= 13;
        DrawSpriteFromRect(sprWindR, r, (cScreenWidth shr 1) - offsetY + 77, cScreenHeight - offsetX + 2, 13, 0);
        end
    else
        if WindBarWidth < 0 then
        begin
        {$WARNINGS OFF}
        r.x:= (Longword(WindBarWidth) + RealTicks shr 6) mod 9;
        {$WARNINGS ON}
        r.y:= 0;
        r.w:= - WindBarWidth;
        r.h:= 13;
        DrawSpriteFromRect(sprWindL, r, (cScreenWidth shr 1) - offsetY + 74 + WindBarWidth, cScreenHeight - offsetX + 2, 13, 0);
        end
    end;

// Indicators for global effects (extra damage, low gravity)
if (UIDisplay <> uiNone) and (isNotHiddenByCinematic) then
    begin
{$IFDEF USE_TOUCH_INTERFACE}
    offsetX:= (cScreenWidth shr 1) - 95;
    offsetY:= cScreenHeight - 21;
{$ELSE}
    offsetX:= 45;
    offsetY:= 51;
{$ENDIF}

    if cDamageModifier = _1_5 then
        begin
            DrawTextureF(ropeIconTex, 1, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, 0, 1, 32, 32);
            DrawTextureF(SpritesData[sprAMAmmos].Texture, 0.90, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, ord(amExtraDamage) - 1, 1, 32, 32);
{$IFDEF USE_TOUCH_INTERFACE}
            offsetX := offsetX - 33
{$ELSE}
            offsetX := offsetX + 33
{$ENDIF}
        end;
    if (cLowGravity) or ((GameFlags and gfLowGravity) <> 0) then
        begin
            DrawTextureF(ropeIconTex, 1, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, 0, 1, 32, 32);
            DrawTextureF(SpritesData[sprAMAmmos].Texture, 0.90, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, ord(amLowGravity) - 1, 1, 32, 32);
{$IFDEF USE_TOUCH_INTERFACE}
            offsetX := offsetX - 33
{$ELSE}
            offsetX := offsetX + 33
{$ENDIF}
        end;
    if cLaserSighting then
        begin
            DrawTextureF(ropeIconTex, 1, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, 0, 1, 32, 32);
            DrawTextureF(SpritesData[sprAMAmmos].Texture, 0.90, (cScreenWidth shr 1) - offsetX, cScreenHeight - offsetY, ord(amLaserSight) - 1, 1, 32, 32);
        end;
    end;

// Cinematic Mode: Render black bars
if CinematicSteps > 0 then
    begin
    r.x:= ViewLeftX;
    r.w:= ViewWidth;
    r.y:= ViewTopY;
    CinematicBarH:= (ViewHeight * CinematicSteps) div 2048;
    r.h:= CinematicBarH;
    DrawRect(r, 0, 0, 0, $FF, true);
    r.y:= ViewBottomY - r.h;
    DrawRect(r, 0, 0, 0, $FF, true);
    end;

// Touchscreen interface widgets
{$IFDEF USE_TOUCH_INTERFACE}
DrawScreenWidget(@arrowLeft);
DrawScreenWidget(@arrowRight);
DrawScreenWidget(@arrowUp);
DrawScreenWidget(@arrowDown);

DrawScreenWidget(@fireButton);
DrawScreenWidget(@jumpWidget);
DrawScreenWidget(@AMWidget);
DrawScreenWidget(@utilityWidget);
DrawScreenWidget(@utilityWidget2);
DrawScreenWidget(@pauseButton);
{$ENDIF}

// Captions
if UIDisplay <> uiNone then
    DrawCaptions;

// Lag alert
if isInLag then
    DrawSprite(sprLag, 32 - (cScreenWidth shr 1), 32, (RealTicks shr 7) mod 12);

// Chat
DrawChat;


// Mission panel
if not isFirstFrame and (missionTimer <> 0) or isShowMission or isPaused or fastUntilLag or (GameState = gsConfirm) then
    begin
    if (ReadyTimeLeft = 0) and (missionTimer > 0) then
        dec(missionTimer, Lag);
    if missionTimer < 0 then
        missionTimer:= 0; // avoid subtracting below 0
    if missionTex <> nil then
        DrawTextureCentered(0, Min((cScreenHeight shr 1) + 100, cScreenHeight - 48 - missionTex^.h), missionTex);
    end;
if missionTimer = 0 then
    isForceMission := false;

// AmmoMenu
if bShowAmmoMenu and ((AMState = AMHidden) or (AMState = AMHiding)) then
    begin
    if (AMState = AMHidden) then
        AMAnimStartTime:= RealTicks
    else
        AMAnimStartTime:= RealTicks - (AMAnimDuration - (RealTicks - AMAnimStartTime));
    AMState:= AMShowingUp;
    end;
if (not bShowAmmoMenu) and ((AMstate = AMShowing) or (AMState = AMShowingUp)) then
    begin
    if (AMState = AMShowing) then
        AMAnimStartTime:= RealTicks
    else
        AMAnimStartTime:= RealTicks - (AMAnimDuration - (RealTicks - AMAnimStartTime));
    AMState:= AMHiding;
    end;

if bShowAmmoMenu or (AMState = AMHiding) then
    ShowAmmoMenu;

// Centered status/menu messages (synchronizing, auto skip, pause, etc.)
if fastUntilLag then
    DrawTextureCentered(0, (cScreenHeight shr 1), SyncTexture)
else if isAFK then
    DrawTextureCentered(0, (cScreenHeight shr 1), AFKTexture)
else if isPaused then
    DrawTextureCentered(0, (cScreenHeight shr 1), PauseTexture);

// Cursor
if isCursorVisible and bShowAmmoMenu then
    DrawSprite(sprArrow, CursorPoint.X, cScreenHeight - CursorPoint.Y, (RealTicks shr 6) mod 8);

// FPS and demo replay time
{$IFDEF USE_TOUCH_INTERFACE}
offsetY:= cDemoClockFPSOffsetY + 10 + pauseButton.frame.y + pauseButton.frame.h;
{$ELSE}
offsetY:= cDemoClockFPSOffsetY + 10;
{$ENDIF}
offsetX:= cOffsetY;
if (RM = rmDefault) or (RM = rmRightEye) then
    begin
    inc(Frames);

    if cShowFPS or (GameType = gmtDemo) then
        inc(CountTicks, Lag);

    // Demo replay time
    if (GameType = gmtDemo) and (CountTicks >= 1000) then
        begin
        i:= GameTicks div 1000;
        t:= i mod 60;
        s:= inttostr(t);
        if t < 10 then
            s:= '0' + s;
        i:= i div 60;
        t:= i mod 60;
        s:= inttostr(t) + ':' + s;
        if t < 10 then
            s:= '0' + s;
        s:= inttostr(i div 60) + ':' + s;


        tmpSurface:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(s), cWhiteColorChannels);
        tmpSurface:= doSurfaceConversion(tmpSurface);
        FreeAndNilTexture(timeTexture);
        timeTexture:= Surface2Tex(tmpSurface, false);
        SDL_FreeSurface(tmpSurface)
        end;

    if (timeTexture <> nil) and (UIDisplay <> uiNone) then
        DrawTexture((cScreenWidth shr 1) - 20 - timeTexture^.w - offsetX, offsetY, timeTexture);

    // FPS counter
    if cShowFPS and (UIDisplay <> uiNone) then
        begin
        if CountTicks >= 1000 then
            begin
            FPS:= Frames;
            Frames:= 0;
            CountTicks:= 0;
            s:= Format(shortstring(trmsg[sidFPS]), inttostr(FPS));
            tmpSurface:= TTF_RenderUTF8_Blended(Fontz[CheckCJKFont(trmsg[sidFPS],fnt16)].Handle, Str2PChar(s), cWhiteColorChannels);
            tmpSurface:= doSurfaceConversion(tmpSurface);
            FreeAndNilTexture(fpsTexture);
            fpsTexture:= Surface2Tex(tmpSurface, false);
            SDL_FreeSurface(tmpSurface)
            end;
        if fpsTexture <> nil then
            begin
            if timeTexture <> nil then
                i:= fpsTexture^.h + 5
            else
                i:= 0;
            DrawTexture((cScreenWidth shr 1) - 20 - fpsTexture^.w - offsetX, offsetY + i, fpsTexture);
            end;
        end;
end;

// Quit Y/N question
if GameState = gsConfirm then
    DrawTextureCentered(0, (cScreenHeight shr 1)-40, ConfirmTexture);

if ScreenFade <> sfNone then
    begin
    if (not isFirstFrame) then
        case ScreenFade of
            sfToBlack, sfToWhite:     if ScreenFadeValue + Lag * ScreenFadeSpeed < sfMax then
                                          inc(ScreenFadeValue, Lag * ScreenFadeSpeed)
                                      else
                                          ScreenFadeValue:= sfMax;
            sfFromBlack, sfFromWhite: if ScreenFadeValue - Lag * ScreenFadeSpeed > 0 then
                                          dec(ScreenFadeValue, Lag * ScreenFadeSpeed)
                                      else
                                          ScreenFadeValue:= 0;
            end;
    if ScreenFade <> sfNone then
        begin
        r.x:= ViewLeftX;
        r.y:= ViewTopY;
        r.w:= ViewWidth;
        r.h:= ViewHeight;

        case ScreenFade of
            sfToBlack, sfFromBlack: DrawRect(r, 0, 0, 0, ScreenFadeValue * 255 div 1000, true);
            sfToWhite, sfFromWhite: DrawRect(r, $FF, $FF, $FF, ScreenFadeValue * 255 div 1000, true);
            end;

        if not isFirstFrame and ((ScreenFadeValue = 0) or (ScreenFadeValue = sfMax)) then
            ScreenFade:= sfNone
        end
    end;

{$IFDEF USE_VIDEO_RECORDING}
// During video prerecording draw red blinking circle and text 'rec'
if flagPrerecording then
    begin
    if recTexture = nil then
        begin
        s:= 'rec';
        tmpSurface:= TTF_RenderUTF8_Blended(Fontz[fntBig].Handle, Str2PChar(s), cWhiteColorChannels);
        tmpSurface:= doSurfaceConversion(tmpSurface);
        FreeAndNilTexture(recTexture);
        recTexture:= Surface2Tex(tmpSurface, false);
        SDL_FreeSurface(tmpSurface)
        end;
    DrawTexture( -(cScreenWidth shr 1) + 50, 20, recTexture);

    t:= -255 + ((RealTicks div 2) and 511);
    a:= Byte(min(255, abs(t)));

    // draw red circle
    DrawCircleFilled(-(cScreenWidth shr 1) + 30, 35, 10, $FF, $00, $00, a);
    end;
{$ENDIF}

SetScale(zoom);

// Cursor
if isCursorVisible and (not bShowAmmoMenu) then
    begin
    if not CurrentTeam^.ExtDriven then TargetCursorPoint:= CursorPoint;
    with CurrentHedgehog^ do
        if (Gear <> nil) and ((Gear^.State and gstChooseTarget) <> 0) then
            begin
        i:= GetCurAmmoEntry(CurrentHedgehog^)^.Pos;
        with Ammoz[CurAmmoType] do
            if PosCount > 0 then
                begin
                if (CurAmmoType = amGirder) or (CurAmmoType = amTeleport) then
                    begin
                // pulsating transparency
                    if ((GameTicks div 16) mod $80) >= $40 then
                        Tint($FF, $FF, $FF, $C0 - (GameTicks div 16) mod $40)
                    else
                        Tint($FF, $FF, $FF, $80 + (GameTicks div 16) mod $40);
                    end;
                DrawSprite(PosSprite, TargetCursorPoint.X - (SpritesData[PosSprite].Width shr 1), cScreenHeight - TargetCursorPoint.Y - (SpritesData[PosSprite].Height shr 1),i);
                Untint();
                if (WorldEdge = weWrap) and (CurAmmoType = amBee) then
                    begin
                    if (TargetCursorPoint.X - WorldDx > rightX) then
                        DrawSprite(sprThroughWrap, TargetCursorPoint.X - (SpritesData[sprThroughWrap].Width shr 1), cScreenHeight - TargetCursorPoint.Y - (SpritesData[PosSprite].Height shr 1) - SpritesData[sprThroughWrap].Height - 2, 0)
                    else if (TargetCursorPoint.X - WorldDx < leftX) then
                        DrawSprite(sprThroughWrap, TargetCursorPoint.X - (SpritesData[sprThroughWrap].Width shr 1), cScreenHeight - TargetCursorPoint.Y - (SpritesData[PosSprite].Height shr 1) - SpritesData[sprThroughWrap].Height - 2, 1);
                    end;
                end;
            end;
    DrawTextureF(SpritesData[sprArrow].Texture, cDefaultZoomLevel / cScaleFactor, TargetCursorPoint.X + round(SpritesData[sprArrow].Width / cScaleFactor), cScreenHeight + round(SpritesData[sprArrow].Height / cScaleFactor) - TargetCursorPoint.Y, (RealTicks shr 6) mod 8, 1, SpritesData[sprArrow].Width, SpritesData[sprArrow].Height);
    end;

// debug stuff
if cViewLimitsDebug then
    begin
    r.x:= ViewLeftX;
    r.y:= ViewTopY;
    r.w:= ViewWidth;
    r.h:= ViewHeight;
    DrawRect(r, 255, 0, 0, 128, false);
    end;

isFirstFrame:= false
end;

var PrevSentPointTime: LongWord = 0;

procedure MoveCamera;
var EdgesDist, shs,z, dstX: LongInt;
    inbtwnTrgtAttks: Boolean;
begin
{$IFNDEF MOBILE}
if (not (CurrentTeam^.ExtDriven and isCursorVisible and (not bShowAmmoMenu) and autoCameraOn)) and cHasFocus and (GameState <> gsConfirm) then
    uCursor.updatePosition();
{$ENDIF}
z:= round(200/zoom);
inbtwnTrgtAttks := ((GameFlags and gfInfAttack) <> 0) and (CurrentHedgehog <> nil) and ((CurrentHedgehog^.Gear = nil) or (CurrentHedgehog^.Gear <> FollowGear)) and ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NeedTarget) <> 0);
if autoCameraOn and (not PlacingHogs) and (FollowGear <> nil) and (not isCursorVisible) and (not bShowAmmoMenu) and (not fastUntilLag) and (not inbtwnTrgtAttks) then
    if ((abs(CursorPoint.X - prevPoint.X) + abs(CursorPoint.Y - prevpoint.Y)) > 4) then
        begin
        FollowGear:= nil;
        prevPoint:= CursorPoint;
        exit
        end
    else
        begin
            dstX:= hwRound(FollowGear^.X) + hwSign(FollowGear^.dX) * z + WorldDx;

            if (WorldEdge = weWrap) then
                begin
                    if dstX - prevPoint.X < (leftX - rightX) div 2 then
                        CursorPoint.X:= (prevPoint.X * 7 + dstX - (leftX - rightX)) div 8
                    else if dstX - prevPoint.X > (rightX - leftX) div 2 then
                        CursorPoint.X:= (prevPoint.X * 7 + dstX - (rightX - leftX)) div 8
                    else
                        CursorPoint.X:= (prevPoint.X * 7 + dstX) div 8;
                end
            else // usual camera movement routine
                begin
                    CursorPoint.X:= (prevPoint.X * 7 + dstX) div 8;
                end;

        if isPhone() or (cScreenHeight < 600) or (hwFloat(FollowGear^.dY * z).Round < 10) then
            CursorPoint.Y:= (prevPoint.Y * 7 + cScreenHeight - (hwRound(FollowGear^.Y) + WorldDy)) div 8
        else
            CursorPoint.Y:= (prevPoint.Y * 7 + cScreenHeight - (hwRound(FollowGear^.Y) + hwSign(FollowGear^.dY) * z + WorldDy)) div 8;
        end;

if (WorldEdge = weWrap) then
    begin
        if -WorldDx < leftX then
            WorldDx:= WorldDx - rightX + leftX
        else if -WorldDx > rightX then
            WorldDx:= WorldDx + rightX - leftX;
    end;

if ((CursorPoint.X = prevPoint.X) and (CursorPoint.Y = prevpoint.Y)) then
    begin
    CameraBounds;
    exit;
    end;

if (AMState = AMShowingUp) or (AMState = AMShowing) then
begin
    if CursorPoint.X < AmmoRect.x + amNumOffsetX + 3 then//check left
        CursorPoint.X:= AmmoRect.x + amNumOffsetX + 3;
    if CursorPoint.X > AmmoRect.x + AmmoRect.w - 3 then//check right
        CursorPoint.X:= AmmoRect.x + AmmoRect.w - 3;
    if CursorPoint.Y > cScreenHeight - AmmoRect.y -amNumOffsetY - 1 then//check top
        CursorPoint.Y:= cScreenHeight - AmmoRect.y - amNumOffsetY - 1;
    if CursorPoint.Y < cScreenHeight - (AmmoRect.y + AmmoRect.h - AMSlotSize - 5) then//check bottom
        CursorPoint.Y:= cScreenHeight - (AmmoRect.y + AmmoRect.h - AMSlotSize - 5);
    prevPoint:= CursorPoint;
    CameraBounds;
    exit
end;

if isCursorVisible then
    begin
    if (not CurrentTeam^.ExtDriven) and (GameTicks >= PrevSentPointTime + cSendCursorPosTime) then
        begin
        SendIPCXY('P', CursorPoint.X - WorldDx, cScreenHeight - CursorPoint.Y - WorldDy);
        PrevSentPointTime:= GameTicks
        end;
    EdgesDist:= cCursorEdgesDist
    end
else
    EdgesDist:= cGearScrEdgesDist;

// this generates the border around the screen that moves the camera when cursor is near it
if (CurrentTeam^.ExtDriven and isCursorVisible and autoCameraOn) or
   (not CurrentTeam^.ExtDriven and isCursorVisible) or ((FollowGear <> nil) and autoCameraOn) then
    begin
    if CursorPoint.X < - trunc(cScreenWidth / cScaleFactor) + EdgesDist then
        begin
        WorldDx:= WorldDx - CursorPoint.X - trunc(cScreenWidth / cScaleFactor) + EdgesDist;
        CursorPoint.X:= - trunc(cScreenWidth / cScaleFactor) + EdgesDist
        end
    else
        if CursorPoint.X > trunc(cScreenWidth / cScaleFactor) - EdgesDist then
            begin
            WorldDx:= WorldDx - CursorPoint.X + trunc(cScreenWidth / cScaleFactor) - EdgesDist;
            CursorPoint.X:= trunc(cScreenWidth / cScaleFactor) - EdgesDist
            end;

    shs:= min(cScreenHeight div 2 - trunc(cScreenHeight / cScaleFactor) + EdgesDist, cScreenHeight - EdgesDist);
    if CursorPoint.Y < shs then
        begin
        WorldDy:= WorldDy + CursorPoint.Y - shs;
        CursorPoint.Y:= shs;
        end
    else
        if (CursorPoint.Y > cScreenHeight - EdgesDist) then
            begin
           WorldDy:= WorldDy + CursorPoint.Y - cScreenHeight + EdgesDist;
           CursorPoint.Y:= cScreenHeight - EdgesDist
            end;
    end
else
    if cHasFocus then
        begin
        WorldDx:= WorldDx - CursorPoint.X + prevPoint.X;
        WorldDy:= WorldDy + CursorPoint.Y - prevPoint.Y;
        CursorPoint.X:= 0;
        CursorPoint.Y:= cScreenHeight div 2;
        end;

// this moves the camera according to CursorPoint X and Y
prevPoint:= CursorPoint;

// enforce camera bounds
CameraBounds();

end;

procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt);
begin
    ShowMission(caption, subcaption, text, icon, time, false);
end;

procedure ShowMission(caption, subcaption, text: ansistring; icon, time : LongInt; forceDisplay : boolean);
var r: TSDL_Rect;
begin
if cOnlyStats then exit;

r.w:= 32;
r.h:= 32;

// If true, then mission panel cannot be hidden by releasing the mission panel key.
// Is in effect until timer runs out, is hidden with HideMission or ShowMission is called with forceDisplay=false.
isForceMission := forceDisplay;

if time = 0 then
    time:= 5000;
missionTimer:= time;
FreeAndNilTexture(missionTex);

if icon > -1 then
    begin
    r.x:= 0;
    r.y:= icon * 32;
    missionTex:= RenderHelpWindow(caption, subcaption, text, ansistring(''), 0, MissionIcons, @r)
    end
else
    begin
    r.x:= ((-icon - 1) shr 4) * 32;
    r.y:= ((-icon - 1) mod 16) * 32;
    missionTex:= RenderHelpWindow(caption, subcaption, text, ansistring(''), 0, SpritesData[sprAMAmmos].Surface, @r)
    end;
end;

procedure HideMission;
begin
    missionTimer:= 0;
    isForceMission:= false;
end;

procedure SetAmmoTexts(ammoType: TAmmoType; name: ansistring; caption: ansistring; description: ansistring; autoLabels: boolean);
var
    ammoStrId: TAmmoStrId;
    ammoStr: ansistring;
    tmpsurf: PSDL_Surface;
begin
    if cOnlyStats then exit;
    
    ammoStrId := Ammoz[ammoType].NameId;

    trluaammo[ammoStrId] := name;
    if length(trluaammo[ammoStrId]) > 0 then
        ammoStr:= trluaammo[ammoStrId]
    else
        ammoStr:= trammo[ammoStrId];

    if checkFails(length(ammoStr) > 0,'No default text/translation found for ammo type #' + intToStr(ord(ammoType)) + '!',true) then exit;

    tmpsurf:= TTF_RenderUTF8_Blended(Fontz[CheckCJKFont(ammoStr,fnt16)].Handle, PChar(ammoStr), cWhiteColorChannels);
    if checkFails(tmpsurf <> nil,'Name-texture creation for ammo type #' + intToStr(ord(ammoType)) + ' failed!',true) then exit;
    tmpsurf:= doSurfaceConversion(tmpsurf);
    FreeAndNilTexture(Ammoz[ammoType].NameTex);
    Ammoz[ammoType].NameTex:= Surface2Tex(tmpsurf, false);
    SDL_FreeSurface(tmpsurf);

    trluaammoc[ammoStrId] := caption;
    trluaammod[ammoStrId] := description;
    trluaammoe[ammoStrId] := autoLabels;
end;

procedure ShakeCamera(amount: LongInt);
begin
if isCursorVisible then
    exit;
amount:= Max(1, round(amount*zoom/2));
WorldDx:= WorldDx - amount + LongInt(random(1 + amount * 2));
WorldDy:= WorldDy - amount + LongInt(random(1 + amount * 2));
end;


procedure onFocusStateChanged;
begin
{$IFDEF MOBILE}
if (not cHasFocus) and (not isPaused) then
    ParseCommand('pause', true);
// when created SDL receives an exposure event that calls UndampenAudio at full power, muting audio
exit;
{$ENDIF}
if (not cHasFocus) and (GameState <> gsConfirm) then
    ParseCommand('quit', true);

{$IFDEF USE_VIDEO_RECORDING}
// do not change volume during prerecording as it will affect sound in video file
if (not flagPrerecording) then
{$ENDIF}
    begin
    if (not cHasFocus) then DampenAudio()
    else UndampenAudio();
    end;
end;

procedure updateCursorVisibility;
begin
    if isPaused or isAFK or (GameState = gsConfirm) then
        begin
{$IFNDEF USE_TOUCH_INTERFACE}
        SDL_SetRelativeMouseMode(SDL_FALSE);
{$ENDIF}
        if SDL_ShowCursor(SDL_QUERY) = SDL_DISABLE then
            begin
            uCursor.resetPosition;
{$IFNDEF USE_TOUCH_INTERFACE}
            SDL_ShowCursor(SDL_ENABLE);
{$ENDIF}
            end;
        end
    else
        begin
        uCursor.resetPositionDelta;
{$IFNDEF USE_TOUCH_INTERFACE}
        SDL_ShowCursor(SDL_DISABLE);
        SDL_SetRelativeMouseMode(SDL_TRUE);
{$ENDIF}
        end;
end;

procedure updateTouchWidgets(ammoType: TAmmoType);
begin
{$IFDEF USE_TOUCH_INTERFACE}
//show the aiming buttons + animation
if (Ammoz[ammoType].Ammo.Propz and ammoprop_NeedUpDown) <> 0 then
    begin
    if (not arrowUp.show) then
        begin
        animateWidget(@arrowUp, true, true);
        animateWidget(@arrowDown, true, true);
        end;
    end
else
    if arrowUp.show then
        begin
        animateWidget(@arrowUp, true, false);
        animateWidget(@arrowDown, true, false);
        end;
SetUtilityWidgetState(ammoType);
{$ELSE}
ammoType:= ammoType; // avoid hint
{$ENDIF}
end;

procedure SetUtilityWidgetState(ammoType: TAmmoType);
begin
{$IFDEF USE_TOUCH_INTERFACE}
if(ammoType = amNothing)then
    ammoType:= CurrentHedgehog^.CurAmmoType;

if(CurrentHedgehog <> nil)then
    if ((Ammoz[ammoType].Ammo.Propz and ammoprop_Timerable) <> 0) and (ammoType <> amDrillStrike) then
        begin
        utilityWidget.sprite:= sprTimerButton;
        if (not utilityWidget.show) then
            animateWidget(@utilityWidget, true, true);
        end
    else if (Ammoz[ammoType].Ammo.Propz and ammoprop_NeedTarget) <> 0 then
        begin
        utilityWidget.sprite:= sprTargetButton;
        if (not utilityWidget.show) then
            animateWidget(@utilityWidget, true, true);
        end
    else if ammoType = amSwitch then
        begin
        utilityWidget.sprite:= sprSwitchButton;
        if (not utilityWidget.show) then
            animateWidget(@utilityWidget, true, true);
        end
    else if utilityWidget.show then
        animateWidget(@utilityWidget, true, false);

    if ((Ammoz[ammoType].Ammo.Propz and ammoprop_SetBounce) <> 0) then
        begin
        utilityWidget2.sprite:= sprBounceButton;
        if (not utilityWidget2.show) then
            animateWidget(@utilityWidget2, true, true);
        end
    else if utilityWidget2.show then
        animateWidget(@utilityWidget2, true, false);
{$ELSE}
ammoType:= ammoType; // avoid hint
{$ENDIF}
end;

procedure animateWidget(widget: POnScreenWidget; fade, showWidget: boolean);
begin
with widget^ do
    begin
    show:= showWidget;
    if fade then fadeAnimStart:= RealTicks;

    with moveAnim do
        begin
        animate:= true;
        startTime:= RealTicks;
        source.x:= source.x xor target.x; //swap source <-> target
        target.x:= source.x xor target.x;
        source.x:= source.x xor target.x;
        source.y:= source.y xor target.y;
        target.y:= source.y xor target.y;
        source.y:= source.y xor target.y;
        end;
    end;
end;


procedure initModule;
begin
    fpsTexture:= nil;
    recTexture:= nil;
    FollowGear:= nil;
    WindBarWidth:= 0;
    bShowAmmoMenu:= false;
    bSelected:= false;
    bShowFinger:= false;
    Frames:= 0;
    WorldDx:= -512;
    WorldDy:= -256;
    PrevSentPointTime:= 0;

    FPS:= 0;
    CountTicks:= 0;
    SoundTimerTicks:= 0;
    prevPoint.X:= 0;
    prevPoint.Y:= 0;
    missionTimer:= 0;
    missionTex:= nil;
    cOffsetY:= 0;
    AMState:= AMHidden;
    isFirstFrame:= true;

    FillChar(WorldFade, sizeof(WorldFade), 0);
    WorldFade[0].a:= 255;
    WorldFade[1].a:= 255;
    FillChar(WorldEnd, sizeof(WorldEnd), 0);
    WorldEnd[0].a:= 255;
    WorldEnd[1].a:= 255;
    WorldEnd[2].a:= 255;
    WorldEnd[3].a:= 255;

    AmmoMenuTex:= nil;
    AmmoMenuInvalidated:= true
end;

procedure freeModule;
begin
    ResetWorldTex();
end;

end.