unit g_gfx;

interface

uses
  g_textures;

const
  BLOOD_NORMAL = 0;
  BLOOD_SPARKS = 1;
  ONCEANIM_NONE  = 0;
  ONCEANIM_SMOKE = 1;
  MARK_FREE     = 0;
  MARK_WALL     = 1;
  MARK_WATER    = 2;
  MARK_ACID     = 4;
  MARK_LIFTDOWN = 8;
  MARK_LIFTUP   = 16;
  MARK_DOOR     = 32;
  MARK_LIFTLEFT  = 64;
  MARK_LIFTRIGHT = 128;
  MARK_BLOCKED  = MARK_WALL + MARK_DOOR;
  MARK_LIQUID   = MARK_WATER + MARK_ACID;
  MARK_LIFT     = MARK_LIFTDOWN + MARK_LIFTUP + MARK_LIFTLEFT + MARK_LIFTRIGHT;

procedure g_GFX_Init();
procedure g_GFX_Free();

procedure g_GFX_Blood(fX, fY: Integer; Count: Word; vx, vy: Integer;
                      DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
procedure g_GFX_SetMax(Count: Integer);
function  g_GFX_GetMax(): Integer;

procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);

procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);

procedure g_GFX_Update();
procedure g_GFX_Draw();

var
  gCollideMap: Array of Array of Byte;

implementation

uses
  g_map, g_basic, Math, e_graphics, dglOpenGL, windows,
  g_options, g_console, SysUtils, g_triggers, MAPDEF,
  g_game, g_language, g_net;

type
  TParticle = record
    X, Y:               Integer;
    VelX, VelY:         Single;
    AccelX, AccelY:     Single;
    Red, Green, Blue:   Byte;
    Alpha:              Byte;
    Time, LiveTime:     Word;
    State:              Byte;
    ParticleType:       Byte;
    offsetX, offsetY:   ShortInt;
  end;

  TOnceAnim = record
    AnimType:   Byte;
    X, Y:       Integer;
    Animation:  TAnimation;
  end;

const
  PARTICLE_BLOOD   = 0;
  PARTICLE_SPARK   = 1;
  PARTICLE_BUBBLES = 2;
  PARTICLE_WATER   = 3;
  STATE_FREE   = 0;
  STATE_NORMAL = 1;
  STATE_STICK  = 2;

var
  Particles: Array of TParticle;
  OnceAnims: Array of TOnceAnim;
  MaxParticles: Integer;
  CurrentParticle: Integer;

procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);
var
  yy, y2, xx, x2: Integer;
begin
  if x < 0 then
  begin
    Width := Width + x;
    x := 0;
  end;

  if Width < 0 then
    Exit;
 
  if y < 0 then
  begin
    Height := Height + y;
    y := 0;
  end;

  if Height < 0 then
    Exit;

  if x > gMapInfo.Width then
    Exit;
  if y > gMapInfo.Height then
    Exit;

  y2 := y + Height - 1;
  if y2 > gMapInfo.Height then
    y2 := gMapInfo.Height;

  x2 := x + Width - 1;
  if x2 > gMapInfo.Width then
    x2 := gMapInfo.Width;

  if st then
    begin // Установить признак
      for yy := y to y2 do
        for xx := x to x2 do
          gCollideMap[yy][xx] := gCollideMap[yy][xx] or t;
    end
  else
    begin // Убрать признак
      t := not t;
      for yy := y to y2 do
        for xx := x to x2 do
          gCollideMap[yy][xx] := gCollideMap[yy][xx] and t;
    end;
end;

procedure CreateCollideMap();
var
  a: Integer;
