DEADSOFTWARE

added d_mem command
[d2df-sdl.git] / src / game / g_game.pas
index 93368940da0d943a1223f77191f923c7432b5bfd..d1ffd7ade5d35568897b3a5b825e0ec55a682da4 100644 (file)
@@ -20,7 +20,7 @@ interface
 
 uses
   g_basic, g_player, e_graphics, Classes, g_res_downloader,
-  SysUtils, g_sound, g_gui, MAPSTRUCT, wadreader, md5, xprofiler;
+  SysUtils, g_sound, g_gui, MAPDEF, wadreader, md5, xprofiler;
 
 type
   TGameSettings = record
@@ -63,14 +63,14 @@ type
 
   THearPoint = record
     Active: Boolean;
-    Coords: TPoint;
+    Coords: TDFPoint;
   end;
 
 function  g_Game_IsNet(): Boolean;
 function  g_Game_IsServer(): Boolean;
 function  g_Game_IsClient(): Boolean;
 procedure g_Game_Init();
-procedure g_Game_Free();
+procedure g_Game_Free (freeTextures: Boolean=true);
 procedure g_Game_LoadData();
 procedure g_Game_FreeData();
 procedure g_Game_Update();
@@ -95,9 +95,9 @@ procedure g_Game_RestartLevel();
 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
 procedure g_Game_ClientWAD(NewWAD: String; WHash: TMD5Digest);
 procedure g_Game_SaveOptions();
-function  g_Game_StartMap(Map: String; Force: Boolean = False): Boolean;
-procedure g_Game_ChangeMap(MapPath: String);
-procedure g_Game_ExitLevel(Map: Char16);
+function  g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
+procedure g_Game_ChangeMap(const MapPath: String);
+procedure g_Game_ExitLevel(const Map: AnsiString);
 function  g_Game_GetFirstMap(WAD: String): String;
 function  g_Game_GetNextMap(): String;
 procedure g_Game_NextLevel();
@@ -209,9 +209,9 @@ var
   gPlayer1Settings: TPlayerSettings;
   gPlayer2Settings: TPlayerSettings;
   gGameOn: Boolean;
-  gPlayerScreenSize: TPoint;
-  gPlayer1ScreenCoord: TPoint;
-  gPlayer2ScreenCoord: TPoint;
+  gPlayerScreenSize: TDFPoint;
+  gPlayer1ScreenCoord: TDFPoint;
+  gPlayer2ScreenCoord: TDFPoint;
   gPlayer1: TPlayer = nil;
   gPlayer2: TPlayer = nil;
   gPlayerDrawn: TPlayer = nil;
@@ -335,10 +335,10 @@ function conIsCheatsEnabled (): Boolean;
 implementation
 
 uses
-  g_textures, g_main, g_window, g_menu,
+  e_texture, g_textures, g_main, g_window, g_menu,
   e_input, e_log, g_console, g_items, g_map, g_panel,
   g_playermodel, g_gfx, g_options, g_weapons, Math,
-  g_triggers, MAPDEF, g_monsters, e_sound, CONFIG,
+  g_triggers, g_monsters, e_sound, CONFIG,
   BinEditor, g_language, g_net, SDL,
   ENet, e_msg, g_netmsg, g_netmaster, GL, GLExt,
   utils, sfs, g_holmes;
@@ -792,7 +792,7 @@ begin
 
   {$IF DEFINED(D2F_DEBUG)}
   if gPlayer1 <> nil then gPlayer1.NoTarget := True;
-  gAimLine := true;
+  gAimLine := g_dbg_aimline_on;
   {$ENDIF}
 end;
 
@@ -1297,12 +1297,12 @@ begin
   end;
 end;
 
-procedure g_Game_Free();
+procedure g_Game_Free(freeTextures: Boolean=true);
 begin
   if NetMode = NET_CLIENT then g_Net_Disconnect();
   if NetMode = NET_SERVER then g_Net_Host_Die();
 
-  g_Map_Free();
+  g_Map_Free(freeTextures);
   g_Player_Free();
   g_Player_RemoveAllCorpses();
 
@@ -1485,20 +1485,30 @@ var
   function sendMonsPos (mon: TMonster): Boolean;
   begin
     result := false; // don't stop
-    if (mon.MonsterType = MONSTER_BARREL) then
+    // this will also reset "need-send" flag
+    if mon.gncNeedSend then
+    begin
+      MH_SEND_MonsterPos(mon.UID);
+    end
+    else if (mon.MonsterType = MONSTER_BARREL) then
     begin
       if (mon.GameVelX <> 0) or (mon.GameVelY <> 0) then MH_SEND_MonsterPos(mon.UID);
     end
