hedgewars/uVisualGears.pas
author unc0rr
Wed, 10 Apr 2013 22:48:14 +0400
changeset 8897 d6c310c65c91
parent 8370 0c79946e96f8
child 8444 75db7bb8dce8
child 9080 9b42757d7e71
permissions -rw-r--r--
- Revert server workaround over desync from r98e2dbdda8c0 - Don't allow more than 48 hedgehogs

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 *)

{$INCLUDE "options.inc"}

unit uVisualGears;
(*
 * This unit defines the behavior and the appearance of visual gears.
 *
 * Visual gears are "things"/"objects" in the game that do not need to be
 * perfectly synchronized over all clients since their effect is only
 * of visual nature.
 *
 * E.g.: background flakes, visual effects: explosion, smoke trails, etc.
 *)
interface
uses uConsts, uFloat, GLunit, uTypes, uWorld;

procedure initModule;
procedure freeModule;

function  AddVisualGear(X, Y: LongInt; Kind: TVisualGearType): PVisualGear; inline;
function  AddVisualGear(X, Y: LongInt; Kind: TVisualGearType; State: LongWord): PVisualGear; inline;
function  AddVisualGear(X, Y: LongInt; Kind: TVisualGearType; State: LongWord; Critical: Boolean): PVisualGear;

procedure ProcessVisualGears(Steps: Longword);
procedure DrawVisualGears(Layer: LongWord);
procedure DeleteVisualGear(Gear: PVisualGear);
function  VisualGearByUID(uid : Longword) : PVisualGear;

procedure AddClouds;
procedure AddFlakes;
procedure AddDamageTag(X, Y, Damage, Color: LongWord);

procedure ChangeToSDClouds;
procedure ChangeToSDFlakes;

procedure KickFlakes(Radius, X, Y: LongInt);

implementation
uses uSound, uVariables, uTextures, uRender, Math, uRenderUtils, uStore, uUtils;

const 
    cExplFrameTicks = 110;
    //cSmokeZ = 499;
var VGCounter: LongWord;
    VisualGearLayers: array[0..6] of PVisualGear;

// For better maintainability the step handlers of visual gears are stored
// in a separate file.
{$INCLUDE "VGSHandlers.inc"}

procedure AddDamageTag(X, Y, Damage, Color: LongWord);
var s: shortstring;
    Gear: PVisualGear;
begin
if cAltDamage then
    begin
    Gear:= AddVisualGear(X, Y, vgtSmallDamageTag);
    if Gear <> nil then
        with Gear^ do
            begin
            str(Damage, s);
            Tex:= RenderStringTex(s, Color, fntSmall);
            end
    end
end;


// ==================================================================

// ==================================================================
const doStepHandlers: array[TVisualGearType] of TVGearStepProcedure =
        (
            @doStepFlake,
            @doStepCloud,
            @doStepExpl,
            @doStepExpl,
            @doStepFire,
            @doStepSmallDamage,
            @doStepTeamHealthSorter,
            @doStepSpeechBubble,
            @doStepBubble,
            @doStepSteam,
            @doStepAmmo,
            @doStepSmoke,
            @doStepSmoke,
            @doStepShell,
            @doStepDust,
            @doStepSplash,
            @doStepDroplet,
            @doStepSmokeRing,
            @doStepBeeTrace,
            @doStepEgg,
            @doStepFeather,
            @doStepHealthTag,
            @doStepSmokeTrace,
            @doStepSmokeTrace,
            @doStepExplosion,
            @doStepBigExplosion,
            @doStepChunk,
            @doStepNote,
            @doStepLineTrail,
            @doStepBulletHit,
            @doStepCircle,
            @doStepSmoothWindBar,
            @doStepStraightShot
        );

function AddVisualGear(X, Y: LongInt; Kind: TVisualGearType): PVisualGear; inline;
begin
    AddVisualGear:= AddVisualGear(X, Y, Kind, 0, false);
end;

function  AddVisualGear(X, Y: LongInt; Kind: TVisualGearType; State: LongWord): PVisualGear; inline;
begin
    AddVisualGear:= AddVisualGear(X, Y, Kind, State, false);
end;

function AddVisualGear(X, Y: LongInt; Kind: TVisualGearType; State: LongWord; Critical: Boolean): PVisualGear;
var gear: PVisualGear;
    t: Longword;
    sp: real;
begin
AddVisualGear:= nil;
if ((GameType = gmtSave) or (fastUntilLag and (GameType = gmtNet)) or fastScrolling) and // we are scrolling now
   ((Kind <> vgtCloud) and (not Critical)) then
       exit;