begin
  g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 1/6', 0, False);
  SetLength(gCollideMap, gMapInfo.Height+1);
  for a := 0 to High(gCollideMap) do
    SetLength(gCollideMap[a], gMapInfo.Width+1);

  if gWater <> nil then
  begin
    g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 2/6', 0, True);
    for a := 0 to High(gWater) do
      with gWater[a] do
        g_Mark(X, Y, Width, Height, MARK_WATER, True);
  end;

  if gAcid1 <> nil then
  begin
    g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 3/6', 0, True);
    for a := 0 to High(gAcid1) do
      with gAcid1[a] do
        g_Mark(X, Y, Width, Height, MARK_ACID, True);
  end;

  if gAcid2 <> nil then
  begin
    g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 4/6', 0, True);
    for a := 0 to High(gAcid2) do
      with gAcid2[a] do
        g_Mark(X, Y, Width, Height, MARK_ACID, True);
  end;

  if gLifts <> nil then
  begin
    g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 5/6', 0, True);
    for a := 0 to High(gLifts) do
      with gLifts[a] do
      begin
        g_Mark(X, Y, Width, Height, MARK_LIFT, False);

        if LiftType = 0 then
          g_Mark(X, Y, Width, Height, MARK_LIFTUP, True)
        else if LiftType = 1 then
          g_Mark(X, Y, Width, Height, MARK_LIFTDOWN, True)
        else if LiftType = 2 then
          g_Mark(X, Y, Width, Height, MARK_LIFTLEFT, True)
        else if LiftType = 3 then
          g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT, True)
      end;
  end;

  if gWalls <> nil then
  begin
    g_Game_SetLoadingText(_lc[I_LOAD_COLLIDE_MAP]+' 6/6', 0, True);
    for a := 0 to High(gWalls) do
    begin
      if gWalls[a].Door then
        begin
        // Закрытая дверь:
          if gWalls[a].Enabled then
            with gWalls[a] do
              g_Mark(X, Y, Width, Height, MARK_DOOR, True)
          else // Открытая дверь:
            if gWalls[a].Enabled then
              with gWalls[a] do
                g_Mark(X, Y, Width, Height, MARK_DOOR, False);
        end
      else // Стена
        with gWalls[a] do
          g_Mark(X, Y, Width, Height, MARK_WALL, True);
    end;
  end;
end;

procedure g_GFX_Init();
begin
  CreateCollideMap();
end;

procedure g_GFX_Free();
var
  a: Integer;
begin
  Particles := nil;
  SetLength(Particles, MaxParticles);
  CurrentParticle := 0;

  if OnceAnims <> nil then
  begin
    for a := 0 to High(OnceAnims) do
      OnceAnims[a].Animation.Free();

    OnceAnims := nil;
  end;

  gCollideMap := nil;
end;

procedure CorrectOffsets(id: Integer);
begin
  with Particles[id] do
  begin
    if (X >= 0) and (Y > 0) and
    (Y < Length(gCollideMap)) and (X < Length(gCollideMap[0])) and
    (ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) then
      offsetY := 1 // Стена сверху
    else
      offsetY := 0;

    if (X > 0) and (Y >= 0) and
    (Y < Length(gCollideMap)) and (X < Length(gCollideMap[0])) and
    (ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) then
      offsetX := 1 // Стена слева
    else
      offsetX := 0;
  end;
end;

procedure g_GFX_SparkVel(fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte);
var
  a: Integer;
  DevX1, DevX2,
  DevY1, DevY2: Byte;
  l: Integer;
begin
  l := Length(Particles);
  if l = 0 then
    Exit;
  if Count > l then
    Count := l;

  DevX1 := DevX div 2;
  DevX2 := DevX + 1;
  DevY1 := DevY div 2;
  DevY2 := DevY + 1;

  for a := 1 to Count do
  begin
    with Particles[CurrentParticle] do
    begin
      X := fX-DevX1+Random(DevX2);
      Y := fY-DevY1+Random(DevY2);

      VelX := VX + (Random-Random)*3;
      VelY := VY + (Random-Random)*3;

      if VelY > -4 then
        if VelY-4 < -4 then
          VelY := -4
        else
          VelY := VelY-4;

      AccelX := -Sign(VelX)*Random/100;
      AccelY := 0.8;

      Red := 255;
      Green := 100+Random(155);
      Blue := 64;
      Alpha := 255;

      State := STATE_NORMAL;
      Time := 0;
      LiveTime := 30+Random(60);
      ParticleType := PARTICLE_SPARK;

      CorrectOffsets(CurrentParticle);
    end;

    if CurrentParticle+2 > MaxParticles then
      CurrentParticle := 0
    else
      CurrentParticle := CurrentParticle+1;
  end;
