DEADSOFTWARE

renamed `SArray` type to `SSArray`, and moved it to "utils.pas"
[d2df-sdl.git] / src / game / g_map.pas
index ad9c68ddbb5265a3c5619febfb53e696bc53d607..745f26166a931226c604f90871e2213d1291f288 100644 (file)
@@ -20,8 +20,9 @@ unit g_map;
 interface
 
 uses
-  e_graphics, g_basic, MAPDEF, g_textures, Classes,
-  g_phys, wadreader, BinEditor, g_panel, g_grid, md5, binheap, xprofiler, xparser, xdynrec;
+  SysUtils, Classes,
+  e_graphics, g_basic, MAPDEF, g_textures,
+  g_phys, utils, g_panel, g_grid, md5, binheap, xprofiler, xparser, xdynrec;
 
 type
   TMapInfo = record
@@ -58,7 +59,7 @@ type
 
 function  g_Map_Load(Res: String): Boolean;
 function  g_Map_GetMapInfo(Res: String): TMapInfo;
-function  g_Map_GetMapsList(WADName: String): SArray;
+function  g_Map_GetMapsList(WADName: String): SSArray;
 function  g_Map_Exist(Res: String): Boolean;
 procedure g_Map_Free(freeTextures: Boolean=true);
 procedure g_Map_Update();
@@ -82,7 +83,7 @@ procedure g_Map_EnableWall_XXX (ID: DWORD);
 procedure g_Map_DisableWall_XXX (ID: DWORD);
 procedure g_Map_SetLift_XXX (ID: DWORD; t: Integer);
 
-procedure g_Map_SwitchTextureGUID (PanelType: Word; pguid: Integer; AnimLoop: Byte = 0);
+procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
 
 procedure g_Map_ReAdd_DieTriggers();
 function  g_Map_IsSpecialTexture(Texture: String): Boolean;
@@ -95,8 +96,8 @@ function  g_Map_HaveFlagPoints(): Boolean;
 procedure g_Map_ResetFlag(Flag: Byte);
 procedure g_Map_DrawFlags();
 
-procedure g_Map_SaveState(Var Mem: TBinMemoryWriter);
-procedure g_Map_LoadState(Var Mem: TBinMemoryReader);
+procedure g_Map_SaveState (st: TStream);
+procedure g_Map_LoadState (st: TStream);
 
 procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
 
@@ -242,11 +243,11 @@ var
 implementation
 
 uses
-  e_input, g_main, e_log, e_texture, SysUtils, g_items, g_gfx, g_console,
+  e_input, g_main, e_log, e_texture, g_items, g_gfx, g_console,
   GL, GLExt, g_weapons, g_game, g_sound, e_sound, CONFIG,
   g_options, g_triggers, g_player,
   Math, g_monsters, g_saveload, g_language, g_netmsg,
-  utils, sfs, xstreams, hashtable,
+  sfs, xstreams, hashtable, wadreader,
   ImagingTypes, Imaging, ImagingUtility,
   ImagingGif, ImagingNetworkGraphics;
 
@@ -256,6 +257,21 @@ const
   FLAG_SIGNATURE = $47414C46; // 'FLAG'
 
 
+// ////////////////////////////////////////////////////////////////////////// //
+procedure mapWarningCB (const msg: AnsiString; line, col: Integer);
+begin
+  if (line > 0) then
+  begin
+    e_LogWritefln('parse error at (%s,%s): %s', [line, col, msg], TMsgType.Warning);
+  end
+  else
+  begin
+    e_LogWritefln('parse error: %s', [msg], TMsgType.Warning);
+  end;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
 var
   panByGUID: array of TPanel = nil;
 
@@ -884,7 +900,7 @@ begin
   if TextNameHash.get(RecName, result) then
   begin
     // i found her!
-    //e_LogWritefln('texture ''%s'' already loaded', [RecName]);
+    //e_LogWritefln('texture ''%s'' already loaded (%s)', [RecName, result]);
     exit;
   end;
 
@@ -952,7 +968,9 @@ begin
     SetLength(Textures, Length(Textures)+1);
     if not e_CreateTextureMem(TextureData, ResLength, Textures[High(Textures)].TextureID) then
     begin
+      e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
       SetLength(Textures, Length(Textures)-1);
+      result := -1;
       Exit;
     end;
     e_GetTextureSize(Textures[High(Textures)].TextureID, @Textures[High(Textures)].Width, @Textures[High(Textures)].Height);
@@ -969,7 +987,7 @@ begin
     if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt();
     if log and (not BadTextNameHash.get(RecName, a)) then
     begin
-      e_WriteLog(Format('Error loading texture %s', [RecName]), MSG_WARNING);
+      e_WriteLog(Format('Error loading texture %s', [RecName]), TMsgType.Warning);
       //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
     end;
     BadTextNameHash.put(RecName, -1);