if ((cReducedQuality and rqAntiBoom) <> 0) and
   (not Critical) and
   (not (Kind in
   [vgtTeamHealthSorter,
    vgtSmallDamageTag,
    vgtSpeechBubble,
    vgtHealthTag,
    vgtExplosion,
    vgtSmokeTrace,
    vgtEvilTrace,
    vgtNote,
    vgtSmoothWindBar])) then
    
        exit;

inc(VGCounter);
New(gear);
FillChar(gear^, sizeof(TVisualGear), 0);
gear^.X:= real(X);
gear^.Y:= real(Y);
gear^.Kind := Kind;
gear^.doStep:= doStepHandlers[Kind];
gear^.State:= 0;
gear^.Tint:= $FFFFFFFF;
gear^.uid:= VGCounter;
gear^.Layer:= 0;

with gear^ do
    case Kind of
    vgtFlake:
                begin
                Timer:= 0;
                tdX:= 0;
                tdY:= 0;
                Scale:= 1.0;
                if SuddenDeathDmg then
                    begin
                    FrameTicks:= random(vobSDFrameTicks);
                    Frame:= random(vobSDFramesCount);
                    end
                else
                    begin
                    FrameTicks:= random(vobFrameTicks);
                    Frame:= random(vobFramesCount);
                    end;
                Angle:= random(360);
                dx:= 0.0000038654705 * random(10000);
                dy:= 0.000003506096 * random(7000);
                if random(2) = 0 then
                    dx := -dx;
                if SuddenDeathDmg then
                    dAngle:= (random(2) * 2 - 1) * (vobSDVelocity + random(vobSDVelocity)) / 1000
                else
                    dAngle:= (random(2) * 2 - 1) * (vobVelocity + random(vobVelocity)) / 1000
                end;
    vgtCloud:
                begin
                Frame:= random(4);
                dx:= 0.5 + 0.1 * random(5); // how much the cloud will be affected by wind
                timer:= random(4096);
                Scale:= 1.0
                end;
    vgtExplPart,
    vgtExplPart2:
                begin
                t:= random(1024);
                sp:= 0.001 * (random(95) + 70);
                dx:= hwFloat2Float(AngleSin(t)) * sp;
                dy:= hwFloat2Float(AngleCos(t)) * sp;
                if random(2) = 0 then
                    dx := -dx;
                if random(2) = 0 then
                    dy := -dy;
                Frame:= 7 - random(3);
                FrameTicks:= cExplFrameTicks
                end;
        vgtFire:
                begin
                t:= random(1024);
                sp:= 0.001 * (random(85) + 95);
                dx:= hwFloat2Float(AngleSin(t)) * sp;
                dy:= hwFloat2Float(AngleCos(t)) * sp;
                if random(2) = 0 then
                    dx := -dx;
                if random(2) = 0 then
                    dy := -dy;
                FrameTicks:= 650 + random(250);
                Frame:= random(8)
                end;
         vgtEgg:
                begin
                t:= random(1024);
                sp:= 0.001 * (random(85) + 95);
                dx:= hwFloat2Float(AngleSin(t)) * sp;
                dy:= hwFloat2Float(AngleCos(t)) * sp;
                if random(2) = 0 then
                    dx := -dx;
                if random(2) = 0 then
                    dy := -dy;
                FrameTicks:= 650 + random(250);
                Frame:= 1
                end;
        vgtShell: FrameTicks:= 500;
    vgtSmallDamageTag:
                begin
                gear^.FrameTicks:= 1100
                end;
    vgtBubble:
                begin
                dx:= 0.0000038654705 * random(10000);
                dy:= 0;
                if random(2) = 0 then
                    dx := -dx;
                FrameTicks:= 250 + random(1751);
                Frame:= random(5)
                end;
    vgtSteam:
                begin
                dx:= 0.0000038654705 * random(10000);
                dy:= 0.001 * (random(85) + 95);
                if random(2) = 0 then
                    dx := -dx;
                Frame:= 7 - random(3);
                FrameTicks:= cExplFrameTicks * 2;
                end;
    vgtAmmo:
                begin
                alpha:= 1.0;
                scale:= 1.0
                end;
  vgtSmokeWhite,
  vgtSmoke:
                begin
                Scale:= 1.0;
                dx:= 0.0002 * (random(45) + 10);
                dy:= 0.0002 * (random(45) + 10);
                if random(2) = 0 then
                    dx := -dx;
                Frame:= 7 - random(2);
                FrameTicks:= cExplFrameTicks * 2;
                end;
  vgtDust:
                begin
                dx:= 0.005 * (random(15) + 10);
                dy:= 0.001 * (random(40) + 20);
                if random(2) = 0 then dx := -dx;
                if random(2) = 0 then Tag:= 1
                else Tag:= -1;
                Frame:= 7 - random(2);
                FrameTicks:= random(20) + 15;
                end;
  vgtSplash:
                begin
                dx:= 0;
                dy:= 0;
                FrameTicks:= 740;
                Frame:= 19;
                Scale:= 0.75;
                Timer:= 1;
                end;
    vgtDroplet:
                begin
                dx:= 0.001 * (random(180) - 90);
                dy:= -0.001 * (random(160) + 40);
                FrameTicks:= 250 + random(1751);
                Frame:= random(3)
                end;
   vgtBeeTrace:
                begin
                FrameTicks:= 1000;
                Frame:= random(16);
                end;
    vgtSmokeRing:
                begin
                dx:= 0;
                dy:= 0;
                FrameTicks:= 600;
                Timer:= 0;
                Frame:= 0;
                scale:= 0.6;
                alpha:= 1;
                angle:= random(360);
                end;
     vgtFeather:
                begin
                t:= random(1024);
                sp:= 0.001 * (random(85) + 95);
                dx:= hwFloat2Float(AngleSin(t)) * sp;
                dy:= hwFloat2Float(AngleCos(t)) * sp;
                if random(2) = 0 then
                    dx := -dx;
                if random(2) = 0 then
                    dy := -dy;
                FrameTicks:= 650 + random(250);
                Frame:= 1
                end;
  vgtHealthTag:
                begin
                Frame:= 0;
                Timer:= 1500;
                dY:= -0.08;
                dX:= 0;
                //gear^.Z:= 2002;
                end;
  vgtSmokeTrace,
  vgtEvilTrace:
                begin
                gear^.X:= gear^.X - 16;
                gear^.Y:= gear^.Y - 16;
                gear^.State:= 8;
                //gear^.Z:= cSmokeZ
                end;
