hedgewars/uVisualGears.pas
author unc0rr
Mon, 20 Jun 2011 21:40:08 +0400
changeset 5269 e32fc0fcaad0
parent 5256 e3fada9358b0
child 5357 ec36f3d53f3c
permissions -rw-r--r--
Implement testing for illegal characters

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2011 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;

procedure initModule;
procedure freeModule;

function  AddVisualGear(X, Y: LongInt; Kind: TVisualGearType; State: LongWord = 0; Critical: Boolean = false): PVisualGear;
procedure ProcessVisualGears(Steps: Longword);
procedure KickFlakes(Radius, X, Y: LongInt);
procedure DrawVisualGears(Layer: LongWord);
procedure DeleteVisualGear(Gear: PVisualGear);
function  VisualGearByUID(uid : Longword) : PVisualGear;
procedure AddClouds;
procedure ChangeToSDClouds;
procedure AddFlakes;
procedure ChangeToSDFlakes;
procedure AddDamageTag(X, Y, Damage, Color: LongWord);

implementation
uses uSound, uMobile, uVariables, uTextures, uRender, Math, uRenderUtils;

const cExplFrameTicks = 110;

// 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,
            @doStepHealth,
            @doStepShell,
            @doStepDust,
            @doStepSplash,
            @doStepDroplet,
            @doStepSmokeRing,
            @doStepBeeTrace,
            @doStepEgg,
            @doStepFeather,
            @doStepHealthTag,
            @doStepSmokeTrace,
            @doStepSmokeTrace,
            @doStepExplosion,
            @doStepBigExplosion,
            @doStepChunk,
            @doStepNote,
            @doStepLineTrail,
            @doStepBulletHit,
            @doStepCircle
        );

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

if ((cReducedQuality and rqAntiBoom) <> 0) and
   not Critical and
   not (Kind in
   [vgtTeamHealthSorter,
    vgtSmallDamageTag,
    vgtSpeechBubble,
    vgtHealthTag,
    vgtExplosion,
    vgtSmokeTrace,
    vgtEvilTrace,
    vgtNote]) then
    begin
      AddVisualGear:= nil;
      exit
    end;

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;

with gear^ do
    case Kind of
    vgtFlake: begin
                Timer:= 0;
                tdX:= 0;
                tdY:= 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) * (1 + random) * vobSDVelocity / 1000
                else dAngle:= (random(2) * 2 - 1) * (1 + 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);
                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
                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;
    vgtHealth: begin
                dx:= 0.001 * random(45);
                dy:= 0.001 * (random(20) + 25);
                if random(2) = 0 then dx := -dx;
                Frame:= 0;
                FrameTicks:= random(750) + 1250;
                end;
  vgtDust: begin
                dx:= 0.005 * (random(15) + 10);
                dy:= 0.001 * (random(40) + 20);
                if random(2) = 0 then dx := -dx;
                Frame:= 7 - random(2);
                FrameTicks:= random(20) + 15;
                end;
  vgtSplash: begin
                dx:= 0;
                dy:= 0;
                FrameTicks:= 740;
                Frame:= 19;
                end;
    vgtDroplet: begin
                dx:= 0.001 * (random(75) + 15);
                dy:= -0.001 * (random(80) + 120);
                if random(2) = 0 then dx := -dx;
                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
                gear^.Timer:= 1500;
                //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;
        end;

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

if VisualGearsList <> nil then
    begin
    VisualGearsList^.PrevGear:= gear;
    gear^.NextGear:= VisualGearsList
    end;
VisualGearsList:= gear;

AddVisualGear:= gear;
end;

procedure DeleteVisualGear(Gear: PVisualGear);
begin
    if Gear^.Tex <> nil then
        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 VisualGearsList:= Gear^.NextGear;

    if lastVisualGearByUID = Gear then lastVisualGearByUID:= nil;

    Dispose(Gear);
end;

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

t:= VisualGearsList;
while t <> nil do
      begin
      Gear:= t;
      t:= Gear^.NextGear;
      Gear^.doStep(Gear, Steps)
      end
end;

procedure KickFlakes(Radius, X, Y: LongInt);
var Gear, t: PVisualGear;
    dmg: LongInt;
begin
if (vobCount = 0) or (vobCount > 200) then exit;
t:= VisualGearsList;
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;

procedure DrawVisualGears(Layer: LongWord);
var Gear: PVisualGear;
    tinted: boolean;
    tmp: real;