@@ -1000,7 +1018,7 @@ begin
   if TextNameHash.get(RecName, result) then
   begin
     // i found her!
-    //e_LogWritefln('animated texture ''%s'' already loaded', [RecName]);
+    //e_LogWritefln('animated texture ''%s'' already loaded (%s)', [RecName, result]);
     exit;
   end;
 
@@ -1029,7 +1047,7 @@ begin
       if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt();
       if log and (not BadTextNameHash.get(RecName, f)) then
       begin
-        e_WriteLog(Format('Error loading animation texture %s', [RecName]), MSG_WARNING);
+        e_WriteLog(Format('Error loading animation texture %s', [RecName]), TMsgType.Warning);
         //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
       end;
       BadTextNameHash.put(RecName, -1);
@@ -1048,7 +1066,7 @@ begin
 
     if ResLength < 6 then
     begin
-      e_WriteLog(Format('Animated texture file "%s" too short', [RecName]), MSG_WARNING);
+      e_WriteLog(Format('Animated texture file "%s" too short', [RecName]), TMsgType.Warning);
       BadTextNameHash.put(RecName, -1);
       exit;
     end;
@@ -1060,7 +1078,7 @@ begin
       // íåò, ýòî ñóïåðìåí!
       if not WAD.ReadMemory(TextureWAD, ResLength) then
       begin
-        e_WriteLog(Format('Animated texture WAD file "%s" is invalid', [RecName]), MSG_WARNING);
+        e_WriteLog(Format('Animated texture WAD file "%s" is invalid', [RecName]), TMsgType.Warning);
         BadTextNameHash.put(RecName, -1);
         exit;
       end;
@@ -1068,7 +1086,7 @@ begin
       // ×èòàåì INI-ðåñóðñ àíèì. òåêñòóðû è çàïîìèíàåì åãî óñòàíîâêè:
       if not WAD.GetResource('TEXT/ANIM', TextData, ResLength) then
       begin
-        e_WriteLog(Format('Animated texture file "%s" has invalid INI', [RecName]), MSG_WARNING);
+        e_WriteLog(Format('Animated texture file "%s" has invalid INI', [RecName]), TMsgType.Warning);
         BadTextNameHash.put(RecName, -1);
         exit;
       end;
@@ -1078,7 +1096,7 @@ begin
       TextureResource := cfg.ReadStr('', 'resource', '');
       if TextureResource = '' then
       begin
-        e_WriteLog(Format('Animated texture WAD file "%s" has no "resource"', [RecName]), MSG_WARNING);
+        e_WriteLog(Format('Animated texture WAD file "%s" has no "resource"', [RecName]), TMsgType.Warning);
         BadTextNameHash.put(RecName, -1);
         exit;
       end;
@@ -1095,7 +1113,7 @@ begin
       // ×èòàåì ðåñóðñ òåêñòóð (êàäðîâ) àíèì. òåêñòóðû â ïàìÿòü:
       if not WAD.GetResource('TEXTURES/'+TextureResource, TextureData, ResLength) then
       begin
-        e_WriteLog(Format('Animated texture WAD file "%s" has no texture "%s"', [RecName, 'TEXTURES/'+TextureResource]), MSG_WARNING);
+        e_WriteLog(Format('Animated texture WAD file "%s" has no texture "%s"', [RecName, 'TEXTURES/'+TextureResource]), TMsgType.Warning);
         BadTextNameHash.put(RecName, -1);
         exit;
       end;
@@ -1123,7 +1141,7 @@ begin
           if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt();
           if log and (not BadTextNameHash.get(RecName, f)) then
           begin
-            e_WriteLog(Format('Error loading animation texture %s', [RecName]), MSG_WARNING);
+            e_WriteLog(Format('Error loading animation texture %s', [RecName]), TMsgType.Warning);
           end;
           BadTextNameHash.put(RecName, -1);
         end;
@@ -1144,13 +1162,13 @@ begin
       GlobalMetadata.ClearMetaItemsForSaving();
       if not LoadMultiImageFromMemory(TextureWAD, ResLength, ia) then
       begin
-        e_WriteLog(Format('Animated texture file "%s" cannot be loaded', [RecName]), MSG_WARNING);
+        e_WriteLog(Format('Animated texture file "%s" cannot be loaded', [RecName]), TMsgType.Warning);
         BadTextNameHash.put(RecName, -1);
         exit;
       end;
       if length(ia) = 0 then
       begin
-        e_WriteLog(Format('Animated texture file "%s" has no frames', [RecName]), MSG_WARNING);
+        e_WriteLog(Format('Animated texture file "%s" has no frames', [RecName]), TMsgType.Warning);
         BadTextNameHash.put(RecName, -1);
         exit;
       end;