end;

procedure g_GFX_Blood(fX, fY: Integer; Count: Word; vx, vy: Integer;
  DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
var
  a: Integer;
  DevX1, DevX2,
  DevY1, DevY2: Word;
  l: Integer;
  CRnd: Byte;
  CC: SmallInt;
begin
  if Kind = BLOOD_SPARKS then
  begin
    g_GFX_SparkVel(fX, fY, 2 + Random(2), -VX div 2, -VY div 2, DevX, DevY);
    Exit;
  end;
  l := Length(Particles);
  if l = 0 then
    Exit;
  if Count > l then
    Count := l;

  DevX1 := DevX div 2;
  DevX2 := DevX + 1;
  DevY1 := DevY div 2;
  DevY2 := DevY + 1;

  for a := 1 to Count do
  begin
    with Particles[CurrentParticle] do
    begin
      X := fX - DevX1 + Random(DevX2);
      Y := fY - DevY1 + Random(DevY2);

      if (X < 0) or (X > gMapInfo.Width-1) or
         (Y < 0) or (Y > gMapInfo.Height-1) or
         ByteBool(gCollideMap[Y, X] and MARK_WALL) then
        Continue;

      VelX := vx + (Random-Random)*3;
      VelY := vy + (Random-Random)*3;

      if VelY > -4 then
        if VelY-4 < -4 then
          VelY := -4
        else
          VelY := VelY-4;

      AccelX := -Sign(VelX)*Random/100;
      AccelY := 0.8;

      CRnd := 20*Random(6);
      if CR > 0 then
      begin
        CC := CR + CRnd - 50;
        if CC < 0   then CC := 0;
        if CC > 255 then CC := 255;
        Red := CC;
      end else
        Red := 0;
      if CG > 0 then
      begin
        CC := CG + CRnd - 50;
        if CC < 0   then CC := 0;
        if CC > 255 then CC := 255;
        Green := CC;
      end else
        Green := 0;
      if CB > 0 then
      begin
        CC := CB + CRnd - 50;
        if CC < 0   then CC := 0;
        if CC > 255 then CC := 255;
        Blue := CC;
      end else
        Blue := 0;

      Alpha := 255;

      State := STATE_NORMAL;
      Time := 0;
      LiveTime := 120+Random(40);
      ParticleType := PARTICLE_BLOOD;

      CorrectOffsets(CurrentParticle);
    end;

    if CurrentParticle >= MaxParticles-1 then
      CurrentParticle := 0
    else
      CurrentParticle := CurrentParticle+1;
  end;
end;

procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
var
  a: Integer;
  b: Single;
  DevX1, DevX2,
  DevY1, DevY2: Byte;
  BaseVelX, BaseVelY: Single;
  l: Integer;
begin
  l := Length(Particles);
  if l = 0 then
    Exit;
  if Count > l then
    Count := l;

  Angle := 360 - Angle;

  DevX1 := DevX div 2;
  DevX2 := DevX + 1;
  DevY1 := DevY div 2;
  DevY2 := DevY + 1;

  b := DegToRad(Angle);
  BaseVelX := cos(b);
  BaseVelY := 1.6*sin(b);
  if Abs(BaseVelX) < 0.01 then
    BaseVelX := 0.0;
  if Abs(BaseVelY) < 0.01 then
    BaseVelY := 0.0;
  for a := 1 to Count do
  begin
    with Particles[CurrentParticle] do
    begin
      X := fX-DevX1+Random(DevX2);
      Y := fY-DevY1+Random(DevY2);

      VelX := BaseVelX*Random;
      VelY := BaseVelY-Random;
      AccelX := VelX/3.0;
      AccelY := VelY/5.0;

      Red := 255;
      Green := 100+Random(155);
      Blue := 64;
      Alpha := 255;

      State := STATE_NORMAL;
      Time := 0;
      LiveTime := 30+Random(60);
      ParticleType := PARTICLE_SPARK;

      CorrectOffsets(CurrentParticle);
    end;

    if CurrentParticle+2 > MaxParticles then
      CurrentParticle := 0
    else
      CurrentParticle := CurrentParticle+1;
  end;
end;

procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
var
  a: Integer;
  DevX1, DevX2,
  DevY1, DevY2: Byte;
  l: Integer;
begin
  l := Length(Particles);
  if l = 0 then
    Exit;
  if Count > l then
    Count := l;

  if Abs(fVelX) < 3.0 then
    fVelX := 3.0 - 6.0*Random;

  DevX1 := DevX div 2;
  DevX2 := DevX + 1;
  DevY1 := DevY div 2;
  DevY2 := DevY + 1;

  for a := 1 to Count do
  begin
    with Particles[CurrentParticle] do
    begin
      X := fX-DevX1+Random(DevX2);
      Y := fY-DevY1+Random(DevY2);

      if Abs(fVelX) < 0.5 then
        VelX := 1.0 - 2.0*Random
      else
        VelX := fVelX*Random;
      if Random(10) < 7 then
        VelX := -VelX;
      VelY := fVelY*Random;
      AccelX := 0.0;
      AccelY := 0.8;

      case Color of
        1: // Красный
          begin
            Red := 155 + Random(9)*10;
            Green := Trunc(150*Random);
            Blue := Green;
          end;
        2: // Зеленый
          begin
            Red := Trunc(150*Random);
            Green := 175 + Random(9)*10;
            Blue := Red;
          end;
        3: // Синий
          begin
            Red := Trunc(200*Random);
            Green := Red;
            Blue := 175 + Random(9)*10;
          end;
        else // Серый
          begin
            Red := 90 + Random(12)*10;
            Green := Red;
            Blue := Red;
          end;
      end;

      Alpha := 255;

      State := STATE_NORMAL;
      Time := 0;
      LiveTime := 60+Random(60);
      ParticleType := PARTICLE_WATER;

      CorrectOffsets(CurrentParticle);
    end;

    if CurrentParticle+2 > MaxParticles then
      CurrentParticle := 0
    else
      CurrentParticle := CurrentParticle+1;
  end;
end;

procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
var
  a: Integer;
  l: Integer;
begin
  l := Length(Particles);
  if l = 0 then
    Exit;
  if Count > l then
    Count := l;

  for a := 1 to Count do
  begin
    with Particles[CurrentParticle] do
    begin
      X := fX;
      Y := fY;

      VelX := fVelX;
      VelY := fVelY;
      AccelX := 0.0;
      AccelY := 0.8;

      case DefColor of
        1: // Красный
          begin
            Red := 155 + Random(9)*10;
            Green := Trunc(150*Random);
            Blue := Green;
          end;
        2: // Зеленый
          begin
            Red := Trunc(150*Random);
            Green := 175 + Random(9)*10;
            Blue := Red;
          end;
        3: // Синий
          begin
            Red := Trunc(200*Random);
            Green := Red;
            Blue := 175 + Random(9)*10;
          end;
        4: // Свой цвет, светлее
          begin
            Red := 20 + Random(19)*10;
            Green := Red;
            Blue := Red;
            Red := Min(Red + CR, 255);
            Green := Min(Green + CG, 255);
            Blue := Min(Blue + CB, 255);
          end;
        5: // Свой цвет, темнее
          begin
            Red := 20 + Random(19)*10;
            Green := Red;
            Blue := Red;
            Red := Max(CR - Red, 0);
            Green := Max(CG - Green, 0);
            Blue := Max(CB - Blue, 0);
          end;
        else // Серый
          begin
            Red := 90 + Random(12)*10;
            Green := Red;
            Blue := Red;
          end;
      end;

      Alpha := 255;

      State := STATE_NORMAL;
      Time := 0;
      LiveTime := 60+Random(60);
      ParticleType := PARTICLE_WATER;

      CorrectOffsets(CurrentParticle);
    end;

    if CurrentParticle+2 > MaxParticles then
      CurrentParticle := 0
    else
      CurrentParticle := CurrentParticle+1;
  end;
end;

procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
var
  a: Integer;
  DevX1, DevX2,
  DevY1, DevY2: Byte;
  l: Integer;
begin
  l := Length(Particles);
  if l = 0 then
    Exit;
  if Count > l then
    Count := l;

  DevX1 := DevX div 2;
  DevX2 := DevX + 1;
  DevY1 := DevY div 2;
  DevY2 := DevY + 1;

  for a := 1 to Count do
  begin
    with Particles[CurrentParticle] do
    begin
      X := fX-DevX1+Random(DevX2);
      Y := fY-DevY1+Random(DevY2);

      if (X >= gMapInfo.Width) or (X <= 0) or
         (Y >= gMapInfo.Height) or (Y <= 0) then
        Continue;

      if not ByteBool(gCollideMap[Y, X] and MARK_LIQUID) then
        Continue;

      VelX := 0;
      VelY := -1-Random;
      AccelX := 0;
      AccelY := VelY/10;

      Red := 255;
      Green := 255;
      Blue := 255;
      Alpha := 255;
      
      State := STATE_NORMAL;
      Time := 0;
      LiveTime := 65535;
      ParticleType := PARTICLE_BUBBLES;
      
      CorrectOffsets(CurrentParticle);
    end;

    if CurrentParticle+2 > MaxParticles then
      CurrentParticle := 0
    else
      CurrentParticle := CurrentParticle+1;
  end;
end;

procedure g_GFX_SetMax(Count: Integer);
begin
  if Count > 50000 then
    Count := 50000;

  SetLength(Particles, Count);
  MaxParticles := Count;
  if CurrentParticle >= Count then
    CurrentParticle := 0;
end;

function g_GFX_GetMax(): Integer;
begin
  Result := MaxParticles;
end;

function FindOnceAnim: DWORD;
var
  i: Integer;
begin
  if OnceAnims <> nil then
    for i := 0 to High(OnceAnims) do
      if OnceAnims[i].Animation = nil then
      begin
        Result := i;
        Exit;
      end;

  if OnceAnims = nil then
    begin
      SetLength(OnceAnims, 16);
      Result := 0;
    end
  else
    begin
      Result := High(OnceAnims) + 1;
      SetLength(OnceAnims, Length(OnceAnims) + 16);
    end;
end;

procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);
var
  find_id: DWORD;