-    else
-      if (mon.MonsterState <> MONSTATE_SLEEP) then
-      begin
-        if (mon.MonsterState <> MONSTATE_DEAD) or (mon.GameVelX <> 0) or (mon.GameVelY <> 0) then
-        begin
-          MH_SEND_MonsterPos(mon.UID);
-        end;
-      end;
+    else if (mon.MonsterState <> MONSTATE_SLEEP) then
+    begin
+      if (mon.MonsterState <> MONSTATE_DEAD) or (mon.GameVelX <> 0) or (mon.GameVelY <> 0) then MH_SEND_MonsterPos(mon.UID);
+    end;
+  end;
+
+  function sendMonsPosUnexpected (mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    // this will also reset "need-send" flag
+    if mon.gncNeedSend then MH_SEND_MonsterPos(mon.UID);
   end;
 
+var
+  reliableUpdate: Boolean;
 begin
   g_ResetDynlights();
 // Ïîðà âûêëþ÷àòü èãðó:
@@ -1842,13 +1852,16 @@ begin
     g_GFX_Update();
     g_Player_UpdateAll();
     g_Player_UpdatePhysicalObjects();
-    if gGameSettings.GameType = GT_SERVER then
-      if Length(gMonstersSpawned) > 0 then
+
+    // server: send newly spawned monsters unconditionally
+    if (gGameSettings.GameType = GT_SERVER) then
+    begin
+      if (Length(gMonstersSpawned) > 0) then
       begin
-        for I := 0 to High(gMonstersSpawned) do
-          MH_SEND_MonsterSpawn(gMonstersSpawned[I]);
+        for I := 0 to High(gMonstersSpawned) do MH_SEND_MonsterSpawn(gMonstersSpawned[I]);
         SetLength(gMonstersSpawned, 0);
       end;
+    end;
 
     if (gSoundTriggerTime > 8) then
     begin
@@ -1856,49 +1869,65 @@ begin
       gSoundTriggerTime := 0;
     end
     else
+    begin
       Inc(gSoundTriggerTime);
+    end;
 
     if (NetMode = NET_SERVER) then
     begin
       Inc(NetTimeToUpdate);
       Inc(NetTimeToReliable);
-      if NetTimeToReliable >= NetRelupdRate then
+
+      // send monster updates
+      if (NetTimeToReliable >= NetRelupdRate) or (NetTimeToUpdate >= NetUpdateRate) then
       begin
+        // send all monsters (periodic sync)
+        reliableUpdate := (NetTimeToReliable >= NetRelupdRate);
+
         for I := 0 to High(gPlayers) do
-          if gPlayers[I] <> nil then
-            MH_SEND_PlayerPos(True, gPlayers[I].UID);
+        begin
+          if (gPlayers[I] <> nil) then MH_SEND_PlayerPos(reliableUpdate, gPlayers[I].UID);
+        end;
 
         g_Mons_ForEach(sendMonsPos);
 
-        NetTimeToReliable := 0;
-        NetTimeToUpdate := NetUpdateRate;
+        if reliableUpdate then
+        begin
+          NetTimeToReliable := 0;
+          NetTimeToUpdate := NetUpdateRate;
+        end
+        else
+        begin
+          NetTimeToUpdate := 0;
+        end;
       end
-      else if NetTimeToUpdate >= NetUpdateRate then
+      else
       begin
-        if gPlayers <> nil then
-          for I := 0 to High(gPlayers) do
-            if gPlayers[I] <> nil then
-              MH_SEND_PlayerPos(False, gPlayers[I].UID);
-
-        g_Mons_ForEach(sendMonsPos);
-
-        NetTimeToUpdate := 0;
+        // send only mosters with some unexpected changes
+        g_Mons_ForEach(sendMonsPosUnexpected);
       end;
 
+      // send unexpected platform changes
+      g_Map_NetSendInterestingPanels();
+
       if NetUseMaster then
+      begin
         if gTime >= NetTimeToMaster then
         begin
           if (NetMHost = nil) or (NetMPeer = nil) then
-            if not g_Net_Slist_Connect then
-              g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR]);
+          begin
+            if not g_Net_Slist_Connect then g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR]);
+          end;
 
           g_Net_Slist_Update;
           NetTimeToMaster := gTime + NetMasterRate;
         end;
+      end;
     end
-    else
-      if NetMode = NET_CLIENT then
-        MC_SEND_PlayerPos();
+    else if (NetMode = NET_CLIENT) then
+    begin
+      MC_SEND_PlayerPos();
+    end;
   end; // if gameOn ...
 
 // Àêòèâíî îêíî èíòåðôåéñà - ïåðåäàåì êëàâèøè åìó:
@@ -2009,12 +2038,12 @@ begin
   g_Texture_CreateWADEx('TEXTURE_PLAYER_HUDJET', GameWAD+':TEXTURES\JETBAR');
   g_Texture_CreateWADEx('TEXTURE_PLAYER_HUDBG', GameWAD+':TEXTURES\HUDBG');
   g_Texture_CreateWADEx('TEXTURE_PLAYER_ARMORHUD', GameWAD+':TEXTURES\ARMORHUD');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG', GameWAD+':TEXTURES\FLAGHUD_RB');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG_S', GameWAD+':TEXTURES\FLAGHUD_RS');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG_D', GameWAD+':TEXTURES\FLAGHUD_RD');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG', GameWAD+':TEXTURES\FLAGHUD_BB');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG_S', GameWAD+':TEXTURES\FLAGHUD_BS');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG_D', GameWAD+':TEXTURES\FLAGHUD_BD');
+  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG', GameWAD+':TEXTURES\FLAGHUD_RB', GameWAD+':TEXTURES\FLAGHUD_R_BASE');
+  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG_S', GameWAD+':TEXTURES\FLAGHUD_RS', GameWAD+':TEXTURES\FLAGHUD_R_STOLEN');
+  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG_D', GameWAD+':TEXTURES\FLAGHUD_RD', GameWAD+':TEXTURES\FLAGHUD_R_DROP');
+  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG', GameWAD+':TEXTURES\FLAGHUD_BB', GameWAD+':TEXTURES\FLAGHUD_B_BASE');
+  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG_S', GameWAD+':TEXTURES\FLAGHUD_BS', GameWAD+':TEXTURES\FLAGHUD_B_STOLEN');
+  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG_D', GameWAD+':TEXTURES\FLAGHUD_BD', GameWAD+':TEXTURES\FLAGHUD_B_DROP');
   g_Texture_CreateWADEx('TEXTURE_PLAYER_TALKBUBBLE', GameWAD+':TEXTURES\TALKBUBBLE');
   g_Texture_CreateWADEx('TEXTURE_PLAYER_INVULPENTA', GameWAD+':TEXTURES\PENTA');
   g_Frames_CreateWAD(nil, 'FRAMES_TELEPORT', GameWAD+':TEXTURES\TELEPORT', 64, 64, 10, False);
@@ -2493,7 +2522,7 @@ var
     result := false; // don't stop
     with mon do
     begin
-      if Live then
+      if alive then
       begin
         // Ëåâûé âåðõíèé óãîë
         aX := Obj.X div ScaleSz + 1;
@@ -2656,7 +2685,7 @@ begin
     // Ðèñóåì èãðîêîâ:
       for a := 0 to High(gPlayers) do
         if gPlayers[a] <> nil then with gPlayers[a] do
-          if Live then begin
+          if alive then begin
           // Ëåâûé âåðõíèé óãîë:
             aX := Obj.X div ScaleSz + 1;
             aY := Obj.Y div ScaleSz + 1;
@@ -2683,15 +2712,57 @@ begin
 end;
 
 
+procedure renderAmbientQuad (hasAmbient: Boolean; constref ambColor: TDFColor);
+begin
+  if not hasAmbient then exit;
+  e_AmbientQuad(sX, sY, sWidth, sHeight, ambColor.r, ambColor.g, ambColor.b, ambColor.a);
+end;
+
+
 // setup sX, sY, sWidth, sHeight, and transformation matrix before calling this!
+//FIXME: broken for splitscreen mode
 procedure renderDynLightsInternal ();
 var
+  //hasAmbient: Boolean;
+  //ambColor: TDFColor;
   lln: Integer;
   lx, ly, lrad: Integer;
+  scxywh: array[0..3] of GLint;
+  wassc: Boolean;
 begin
+  if e_NoGraphics then exit;
+
   //TODO: lights should be in separate grid, i think
   //      but on the other side: grid may be slower for dynlights, as their lifetime is short