vgtBigExplosion:
                begin
                gear^.Angle:= random(360);
                end;
      vgtChunk:
                begin
                gear^.Frame:= random(4);
                t:= random(1024);
                sp:= 0.001 * (random(85) + 47);
                dx:= hwFloat2Float(AngleSin(t)) * sp;
                dy:= hwFloat2Float(AngleCos(t)) * sp * -2;
                if random(2) = 0 then
                    dx := -dx;
                end;
      vgtNote: 
                begin
                dx:= 0.005 * (random(15) + 10);
                dy:= -0.001 * (random(40) + 20);
                if random(2) = 0 then
                    dx := -dx;
                Frame:= random(4);
                FrameTicks:= random(2000) + 1500;
                end;
  vgtBulletHit:
                begin
                dx:= 0;
                dy:= 0;
                FrameTicks:= 350;
                Frame:= 7;
                Angle:= 0;
                end;
vgtSmoothWindBar: 
                begin
                Angle:= hwFloat2Float(cMaxWindSpeed)*2 / 1440; // seems rate below is supposed to change wind bar at 1px per 10ms. Max time, 1440ms. This tries to match the rate of change
                Tag:= hwRound(cWindSpeed * 72 / cMaxWindSpeed);
                end;
 vgtStraightShot:
                begin
                Angle:= 0;
                Scale:= 1.0;
                dx:= 0.001 * random(45);
                dy:= 0.001 * (random(20) + 25);
                State:= ord(sprHealth);
                if random(2) = 0 then
                    dx := -dx;
                Frame:= 0;
                FrameTicks:= random(750) + 1250;
                State:= ord(sprSnowDust);
                end;
        end;

if State <> 0 then
    gear^.State:= State;

case Gear^.Kind of
    vgtFlake: if cFlattenFlakes then
        gear^.Layer:= 0
              else if random(3) = 0 then 
                  begin
                  gear^.Scale:= 0.5;
                  gear^.Layer:= 0   // 33% - far back
                  end
              else if random(3) = 0 then
                  begin
                  gear^.Scale:= 0.8;
                  gear^.Layer:= 4   // 22% - mid-distance
                  end
              else if random(3) <> 0 then
                  gear^.Layer:= 5  // 30% - just behind land
              else if random(2) = 0 then
                  gear^.Layer:= 6   // 7% - just in front of land
              else
                  begin
                  gear^.Scale:= 1.5;
                  gear^.Layer:= 2;  // 7% - close up
                  end;

    vgtCloud: if cFlattenClouds then gear^.Layer:= 5
              else if random(3) = 0 then
                  begin
                  gear^.Scale:= 0.25;
                  gear^.Layer:= 0
                  end
              else if random(2) = 0 then
                  gear^.Layer:= 5
              else
                  begin
                  gear^.Scale:= 0.4;
                  gear^.Layer:= 4
                  end;

    // 0: this layer is very distant in the background when in stereo
    vgtTeamHealthSorter,
    vgtSmoothWindBar: gear^.Layer:= 0;


    // 1: this layer is on the land level (which is close but behind the screen plane) when stereo
    vgtSmokeTrace,
    vgtEvilTrace,
    vgtLineTrail,
    vgtSmoke,
    vgtSmokeWhite,
    vgtDust,
    vgtFire,
    vgtSplash,
    vgtDroplet,
    vgtBubble: gear^.Layer:= 1;

    // 3: this layer is on the screen plane (depth = 0) when stereo
    vgtSpeechBubble,
    vgtSmallDamageTag,
    vgtHealthTag,
    vgtStraightShot,
    vgtChunk: gear^.Layer:= 3;

    // 2: this layer is outside the screen when stereo
    vgtExplosion,
    vgtBigExplosion,
    vgtExplPart,
    vgtExplPart2,
    vgtSteam,
    vgtAmmo,
    vgtShell,
    vgtFeather,
    vgtEgg,
    vgtBeeTrace,
    vgtSmokeRing,
    vgtNote,
    vgtBulletHit,
    vgtCircle: gear^.Layer:= 2