@@ -1194,7 +1212,7 @@ begin
       //writeln(' creating animated texture with ', length(ia), ' frames (delay:', _speed, '; backloop:', _backanimation, ') from "', RecName, '"...');
       //for f := 0 to high(ia) do writeln('  frame #', f, ': ', ia[f].width, 'x', ia[f].height);
       f := ord(_backanimation);
-      e_WriteLog(Format('Animated texture file "%s": %d frames (delay:%d; back:%d; frdelay:%d; frloop:%d), %dx%d', [RecName, length(ia), _speed, f, frdelay, frloop, _width, _height]), MSG_NOTIFY);
+      e_WriteLog(Format('Animated texture file "%s": %d frames (delay:%d; back:%d; frdelay:%d; frloop:%d), %dx%d', [RecName, length(ia), _speed, f, frdelay, frloop, _width, _height]), TMsgType.Notify);
 
       SetLength(Textures, Length(Textures)+1);
       // cîçäàåì êàäðû àíèì. òåêñòóðû èç êàðòèíîê
@@ -1215,7 +1233,7 @@ begin
         if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt();
         if log  and (not BadTextNameHash.get(RecName, f)) then
         begin
-          e_WriteLog(Format('Error loading animation texture "%s" images', [RecName]), MSG_WARNING);
+          e_WriteLog(Format('Error loading animation texture "%s" images', [RecName]), TMsgType.Warning);
         end;
         BadTextNameHash.put(RecName, -1);
       end;
@@ -1312,7 +1330,7 @@ begin
   end;
 end;
 
-function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer; fTexturePanel1Type, fTexturePanel2Type: Word): Integer;
+function CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer): Integer;
 var
   _trigger: TTrigger;
 begin
@@ -1328,16 +1346,11 @@ begin
     Width := Trigger.Width;
     Height := Trigger.Height;
     Enabled := Trigger.Enabled;
-    //TexturePanel := Trigger.TexturePanel;
     TexturePanelGUID := atpanid;
-    TexturePanelType := fTexturePanel1Type;
-    ShotPanelType := fTexturePanel2Type;
     TriggerType := Trigger.TriggerType;
     ActivateType := Trigger.ActivateType;
     Keys := Trigger.Keys;
     trigPanelGUID := atrigpanid;
-    //trigShotPanelId := ashotpanid;
-    //Data.Default := Trigger.DATA;
   end;
 
   result := Integer(g_Triggers_Create(_trigger, Trigger));
@@ -1503,7 +1516,7 @@ var
       if (pan.proxyId <> -1) then
       begin
         {$IF DEFINED(D2F_DEBUG)}
-        e_WriteLog(Format('DUPLICATE wall #%d(%d) enabled (%d); type:%08x', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.PanelType]), MSG_NOTIFY);
+        e_WriteLog(Format('DUPLICATE wall #%d(%d) enabled (%d); type:%08x', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.PanelType]), TMsgType.Notify);
         {$ENDIF}
         continue;
       end;
@@ -1608,7 +1621,7 @@ var
   PanelID: DWORD;
   AddTextures: TAddTextureArray;
   TriggersTable: array of TTRec;
-  FileName, mapResName, s, TexName: String;
+  FileName, mapResName, TexName, s: AnsiString;
   Data: Pointer;
   Len: Integer;
   ok, isAnim: Boolean;
@@ -1621,6 +1634,7 @@ var
   //moveActive: Boolean;
   pan: TPanel;
   mapOk: Boolean = false;
+  usedTextures: THashStrInt = nil; // key: mapTextureList
 begin
   mapGrid.Free();
   mapGrid := nil;
@@ -1641,7 +1655,7 @@ begin
     if (gCurrentMap = nil) then
     begin
       FileName := g_ExtractWadName(Res);
-      e_WriteLog('Loading map WAD: '+FileName, MSG_NOTIFY);
+      e_WriteLog('Loading map WAD: '+FileName, TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_WAD_FILE], 0, False);
 
       WAD := TWADFile.Create();
@@ -1671,7 +1685,7 @@ begin
       end;
 
       // Çàãðóçêà êàðòû:
-      e_LogWritefln('Loading map: %s', [mapResName], MSG_NOTIFY);
+      e_LogWritefln('Loading map: %s', [mapResName], TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
 
       stt := getTimeMicro();
@@ -1712,7 +1726,7 @@ begin
     monsters := gCurrentMap['monster'];
 
     // Çàãðóçêà îïèñàíèÿ êàðòû:
-    e_WriteLog('  Reading map info...', MSG_NOTIFY);
+    e_WriteLog('  Reading map info...', TMsgType.Notify);
     g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
 
     with gMapInfo do
@@ -1731,34 +1745,56 @@ begin
     // Äîáàâëåíèå òåêñòóð â Textures[]:
     if (mapTextureList <> nil) and (mapTextureList.count > 0) then
     begin
-      e_WriteLog('  Loading textures:', MSG_NOTIFY);
+      e_WriteLog('  Loading textures:', TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
 
-      cnt := -1;
-      for rec in mapTextureList do
-      begin
-        Inc(cnt);
-        s := rec.Resource;
-        {$IF DEFINED(D2F_DEBUG_TXLOAD)}
-        e_WriteLog(Format('    Loading texture #%d: %s', [cnt, s]), MSG_NOTIFY);
-        {$ENDIF}
-        //if g_Map_IsSpecialTexture(s) then e_WriteLog('      SPECIAL!', MSG_NOTIFY);
-        if rec.Anim then
-        begin
-          // Àíèìèðîâàííàÿ òåêñòóðà
-          ntn := CreateAnimTexture(rec.Resource, FileName, True);
-          if (ntn < 0) then g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [s]));
-        end
-        else
+      // find used textures
+      usedTextures := hashNewStrInt();
+      try
+        if (panels <> nil) and (panels.count > 0) then
         begin
-          // Îáû÷íàÿ òåêñòóðà
-          ntn := CreateTexture(rec.Resource, FileName, True);
-          if (ntn < 0) then g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [s]));
+          for rec in panels do
+          begin
+            texrec := rec.TextureRec;
+            if (texrec <> nil) then usedTextures.put(toLowerCase1251(texrec.Resource), 42);
+          end;
         end;
-        if (ntn < 0) then ntn := CreateNullTexture(rec.Resource);
 
-        rec.tagInt := ntn; // remember texture number
-        g_Game_StepLoading();
+        cnt := -1;
+        for rec in mapTextureList do
+        begin
+          Inc(cnt);
+          if not usedTextures.has(toLowerCase1251(rec.Resource)) then
+          begin
+            rec.tagInt := -1; // just in case
+            e_LogWritefln('    Unused texture #%d: %s', [cnt, rec.Resource]);
+          end
+          else
+          begin
+            {$IF DEFINED(D2F_DEBUG_TXLOAD)}
+            e_LogWritefln('    Loading texture #%d: %s', [cnt, rec.Resource]);
+            {$ENDIF}
+            //if g_Map_IsSpecialTexture(s) then e_WriteLog('      SPECIAL!', MSG_NOTIFY);
+            if rec.Anim then
+            begin
+              // Àíèìèðîâàííàÿ òåêñòóðà
+              ntn := CreateAnimTexture(rec.Resource, FileName, True);
+              if (ntn < 0) then g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_ANIM], [rec.Resource]));
+            end
+            else
+            begin
+              // Îáû÷íàÿ òåêñòóðà
+              ntn := CreateTexture(rec.Resource, FileName, True);
+              if (ntn < 0) then g_SimpleError(Format(_lc[I_GAME_ERROR_TEXTURE_SIMPLE], [rec.Resource]));
+            end;
+            if (ntn < 0) then ntn := CreateNullTexture(rec.Resource);
+
+            rec.tagInt := ntn; // remember texture number
+          end;
+          g_Game_StepLoading();
+        end;
+      finally
+        usedTextures.Free();
       end;
 
       // set panel tagInt to texture index
@@ -1772,13 +1808,14 @@ begin
       end;
     end;
 
+
     // Çàãðóçêà òðèããåðîâ
     gTriggerClientID := 0;
-    e_WriteLog('  Loading triggers...', MSG_NOTIFY);
+    e_WriteLog('  Loading triggers...', TMsgType.Notify);
     g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS], 0, False);
 
     // Çàãðóçêà ïàíåëåé
-    e_WriteLog('  Loading panels...', MSG_NOTIFY);
+    e_WriteLog('  Loading panels...', TMsgType.Notify);
     g_Game_SetLoadingText(_lc[I_LOAD_PANELS], 0, False);
 
     // check texture numbers for panels
@@ -1788,7 +1825,7 @@ begin
       begin
         if (rec.tagInt < 0) then
         begin
-          e_WriteLog('error loading map: invalid texture index for panel', MSG_FATALERROR);
+          e_WriteLog('error loading map: invalid texture index for panel', TMsgType.Fatal);
           result := false;
           gCurrentMap.Free();
           gCurrentMap := nil;
@@ -1801,7 +1838,7 @@ begin
     // Ñîçäàíèå òàáëèöû òðèããåðîâ (ñîîòâåòñòâèå ïàíåëåé òðèããåðàì)
     if (triggers <> nil) and (triggers.count > 0) then
     begin
-      e_WriteLog('  Setting up trigger table...', MSG_NOTIFY);
+      e_WriteLog('  Setting up trigger table...', TMsgType.Notify);
       //SetLength(TriggersTable, triggers.count);
       g_Game_SetLoadingText(_lc[I_LOAD_TRIGGERS_TABLE], triggers.count-1, False);
 