-  if not gwin_has_stencil or (g_dynLightCount < 1) then exit;
+  if (not g_playerLight) or (not gwin_has_stencil) or (g_dynLightCount < 1) then exit;
+
+  // rendering mode
+  //ambColor := gCurrentMap['light_ambient'].rgba;
+  //hasAmbient := (not ambColor.isOpaque) or (not ambColor.isBlack);
+
+  { // this will multiply incoming color to alpha from framebuffer
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_DST_ALPHA, GL_ONE);
+  }
+
+  (*
+   * light rendering: (INVALID!)
+   *   glStencilFunc(GL_EQUAL, 0, $ff);
+   *   for each light:
+   *     glClear(GL_STENCIL_BUFFER_BIT);
+   *     glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+   *     draw shadow volume into stencil buffer
+   *     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
+   *     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // don't modify stencil buffer
+   *     turn off blending
+   *     draw color-less quad with light alpha (WARNING! don't touch color!)
+   *     glEnable(GL_BLEND);
+   *     glBlendFunc(GL_DST_ALPHA, GL_ONE);
+   *     draw all geometry up to and including walls (with alpha-testing, probably) -- this does lighting
+   *)
+
+  wassc := (glIsEnabled(GL_SCISSOR_TEST) <> 0);
+  if wassc then glGetIntegerv(GL_SCISSOR_BOX, @scxywh[0]) else glGetIntegerv(GL_VIEWPORT, @scxywh[0]);
 
   // setup OpenGL parameters
   glStencilMask($FFFFFFFF);
@@ -2706,17 +2777,24 @@ begin
     lx := g_dynLights[lln].x;
     ly := g_dynLights[lln].y;
     lrad := g_dynLights[lln].radius;
-    if lrad < 3 then continue;
+    if (lrad < 3) then continue;
 
-    if lx-sX+lrad < 0 then continue;
-    if ly-sY+lrad < 0 then continue;
-    if lx-sX-lrad >= gPlayerScreenSize.X then continue;
-    if ly-sY-lrad >= gPlayerScreenSize.Y then continue;
+    if (lx-sX+lrad < 0) then continue;
+    if (ly-sY+lrad < 0) then continue;
+    if (lx-sX-lrad >= gPlayerScreenSize.X) then continue;
+    if (ly-sY-lrad >= gPlayerScreenSize.Y) then continue;
 
     // set scissor to optimize drawing
-    //FIXME: broken for splitscreen mode
-    glScissor((lx-sX)-lrad+2, gPlayerScreenSize.Y-(ly-sY)-lrad-1+2, lrad*2-4, lrad*2-4);
-    // no need to clear stencil buffer, light blitting will do it for us
+    if (g_dbg_scale = 1.0) then
+    begin
+      glScissor((lx-sX)-lrad+2, gPlayerScreenSize.Y-(ly-sY)-lrad-1+2, lrad*2-4, lrad*2-4);
+    end
+    else
+    begin
+      glScissor(0, 0, gWinSizeX, gWinSizeY);
+    end;
+    // no need to clear stencil buffer, light blitting will do it for us... but only for normal scale
+    if (g_dbg_scale <> 1.0) then glClear(GL_STENCIL_BUFFER_BIT);
     glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
     // draw extruded panels
     glDisable(GL_TEXTURE_2D);
@@ -2745,16 +2823,44 @@ begin
   glDisable(GL_STENCIL_TEST);
   glDisable(GL_BLEND);
   glDisable(GL_SCISSOR_TEST);
-  glScissor(0, 0, sWidth, sHeight);
+  //glScissor(0, 0, sWidth, sHeight);
+
+  glScissor(scxywh[0], scxywh[1], scxywh[2], scxywh[3]);
+  if wassc then glEnable(GL_SCISSOR_TEST) else glDisable(GL_SCISSOR_TEST);
+end;
+
+
+function fixViewportForScale (): Boolean;
+var
+  nx0, ny0, nw, nh: Integer;
+begin
+  result := false;
+  if (g_dbg_scale <> 1.0) then
+  begin
+    result := true;
+    nx0 := round(sX-(gPlayerScreenSize.X-(sWidth*g_dbg_scale))/2/g_dbg_scale);
+    ny0 := round(sY-(gPlayerScreenSize.Y-(sHeight*g_dbg_scale))/2/g_dbg_scale);
+    nw := round(sWidth/g_dbg_scale);
+    nh := round(sHeight/g_dbg_scale);
+    sX := nx0;
+    sY := ny0;
+    sWidth := nw;
+    sHeight := nh;
+  end;
 end;
 
 
 // setup sX, sY, sWidth, sHeight, and transformation matrix before calling this!
 // WARNING! this WILL CALL `glTranslatef()`, but won't restore matrices!
-procedure renderMapInternal (backXOfs, backYOfs: Integer; transX, transY: Integer; setTransMatrix: Boolean);
+procedure renderMapInternal (backXOfs, backYOfs: Integer; setTransMatrix: Boolean);
 type
   TDrawCB = procedure ();
 
+var
+  hasAmbient: Boolean;
+  ambColor: TDFColor;
+  doAmbient: Boolean = false;
+
   procedure drawPanelType (profname: AnsiString; panType: DWord; doDraw: Boolean);
   var
     tagmask: Integer;
@@ -2768,13 +2874,13 @@ type
       begin
         pan := TPanel(gDrawPanelList.front());
         if ((pan.tag and tagmask) = 0) then break;
-        if doDraw then pan.Draw();
+        if doDraw then pan.Draw(doAmbient, ambColor);
         gDrawPanelList.popFront();
       end;
     end
     else
     begin
-      if doDraw then g_Map_DrawPanels(panType);
+      if doDraw then g_Map_DrawPanels(panType, hasAmbient, ambColor);
     end;
     profileFrameDraw.sectionEnd();
   end;
@@ -2802,7 +2908,27 @@ begin
   g_Map_DrawBack(backXOfs, backYOfs);
   profileFrameDraw.sectionEnd();
 
-  if (setTransMatrix) then glTranslatef(transX, transY, 0);
+  if setTransMatrix then
+  begin
+    //if (g_dbg_scale <> 1.0) then glTranslatef(0.0, -0.375/2, 0);
+    glScalef(g_dbg_scale, g_dbg_scale, 1.0);
+    glTranslatef(-sX, -sY, 0);
+  end;
+
+  // rendering mode
+  ambColor := gCurrentMap['light_ambient'].rgba;
+  hasAmbient := (not ambColor.isOpaque) or (not ambColor.isBlack);
+
+  {
+  if hasAmbient then
+  begin
+    //writeln('color: (', ambColor.r, ',', ambColor.g, ',', ambColor.b, ',', ambColor.a, ')');
+    glColor4ub(ambColor.r, ambColor.g, ambColor.b, ambColor.a);
+    glClear(GL_COLOR_BUFFER_BIT);
+  end;
+  }
+  //writeln('color: (', ambColor.r, ',', ambColor.g, ',', ambColor.b, ',', ambColor.a, ')');
+
 
   drawPanelType('*back', PANEL_BACK, g_rlayer_back);
   drawPanelType('*step', PANEL_STEP, g_rlayer_step);
@@ -2820,8 +2946,16 @@ begin
   drawPanelType('*acid2', PANEL_ACID2, g_rlayer_acid2);
   drawPanelType('*water', PANEL_WATER, g_rlayer_water);
   drawOther('dynlights', @renderDynLightsInternal);
+
+  if hasAmbient {and ((not g_playerLight) or (not gwin_has_stencil) or (g_dynLightCount < 1))} then
+  begin
+    renderAmbientQuad(hasAmbient, ambColor);
+  end;
+
+  doAmbient := true;
   drawPanelType('*fore', PANEL_FORE, g_rlayer_fore);
 
+
   if g_debug_HealthBar then
   begin
     g_Monsters_DrawHealth();
@@ -2847,7 +2981,8 @@ begin
   sWidth := w;
   sHeight := h;
 
-  renderMapInternal(-bx, -by, -x, -y, true);
+  fixViewportForScale();
+  renderMapInternal(-bx, -by, true);
 
   glPopMatrix();
 end;
@@ -2857,7 +2992,6 @@ procedure DrawPlayer(p: TPlayer);
 var
   px, py, a, b, c, d: Integer;
   //R: TRect;
-
 begin
   if (p = nil) or (p.FDummy) then
   begin
@@ -2875,26 +3009,35 @@ begin
   glPushMatrix();
 
   px := p.GameX + PLAYER_RECT_CX;
-  py := p.GameY + PLAYER_RECT_CY;
+  py := p.GameY + PLAYER_RECT_CY+p.Obj.slopeUpLeft;
 
-  if px > (gPlayerScreenSize.X div 2) then a := -px+(gPlayerScreenSize.X div 2) else a := 0;
-  if py > (gPlayerScreenSize.Y div 2) then b := -py+(gPlayerScreenSize.Y div 2) else b := 0;
+  if (g_dbg_scale = 1.0) then
+  begin
+    if (px > (gPlayerScreenSize.X div 2)) then a := -px+(gPlayerScreenSize.X div 2) else a := 0;
+    if (py > (gPlayerScreenSize.Y div 2)) then b := -py+(gPlayerScreenSize.Y div 2) else b := 0;
 
-  if px > gMapInfo.Width-(gPlayerScreenSize.X div 2) then a := -gMapInfo.Width+gPlayerScreenSize.X;
-  if py > gMapInfo.Height-(gPlayerScreenSize.Y div 2) then b := -gMapInfo.Height+gPlayerScreenSize.Y;
+    if (px > gMapInfo.Width-(gPlayerScreenSize.X div 2)) then a := -gMapInfo.Width+gPlayerScreenSize.X;
+    if (py > gMapInfo.Height-(gPlayerScreenSize.Y div 2)) then b := -gMapInfo.Height+gPlayerScreenSize.Y;
 
-       if (gMapInfo.Width = gPlayerScreenSize.X) then a := 0
-  else if (gMapInfo.Width < gPlayerScreenSize.X) then
-  begin
-    // hcenter
-    a := (gPlayerScreenSize.X-gMapInfo.Width) div 2;
-  end;
+         if (gMapInfo.Width = gPlayerScreenSize.X) then a := 0
+    else if (gMapInfo.Width < gPlayerScreenSize.X) then
+    begin
+      // hcenter
+      a := (gPlayerScreenSize.X-gMapInfo.Width) div 2;
+    end;
 
-       if (gMapInfo.Height = gPlayerScreenSize.Y) then b := 0
-  else if (gMapInfo.Height < gPlayerScreenSize.Y) then
+         if (gMapInfo.Height = gPlayerScreenSize.Y) then b := 0
+    else if (gMapInfo.Height < gPlayerScreenSize.Y) then
+    begin
+      // vcenter
+      b := (gPlayerScreenSize.Y-gMapInfo.Height) div 2;
+    end;
+  end
+  else
   begin
-    // vcenter
-    b := (gPlayerScreenSize.Y-gMapInfo.Height) div 2;
+    // scaled, ignore level bounds
+    a := -px+(gPlayerScreenSize.X div 2);
+    b := -py+(gPlayerScreenSize.Y div 2);
   end;
 
   if p.IncCam <> 0 then
@@ -2941,17 +3084,17 @@ begin
 
   //glTranslatef(a, b+p.IncCam, 0);
 
+  if (p = gPlayer1) then g_Holmes_plrViewSize(sWidth, sHeight);
+
+  fixViewportForScale();
   p.viewPortX := sX;
   p.viewPortY := sY;
   p.viewPortW := sWidth;
   p.viewPortH := sHeight;
 
-  if (p = gPlayer1) then
-  begin
-    g_Holmes_plrView(p.viewPortX, p.viewPortY, p.viewPortW, p.viewPortH);
-  end;
+  if (p = gPlayer1) then g_Holmes_plrViewPos(sX, sY);
 
-  renderMapInternal(-c, -d, a, b+p.IncCam, true);
+  renderMapInternal(-c, -d, true);
 
   if p.FSpectator then
     e_TextureFontPrintEx(p.GameX + PLAYER_RECT_CX - 4,
@@ -3211,16 +3354,26 @@ begin
   begin
     if (gState = STATE_MENU) then
     begin
-      if  ((g_ActiveWindow = nil) or (g_ActiveWindow.BackTexture = '')) then
+      if (g_ActiveWindow = nil) or (g_ActiveWindow.BackTexture = '') then
       begin
-        if g_Texture_Get('MENU_BACKGROUND', ID) then
-          e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight)
+        if g_Texture_Get('MENU_BACKGROUND', ID) then e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight)
         else e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
       end;