end;

if VisualGearLayers[gear^.Layer] <> nil then
    begin
    VisualGearLayers[gear^.Layer]^.PrevGear:= gear;
    gear^.NextGear:= VisualGearLayers[gear^.Layer]
    end;
VisualGearLayers[gear^.Layer]:= gear;

AddVisualGear:= gear;
end;

procedure DeleteVisualGear(Gear: PVisualGear);
begin
    FreeTexture(Gear^.Tex);
    Gear^.Tex:= nil;

    if Gear^.NextGear <> nil then
        Gear^.NextGear^.PrevGear:= Gear^.PrevGear;
    if Gear^.PrevGear <> nil then
        Gear^.PrevGear^.NextGear:= Gear^.NextGear
    else
        VisualGearLayers[Gear^.Layer]:= Gear^.NextGear;

    if lastVisualGearByUID = Gear then
        lastVisualGearByUID:= nil;

    Dispose(Gear);
end;

procedure ProcessVisualGears(Steps: Longword);
var Gear, t: PVisualGear;
    i: LongWord;
begin
if Steps = 0 then
    exit;

for i:= 0 to 6 do
    begin
    t:= VisualGearLayers[i];
    while t <> nil do
        begin
        Gear:= t;
        t:= Gear^.NextGear;
        Gear^.doStep(Gear, Steps)
        end;
    end
end;

procedure KickFlakes(Radius, X, Y: LongInt);
var Gear, t: PVisualGear;
    dmg, i: LongInt;
begin
if (vobCount = 0) or (vobCount > 200) then
    exit;
for i:= 2 to 6 do
    if i <> 3 then
        begin
        t:= VisualGearLayers[i];
        while t <> nil do
            begin
            Gear:= t;
            if Gear^.Kind = vgtFlake then
                begin
                // Damage calc from doMakeExplosion
                dmg:= Min(101, Radius + cHHRadius div 2 - LongInt(abs(round(Gear^.X) - X) + abs(round(Gear^.Y) - Y)) div 5);
                if dmg > 1 then
                    begin
                    Gear^.tdX:= 0.02 * dmg + 0.01;
                    if Gear^.X - X < 0 then
                        Gear^.tdX := -Gear^.tdX;
                    Gear^.tdY:= 0.02 * dmg + 0.01;
                    if Gear^.Y - Y < 0 then
                        Gear^.tdY := -Gear^.tdY;
                    Gear^.Timer:= 200
                    end
                end;
            t:= Gear^.NextGear
            end
        end
end;

procedure DrawVisualGears(Layer: LongWord);
var Gear: PVisualGear;
    tinted: boolean;
    tmp: real;
    i: LongInt;