@@ -1829,7 +1866,7 @@ begin
     // Ñîçäàåì ïàíåëè
     if (panels <> nil) and (panels.count > 0) then
     begin
-      e_WriteLog('  Setting up trigger links...', MSG_NOTIFY);
+      e_WriteLog('  Setting up trigger links...', TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_LINK_TRIGGERS], panels.count-1, False);
 
       pannum := -1;
@@ -1985,6 +2022,7 @@ begin
         //e_LogWritefln('PANADD: pannum=%s', [pannum]);
 
         // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå GUID
+        //e_LogWritefln('new panel; tcount=%s; curtex=%s', [Length(AddTextures), CurTex]);
         PanelID := CreatePanel(rec, AddTextures, CurTex);
         //e_LogWritefln('panel #%s of type %s got guid #%s', [pannum, rec.PanelType, PanelID]);
         rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
@@ -2015,7 +2053,7 @@ begin
     end;
 
     // create map grid, init other grids (for monsters, for example)
-    e_WriteLog('Creating map grid', MSG_NOTIFY);
+    e_WriteLog('Creating map grid', TMsgType.Notify);
     mapCreateGrid();
 
     // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû
@@ -2028,13 +2066,10 @@ begin
       for rec in triggers do
       begin
         Inc(trignum);
-        if (TriggersTable[trignum].texPanel <> nil) then b := TriggersTable[trignum].texPanel.PanelType else b := 0;
-        if (TriggersTable[trignum].actPanel <> nil) then c := TriggersTable[trignum].actPanel.PanelType else c := 0;
-        // we can have only one of those
         tgpid := TriggersTable[trignum].actPanelIdx;
         //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
         TriggersTable[trignum].tnum := trignum;
-        TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid, Word(b), Word(c));
+        TriggersTable[trignum].id := CreateTrigger(trignum, rec, TriggersTable[trignum].texPanelIdx, tgpid);
       end;
     end;
 
@@ -2052,31 +2087,31 @@ begin
     end;
 
     // Çàãðóçêà ïðåäìåòîâ
-    e_WriteLog('  Loading items...', MSG_NOTIFY);
+    e_WriteLog('  Loading items...', TMsgType.Notify);
     g_Game_SetLoadingText(_lc[I_LOAD_ITEMS], 0, False);
 
     // Åñëè íå LoadState, òî ñîçäàåì ïðåäìåòû
     if (items <> nil) and not gLoadGameMode then
     begin
-      e_WriteLog('  Spawning items...', MSG_NOTIFY);
+      e_WriteLog('  Spawning items...', TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_CREATE_ITEMS], 0, False);
       for rec in items do CreateItem(rec);
     end;
 
     // Çàãðóçêà îáëàñòåé
-    e_WriteLog('  Loading areas...', MSG_NOTIFY);
+    e_WriteLog('  Loading areas...', TMsgType.Notify);
     g_Game_SetLoadingText(_lc[I_LOAD_AREAS], 0, False);
 
     // Åñëè íå LoadState, òî ñîçäàåì îáëàñòè
     if areas <> nil then
     begin
-      e_WriteLog('  Creating areas...', MSG_NOTIFY);
+      e_WriteLog('  Creating areas...', TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_CREATE_AREAS], 0, False);
       for rec in areas do CreateArea(rec);
     end;
 
     // Çàãðóçêà ìîíñòðîâ
-    e_WriteLog('  Loading monsters...', MSG_NOTIFY);
+    e_WriteLog('  Loading monsters...', TMsgType.Notify);
     g_Game_SetLoadingText(_lc[I_LOAD_MONSTERS], 0, False);
 
     gTotalMonsters := 0;
@@ -2084,7 +2119,7 @@ begin
     // Åñëè íå LoadState, òî ñîçäàåì ìîíñòðîâ
     if (monsters <> nil) and not gLoadGameMode then
     begin
-      e_WriteLog('  Spawning monsters...', MSG_NOTIFY);
+      e_WriteLog('  Spawning monsters...', TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_CREATE_MONSTERS], 0, False);
       for rec in monsters do CreateMonster(rec);
     end;
@@ -2096,7 +2131,7 @@ begin
     // Çàãðóçêà íåáà
     if (gMapInfo.SkyName <> '') then
     begin
-      e_WriteLog('  Loading sky: ' + gMapInfo.SkyName, MSG_NOTIFY);
+      e_WriteLog('  Loading sky: ' + gMapInfo.SkyName, TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_SKY], 0, False);
       FileName := g_ExtractWadName(gMapInfo.SkyName);
 
@@ -2122,7 +2157,7 @@ begin
     ok := False;
     if gMapInfo.MusicName <> '' then
     begin
