DEADSOFTWARE

added actor rendering interpolation; fixed vsync on startup
[d2df-sdl.git] / src / game / g_gfx.pas
index 42dfa8c8eae83811f7bb35f823ac7d7b47ea3e2d..bf8fc5aa2fd8a5e4d90b3dbd8ca47cbfb13ecfaa 100644 (file)
@@ -1,9 +1,8 @@
-(* Copyright (C)  DooM 2D:Forever Developers
+(* Copyright (C)  Doom 2D: Forever Developers
  *
  * 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, either version 3 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, version 3 of the License ONLY.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -26,6 +25,8 @@ uses
 const
   BLOOD_NORMAL = 0;
   BLOOD_SPARKS = 1;
+  BLOOD_CSPARKS = 2;
+  BLOOD_COMBINE = 3;
 
   ONCEANIM_NONE  = 0;
   ONCEANIM_SMOKE = 1;
@@ -78,7 +79,8 @@ function awmIsSetHolmes (x, y: Integer): Boolean; inline;
 implementation
 
 uses
-  g_map, g_panel, g_basic, Math, e_graphics, GL, GLExt,
+  {$INCLUDE ../nogl/noGLuses.inc}
+  g_map, g_panel, g_basic, Math, e_graphics,
   g_options, g_console, SysUtils, g_triggers, MAPDEF,
   g_game, g_language, g_net, utils, xprofiler;
 
@@ -101,13 +103,14 @@ type
   PParticle = ^TParticle;
   TParticle = record
     x, y: Integer;
+    oldX, oldY: Integer;
     velX, velY: Single;
     accelX, accelY: Single;
     state: TPartState;
     particleType: TPartType;
     red, green, blue: Byte;
     alpha: Byte;
-    time, liveTime: Word;
+    time, liveTime, waitTime: Word;
     stickDX: Integer; // STATE_STICK: -1,1: stuck to a wall; 0: stuck to ceiling
     justSticked: Boolean; // not used
     floorY: Integer; // actually, floor-1; `Unknown`: unknown
@@ -137,6 +140,7 @@ type
   TOnceAnim = record
     AnimType:   Byte;
     x, y:       Integer;
+    oldX, oldY: Integer;
     Animation:  TAnimation;
   end;
 
@@ -331,31 +335,38 @@ end;
 function TParticle.checkAirStreams (): Boolean;
 var
   pan: TPanel;
+  r: Integer;
 begin
   pan := g_Map_PanelAtPoint(x, y, GridTagLift);
-  result := (pan <> nil);
+  result := (pan <> nil) and WordBool(pan.PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT));
+  r := Random(3);
   if result then
   begin
-    if ((pan.PanelType and PANEL_LIFTUP) <> 0) then
-    begin
-      if (velY > -4-Random(3)) then velY -= 0.8;
-      if (abs(velX) > 0.1) then velX -= velX/10.0;
-      velX += (Random-Random)*0.2;
-      accelY := 0.15;
-    end
-    else if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then
-    begin
-      if (velX > -8-Random(3)) then velX -= 0.8;
-      accelY := 0.15;
-    end
-    else if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then
-    begin
-      if (velX < 8+Random(3)) then velX += 0.8;
-      accelY := 0.15;
-    end
-    else
-    begin
-      result := false;
+    case pan.LiftType of
+      LIFTTYPE_UP:
+      begin
+        if (velY > -1-r) then velY -= 0.8;
+        if (abs(velX) > 0.1) then velX -= velX/10.0;
+        velX += (Random-Random)*0.2;
+        accelY := 0.15;
+      end;
+      LIFTTYPE_DOWN:
+      begin
+        if (velY < 1+r) then velY += 0.8;
+        accelY := 0.15;
+      end;
+      LIFTTYPE_LEFT:
+      begin
+        if (velX > -8-r) then velX -= (8+r) div 2;
+        accelY := 0.15;
+      end;
+      LIFTTYPE_RIGHT:
+      begin
+        if (velX < 8+r) then velX += (8+r) div 2;
+        accelY := 0.15;
+      end;
+      else
+        result := false;
     end;
     // awake
     if result and (state = TPartState.Sleeping) then state := TPartState.Normal;
@@ -447,7 +458,7 @@ var
   ex: Integer;
 begin
   if (not force) and (ceilingY <> Unknown) then exit;
-  if (nil = g_Map_traceToNearest(x, y, x, g_Map_MinY, GridTagObstacle, @ex, @ceilingY)) then
+  if (nil = g_Map_traceToNearest(x, y, x, g_Map_MinY, GridTagSolid, @ex, @ceilingY)) then
   begin
     ceilingY := g_Map_MinY-2;
   end;
@@ -491,6 +502,8 @@ procedure TParticle.think (); inline;
   end;
 
 begin
+  oldx := x;
+  oldy := y;
   // awake sleeping particle, if necessary
   if awakeDirty then
   begin
@@ -589,7 +602,7 @@ var
   pan: TPanel;
   dx, dy: SmallInt;
   ex, ey: Integer;
-  checkEnv: Boolean;
+  checkEnv, inAir, inStep: Boolean;
   floorJustTraced: Boolean;
   {$IF DEFINED(D2F_DEBUG_FALL_MPLAT)}
   oldFloorY: Integer;
@@ -685,7 +698,7 @@ begin
     dx := round(velX);
     dy := round(velY);
 
-    if (state = TPartState.Normal) then checkAirStreams();
+    inAir := checkAirStreams();
 
     // gravity, if not stuck
     if (state <> TPartState.Stuck) and (abs(velX) < 0.1) and (abs(velY) < 0.1) then
@@ -733,7 +746,24 @@ begin
     if (dx <> 0) then
     begin
       // has some horizontal velocity
-      pan := g_Map_traceToNearest(x, y, x+dx, y+dy, GridTagObstacle, @ex, @ey);
+      inStep := False;
+      pan := g_Map_traceToNearest(x, y, x+dx, y+dy, GridTagSolid, @ex, @ey);
+      if (pan = nil) and (dy >= 0) then
+      begin
+        // do not stuck inside step
+        if g_Map_traceToNearest(x, y, x, y, GridTagStep, nil, nil) = nil then
+          // check for step panel below
+          pan := g_Map_traceToNearest(x, y, x, y+dy, GridTagStep, nil, @ey);
+        inStep := pan <> nil;
+        if inStep then
+        begin
+          // stick to panel edges
+          if ex < pan.X then
+            ex := pan.X
+          else if ex > pan.X + pan.Width - 1 then
+            ex := pan.X + pan.Width - 1;
+        end;
+      end;
       checkEnv := (x <> ex);
       x := ex;
       y := ey;
@@ -747,19 +777,24 @@ begin
       end;
       if (pan <> nil) then
       begin
-        // we stuck
-        // the only case when we can have both ceiling and wall is corner; stick to wall in this case
-        // check if we stuck to a wall
-        if (dx < 0) then dx := -1 else dx := 1;
-        if (g_Map_PanelAtPoint(x+dx, y, GridTagObstacle) <> nil) then
-        begin
-          // stuck to a wall
-          stickToWall(dx);
-        end
+        if inStep then
+          stickToWall(dx)
         else
         begin
-          // stuck to a ceiling
-          stickToCeiling();
+          // we stuck
+          // the only case when we can have both ceiling and wall is corner; stick to wall in this case
+          // check if we stuck to a wall
+          if (dx < 0) then dx := -1 else dx := 1;
+          if (g_Map_PanelAtPoint(x+dx, y, GridTagSolid) <> nil) then
+          begin
+            // stuck to a wall
+            stickToWall(dx);
+          end
+          else
+          begin
+            // stuck to a ceiling
+            stickToCeiling();
+          end;
         end;
       end;
     end
@@ -810,7 +845,7 @@ begin
                     if (y <> floorY) then continue;
                   end;
                   // environment didn't changed
-                  hitAFloor();
+                  if not inAir then hitAFloor();
                   break; // done with vertical movement
                 end;
               TFloorType.LiquidIn: // entering the liquid
@@ -828,7 +863,7 @@ begin
                   findFloor(true); // force rescan
                   if (floorType = TFloorType.Wall) and (floorY = y) then
                   begin
-                    hitAFloor();
+                    if not inAir then hitAFloor();
                     break; // done with vertical movement
                   end;
                 end;
@@ -863,7 +898,10 @@ _done:
   begin
     if (env = TEnvType.ELiquid) then
     begin
-      time += 1;
+      if waitTime > 0 then
+        waitTime -= 1
+      else
+        time += 1;
       if (liveTime <= 0) then begin die(); exit; end;
       ex := 255-trunc(255.0*time/liveTime);
       if (ex <= 10) then begin die(); exit; end;
@@ -875,7 +913,10 @@ _done:
   begin
     // water will disappear in any liquid
     if (env = TEnvType.ELiquid) then begin die(); exit; end;
-    time += 1;
+    if waitTime > 0 then
+      waitTime -= 1
+    else
+      time += 1;
     // dry water
     if (liveTime <= 0) then begin die(); exit; end;
     ex := 255-trunc(255.0*time/liveTime);
@@ -920,6 +961,11 @@ begin
   begin
     g_GFX_SparkVel(fX, fY, 2+Random(2), -vx div 2, -vy div 2, devX, devY);
     exit;
+  end
+  else if (kind = BLOOD_CSPARKS) OR (kind = BLOOD_COMBINE) then
+  begin
+    g_GFX_SparkVel(fX, fY, count, -vx div 2, -vy div 2, devX, devY);
+    if kind <> BLOOD_COMBINE then exit
   end;
 
   l := Length(Particles);
@@ -937,16 +983,18 @@ begin
     begin
       x := fX-devX1+Random(devX2);
       y := fY-devY1+Random(devY2);
+      oldx := x;
+      oldy := y;
 
       // check for level bounds
       if (x < g_Map_MinX) or (y < g_Map_MinY) or (x > g_Map_MaxX) or (y > g_Map_MaxY) then continue;
 
       // in what environment we are starting in?
-      pan := g_Map_PanelAtPoint(x, y, (GridTagObstacle or GridTagLiquid));
+      pan := g_Map_PanelAtPoint(x, y, (GridTagSolid or GridTagLiquid));
       if (pan <> nil) then
       begin
         // either in a wall, or in a liquid
-        if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
+        if ((pan.tag and GridTagSolid) <> 0) then continue; // don't spawn in walls
         env := TEnvType.ELiquid;
       end
       else
@@ -976,6 +1024,7 @@ begin
       state := TPartState.Normal;
       time := 0;
       liveTime := 120+Random(40);
+      waitTime := 20;
       floorY := Unknown;
       ceilingY := Unknown;
     end;
@@ -1034,6 +1083,9 @@ begin
         accelY := 0.8;
       end;
 
+      oldx := x;
+      oldy := y;
+
       // check for level bounds
       if (x < g_Map_MinX) or (y < g_Map_MinY) or (x > g_Map_MaxX) or (y > g_Map_MaxY) then continue;
 
@@ -1102,6 +1154,7 @@ begin
       state := TPartState.Normal;
       time := 0;
       liveTime := 60+Random(60);
+      waitTime := 120;
       floorY := Unknown;
       ceilingY := Unknown;
     end;
@@ -1140,7 +1193,10 @@ begin
 
   if (velY > -4) then velY += accelY;
 
-  time += 1;
+  if waitTime > 0 then
+    waitTime -= 1
+  else
+    time += 1;
 end;
 
 
@@ -1172,6 +1228,8 @@ begin
     begin
       x := fX-devX1+Random(devX2);
       y := fY-devY1+Random(devY2);
+      oldx := x;
+      oldy := y;
 
       // check for level bounds
       if (x < g_Map_MinX) or (y < g_Map_MinY) or (x > g_Map_MaxX) or (y > g_Map_MaxY) then continue;
@@ -1215,6 +1273,7 @@ begin
       particleType := TPartType.Bubbles;
       time := 0;
       liveTime := 65535;
+      waitTime := 0;
     end;
 
     if (CurrentParticle >= MaxParticles-1) then CurrentParticle := 0 else CurrentParticle += 1;
@@ -1249,7 +1308,7 @@ begin
   if (dx <> 0) then
   begin
     // has some horizontal velocity
-    pan := g_Map_traceToNearest(x, y, x+dx, y+dy, (GridTagObstacle or GridTagLiquid), @ex, @ey);
+    pan := g_Map_traceToNearest(x, y, x+dx, y+dy, (GridTagSolid or GridTagLiquid), @ex, @ey);
     if (x <> ex) then begin floorY := Unknown; ceilingY := Unknown; end; // dunno yet
     x := ex;
     y := ey;
@@ -1308,7 +1367,10 @@ _done:
 
   //writeln('spark1: pos=(', x, ',', y, '); delta=(', velX:6:3, ',', velY:6:3, '); state=', state, '; ceilingY=', ceilingY, '; floorY=', floorY);
 
-  time += 1;
+  if waitTime > 0 then
+    waitTime -= 1
+  else
+    time += 1;
 end;
 
 
@@ -1337,16 +1399,18 @@ begin
     begin
       x := fX-devX1+Random(devX2);
       y := fY-devY1+Random(devY2);
+      oldx := x;
+      oldy := y;
 
       // check for level bounds
       if (x < g_Map_MinX) or (y < g_Map_MinY) or (x > g_Map_MaxX) or (y > g_Map_MaxY) then continue;
 
       // in what environment we are starting in?
-      pan := g_Map_PanelAtPoint(x, y, (GridTagObstacle or GridTagLiquid));
+      pan := g_Map_PanelAtPoint(x, y, (GridTagSolid or GridTagLiquid));
       if (pan <> nil) then
       begin
         // either in a wall, or in a liquid
-        //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
+        //if ((pan.tag and GridTagSolid) <> 0) then continue; // don't spawn in walls
         //env := TEnvType.ELiquid;
         continue;
       end
@@ -1375,6 +1439,7 @@ begin
       state := TPartState.Normal;
       time := 0;
       liveTime := 30+Random(60);
+      waitTime := 0;
       floorY := Unknown;
       ceilingY := Unknown;
     end;
@@ -1418,16 +1483,18 @@ begin
     begin
       x := fX-devX1+Random(devX2);
       y := fY-devY1+Random(devY2);
+      oldx := x;
+      oldy := y;
 
       // check for level bounds
       if (x < g_Map_MinX) or (y < g_Map_MinY) or (x > g_Map_MaxX) or (y > g_Map_MaxY) then continue;
 
       // in what environment we are starting in?
-      pan := g_Map_PanelAtPoint(x, y, (GridTagObstacle or GridTagLiquid));
+      pan := g_Map_PanelAtPoint(x, y, (GridTagSolid or GridTagLiquid));
       if (pan <> nil) then
       begin
         // either in a wall, or in a liquid
-        //if ((pan.tag and GridTagObstacle) <> 0) then continue; // don't spawn in walls
+        //if ((pan.tag and GridTagSolid) <> 0) then continue; // don't spawn in walls
         //env := TEnvType.ELiquid;
         continue;
       end
@@ -1450,6 +1517,7 @@ begin
       state := TPartState.Normal;
       time := 0;
       liveTime := 30+Random(60);
+      waitTime := 0;
       floorY := Unknown;
       ceilingY := Unknown;
     end;
@@ -1596,6 +1664,9 @@ begin
     for a := 0 to High(OnceAnims) do
       if OnceAnims[a].Animation <> nil then
       begin
+        OnceAnims[a].oldx := OnceAnims[a].x;
+        OnceAnims[a].oldy := OnceAnims[a].y;
+
         case OnceAnims[a].AnimType of
           ONCEANIM_SMOKE:
             begin
@@ -1619,8 +1690,8 @@ end;
 
 
 procedure g_GFX_Draw ();
-var
-  a, len: Integer;
+  var
+    a, len, fx, fy: Integer;
 begin
   if not gpart_dbg_enabled then exit;
 
@@ -1645,8 +1716,10 @@ begin
         if not alive then continue;
         if (x >= sX) and (y >= sY) and (x <= sX+sWidth) and (sY <= sY+sHeight) then
         begin
+          fx := nlerp(oldx, x, gLerpFactor);
+          fy := nlerp(oldy, y, gLerpFactor);
           glColor4ub(red, green, blue, alpha);
-          glVertex2f(x+0.37, y+0.37);
+          glVertex2f(fx+0.37, fy+0.37);
         end;
       end;
     end;
@@ -1663,7 +1736,12 @@ begin
     begin
       if (OnceAnims[a].Animation <> nil) then
       begin
-        with OnceAnims[a] do Animation.Draw(x, y, TMirrorType.None);
+        with OnceAnims[a] do
+        begin
+          fx := nlerp(oldx, x, gLerpFactor);
+          fy := nlerp(oldy, y, gLerpFactor);
+          Animation.Draw(x, y, TMirrorType.None);
+        end;
       end;
     end;
   end;