begin
case Layer of
    // this layer is very distant in the background when stereo
    0: begin
        Gear:= VisualGearLayers[0];
        while Gear <> nil do
            begin
            if Gear^.Tint <> $FFFFFFFF then Tint(Gear^.Tint);
            case Gear^.Kind of
              vgtCloud: if SuddenDeathDmg then
                             DrawTextureF(SpritesData[sprSDCloud].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 2, SpritesData[sprCloud].Width, SpritesData[sprCloud].Height)
                         else
                            DrawTextureF(SpritesData[sprCloud].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 2, SpritesData[sprCloud].Width, SpritesData[sprCloud].Height);
               vgtFlake: if cFlattenFlakes then
                             begin
                             if SuddenDeathDmg then
                                 if vobSDVelocity = 0 then
                                     DrawSprite(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                                 else
                                     DrawSpriteRotatedF(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
                             else
                                 if vobVelocity = 0 then
                                     DrawSprite(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                                 else
                                     DrawSpriteRotatedF(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
                             end
                         else
                             begin
                             if SuddenDeathDmg then
                                 if vobSDVelocity = 0 then
                                     DrawTextureF(SpritesData[sprSDFlake].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height)
                                 else
                                     DrawTextureRotatedF(SpritesData[sprSDFlake].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height, Gear^.Angle)
                             else
                                 if vobVelocity = 0 then
                                     DrawTextureF(SpritesData[sprFlake].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height)
                                 else
                                     DrawTextureRotatedF(SpritesData[sprFlake].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height, Gear^.Angle)
                             end;
               end;
           if Gear^.Tint <> $FFFFFFFF then
               Tint($FF,$FF,$FF,$FF);
           Gear:= Gear^.NextGear
           end
       end;
    // this layer is on the land level (which is close but behind the screen plane) when stereo
    1: begin
       Gear:= VisualGearLayers[1];
       while Gear <> nil do
          begin
          //tinted:= false;
          if Gear^.Tint <> $FFFFFFFF then
              Tint(Gear^.Tint);
          case Gear^.Kind of
              vgtFlake: if SuddenDeathDmg then
                             if vobSDVelocity = 0 then
                                 DrawSprite(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                             else
                                 DrawSpriteRotatedF(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
                         else
                             if vobVelocity = 0 then
                                 DrawSprite(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                             else
                                 DrawSpriteRotatedF(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
              vgtSmokeTrace: if Gear^.State < 8 then
                  DrawSprite(sprSmokeTrace, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.State);
              vgtEvilTrace: if Gear^.State < 8 then
                  DrawSprite(sprEvilTrace, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.State);
              vgtLineTrail: DrawLine(Gear^.X, Gear^.Y, Gear^.dX, Gear^.dY, 1.0, $FF, min(Gear^.Timer, $C0), min(Gear^.Timer, $80), min(Gear^.Timer, $FF));
          end;
          if (cReducedQuality and rqAntiBoom) = 0 then
              case Gear^.Kind of
                  vgtSmoke: DrawTextureF(SpritesData[sprSmoke].Texture, Gear^.scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, 7 - Gear^.Frame, 1, SpritesData[sprSmoke].Width, SpritesData[sprSmoke].Height);
                  vgtSmokeWhite: DrawSprite(sprSmokeWhite, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame);
                  vgtDust: if Gear^.State = 1 then
                               DrawSpriteRotatedF(sprSnowDust, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame, Gear^.Tag, Gear^.Angle)
                           else
                               DrawSpriteRotatedF(sprDust, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame, Gear^.Tag, Gear^.Angle);
                  vgtFire: if (Gear^.State and gstTmpFlag) = 0 then
                               DrawSprite(sprFlame, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy, (RealTicks shr 6 + Gear^.Frame) mod 8)
                           else
                               DrawTextureF(SpritesData[sprFlame].Texture, Gear^.FrameTicks / 900, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, (RealTicks shr 7 + Gear^.Frame) mod 8, 1, 16, 16);
                  vgtSplash: if SuddenDeathDmg then
                                 //DrawSprite(sprSDSplash, round(Gear^.X) + WorldDx - 40, round(Gear^.Y) + WorldDy - 58, 19 - (Gear^.FrameTicks div 37))
                                 DrawTextureF(SpritesData[sprSDSplash].Texture, Gear^.scale, round(Gear^.X + WorldDx), round(Gear^.Y + WorldDy - ((SpritesData[sprSDSplash].Height+8)*Gear^.Scale)/2), 19 - (Gear^.FrameTicks div Gear^.Timer div 37), 1, SpritesData[sprSDSplash].Width, SpritesData[sprSDSplash].Height)
                             else
                                 //DrawSprite(sprSplash, round(Gear^.X) + WorldDx - 40, round(Gear^.Y) + WorldDy - 58, 19 - (Gear^.FrameTicks div 37));
                                 DrawTextureF(SpritesData[sprSplash].Texture, Gear^.scale, round(Gear^.X + WorldDx), round(Gear^.Y + WorldDy - ((SpritesData[sprSplash].Height+8)*Gear^.Scale)/2), 19 - (Gear^.FrameTicks div Gear^.Timer div 37), 1, SpritesData[sprSplash].Width, SpritesData[sprSplash].Height);
                  vgtDroplet: if SuddenDeathDmg then
                                  DrawSprite(sprSDDroplet, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame)
                              else
                                  DrawSprite(sprDroplet, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame);
                  vgtBubble: DrawSprite(sprBubbles, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, Gear^.Frame);//(RealTicks div 64 + Gear^.Frame) mod 8);
              end;
          //if (Gear^.Tint <> $FFFFFFFF) or tinted then Tint($FF,$FF,$FF,$FF);
          if (Gear^.Tint <> $FFFFFFFF) then
              Tint($FF,$FF,$FF,$FF);
          Gear:= Gear^.NextGear
          end
       end;
    // this layer is on the screen plane (depth = 0) when stereo
    3: begin
       Gear:= VisualGearLayers[3];
       while Gear <> nil do
           begin
           tinted:= false;
           if Gear^.Tint <> $FFFFFFFF then
               Tint(Gear^.Tint);
           case Gear^.Kind of
(*
              vgtFlake: if SuddenDeathDmg then
                             if vobSDVelocity = 0 then
                                 DrawSprite(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                             else
                                 DrawSpriteRotatedF(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
                         else
                             if vobVelocity = 0 then
                                 DrawSprite(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                             else
                                 DrawSpriteRotatedF(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);*)
               vgtSpeechBubble: begin
                                if (Gear^.Tex <> nil) and (((Gear^.State = 0) and (Gear^.Hedgehog^.Team <> CurrentTeam)) or (Gear^.State = 1)) then
                                    begin
                                    tinted:= true;
                                    Tint($FF, $FF, $FF,  $66);
                                    DrawTextureCentered(round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Tex)
                                    end
                                else if (Gear^.Tex <> nil) and (((Gear^.State = 0) and (Gear^.Hedgehog^.Team = CurrentTeam)) or (Gear^.State = 2)) then
                                    DrawTextureCentered(round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Tex);
                                end;
               vgtSmallDamageTag: DrawTextureCentered(round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Tex);
               vgtHealthTag: if Gear^.Tex <> nil then 
                               begin
                               if Gear^.Frame = 0 then 
                                   DrawTextureCentered(round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Tex)
                               else
                                   begin
                                   SetScale(cDefaultZoomLevel);
                                   if Gear^.Angle = 0 then
                                       DrawTexture(round(Gear^.X), round(Gear^.Y), Gear^.Tex)
                                   else
                                       DrawTexture(round(Gear^.X), round(Gear^.Y), Gear^.Tex, Gear^.Angle); 
                                   SetScale(zoom)
                                   end
                               end;
               vgtStraightShot: begin 
                                if Gear^.dX < 0 then
                                    i:= -1
                                else
                                    i:= 1;
                                DrawTextureRotatedF(SpritesData[TSprite(Gear^.State)].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, i, SpritesData[TSprite(Gear^.State)].Width, SpritesData[TSprite(Gear^.State)].Height, Gear^.Angle);
                                end;
           end;
           if (cReducedQuality and rqAntiBoom) = 0 then
               case Gear^.Kind of
                   vgtChunk: DrawSpriteRotatedF(sprChunk, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
               end;
           if (Gear^.Tint <> $FFFFFFFF) or tinted then
               Tint($FF,$FF,$FF,$FF);
           Gear:= Gear^.NextGear
           end
       end;
    // this layer is outside the screen when stereo
    2: begin
       Gear:= VisualGearLayers[2];
       while Gear <> nil do
           begin
           tinted:= false;
           if Gear^.Tint <> $FFFFFFFF then
               Tint(Gear^.Tint);
           case Gear^.Kind of
               vgtExplosion: DrawSprite(sprExplosion50, round(Gear^.X) - 32 + WorldDx, round(Gear^.Y) - 32 + WorldDy, Gear^.State);
               vgtBigExplosion: begin
                                tinted:= true;
                                Tint($FF, $FF, $FF, round($FF * (1 - power(Gear^.Timer / 250, 4))));
                                DrawTextureRotatedF(SpritesData[sprBigExplosion].Texture, 0.85 * (-power(2, -10 * Int(Gear^.Timer)/250) + 1) + 0.4, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, 0, 1, 385, 385, Gear^.Angle);
                                end;
           end;
           if (cReducedQuality and rqAntiBoom) = 0 then
               case Gear^.Kind of
                   vgtExplPart: DrawSprite(sprExplPart, round(Gear^.X) + WorldDx - 16, round(Gear^.Y) + WorldDy - 16, 7 - Gear^.Frame);
                   vgtExplPart2: DrawSprite(sprExplPart2, round(Gear^.X) + WorldDx - 16, round(Gear^.Y) + WorldDy - 16, 7 - Gear^.Frame);
                   vgtSteam: DrawSprite(sprSmokeWhite, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame);
                   vgtAmmo: begin
                            tinted:= true;
                            Tint($FF, $FF, $FF, round(Gear^.alpha * $FF));
                            DrawTextureF(ropeIconTex, Gear^.scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, 0, 1, 32, 32);
                            DrawTextureF(SpritesData[sprAMAmmos].Texture, Gear^.scale * 0.90, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame - 1, 1, 32, 32);
                            end;
                   vgtShell: begin
                             if Gear^.FrameTicks < $FF then
                                 begin
                                 Tint($FF, $FF, $FF, Gear^.FrameTicks);
                                 tinted:= true
                                 end;
                             DrawSpriteRotatedF(sprShell, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                             end;
                   vgtFeather: begin
                               if Gear^.FrameTicks < 255 then
                                   begin
                                   Tint($FF, $FF, $FF, Gear^.FrameTicks);
                                   tinted:= true
                                   end;
                               DrawSpriteRotatedF(sprFeather, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                             end;
                   vgtEgg: DrawSpriteRotatedF(sprEgg, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                   vgtBeeTrace: begin
                                if Gear^.FrameTicks < $FF then
                                    Tint($FF, $FF, $FF, Gear^.FrameTicks div 2)
                                else
                                    Tint($FF, $FF, $FF, $80);
                                tinted:= true;
                                DrawSpriteRotatedF(sprBeeTrace, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, (RealTicks shr 4) mod cMaxAngle);
                                end;
                   vgtSmokeRing: begin
                                 tinted:= true;
                                 Tint($FF, $FF, $FF, round(Gear^.alpha * $FF));
                                 DrawTextureRotatedF(SpritesData[sprSmokeRing].Texture, Gear^.scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, 0, 1, 200, 200, Gear^.Angle);
                                 end;
                   vgtNote: DrawSpriteRotatedF(sprNote, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                   vgtBulletHit: DrawSpriteRotatedF(sprBulletHit, round(Gear^.X) + WorldDx - 0, round(Gear^.Y) + WorldDy - 0, 7 - (Gear^.FrameTicks div 50), 1, Gear^.Angle);
               end;
           case Gear^.Kind of
               vgtFlake: if SuddenDeathDmg then
                             if vobSDVelocity = 0 then
                                 DrawTextureF(SpritesData[sprSDFlake].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height)
                             else
                                 DrawTextureRotatedF(SpritesData[sprSDFlake].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height, Gear^.Angle)
                         else
                             if vobVelocity = 0 then
                                 DrawTextureF(SpritesData[sprFlake].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height)
                             else
                                 DrawTextureRotatedF(SpritesData[sprFlake].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height, Gear^.Angle);
               vgtCircle: if gear^.Angle = 1 then
                              begin
                              tmp:= Gear^.State / 100;
                              DrawTexture(round(Gear^.X-24*tmp) + WorldDx, round(Gear^.Y-24*tmp) + WorldDy, SpritesData[sprVampiric].Texture, tmp)
                              end
                          else
                              DrawCircle(round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.State, Gear^.Timer);
           end;
           if (Gear^.Tint <> $FFFFFFFF) or tinted then
               Tint($FF,$FF,$FF,$FF);
           Gear:= Gear^.NextGear
           end
       end;
     // this layer is half-way between the screen plane (depth = 0) when in stereo, and the land
     4: begin
        Gear:= VisualGearLayers[4];
        while Gear <> nil do
            begin
            if Gear^.Tint <> $FFFFFFFF then
                Tint(Gear^.Tint);
            case Gear^.Kind of
               vgtCloud: if SuddenDeathDmg then
                            DrawTextureF(SpritesData[sprSDCloud].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 2, SpritesData[sprCloud].Width, SpritesData[sprCloud].Height)
                        else
                            DrawTextureF(SpritesData[sprCloud].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 2, SpritesData[sprCloud].Width, SpritesData[sprCloud].Height);
              vgtFlake: if SuddenDeathDmg then
                            if vobSDVelocity = 0 then
                                DrawTextureF(SpritesData[sprSDFlake].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height)
                            else
                                DrawTextureRotatedF(SpritesData[sprSDFlake].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height, Gear^.Angle)
                        else
                            if vobVelocity = 0 then
                                DrawTextureF(SpritesData[sprFlake].Texture, Gear^.Scale, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height)
                            else
                                DrawTextureRotatedF(SpritesData[sprFlake].Texture, Gear^.Scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, SpritesData[sprFlake].Width, SpritesData[sprFlake].Height, Gear^.Angle);
            end;
            if (Gear^.Tint <> $FFFFFFFF) then
                Tint($FF,$FF,$FF,$FF);
            Gear:= Gear^.NextGear
            end
        end;
     // this layer is on the screen plane (depth = 0) when stereo, but just behind the land
     5: begin
        Gear:= VisualGearLayers[5];
        while Gear <> nil do
            begin
            if Gear^.Tint <> $FFFFFFFF then
                Tint(Gear^.Tint);
            case Gear^.Kind of
                vgtCloud: if SuddenDeathDmg then
                            DrawSprite(sprSDCloud, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                          else
                            DrawSprite(sprCloud, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame);
              vgtFlake: if SuddenDeathDmg then
                            if vobSDVelocity = 0 then
                                DrawSprite(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                            else
                                DrawSpriteRotatedF(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
                          else
                            if vobVelocity = 0 then
                                DrawSprite(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                            else
                                DrawSpriteRotatedF(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
                end;
            if (Gear^.Tint <> $FFFFFFFF) then
                Tint($FF,$FF,$FF,$FF);
            Gear:= Gear^.NextGear
            end
        end;
     // this layer is on the screen plane (depth = 0) when stereo, but just in front of the land
    6: begin
        Gear:= VisualGearLayers[6];
        while Gear <> nil do
            begin
            if Gear^.Tint <> $FFFFFFFF then
                Tint(Gear^.Tint);
            case Gear^.Kind of
                vgtFlake: if SuddenDeathDmg then
                            if vobSDVelocity = 0 then
                                DrawSprite(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                            else
                                DrawSpriteRotatedF(sprSDFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle)
                          else
                            if vobVelocity = 0 then
                                DrawSprite(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame)
                            else
                                DrawSpriteRotatedF(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
                end;
            if (Gear^.Tint <> $FFFFFFFF) then
                Tint($FF,$FF,$FF,$FF);
            Gear:= Gear^.NextGear
            end
        end;
    end;
end;

function  VisualGearByUID(uid : Longword) : PVisualGear;
var vg: PVisualGear;
    i: LongWord;
begin
VisualGearByUID:= nil;
if uid = 0 then
    exit;
if (lastVisualGearByUID <> nil) and (lastVisualGearByUID^.uid = uid) then
    begin
    VisualGearByUID:= lastVisualGearByUID;
    exit
    end;
// search in an order that is more likely to return layers they actually use.  Could perhaps track statistically AddVisualGear in uScript, since that is most likely the ones they want
for i:= 2 to 5 do
    begin
    vg:= VisualGearLayers[i mod 4];
    while vg <> nil do
        begin
        if vg^.uid = uid then
            begin
            lastVisualGearByUID:= vg;
            VisualGearByUID:= vg;
            exit
            end;
        vg:= vg^.NextGear
        end
    end
end;

procedure AddClouds;
var i: LongInt;
begin
for i:= 0 to cCloudsNumber - 1 do
    AddVisualGear(cLeftScreenBorder + i * LongInt(cScreenSpace div (cCloudsNumber + 1)), LAND_HEIGHT-1184, vgtCloud)
end;

procedure ChangeToSDClouds;
var       i: LongInt;
    vg, tmp: PVisualGear;
begin
if cCloudsNumber = cSDCloudsNumber then
    exit;
vg:= VisualGearLayers[0];
while vg <> nil do
    if vg^.Kind = vgtCloud then
        begin
        tmp:= vg^.NextGear;
        DeleteVisualGear(vg);
        vg:= tmp
        end
    else vg:= vg^.NextGear;
for i:= 0 to cSDCloudsNumber - 1 do
    AddVisualGear(cLeftScreenBorder + i * LongInt(cScreenSpace div (cSDCloudsNumber + 1)), LAND_HEIGHT-1184, vgtCloud)
end;

procedure AddFlakes;
var i: LongInt;
begin
if (cReducedQuality and rqKillFlakes) <> 0 then
    exit;

if hasBorder or ((Theme <> 'Snow') and (Theme <> 'Christmas')) then
    for i:= 0 to Pred(vobCount * cScreenSpace div 4096) do
        AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake)
else
    for i:= 0 to Pred((vobCount * cScreenSpace div 4096) div 3) do
        AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake);
end;

procedure ChangeToSDFlakes;
var       i: LongInt;
    vg, tmp: PVisualGear;
begin
if (cReducedQuality and rqKillFlakes) <> 0 then
    exit;
if vobCount = vobSDCount then
    exit;
for i:= 0 to 6 do
    begin
    vg:= VisualGearLayers[i];
    while vg <> nil do
        if vg^.Kind = vgtFlake then
        begin
        tmp:= vg^.NextGear;
        DeleteVisualGear(vg);
        vg:= tmp
        end
        else vg:= vg^.NextGear;
    end;
if ((GameFlags and gfBorder) <> 0) or ((Theme <> 'Snow') and (Theme <> 'Christmas')) then
    for i:= 0 to Pred(vobSDCount * cScreenSpace div 4096) do
        AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake)
else
    for i:= 0 to Pred((vobSDCount * cScreenSpace div 4096) div 3) do
        AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake);
end;

procedure initModule;
var i: LongWord;
begin
VGCounter:= 0;
for i:= 0 to 6 do
    VisualGearLayers[i]:= nil;
end;

procedure freeModule;
var i: LongWord;
begin
VGCounter:= 0;
for i:= 0 to 6 do
    while VisualGearLayers[i] <> nil do DeleteVisualGear(VisualGearLayers[i]);
end;

end.