begin
Gear:= VisualGearsList;
case Layer of
    // this layer is very distant in the background when stereo
    0: 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
                              DrawRotatedF(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
                              DrawRotatedF(sprFlake, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy + SkyOffset, Gear^.Frame, 1, Gear^.Angle);
            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);
            end;
        if Gear^.Tint <> $FFFFFFFF then Tint($FF,$FF,$FF,$FF);
        Gear:= Gear^.NextGear
       end;
    // this layer is on the land level (which is close but behind the screen plane) when stereo
    1: while Gear <> nil do
        begin
        tinted:= false;
        if Gear^.Tint <> $FFFFFFFF then Tint(Gear^.Tint);
        case Gear^.Kind of
            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: DrawSprite(sprSmoke, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame);
                vgtSmokeWhite: DrawSprite(sprSmokeWhite, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame);
                vgtDust: if Gear^.State = 1 then
                             DrawSprite(sprSnowDust, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame)
                         else
                             DrawSprite(sprDust, round(Gear^.X) + WorldDx - 11, round(Gear^.Y) + WorldDy - 11, 7 - Gear^.Frame);
                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))
                           else
                               DrawSprite(sprSplash, round(Gear^.X) + WorldDx - 40, round(Gear^.Y) + WorldDy - 58, 19 - (Gear^.FrameTicks div 37));
                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);
        Gear:= Gear^.NextGear
       end;
    // this layer is on the screen plane (depth = 0) when stereo
    3: while Gear <> nil do
        begin
        tinted:= false;
        if Gear^.Tint <> $FFFFFFFF then Tint(Gear^.Tint);
        case Gear^.Kind of
            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);
                                 DrawCentered(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
                                 DrawCentered(round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Tex);
                             end;
            vgtSmallDamageTag: DrawCentered(round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Tex);
            vgtHealthTag: if Gear^.Tex <> nil then DrawCentered(round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Tex);
            vgtHealth: begin
                       tinted:= true;
                       case Gear^.Frame div 10 of
                           0:Tint(0, $FF, 0, round(Gear^.FrameTicks * $FF / 1000));
                           1:Tint($FF, 0, 0, round(Gear^.FrameTicks * $FF / 1000));
                       end;
                       DrawSprite(sprHealth, round(Gear^.X) + WorldDx - 8, round(Gear^.Y) + WorldDy - 8, 0);
                       end;
        end;
        if (cReducedQuality and rqAntiBoom) = 0 then
            case Gear^.Kind of
                vgtChunk: DrawRotatedF(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;
    // this layer is outside the screen when stereo
    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))));
                             DrawRotatedTextureF(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;
                          DrawRotatedF(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;
                            DrawRotatedF(sprFeather, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                          end;
                vgtEgg: begin
                        if Gear^.FrameTicks < $FF then
                            begin
                                Tint($FF, $FF, $FF, Gear^.FrameTicks);
                                tinted:= true
                            end;
                        DrawRotatedF(sprEgg, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                        end;
                vgtBeeTrace: begin
                             if Gear^.FrameTicks < $FF then
                                 Tint($FF, $FF, $FF, Gear^.FrameTicks div 2)
                             else
                                 Tint($FF, $FF, $FF, $80);
                             tinted:= true;
                             DrawRotatedF(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));
                              DrawRotatedTextureF(SpritesData[sprSmokeRing].Texture, Gear^.scale, 0, 0, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, 0, 1, 200, 200, Gear^.Angle);
                              end;
                vgtNote: DrawRotatedF(sprNote, round(Gear^.X) + WorldDx, round(Gear^.Y) + WorldDy, Gear^.Frame, 1, Gear^.Angle);
                vgtBulletHit: DrawRotatedF(sprBulletHit, round(Gear^.X) + WorldDx - 0, round(Gear^.Y) + WorldDy - 0, 7 - (Gear^.FrameTicks div 50), 1, Gear^.Angle);
            end;
        case Gear^.Kind of
            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;
end;

function  VisualGearByUID(uid : Longword) : PVisualGear;
var vg: PVisualGear;
begin
VisualGearByUID:= nil;
if uid = 0 then exit;
if (lastVisualGearByUID <> nil) and (lastVisualGearByUID^.uid = uid) then
    begin
    VisualGearByUID:= lastVisualGearByUID;
    exit
    end;
vg:= VisualGearsList;
while vg <> nil do
    begin
    if vg^.uid = uid then
        begin
        lastVisualGearByUID:= vg;
        VisualGearByUID:= vg;
        exit
        end;
    vg:= vg^.NextGear
    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:= VisualGearsList;
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 ((GameFlags and gfBorder) <> 0) or ((Theme <> 'Snow') and (Theme <> 'Christmas')) then
    for i:= 0 to Pred(vobCount * cScreenSpace div LAND_WIDTH) do
        AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake)
else
    for i:= 0 to Pred((vobCount * cScreenSpace div LAND_WIDTH) 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;
vg:= VisualGearsList;
while vg <> nil do
    if vg^.Kind = vgtFlake then
        begin
        tmp:= vg^.NextGear;
        DeleteVisualGear(vg);
        vg:= tmp
        end
    else vg:= vg^.NextGear;
if ((GameFlags and gfBorder) <> 0) or ((Theme <> 'Snow') and (Theme <> 'Christmas')) then
    for i:= 0 to Pred(vobSDCount * cScreenSpace div LAND_WIDTH) do
        AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake)
else
    for i:= 0 to Pred((vobSDCount * cScreenSpace div LAND_WIDTH) div 3) do
        AddVisualGear(cLeftScreenBorder + random(cScreenSpace), random(1024+200) - 100 + LAND_HEIGHT, vgtFlake);
end;

procedure initModule;
begin
    VisualGearsList:= nil;
end;

procedure freeModule;
begin
    while VisualGearsList <> nil do DeleteVisualGear(VisualGearsList);
end;

end.