begin
  if Anim = nil then
    Exit;

  find_id := FindOnceAnim();

  OnceAnims[find_id].AnimType := AnimType;
  OnceAnims[find_id].Animation := TAnimation.Create(Anim.FramesID, Anim.Loop, Anim.Speed);
  OnceAnims[find_id].Animation.Blending := Anim.Blending;
  OnceAnims[find_id].Animation.Alpha := Anim.Alpha;
  OnceAnims[find_id].X := X;
  OnceAnims[find_id].Y := Y;
end;

procedure g_GFX_Update();
var
  a: Integer;
  w, h: Integer;
  dX, dY: SmallInt;
  b, len: Integer;
  s: ShortInt;
  c: Byte;
begin
  if Particles <> nil then
  begin
    w := gMapInfo.Width;
    h := gMapInfo.Height;

    len := High(Particles);

    for a := 0 to len do
      if Particles[a].State <> 0 then
        with Particles[a] do
        begin
          if Time = LiveTime then
            State := STATE_FREE;
          if (X+1 >= w) or (Y+1 >= h) or (X <= 0) or (Y <= 0) then
            State := STATE_FREE;
          if State = STATE_FREE then
            Continue;

          case ParticleType of
            PARTICLE_BLOOD:
            begin
              if gAdvBlood then
                begin
                  if (State = STATE_STICK) then
                    if (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
                       (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) and
                       (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
                       (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)) then
                      begin // Отлипла - капает
                        VelY := 0.5;
                        AccelY := 0.15;
                        State := STATE_NORMAL;
                      end
                    else
                      if Random(200) = 100 then
                      begin // Прилеплена - но возможно стекает
                        VelY := 0.5;
                        AccelY := 0.15;
                        Continue;
                      end;

                  if not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED) then
                  begin
                    if ByteBool(gCollideMap[Y, X] and MARK_LIFTUP) then
                    begin // Лифт вверх
                      if VelY > -4-Random(3) then
                        VelY := VelY - 0.8;
                      if Abs(VelX) > 0.1 then
                        VelX := VelX - VelX/10.0;
                      VelX := VelX + (Random-Random)*0.2;
                      AccelY := 0.15;
                    end;
                    if ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT) then
                    begin // Поток влево
                      if VelX > -8-Random(3) then
                        VelX := VelX - 0.8;
                      AccelY := 0.15;
                    end;
                    if ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT) then
                    begin // Поток вправо
                      if VelX < 8+Random(3) then
                        VelX := VelX + 0.8;
                      AccelY := 0.15;
                    end;
                  end;

                  dX := Round(VelX);
                  dY := Round(VelY);

                  if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
                    if (State <> STATE_STICK) and
                       (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
                       (not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)) and
                       (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) then
                    begin // Висит в воздухе - капает
                      VelY := 0.8;
                      AccelY := 0.5;
                      State := STATE_NORMAL;
                    end;

                  if dX <> 0 then
                  begin
                    if dX > 0 then
                      s := 1
                    else
                      s := -1;

                    dX := Abs(dX);

                    for b := 1 to dX do
                    begin
                      if (X+s >= w) or (X+s <= 0) then
                      begin
                        State := STATE_FREE;
                        Break;
                      end;

                      c := gCollideMap[Y, X+s];

                      if ByteBool(c and MARK_BLOCKED) then
                      begin // Стена/дверь
                        VelX := 0;
                        VelY := 0;
                        AccelX := 0;
                        AccelY := 0;
                        State := STATE_STICK;
                        Break;
                      end;

                      X := X+s;
                    end;
                  end;

                  if dY <> 0 then
                  begin
                    if dY > 0 then
                      s := 1
                    else
                      s := -1;

                    dY := Abs(dY);

                    for b := 1 to dY do
                    begin
                      if (Y+s >= h) or (Y+s <= 0) then
                      begin
                        State := STATE_FREE;
                        Break;
                      end;

                      c := gCollideMap[Y+s, X];

                      if ByteBool(c and MARK_BLOCKED) then
                      begin // Стена/дверь
                        VelX := 0;
                        VelY := 0;
                        AccelX := 0;
                        AccelY := 0;
                        if (s > 0) and (State <> STATE_STICK) then
                          State := STATE_NORMAL
                        else
                          State := STATE_STICK;
                        Break;
                      end;

                      Y := Y+s;
                    end;
                  end;
                end // if gAdvBlood
              else
                begin
                  dX := Round(VelX);
                  dY := Round(VelY);

                  if (X+dX >= w) or (Y+dY >= h) or
                     (X+dX <= 0) or (Y+dY <= 0) or
                     ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED) then
                    begin // Стена/дверь/граница
                      State := STATE_FREE;
                      VelX := 0;
                      VelY := 0;
                    end
                  else
                    begin
                      Y := Y + dY;
                      X := X + dX;
                    end;
                end;

              VelX := VelX + AccelX;
              VelY := VelY + AccelY;

            // Кровь растворяется в жидкости:
              if ByteBool(gCollideMap[Y, X] and MARK_LIQUID) then
              begin
                Inc(Time);

                Alpha := 255 - Trunc((255.0 * Time) / LiveTime);
              end;
            end;

            PARTICLE_SPARK:
            begin
              dX := Round(VelX);
              dY := Round(VelY);

              if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) and
                 (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
                 (not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)) and
                 (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) then
              begin // Висит в воздухе
                VelY := 0.8;
                AccelY := 0.5;
              end;

              if dX <> 0 then
              begin
                if dX > 0 then
                  s := 1
                else
                  s := -1;

                dX := Abs(dX);

                for b := 1 to dX do
                begin
                  if (X+s >= w) or (X+s <= 0) then
                  begin
                    State := STATE_FREE;
                    Break;
                  end;

                  c := gCollideMap[Y, X+s];

                  if ByteBool(c and MARK_BLOCKED) then
                    begin // Стена/дверь - падает вертикально
                      VelX := 0;
                      AccelX := 0;
                      Break;
                    end
                  else // Пусто:
                    if c = MARK_FREE then
                      X := X + s
                    else // Остальное:
                      begin
                        State := STATE_FREE;
                        Break;
                      end;
                end;
              end;

              if dY <> 0 then
              begin
                if dY > 0 then
                  s := 1
                else
                  s := -1;

                dY := Abs(dY);

                for b := 1 to dY do
                begin
                  if (Y+s >= h) or (Y+s <= 0) then
                  begin
                    State := STATE_FREE;
                    Break;
                  end;

                  c := gCollideMap[Y+s, X];

                  if ByteBool(c and MARK_BLOCKED) then
                    begin // Стена/дверь - падает вертикально
                      if s < 0 then
                        begin
                          VelY := -VelY;
                          AccelY := Abs(AccelY);
                        end
                      else // Или не падает
                        begin
                          VelX := 0;
                          AccelX := 0;
                          VelY := 0;
                          AccelY := 0.8;
                        end;

                      Break;
                    end
                  else // Пусто:
                    if c = MARK_FREE then
                      Y := Y + s
                    else // Осальное:
                      begin
                        State := STATE_FREE;
                        Break;
                      end;
                end;
              end;

              if VelX <> 0.0 then
                VelX := VelX + AccelX;
              if VelY <> 0.0 then
              begin
                if AccelY < 10 then
                  AccelY := AccelY + 0.08;
                VelY := VelY + AccelY;
              end;

              Time := Time + 1;
            end;

            PARTICLE_WATER:
            begin
              if (State = STATE_STICK) and (Random(30) = 15) then
              begin // Стекает/отлипает
                VelY := 0.5;
                AccelY := 0.15;
                if (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
                   (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)) then
                  State := STATE_NORMAL;
                Continue;
              end;

              if not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED) then
              begin
                if ByteBool(gCollideMap[Y, X] and MARK_LIFTUP) then
                begin // Лифт вверх
                  if VelY > -4-Random(3) then
                    VelY := VelY - 0.8;
                  if Abs(VelX) > 0.1 then
                    VelX := VelX - VelX/10.0;
                  VelX := VelX + (Random-Random)*0.2;
                  AccelY := 0.15;
                end;
                if ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT) then
                begin // Поток влево
                  if VelX > -8-Random(3) then
                    VelX := VelX - 0.8;
                  AccelY := 0.15;
                end;
                if ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT) then
                begin // Поток вправо
                  if VelX < 8+Random(3) then
                    VelX := VelX + 0.8;
                  AccelY := 0.15;
                end;
              end;

              dX := Round(VelX);
              dY := Round(VelY);

              if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
                if (State <> STATE_STICK) and
                   (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
                   (not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)) and
                   (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) then
                begin // Висит в воздухе - капает
                  VelY := 0.8;
                  AccelY := 0.5;
                  State := STATE_NORMAL;
                end;

              if dX <> 0 then
              begin
                if dX > 0 then
                  s := 1
                else
                  s := -1;

                for b := 1 to Abs(dX) do
                begin
                  if (X+s >= w) or (X+s <= 0) then
                  begin // Сбоку граница
                    State := STATE_FREE;
                    Break;
                  end;

                  c := gCollideMap[Y, X+s];

                  if ByteBool(c and MARK_LIQUID) and (dY > 0) then
                  begin // Сбоку жидкость, а частица уже падает
                    State := STATE_FREE;
                    Break;
                  end;

                  if ByteBool(c and MARK_BLOCKED) then
                  begin // Стена/дверь
                    VelX := 0;
                    VelY := 0;
                    AccelX := 0;
                    AccelY := 0;
                    State := STATE_STICK;
                    Break;
                  end;

                  X := X+s;
                end;
              end;

              if dY <> 0 then
              begin
                if dY > 0 then
                  s := 1
                else
                  s := -1;

                for b := 1 to Abs(dY) do
                begin
                  if (Y+s >= h) or (Y+s <= 0) then
                  begin // Снизу/сверху граница
                    State := STATE_FREE;
                    Break;
                  end;

                  c := gCollideMap[Y+s, X];

                  if ByteBool(c and MARK_LIQUID) and (dY > 0) then
                  begin // Снизу жидкость, а частица уже падает
                    State := STATE_FREE;
                    Break;
                  end;

                  if ByteBool(c and MARK_BLOCKED) then
                  begin // Стена/дверь
                    VelX := 0;
                    VelY := 0;
                    AccelX := 0;
                    AccelY := 0;
                    if (s > 0) and (State <> STATE_STICK) then
                      State := STATE_NORMAL
                    else
                      State := STATE_STICK;
                    Break;
                  end;

                  Y := Y+s;
                end;
              end;

              VelX := VelX + AccelX;
              VelY := VelY + AccelY;

              Time := Time + 1;
            end;

            PARTICLE_BUBBLES:
            begin
              dY := Round(VelY);

              if dY <> 0 then
              begin
                if dY > 0 then
                  s := 1
                else
                  s := -1;

                for b := 1 to Abs(dY) do
                begin
                  if (Y+s >= h) or (Y+s <= 0) then
                  begin
                    State := STATE_FREE;
                    Break;
                  end;

                  if not ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID) then
                  begin // Уже не жидкость
                    State := STATE_FREE;
                    Break;
                  end;

                  Y := Y+s;
                end;
              end;

              if VelY > -4 then
                VelY := VelY + AccelY;

              Time := Time + 1;
            end;
          end; // case

          CorrectOffsets(a);
        end;
  end; // Particles <> nil

  if OnceAnims <> nil then
  begin
    for a := 0 to High(OnceAnims) do
      if OnceAnims[a].Animation <> nil then
      begin
        case OnceAnims[a].AnimType of
          ONCEANIM_SMOKE:
            begin
              if Random(3) = 0 then
                OnceAnims[a].X := OnceAnims[a].X-1+Random(3);
              if Random(2) = 0 then
                OnceAnims[a].Y := OnceAnims[a].Y-Random(2);
            end;
        end;

        if OnceAnims[a].Animation.Played then
          begin
            OnceAnims[a].Animation.Free();
            OnceAnims[a].Animation := nil;
          end
        else
          OnceAnims[a].Animation.Update();
      end;
  end;
end;

procedure g_GFX_Draw();
var
  a, len: Integer;
begin
  if Particles <> nil then
  begin
    glDisable(GL_TEXTURE_2D);
    glPointSize(2);
    
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glBegin(GL_POINTS);

    len := High(Particles);

    for a := 0 to len do
      with Particles[a] do
        if (State <> STATE_FREE) and (X >= sX) and (Y >= sY) and
           (X <= sX+sWidth) and (sY <= sY+sHeight) then
        begin
          glColor4ub(Red, Green, Blue, Alpha);
          glVertex2i(X + offsetX, Y + offsetY);
        end;

    glEnd();

    glDisable(GL_BLEND);
  end;

  if OnceAnims <> nil then
    for a := 0 to High(OnceAnims) do
      if OnceAnims[a].Animation <> nil then
        with OnceAnims[a] do
          Animation.Draw(X, Y, M_NONE);
end;

end.