-      e_WriteLog('  Loading music: ' + gMapInfo.MusicName, MSG_NOTIFY);
+      e_WriteLog('  Loading music: ' + gMapInfo.MusicName, TMsgType.Notify);
       g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
       FileName := g_ExtractWadName(gMapInfo.MusicName);
 
@@ -2186,7 +2221,7 @@ begin
     end;
   end;
 
-  e_WriteLog('Done loading map.', MSG_NOTIFY);
+  e_WriteLog('Done loading map.', TMsgType.Notify);
   Result := True;
 end;
 
@@ -2255,11 +2290,11 @@ begin
   mapReader.Free();
 end;
 
-function g_Map_GetMapsList(WADName: string): SArray;
+function g_Map_GetMapsList(WADName: string): SSArray;
 var
   WAD: TWADFile;
   a: Integer;
-  ResList: SArray;
+  ResList: SSArray;
 begin
   Result := nil;
   WAD := TWADFile.Create();
@@ -2284,7 +2319,7 @@ function g_Map_Exist(Res: string): Boolean;
 var
   WAD: TWADFile;
   FileName, mnn: string;
-  ResList: SArray;
+  ResList: SSArray;
   a: Integer;
 begin
   Result := False;
@@ -2926,25 +2961,12 @@ begin
 end;
 
 
-procedure g_Map_SwitchTextureGUID (PanelType: Word; pguid: Integer; AnimLoop: Byte = 0);
+procedure g_Map_SwitchTextureGUID (pguid: Integer; AnimLoop: Byte = 0);
 var
   tp: TPanel;
 begin
   tp := g_Map_PanelByGUID(pguid);
   if (tp = nil) then exit;
-  {
-  case PanelType of
-    PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR: tp := gWalls[ID];
-    PANEL_FORE: tp := gRenderForegrounds[ID];
-    PANEL_BACK: tp := gRenderBackgrounds[ID];
-    PANEL_WATER: tp := gWater[ID];
-    PANEL_ACID1: tp := gAcid1[ID];
-    PANEL_ACID2: tp := gAcid2[ID];
-    PANEL_STEP: tp := gSteps[ID];
-    else exit;
-  end;
-  }
-
   tp.NextTexture(AnimLoop);
   if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelTexture(pguid, AnimLoop);
 end;
@@ -3034,7 +3056,7 @@ begin
     Obj.Y := -1000;
     Obj.Vel.X := 0;
     Obj.Vel.Y := 0;
-    Direction := D_LEFT;
+    Direction := TDirection.D_LEFT;
     State := FLAG_STATE_NONE;
     if FlagPoints[Flag] <> nil then
     begin
@@ -3062,14 +3084,14 @@ begin
         if State = FLAG_STATE_NONE then
           continue;
 
-        if Direction = D_LEFT then
+        if Direction = TDirection.D_LEFT then
           begin
-            Mirror := M_HORIZONTAL;
+            Mirror := TMirrorType.Horizontal;
             dx := -1;
           end
         else
           begin
-            Mirror := M_NONE;
+            Mirror := TMirrorType.None;
             dx := 1;
           end;
 
@@ -3087,80 +3109,62 @@ begin
 end;
 
 
-procedure g_Map_SaveState (var Mem: TBinMemoryWriter);
+procedure g_Map_SaveState (st: TStream);
 var
-  dw: DWORD;
-  b: Byte;
   str: String;
-  boo: Boolean;
 
   procedure savePanels ();
   var
-    PAMem: TBinMemoryWriter;
     pan: TPanel;
   begin
-    // Ñîçäàåì íîâûé ñïèñîê ñîõðàíÿåìûõ ïàíåëåé
-    PAMem := TBinMemoryWriter.Create((Length(panByGUID)+1) * 40);
-
     // Ñîõðàíÿåì ïàíåëè
-    //Mem.WriteInt(Length(panByGUID));
-    for pan in panByGUID do pan.SaveState(PAMem);
-
-    // Ñîõðàíÿåì ýòîò ñïèñîê ïàíåëåé
-    PAMem.SaveToMemory(Mem);
-    PAMem.Free();
+    utils.writeInt(st, LongInt(Length(panByGUID)));
+    for pan in panByGUID do pan.SaveState(st);
   end;
 
-  procedure SaveFlag (flag: PFlag);
+  procedure saveFlag (flag: PFlag);
+  var
+    b: Byte;
   begin
-    // Ñèãíàòóðà ôëàãà
-    dw := FLAG_SIGNATURE; // 'FLAG'
-    Mem.WriteDWORD(dw);
+    utils.writeSign(st, 'FLAG');
+    utils.writeInt(st, Byte(0)); // version
     // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