-      if g_ActiveWindow <> nil then
+      // F3 at menu will show game loading dialog
+      if e_KeyPressed(IK_F3) then g_Menu_Show_LoadMenu(true);
+      if (g_ActiveWindow <> nil) then
       begin
         //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
         e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+      end
+      else
+      begin
+        // F3 at titlepic will show game loading dialog
+        if e_KeyPressed(IK_F3) then
+        begin
+          g_Menu_Show_LoadMenu(true);
+          if (g_ActiveWindow <> nil) then e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+        end;
       end;
     end;
 
@@ -3971,7 +4124,7 @@ begin
         end;
     end;
 
-    ProcessLoading();
+    ProcessLoading(true);
 
     e_PollInput();
 
@@ -4003,7 +4156,7 @@ begin
   g_Options_Write_Video(GameDir+'/'+CONFIG_FILENAME);
 end;
 
-procedure g_Game_ChangeMap(MapPath: String);
+procedure g_Game_ChangeMap(const MapPath: String);
 var
   Force: Boolean;
 begin
@@ -4031,15 +4184,15 @@ begin
   MessageTime := 0;
   gGameOn := False;
   g_Game_ClearLoading();
-  g_Game_StartMap(Map, True);
+  g_Game_StartMap(Map, True, gCurrentMapFileName);
 end;
 
-function g_Game_StartMap(Map: String; Force: Boolean = False): Boolean;
+function g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
 var
   NewWAD, ResName: String;
   I: Integer;
 begin
-  g_Map_Free();
+  g_Map_Free((Map <> gCurrentMapFileName) and (oldMapPath <> gCurrentMapFileName));
   g_Player_RemoveAllCorpses();
 
   if (not g_Game_IsClient) and
@@ -4055,7 +4208,7 @@ begin
 
   g_Player_ResetTeams();
 
-  if Pos(':\', Map) > 0 then
+  if isWadPath(Map) then
   begin
     NewWAD := g_ExtractWadName(Map);
     ResName := g_ExtractFileName(Map);
@@ -4187,7 +4340,7 @@ begin
   MapList := nil;
 end;
 
-procedure g_Game_ExitLevel(Map: Char16);
+procedure g_Game_ExitLevel(const Map: AnsiString);
 begin
   gNextMap := Map;
 
@@ -4422,7 +4575,7 @@ end;
 procedure g_Game_DeleteTestMap();
 var
   a: Integer;
-  MapName: Char16;
+  //MapName: AnsiString;
   WadName: string;
 {
   WAD: TWADFile;
@@ -4430,16 +4583,16 @@ var
   time: Integer;
 }
 begin
-  a := Pos('.wad:\', gMapToDelete);
-  if a = 0 then
-    Exit;
+  a := Pos('.wad:\', toLowerCase1251(gMapToDelete));
+  if (a = 0) then a := Pos('.wad:/', toLowerCase1251(gMapToDelete));
+  if (a = 0) then exit;
 
-// Âûäåëÿåì èìÿ wad-ôàéëà è èìÿ êàðòû:
-  WadName := Copy(gMapToDelete, 1, a + 3);
-  Delete(gMapToDelete, 1, a + 5);
+  // Âûäåëÿåì èìÿ wad-ôàéëà è èìÿ êàðòû
+  WadName := Copy(gMapToDelete, 1, a+3);
+  Delete(gMapToDelete, 1, a+5);
   gMapToDelete := UpperCase(gMapToDelete);
-  MapName := '';
-  CopyMemory(@MapName[0], @gMapToDelete[1], Min(16, Length(gMapToDelete)));
+  //MapName := '';
+  //CopyMemory(@MapName[0], @gMapToDelete[1], Min(16, Length(gMapToDelete)));
 
 {
 // Èìÿ êàðòû íå ñòàíäàðòíîå òåñòîâîå:
@@ -5012,12 +5165,25 @@ begin
   end;
 end;
 
+procedure PrintHeapStats();
+var
+  hs: TFPCHeapStatus;
+begin
+  hs := GetFPCHeapStatus();
+  e_LogWriteLn ('v===== heap status =====v');
+  e_LogWriteFln('max heap size = %d k', [hs.MaxHeapSize div 1024]);
+  e_LogWriteFln('max heap used = %d k', [hs.MaxHeapUsed div 1024]);
+  e_LogWriteFln('cur heap size = %d k', [hs.CurrHeapSize div 1024]);
+  e_LogWriteFln('cur heap used = %d k', [hs.CurrHeapUsed div 1024]);
+  e_LogWriteFln('cur heap free = %d k', [hs.CurrHeapFree div 1024]);
+  e_LogWriteLn ('^=======================^');
+end;
 
 procedure DebugCommands(P: SArray);
 var
   a, b: Integer;
   cmd: string;
-  //pt: TPoint;
+  //pt: TDFPoint;
   mon: TMonster;
 begin
 // Êîìàíäû îòëàäî÷íîãî ðåæèìà:
@@ -5078,18 +5244,18 @@ begin
     end
     else if cmd = 'd_monster' then
     begin
-      if gGameOn and (gPlayer1 <> nil) and (gPlayer1.Live) and (not g_Game_IsNet) then
+      if gGameOn and (gPlayer1 <> nil) and (gPlayer1.alive) and (not g_Game_IsNet) then
         if Length(P) < 2 then
         begin
           g_Console_Add(cmd + ' [ID | Name] [behaviour]');
           g_Console_Add('ID | Name');
           for b := MONSTER_DEMON to MONSTER_MAN do
-            g_Console_Add(Format('%2d | %s', [b, g_Monsters_GetNameByID(b)]));
+            g_Console_Add(Format('%2d | %s', [b, g_Mons_NameByTypeId(b)]));
         end else
         begin
           a := StrToIntDef(P[1], 0);
           if (a < MONSTER_DEMON) or (a > MONSTER_MAN) then
-            a := g_Monsters_GetIDByName(P[1]);
+            a := g_Mons_TypeIdByName(P[1]);
 
           if (a < MONSTER_DEMON) or (a > MONSTER_MAN) then
             g_Console_Add(Format(_lc[I_MSG_NO_MONSTER], [P[1]]))
@@ -5127,6 +5293,10 @@ begin
     begin
       for a := 1 to 8 do
         g_Console_Add(e_JoystickStateToString(a));
+    end
+    else if (cmd = 'd_mem') then
+    begin
+      PrintHeapStats();
     end;
   end
     else
@@ -5179,7 +5349,8 @@ begin
             begin
               g_Console_Add('player left the map');
               gExitByTrigger := True;
-              g_Game_ExitLevel(gTriggers[a].Data.MapName);
+              //g_Game_ExitLevel(gTriggers[a].Data.MapName);
+              g_Game_ExitLevel(gTriggers[a].tgcMap);
               break;
             end;
           end;
@@ -6048,14 +6219,15 @@ begin
             if gTriggers[a].TriggerType = TRIGGER_EXIT then
             begin
               gExitByTrigger := True;
-              gNextMap := gTriggers[a].Data.MapName;
+              //gNextMap := gTriggers[a].Data.MapName;
+              gNextMap := gTriggers[a].tgcMap;
               Break;
             end;
         // Èùåì ñëåäóþùóþ êàðòó â WAD ôàéëå
         if gNextMap = '' then
           gNextMap := g_Game_GetNextMap();
         // Ïðîâåðÿåì, íå çàäàí ëè WAD ôàéë ðåñóðñíîé ñòðîêîé
-        if Pos(':\', gNextMap) = 0 then
+        if not isWadPath(gNextMap) then
           s := gGameSettings.WAD + ':\' + gNextMap
         else
           s := gNextMap;
@@ -6101,6 +6273,21 @@ begin
         Exit;
       end;
   end
+  else if cmd = 'suicide' then
+  begin
+    if gGameOn then
+    begin
+      if g_Game_IsClient then
+        MC_SEND_CheatRequest(NET_CHEAT_SUICIDE)
+      else
+      begin
+        if gPlayer1 <> nil then
+          gPlayer1.Damage(SUICIDE_DAMAGE, gPlayer1.UID, 0, 0, HIT_SELF);
+        if gPlayer2 <> nil then
+          gPlayer2.Damage(SUICIDE_DAMAGE, gPlayer2.UID, 0, 0, HIT_SELF);
+      end;
+    end;
+  end
 // Êîìàíäû Ñâîåé èãðû:
   else if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
   begin
@@ -6118,21 +6305,6 @@ begin
       else
         g_Bot_Add(TEAM_BLUE, 2);
     end
-    else if cmd = 'suicide' then
-    begin
-      if gGameOn then
-      begin
-        if g_Game_IsClient then
-          MC_SEND_CheatRequest(NET_CHEAT_SUICIDE)
-        else
-        begin
-          if gPlayer1 <> nil then
-            gPlayer1.Damage(SUICIDE_DAMAGE, gPlayer1.UID, 0, 0, HIT_SELF);
-          if gPlayer2 <> nil then
-            gPlayer2.Damage(SUICIDE_DAMAGE, gPlayer2.UID, 0, 0, HIT_SELF);
-        end;
-      end;
-    end
     else if cmd = 'spectate' then
     begin
       if not gGameOn then
@@ -6439,17 +6611,17 @@ begin
       with gTriggers[i] do
         if (TriggerType = TRIGGER_SOUND) and
            (Sound <> nil) and
-           (Data.Local) and
+           (tgcLocal) and
            Sound.IsPlaying() then
         begin
           if ((gPlayer1 <> nil) and g_CollidePoint(gPlayer1.GameX, gPlayer1.GameY, X, Y, Width, Height)) or
              ((gPlayer2 <> nil) and g_CollidePoint(gPlayer2.GameX, gPlayer2.GameY, X, Y, Width, Height)) then
           begin
-            Sound.SetPan(0.5 - Data.Pan/255.0);
-            Sound.SetVolume(Data.Volume/255.0);
+            Sound.SetPan(0.5 - tgcPan/255.0);
+            Sound.SetVolume(tgcVolume/255.0);
           end
           else
-            Sound.SetCoords(X+(Width div 2), Y+(Height div 2), Data.Volume/255.0);
+            Sound.SetCoords(X+(Width div 2), Y+(Height div 2), tgcVolume/255.0);
         end;
 end;
 
@@ -6737,7 +6909,7 @@ begin
 
   g_ActiveWindow := nil;
 
-  ProcessLoading;
+  ProcessLoading(true);
 end;
 
 procedure g_Game_StepLoading();
@@ -6749,7 +6921,7 @@ begin
     if (ShowCount > LOADING_SHOW_STEP) then
     begin
       ShowCount := 0;
-      ProcessLoading;
+      ProcessLoading();
     end;
   end;
 end;
@@ -6956,7 +7128,7 @@ begin
   s := Find_Param_Value(pars, '-exec');
   if s <> '' then
   begin
-    if Pos(':\', s) = 0 then
+    if not isWadPath(s) then
       s := GameDir + '/' + s;
 
     {$I-}
@@ -7011,4 +7183,6 @@ begin
   conRegVar('mon_think', @gmon_debug_think, 'enable/disable monster thinking', 'monster thinking', true);
 
   conRegVar('dbg_holmes', @g_holmes_enabled, 'enable/disable Holmes', 'Holmes', true);
+
+  conRegVar('dbg_scale', @g_dbg_scale, 0.01, 100.0, 'experimental deBUG scale mode', '',  false);
 end.