-    Mem.WriteByte(flag^.RespawnType);
+    utils.writeInt(st, Byte(flag^.RespawnType));
     // Ñîñòîÿíèå ôëàãà
-    Mem.WriteByte(flag^.State);
+    utils.writeInt(st, Byte(flag^.State));
     // Íàïðàâëåíèå ôëàãà
-    if flag^.Direction = D_LEFT then b := 1 else b := 2; // D_RIGHT
-    Mem.WriteByte(b);
+    if flag^.Direction = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
+    utils.writeInt(st, Byte(b));
     // Îáúåêò ôëàãà
-    Obj_SaveState(@flag^.Obj, Mem);
+    Obj_SaveState(st, @flag^.Obj);
   end;
 
 begin
-  Mem := TBinMemoryWriter.Create(1024 * 1024); // 1 MB
-
-  ///// Ñîõðàíÿåì ñïèñêè ïàíåëåé: /////
   savePanels();
-  ///// /////
 
-  ///// Ñîõðàíÿåì ìóçûêó: /////
-  // Ñèãíàòóðà ìóçûêè:
-  dw := MUSIC_SIGNATURE; // 'MUSI'
-  Mem.WriteDWORD(dw);
-  // Íàçâàíèå ìóçûêè:
-  Assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
+  // Ñîõðàíÿåì ìóçûêó
+  utils.writeSign(st, 'MUSI');
+  utils.writeInt(st, Byte(0));
+  // Íàçâàíèå ìóçûêè
+  assert(gMusic <> nil, 'g_Map_SaveState: gMusic = nil');
   if gMusic.NoMusic then str := '' else str := gMusic.Name;
-  Mem.WriteString(str, 64);
+  utils.writeStr(st, str);
   // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
-  dw := gMusic.GetPosition();
-  Mem.WriteDWORD(dw);
+  utils.writeInt(st, LongWord(gMusic.GetPosition()));
   // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
-  boo := gMusic.SpecPause;
-  Mem.WriteBoolean(boo);
-  ///// /////
+  utils.writeBool(st, gMusic.SpecPause);
 
   ///// Ñîõðàíÿåì êîëè÷åñòâî ìîíñòðîâ: /////
-  Mem.WriteInt(gTotalMonsters);
+  utils.writeInt(st, LongInt(gTotalMonsters));
   ///// /////
 
   //// Ñîõðàíÿåì ôëàãè, åñëè ýòî CTF: /////
-  if gGameSettings.GameMode = GM_CTF then
+  if (gGameSettings.GameMode = GM_CTF) then
   begin
     // Ôëàã Êðàñíîé êîìàíäû
-    SaveFlag(@gFlags[FLAG_RED]);
+    saveFlag(@gFlags[FLAG_RED]);
     // Ôëàã Ñèíåé êîìàíäû
-    SaveFlag(@gFlags[FLAG_BLUE]);
+    saveFlag(@gFlags[FLAG_BLUE]);
   end;
   ///// /////
 
@@ -3168,64 +3172,53 @@ begin
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
   begin
     // Î÷êè Êðàñíîé êîìàíäû
-    Mem.WriteSmallInt(gTeamStat[TEAM_RED].Goals);
+    utils.writeInt(st, SmallInt(gTeamStat[TEAM_RED].Goals));
     // Î÷êè Ñèíåé êîìàíäû
-    Mem.WriteSmallInt(gTeamStat[TEAM_BLUE].Goals);
+    utils.writeInt(st, SmallInt(gTeamStat[TEAM_BLUE].Goals));
   end;
   ///// /////
 end;
 
 
-procedure g_Map_LoadState (var Mem: TBinMemoryReader);
+procedure g_Map_LoadState (st: TStream);
 var
   dw: DWORD;
-  b: Byte;
   str: String;
   boo: Boolean;
 
   procedure loadPanels ();
   var
-    PAMem: TBinMemoryReader;
     pan: TPanel;
-    //count: LongInt;
   begin
-    // Çàãðóæàåì òåêóùèé ñïèñîê ïàíåëåé
-    PAMem := TBinMemoryReader.Create();
-    PAMem.LoadFromMemory(Mem);
-
     // Çàãðóæàåì ïàíåëè
-    //PAMem.ReadInt(count);
-    //if (count <> Length(panByGUID)) then raise EBinSizeError.Create('g_Map_LoadState: LoadPanelArray: invalid number of panels');
-    //if (count <> Length(panByGUID)) then raise EBinSizeError.Create(Format('g_Map_LoadState: LoadPanelArray: invalid number of panels (%d : %d)', [count, Length(panByGUID)]));
+    if (Length(panByGUID) <> utils.readLongInt(st)) then raise XStreamError.Create('invalid number of saved panels');
     for pan in panByGUID do
     begin
-      pan.LoadState(PAMem);
+      pan.LoadState(st);
       if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
     end;
-
-    // Ýòîò ñïèñîê ïàíåëåé çàãðóæåí
-    PAMem.Free();
   end;
 
-  procedure LoadFlag(flag: PFlag);
+  procedure loadFlag (flag: PFlag);
+  var
+    b: Byte;
   begin
     // Ñèãíàòóðà ôëàãà
-    Mem.ReadDWORD(dw);
-    // 'FLAG'
-    if dw <> FLAG_SIGNATURE then raise EBinSizeError.Create('g_Map_LoadState: LoadFlag: Wrong Flag Signature');
+    if not utils.checkSign(st, 'FLAG') then raise XStreamError.Create('invalid flag signature');
+    if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid flag version');
     // Âðåìÿ ïåðåïîÿâëåíèÿ ôëàãà
-    Mem.ReadByte(flag^.RespawnType);
+    flag^.RespawnType := utils.readByte(st);
     // Ñîñòîÿíèå ôëàãà
-    Mem.ReadByte(flag^.State);
+    flag^.State := utils.readByte(st);
     // Íàïðàâëåíèå ôëàãà
-    Mem.ReadByte(b);
-    if b = 1 then flag^.Direction := D_LEFT else flag^.Direction := D_RIGHT; // b = 2
+    b := utils.readByte(st);
+    if (b = 1) then flag^.Direction := TDirection.D_LEFT else flag^.Direction := TDirection.D_RIGHT; // b = 2
     // Îáúåêò ôëàãà
-    Obj_LoadState(@flag^.Obj, Mem);
+    Obj_LoadState(@flag^.Obj, st);
   end;
 
 begin
-  if Mem = nil then Exit;
+  if (st = nil) then exit;
 
   ///// Çàãðóæàåì ñïèñêè ïàíåëåé: /////
   loadPanels();
@@ -3236,36 +3229,34 @@ begin
   //mapCreateGrid();
 
   ///// Çàãðóæàåì ìóçûêó: /////
-  // Ñèãíàòóðà ìóçûêè
-  Mem.ReadDWORD(dw);
-  // 'MUSI'
-  if dw <> MUSIC_SIGNATURE then raise EBinSizeError.Create('g_Map_LoadState: Wrong Music Signature');
+  if not utils.checkSign(st, 'MUSI') then raise XStreamError.Create('invalid music signature');
+  if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid music version');
   // Íàçâàíèå ìóçûêè
-  Assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
-  Mem.ReadString(str);
+  assert(gMusic <> nil, 'g_Map_LoadState: gMusic = nil');
+  str := utils.readStr(st);
   // Ïîçèöèÿ ïðîèãðûâàíèÿ ìóçûêè
-  Mem.ReadDWORD(dw);
+  dw := utils.readLongWord(st);
   // Ñòîèò ëè ìóçûêà íà ñïåö-ïàóçå
-  Mem.ReadBoolean(boo);
+  boo := utils.readBool(st);
   // Çàïóñêàåì ýòó ìóçûêó
   gMusic.SetByName(str);
   gMusic.SpecPause := boo;
   gMusic.Play();
-  gMusic.Pause(True);
+  gMusic.Pause(true);
   gMusic.SetPosition(dw);
   ///// /////
 
   ///// Çàãðóæàåì êîëè÷åñòâî ìîíñòðîâ: /////
-  Mem.ReadInt(gTotalMonsters);
+  gTotalMonsters := utils.readLongInt(st);
   ///// /////
 
   //// Çàãðóæàåì ôëàãè, åñëè ýòî CTF: /////
-  if gGameSettings.GameMode = GM_CTF then
+  if (gGameSettings.GameMode = GM_CTF) then
   begin
     // Ôëàã Êðàñíîé êîìàíäû
-    LoadFlag(@gFlags[FLAG_RED]);
+    loadFlag(@gFlags[FLAG_RED]);
     // Ôëàã Ñèíåé êîìàíäû
-    LoadFlag(@gFlags[FLAG_BLUE]);
+    loadFlag(@gFlags[FLAG_BLUE]);
   end;
   ///// /////
 
@@ -3273,9 +3264,9 @@ begin
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
   begin
     // Î÷êè Êðàñíîé êîìàíäû
-    Mem.ReadSmallInt(gTeamStat[TEAM_RED].Goals);
+    gTeamStat[TEAM_RED].Goals := utils.readSmallInt(st);
     // Î÷êè Ñèíåé êîìàíäû
-    Mem.ReadSmallInt(gTeamStat[TEAM_BLUE].Goals);
+    gTeamStat[TEAM_BLUE].Goals := utils.readSmallInt(st);
   end;
   ///// /////
 end;
@@ -3304,4 +3295,6 @@ begin
 end;
 
 
+begin
+  DynWarningCB := mapWarningCB;
 end.