DEADSOFTWARE

moving platforms experiment (DO NOT USE YET!)
[d2df-sdl.git] / src / game / g_map.pas
index 6ae077b111553f4193f959550e7ac90774e1b670..9470057306ae27f956b9b1c01b87ca49cdf7cd79 100644 (file)
@@ -60,7 +60,7 @@ function  g_Map_Load(Res: String): Boolean;
 function  g_Map_GetMapInfo(Res: String): TMapInfo;
 function  g_Map_GetMapsList(WADName: String): SArray;
 function  g_Map_Exist(Res: String): Boolean;
-procedure g_Map_Free();
+procedure g_Map_Free(freeTextures: Boolean=true);
 procedure g_Map_Update();
 
 procedure g_Map_DrawPanels (PanelType: Word); // unaccelerated
@@ -85,8 +85,6 @@ function  g_Map_HaveFlagPoints(): Boolean;
 procedure g_Map_ResetFlag(Flag: Byte);
 procedure g_Map_DrawFlags();
 
-function g_Map_PanelForPID(PanelID: Integer; var PanelArrayID: Integer): PPanel;
-
 procedure g_Map_SaveState(Var Mem: TBinMemoryWriter);
 procedure g_Map_LoadState(Var Mem: TBinMemoryReader);
 
@@ -195,6 +193,7 @@ var
   gWADHash: TMD5Digest;
   BackID:  DWORD = DWORD(-1);
   gExternalResources: TStringList;
+  gMovingWallIds: array of Integer = nil;
 
   gdbg_map_use_accel_render: Boolean = true;
   gdbg_map_use_accel_coldet: Boolean = true;
@@ -202,6 +201,7 @@ var
   gDrawPanelList: TBinaryHeapObj = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()`
 
   gCurrentMap: TDynRecord = nil;
+  gCurrentMapFileName: AnsiString = ''; // so we can skip texture reloading
 
 
 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
@@ -217,7 +217,7 @@ var
 implementation
 
 uses
-  g_main, e_log, SysUtils, g_items, g_gfx, g_console,
+  e_input, g_main, e_log, SysUtils, 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,
@@ -438,7 +438,9 @@ type
 
 var
   PanelById:     array of TPanelID;
-  Textures:      TLevelTextureArray;
+  Textures:      TLevelTextureArray = nil;
+  TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures`
+  BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages
   RespawnPoints: Array of TRespawnPoint;
   FlagPoints:    Array [FLAG_RED..FLAG_BLUE] of PFlagPoint;
   //DOMFlagPoints: Array of TFlagPoint;
@@ -784,8 +786,13 @@ begin
   PanelByID[len].PArrID := Result;
 end;
 
+
 function CreateNullTexture(RecName: String): Integer;
 begin
+  RecName := toLowerCase1251(RecName);
+  if (TextNameHash = nil) then TextNameHash := hashNewStrInt();
+  if TextNameHash.get(RecName, result) then exit; // i found her!
+
   SetLength(Textures, Length(Textures)+1);
   result := High(Textures);
 
@@ -797,26 +804,47 @@ begin
     Anim := False;
     TextureID := LongWord(TEXTURE_NONE);
   end;
+
+  TextNameHash.put(RecName, result);
 end;
 
-function CreateTexture(RecName: String; Map: string; log: Boolean): Integer;
+
+function CreateTexture(RecName: AnsiString; Map: string; log: Boolean): Integer;
 var
   WAD: TWADFile;
   TextureData: Pointer;
-  WADName, txname: String;
+  WADName: String;
   a, ResLength: Integer;
 begin
+  RecName := toLowerCase1251(RecName);
+  if (TextNameHash = nil) then TextNameHash := hashNewStrInt();
+  if TextNameHash.get(RecName, result) then
+  begin
+    // i found her!
+    //e_LogWritefln('texture ''%s'' already loaded', [RecName]);
+    exit;
+  end;
+
   Result := -1;
 
+  if (BadTextNameHash <> nil) and BadTextNameHash.has(RecName) then exit; // don't do it again and again
+
+  {
   if Textures <> nil then
+  begin
     for a := 0 to High(Textures) do
-      if Textures[a].TextureName = RecName then
+    begin
+      if (Textures[a].TextureName = RecName) then
       begin // Òåêñòóðà ñ òàêèì èìåíåì óæå åñòü
+        e_LogWritefln('texture ''%s'' already loaded', [RecName]);
         Result := a;
         Exit;
       end;
+    end;
+  end;
+  }
 
-// Òåêñòóðû ñî ñïåöèàëüíûìè èìåíàìè (âîäà, ëàâà, êèñëîòà):
+  // Òåêñòóðû ñî ñïåöèàëüíûìè èìåíàìè (âîäà, ëàâà, êèñëîòà):
   if (RecName = TEXTURE_NAME_WATER) or
      (RecName = TEXTURE_NAME_ACID1) or
      (RecName = TEXTURE_NAME_ACID2) then
@@ -826,36 +854,28 @@ begin
     with Textures[High(Textures)] do
     begin
       TextureName := RecName;
-
-      if TextureName = TEXTURE_NAME_WATER then
-        TextureID := LongWord(TEXTURE_SPECIAL_WATER)
-      else
-        if TextureName = TEXTURE_NAME_ACID1 then
-          TextureID := LongWord(TEXTURE_SPECIAL_ACID1)
-        else
-          if TextureName = TEXTURE_NAME_ACID2 then
-            TextureID := LongWord(TEXTURE_SPECIAL_ACID2);
+           if (TextureName = TEXTURE_NAME_WATER) then TextureID := LongWord(TEXTURE_SPECIAL_WATER)
+      else if (TextureName = TEXTURE_NAME_ACID1) then TextureID := LongWord(TEXTURE_SPECIAL_ACID1)
+      else if (TextureName = TEXTURE_NAME_ACID2) then TextureID := LongWord(TEXTURE_SPECIAL_ACID2);
 
       Anim := False;
     end;
 
     result := High(Textures);
+    TextNameHash.put(RecName, result);
     Exit;
   end;
 
-// Çàãðóæàåì ðåñóðñ òåêñòóðû â ïàìÿòü èç WAD'à:
+  // Çàãðóæàåì ðåñóðñ òåêñòóðû â ïàìÿòü èç WAD'à:
   WADName := g_ExtractWadName(RecName);
 
   WAD := TWADFile.Create();
 
-  if WADName <> '' then
-    WADName := GameDir+'/wads/'+WADName
-  else
-    WADName := Map;
+  if WADName <> '' then WADName := GameDir+'/wads/'+WADName else WADName := Map;
 
   WAD.ReadFile(WADName);
 
-  txname := RecName;
+  //txname := RecName;
   {
   if (WADName = Map) and WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength) then
   begin
@@ -864,33 +884,38 @@ begin
   end;
   }
 
-  if WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength) then
+  if WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength, log) then
+  begin
+    SetLength(Textures, Length(Textures)+1);
+    if not e_CreateTextureMem(TextureData, ResLength, Textures[High(Textures)].TextureID) then
     begin
-      SetLength(Textures, Length(Textures)+1);
-      if not e_CreateTextureMem(TextureData, ResLength, Textures[High(Textures)].TextureID) then
-        Exit;
-      e_GetTextureSize(Textures[High(Textures)].TextureID,
-                       @Textures[High(Textures)].Width,
-                       @Textures[High(Textures)].Height);
-      FreeMem(TextureData);
-      Textures[High(Textures)].TextureName := {RecName}txname;
-      Textures[High(Textures)].Anim := False;
-
-      result := High(Textures);
-    end
+      SetLength(Textures, Length(Textures)-1);
+      Exit;
+    end;
+    e_GetTextureSize(Textures[High(Textures)].TextureID, @Textures[High(Textures)].Width, @Textures[High(Textures)].Height);
+    FreeMem(TextureData);
+    Textures[High(Textures)].TextureName := RecName;
+    Textures[High(Textures)].Anim := False;
+
+    result := High(Textures);
+    TextNameHash.put(RecName, result);
+  end
   else // Íåò òàêîãî ðåóñðñà â WAD'å
   begin
-    //e_WriteLog(Format('SHIT! Error loading texture %s : %s : %s', [RecName, txname, g_ExtractFilePathName(RecName)]), MSG_WARNING);
-    if log then
-      begin
-        e_WriteLog(Format('Error loading texture %s', [RecName]), MSG_WARNING);
-        //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
-      end;
+    //e_WriteLog(Format('SHIT! Error loading texture %s : %s', [RecName, g_ExtractFilePathName(RecName)]), MSG_WARNING);
+    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('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
+    end;
+    BadTextNameHash.put(RecName, -1);
   end;
 
   WAD.Free();
 end;
 
+
 function CreateAnimTexture(RecName: String; Map: string; log: Boolean): Integer;
 var
   WAD: TWADFile;
@@ -907,29 +932,44 @@ var
   ia: TDynImageDataArray = nil;
   f, c, frdelay, frloop: Integer;
 begin
+  RecName := toLowerCase1251(RecName);
+  if (TextNameHash = nil) then TextNameHash := hashNewStrInt();
+  if TextNameHash.get(RecName, result) then
+  begin
+    // i found her!
+    //e_LogWritefln('animated texture ''%s'' already loaded', [RecName]);
+    exit;
+  end;
+
   result := -1;
 
-  //e_WriteLog(Format('*** Loading animated texture "%s"', [RecName]), MSG_NOTIFY);
+  //e_LogWritefln('*** Loading animated texture "%s"', [RecName]);
+
+  if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt();
+  if BadTextNameHash.get(RecName, f) then
+  begin
+    //e_WriteLog(Format('no animation texture %s (don''t worry)', [RecName]), MSG_NOTIFY);
+    exit;
+  end;
 
   // ×èòàåì WAD-ðåñóðñ àíèì.òåêñòóðû èç WAD'à â ïàìÿòü:
   WADName := g_ExtractWadName(RecName);
 
   WAD := TWADFile.Create();
   try
-    if WADName <> '' then
-      WADName := GameDir+'/wads/'+WADName
-    else
-      WADName := Map;
+    if WADName <> '' then WADName := GameDir+'/wads/'+WADName else WADName := Map;
 
     WAD.ReadFile(WADName);
 
-    if not WAD.GetResource(g_ExtractFilePathName(RecName), TextureWAD, ResLength) then
+    if not WAD.GetResource(g_ExtractFilePathName(RecName), TextureWAD, ResLength, log) then
     begin
-      if log then
+      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('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING);
       end;
+      BadTextNameHash.put(RecName, -1);
       exit;
     end;
 
@@ -946,6 +986,7 @@ begin
     if ResLength < 6 then
     begin
       e_WriteLog(Format('Animated texture file "%s" too short', [RecName]), MSG_WARNING);
+      BadTextNameHash.put(RecName, -1);
       exit;
     end;
 
@@ -957,6 +998,7 @@ begin
       if not WAD.ReadMemory(TextureWAD, ResLength) then
       begin
         e_WriteLog(Format('Animated texture WAD file "%s" is invalid', [RecName]), MSG_WARNING);
+        BadTextNameHash.put(RecName, -1);
         exit;
       end;
 
@@ -964,6 +1006,7 @@ begin
       if not WAD.GetResource('TEXT/ANIM', TextData, ResLength) then
       begin
         e_WriteLog(Format('Animated texture file "%s" has invalid INI', [RecName]), MSG_WARNING);
+        BadTextNameHash.put(RecName, -1);
         exit;
       end;
 
@@ -973,6 +1016,7 @@ begin
       if TextureResource = '' then
       begin
         e_WriteLog(Format('Animated texture WAD file "%s" has no "resource"', [RecName]), MSG_WARNING);
+        BadTextNameHash.put(RecName, -1);
         exit;
       end;
 
@@ -989,6 +1033,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);
+        BadTextNameHash.put(RecName, -1);
         exit;
       end;
 
@@ -1008,10 +1053,16 @@ begin
           FramesCount := _framecount;
           Speed := _speed;
           result := High(Textures);
+          TextNameHash.put(RecName, result);
         end
         else
         begin
-          if log then e_WriteLog(Format('Error loading animation texture %s', [RecName]), MSG_WARNING);
+          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);
+          end;
+          BadTextNameHash.put(RecName, -1);
         end;
       end;
     end
@@ -1031,11 +1082,13 @@ begin
       if not LoadMultiImageFromMemory(TextureWAD, ResLength, ia) then
       begin
         e_WriteLog(Format('Animated texture file "%s" cannot be loaded', [RecName]), MSG_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);
+        BadTextNameHash.put(RecName, -1);
         exit;
       end;
 
@@ -1091,20 +1144,26 @@ begin
         Textures[High(Textures)].FramesCount := length(ia);
         Textures[High(Textures)].Speed := _speed;
         result := High(Textures);
+        TextNameHash.put(RecName, result);
         //writeln(' CREATED!');
       end
       else
       begin
-        if log then e_WriteLog(Format('Error loading animation texture "%s" images', [RecName]), MSG_WARNING);
+        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);
+        end;
+        BadTextNameHash.put(RecName, -1);
       end;
     end;
   finally
     for f := 0 to High(ia) do FreeImage(ia[f]);
     WAD.Free();
     cfg.Free();
-    if TextureWAD <> nil then FreeMem(TextureWAD);
-    if TextData <> nil then FreeMem(TextData);
-    if TextureData <> nil then FreeMem(TextureData);
+    if (TextureWAD <> nil) then FreeMem(TextureWAD);
+    if (TextData <> nil) then FreeMem(TextData);
+    if (TextureData <> nil) then FreeMem(TextureData);
   end;
 end;
 
@@ -1190,7 +1249,7 @@ begin
   end;
 end;
 
-procedure CreateTrigger(Trigger: TDynRecord; atpanid, atrigpanid: Integer; fTexturePanel1Type, fTexturePanel2Type: Word);
+procedure CreateTrigger (amapIdx: Integer; Trigger: TDynRecord; atpanid, atrigpanid: Integer; fTexturePanel1Type, fTexturePanel2Type: Word);
 var
   _trigger: TTrigger;
 begin
@@ -1198,11 +1257,13 @@ begin
 
   with _trigger do
   begin
+    mapId := Trigger.id;
+    mapIndex := amapIdx;
     X := Trigger.X;
     Y := Trigger.Y;
     Width := Trigger.Width;
     Height := Trigger.Height;
-    Enabled := ByteBool(Trigger.Enabled);
+    Enabled := Trigger.Enabled;
     //TexturePanel := Trigger.TexturePanel;
     TexturePanel := atpanid;
     TexturePanelType := fTexturePanel1Type;
@@ -1213,7 +1274,18 @@ begin
     trigPanelId := atrigpanid;
     //trigShotPanelId := ashotpanid;
     //Data.Default := Trigger.DATA;
-    trigData := Trigger.trigRec.clone();
+    if (Trigger.trigRec = nil) then
+    begin
+      trigData := nil;
+      if (TriggerType <> TRIGGER_SECRET) then
+      begin
+        e_LogWritefln('trigger of type %s has no triggerdata; wtf?!', [TriggerType], MSG_WARNING);
+      end;
+    end
+    else
+    begin
+      trigData := Trigger.trigRec.clone();
+    end;
   end;
 
   g_Triggers_Create(_trigger);
@@ -1252,6 +1324,7 @@ procedure g_Map_ReAdd_DieTriggers();
   function monsDieTrig (mon: TMonster): Boolean;
   var
     a: Integer;
+    //tw: TStrTextWriter;
   begin
     result := false; // don't stop
     mon.ClearTriggers();
@@ -1260,6 +1333,15 @@ procedure g_Map_ReAdd_DieTriggers();
       if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
       begin
         //if (gTriggers[a].Data.MonsterID-1) = Integer(mon.StartID) then mon.AddTrigger(a);
+        {
+        tw := TStrTextWriter.Create();
+        try
+          gTriggers[a].trigData.writeTo(tw);
+          e_LogWritefln('=== trigger #%s ==='#10'%s'#10'---', [a, tw.str]);
+        finally
+          tw.Free();
+        end;
+        }
         if (gTriggers[a].trigData.trigMonsterId) = Integer(mon.StartID) then mon.AddTrigger(a);
       end;
     end;
@@ -1463,11 +1545,8 @@ type
   end;
 var
   WAD: TWADFile;
-  //MapReader: TMapReader_1;
   mapReader: TDynRecord = nil;
-  //Header: TMapHeaderRec_1;
-  _textures: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
-  //_texnummap: array of Integer = nil; // `_textures` -> `Textures`
+  mapTextureList: TDynField = nil; //TTexturesRec1Array; tagInt: texture index
   panels: TDynField = nil; //TPanelsRec1Array;
   items: TDynField = nil; //TItemsRec1Array;
   monsters: TDynField = nil; //TMonsterRec1Array;
@@ -1476,7 +1555,6 @@ var
   b, c, k: Integer;
   PanelID: DWORD;
   AddTextures: TAddTextureArray;
-  //texture: TTextureRec_1;
   TriggersTable: array of TTRec;
   FileName, mapResName, s, TexName: String;
   Data: Pointer;
@@ -1486,11 +1564,9 @@ var
   rec, texrec: TDynRecord;
   pttit: PTRec;
   pannum, trignum, cnt, tgpid: Integer;
-  // key: panel index; value: `TriggersTable` index
-  hashTextPan: THashIntInt = nil;
-  hashLiftPan: THashIntInt = nil;
-  hashDoorPan: THashIntInt = nil;
-  hashShotPan: THashIntInt = nil;
+  stt: UInt64;
+  moveSpeed{, moveStart, moveEnd}: TDFPoint;
+  //moveActive: Boolean;
 begin
   mapGrid.Free();
   mapGrid := nil;
@@ -1502,7 +1578,6 @@ begin
   gMapInfo.Map := Res;
   TriggersTable := nil;
   mapReader := nil;
-  //FillChar(texture, SizeOf(texture), 0);
 
   sfsGCDisable(); // temporary disable removing of temporary volumes
   try
@@ -1541,6 +1616,8 @@ begin
     e_LogWritefln('Loading map: %s', [mapResName], MSG_NOTIFY);
     g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
 
+    stt := curTimeMicro();
+
     try
       mapReader := g_Map_ParseMap(Data, Len);
     except
@@ -1552,31 +1629,18 @@ begin
 
     FreeMem(Data);
 
-    hashTextPan := hashNewIntInt();
-    hashLiftPan := hashNewIntInt();
-    hashDoorPan := hashNewIntInt();
-    hashShotPan := hashNewIntInt();
-
-    generateExternalResourcesList(MapReader);
-    //_textures := GetTextures(mapReader);
-    _textures := mapReader['texture'];
-    //_texnummap := nil;
+    generateExternalResourcesList(mapReader);
+    mapTextureList := mapReader['texture'];
     // get all other lists here too
-    //panels := GetPanels(mapReader);
     panels := mapReader['panel'];
-    //triggers := GetTriggers(mapReader);
     triggers := mapReader['trigger'];
-    //items := GetItems(mapReader);
     items := mapReader['item'];
-    //areas := GetAreas(mapReader);
     areas := mapReader['area'];
-    //monsters := GetMonsters(mapReader);
     monsters := mapReader['monster'];
 
     // Çàãðóçêà îïèñàíèÿ êàðòû:
     e_WriteLog('  Reading map info...', MSG_NOTIFY);
     g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
-    //Header := GetMapHeader(mapReader);
 
     with gMapInfo do
     begin
@@ -1592,14 +1656,13 @@ begin
     // Çàãðóçêà òåêñòóð:
     g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], 0, False);
     // Äîáàâëåíèå òåêñòóð â Textures[]:
-    if (_textures <> nil) and (_textures.count > 0) then
+    if (mapTextureList <> nil) and (mapTextureList.count > 0) then
     begin
       e_WriteLog('  Loading textures:', MSG_NOTIFY);
-      g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], _textures.count-1, False);
-      //SetLength(_texnummap, _textures.count);
+      g_Game_SetLoadingText(_lc[I_LOAD_TEXTURES], mapTextureList.count-1, False);
 
       cnt := -1;
-      for rec in _textures do
+      for rec in mapTextureList do
       begin
         Inc(cnt);
         s := rec.Resource;
@@ -1621,8 +1684,7 @@ begin
         end;
         if (ntn < 0) then ntn := CreateNullTexture(rec.Resource);
 
-        //_texnummap[a] := ntn; // fix texture number
-        rec.tagInt := ntn;
+        rec.tagInt := ntn; // remember texture number
         g_Game_StepLoading();
       end;
 
@@ -1672,6 +1734,7 @@ begin
         SetLength(TriggersTable, Length(TriggersTable)+1);
         pttit := @TriggersTable[High(TriggersTable)];
         pttit.trigrec := rec;
+        // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
         pttit.texPan := mapReader.panel[rec.TexturePanel];
         pttit.liftPan := nil;
         pttit.doorPan := nil;
@@ -1680,33 +1743,26 @@ begin
         pttit.LiftPanelIdx := -1;
         pttit.DoorPanelIdx := -1;
         pttit.ShotPanelIdx := -1;
-        // Ñìåíà òåêñòóðû (âîçìîæíî, êíîïêè)
-        //if (rec.TexturePanel >= 0) and (pttit.texPan = nil) then e_WriteLog('error loading map: invalid texture panel index for trigger', MSG_WARNING);
         // Ëèôòû
         if rec.TriggerType in [TRIGGER_LIFTUP, TRIGGER_LIFTDOWN, TRIGGER_LIFT] then
         begin
-          //pttit.LiftPanel := TTriggerData(rec.DATA).PanelID
           pttit.liftPan := mapReader.panel[rec.trigRec.tgPanelID];
-          //if (rec.trigRec.trigPanelID >= 0) and (pttit.liftPan = nil) then e_WriteLog('error loading map: invalid lift panel index for trigger', MSG_WARNING);
         end;
         // Äâåðè
         if rec.TriggerType in [TRIGGER_OPENDOOR, TRIGGER_CLOSEDOOR, TRIGGER_DOOR, TRIGGER_DOOR5, TRIGGER_CLOSETRAP, TRIGGER_TRAP] then
         begin
-          //pttit.DoorPanel := TTriggerData(rec.DATA).PanelID
           pttit.doorPan := mapReader.panel[rec.trigRec.tgPanelID];
         end;
         // Òóðåëü
         if (rec.TriggerType = TRIGGER_SHOT) then
         begin
-          //pttit.ShotPanel := TTriggerData(rec.DATA).ShotPanelID
           pttit.shotPan := mapReader.panel[rec.trigRec.tgShotPanelID];
         end;
 
-        // update hashes
-        if (pttit.texPan <> nil) then hashTextPan.put(rec.TexturePanel, High(TriggersTable));
-        if (pttit.liftPan <> nil) then hashLiftPan.put(rec.trigRec.tgPanelID, High(TriggersTable));
-        if (pttit.doorPan <> nil) then hashDoorPan.put(rec.trigRec.tgPanelID, High(TriggersTable));
-        if (pttit.shotPan <> nil) then hashShotPan.put(rec.trigRec.tgShotPanelID, High(TriggersTable));
+        if (pttit.texPan <> nil) then pttit.texPan.userPanelTrigRef := true;
+        if (pttit.liftPan <> nil) then pttit.liftPan.userPanelTrigRef := true;
+        if (pttit.doorPan <> nil) then pttit.doorPan.userPanelTrigRef := true;
+        if (pttit.shotPan <> nil) then pttit.shotPan.userPanelTrigRef := true;
 
         g_Game_StepLoading();
       end;
@@ -1722,13 +1778,14 @@ begin
       for rec in panels do
       begin
         Inc(pannum);
+        //e_LogWritefln('PANSTART: pannum=%s', [pannum]);
         texrec := nil;
         SetLength(AddTextures, 0);
         trigRef := False;
         CurTex := -1;
         ok := false;
 
-        if (_textures <> nil) then
+        if (mapTextureList <> nil) then
         begin
           texrec := rec.TextureRec;
           ok := (texrec <> nil);
@@ -1739,8 +1796,9 @@ begin
           // Ñìîòðèì, ññûëàþòñÿ ëè íà ýòó ïàíåëü òðèããåðû.
           // Åñëè äà - òî íàäî ñîçäàòü åùå òåêñòóð
           ok := false;
-          if (TriggersTable <> nil) and (_textures <> nil) then
+          if (TriggersTable <> nil) and (mapTextureList <> nil) then
           begin
+            {
             for b := 0 to High(TriggersTable) do
             begin
               if (TriggersTable[b].texPan = rec) or (TriggersTable[b].shotPan = rec) then
@@ -1750,6 +1808,13 @@ begin
                 break;
               end;
             end;
+            }
+            if rec.userPanelTrigRef then
+            begin
+              // e_LogWritefln('trigref for panel %s', [pannum]);
+              trigRef := True;
+              ok := True;
+            end;
           end;
         end;
 
@@ -1786,30 +1851,39 @@ begin
                 begin
                   // Íà÷àëüíàÿ - àíèìèðîâàííàÿ, èùåì àíèìèðîâàííóþ
                   isAnim := True;
+                  //e_LogWritefln('000: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
                   ok := CreateAnimTexture(TexName, FileName, False) >= 0;
+                  //e_LogWritefln('001: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
                   if not ok then
                   begin
                     // Íåò àíèìèðîâàííîé, èùåì îáû÷íóþ
                     isAnim := False;
+                    //e_LogWritefln('002: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
                     ok := CreateTexture(TexName, FileName, False) >= 0;
+                    //e_LogWritefln('003: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
                   end;
                 end
                 else
                 begin
                   // Íà÷àëüíàÿ - îáû÷íàÿ, èùåì îáû÷íóþ
                   isAnim := False;
+                  //e_LogWritefln('004: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
                   ok := CreateTexture(TexName, FileName, False) >= 0;
+                  //e_LogWritefln('005: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
                   if not ok then
                   begin
                     // Íåò îáû÷íîé, èùåì àíèìèðîâàííóþ
                     isAnim := True;
+                    //e_LogWritefln('006: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
                     ok := CreateAnimTexture(TexName, FileName, False) >= 0;
+                    //e_LogWritefln('007: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]);
                   end;
                 end;
 
                 // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè
                 if ok then
                 begin
+                  {
                   for c := 0 to High(Textures) do
                   begin
                     if (Textures[c].TextureName = TexName) then
@@ -1820,6 +1894,13 @@ begin
                       break;
                     end;
                   end;
+                  }
+                  if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then
+                  begin
+                    SetLength(AddTextures, Length(AddTextures)+1);
+                    AddTextures[High(AddTextures)].Texture := c;
+                    AddTextures[High(AddTextures)].Anim := isAnim;
+                  end;
                 end;
               end
               else
@@ -1828,8 +1909,7 @@ begin
                 begin
                   // Çàíîñèì òåêóùóþ òåêñòóðó íà ñâîå ìåñòî
                   SetLength(AddTextures, Length(AddTextures)+1);
-                  //AddTextures[High(AddTextures)].Texture := _texnummap[rec.TextureNum]; //panels[a].TextureNum;
-                  AddTextures[High(AddTextures)].Texture := rec.tagInt; //panels[a].TextureNum;
+                  AddTextures[High(AddTextures)].Texture := rec.tagInt; // internal texture number, not map index
                   AddTextures[High(AddTextures)].Anim := texrec.Anim;
                   CurTex := High(AddTextures);
                   ok := true;
@@ -1849,66 +1929,48 @@ begin
         begin
           // Çàíîñèì òîëüêî òåêóùóþ òåêñòóðó
           SetLength(AddTextures, 1);
-          AddTextures[0].Texture := rec.tagInt; //panels[a].TextureNum;
+          AddTextures[0].Texture := rec.tagInt; // internal texture number, not map index
           AddTextures[0].Anim := false;
           if (texrec <> nil) then AddTextures[0].Anim := texrec.Anim;
           CurTex := 0;
         end;
 
-        //e_WriteLog(Format('panel #%d: TextureNum=%d; ht=%d; ht1=%d; atl=%d', [a, panels[a].TextureNum, High(_textures), High(Textures), High(AddTextures)]), MSG_NOTIFY);
+        //e_WriteLog(Format('panel #%d: TextureNum=%d; ht=%d; ht1=%d; atl=%d', [a, panels[a].TextureNum, High(mapTextureList), High(Textures), High(AddTextures)]), MSG_NOTIFY);
+
+        //e_LogWritefln('PANADD: pannum=%s', [pannum]);
 
         // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå íîìåð
         PanelID := CreatePanel(rec, AddTextures, CurTex, trigRef);
         //e_LogWritefln('panel #%s of type %s got id #%s', [pannum, rec.PanelType, PanelID]);
-
-        // Åñëè èñïîëüçóåòñÿ â òðèããåðàõ, òî ñòàâèì òî÷íûé ID
-        if hashTextPan.get(pannum, b) then TriggersTable[b].texPanIdx := PanelID;
-        if hashLiftPan.get(pannum, b) then TriggersTable[b].LiftPanelIdx := PanelID;
-        if hashDoorPan.get(pannum, b) then TriggersTable[b].DoorPanelIdx := PanelID;
-        if hashShotPan.get(pannum, b) then TriggersTable[b].ShotPanelIdx := PanelID;
-
-        (*
-        if (TriggersTable <> nil) then
+        // set 'gamePanelId' field to panel id
+        rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later
+
+        // setup lifts
+        moveSpeed := rec.moveSpeed;
+        //moveStart := rec.moveStart;
+        //moveEnd := rec.moveEnd;
+        //moveActive := rec['move_active'].varvalue;
+        if not moveSpeed.isZero then
         begin
-          for b := 0 to High(TriggersTable) do
-          begin
-            if (TriggersTable[b].texPan = rec) then TriggersTable[b].texPanIdx := PanelID;
-            if (TriggersTable[b].liftPan = rec) then TriggersTable[b].LiftPanelIdx := PanelID;
-            if (TriggersTable[b].doorPan = rec) then TriggersTable[b].DoorPanelIdx := PanelID;
-            if (TriggersTable[b].shotPan = rec) then TriggersTable[b].ShotPanelIdx := PanelID;
-            {
-            // Òðèããåð äâåðè/ëèôòà
-            if (TriggersTable[b].LiftPanel = pannum) or
-               (TriggersTable[b].DoorPanel = pannum) then
-              //TTriggerData(TriggersTable[b].trigrec.DATA).PanelID := PanelID;
-              TriggersTable[b].trigrec.trigRec.trigPanelID := PanelID;
-            // Òðèããåð ñìåíû òåêñòóðû
-            if TriggersTable[b].texPanIdx = pannum then
-              TriggersTable[b].trigrec.TexturePanel := PanelID;
-            // Òðèããåð "Òóðåëü"
-            if TriggersTable[b].ShotPanel = pannum then
-              TriggersTable[b].trigrec.trigRec.trigShotPanelID := PanelID;
-            }
-          end;
+          SetLength(gMovingWallIds, Length(gMovingWallIds)+1);
+          gMovingWallIds[High(gMovingWallIds)] := PanelID;
+          //e_LogWritefln('found moving panel ''%s'' (idx=%s; id=%s)', [rec.id, pannum, PanelID]);
         end;
-        *)
+
+        //e_LogWritefln('PANEND: pannum=%s', [pannum]);
 
         g_Game_StepLoading();
       end;
     end;
 
-    (*
+    // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ
+    for b := 0 to High(TriggersTable) do
     begin
-      for b := 0 to High(TriggersTable) do
-      begin
-        // Òðèããåð äâåðè/ëèôòà
-        if (TriggersTable[b].texPan <> nil) then e_LogWritefln('trigger #%s: textPan=%s; panidx=%s', [b, TriggersTable[b].texPanIdx, mapReader.panelIndex[TriggersTable[b].texPan]]);
-        if (TriggersTable[b].liftPan <> nil) then e_LogWritefln('trigger #%s: liftPan=%s; panidx=%s', [b, TriggersTable[b].LiftPanelIdx, mapReader.panelIndex[TriggersTable[b].liftPan]]);
-        if (TriggersTable[b].doorPan <> nil) then e_LogWritefln('trigger #%s: doorPan=%s; panidx=%s', [b, TriggersTable[b].DoorPanelIdx, mapReader.panelIndex[TriggersTable[b].doorPan]]);
-        if (TriggersTable[b].shotPan <> nil) then e_LogWritefln('trigger #%s: shotPan=%s; panidx=%s', [b, TriggersTable[b].ShotPanelIdx, mapReader.panelIndex[TriggersTable[b].shotPan]]);
-      end;
+      if (TriggersTable[b].texPan <> nil) then TriggersTable[b].texPanIdx := TriggersTable[b].texPan.userPanelId;
+      if (TriggersTable[b].liftPan <> nil) then TriggersTable[b].LiftPanelIdx := TriggersTable[b].liftPan.userPanelId;
+      if (TriggersTable[b].doorPan <> nil) then TriggersTable[b].DoorPanelIdx := TriggersTable[b].doorPan.userPanelId;
+      if (TriggersTable[b].shotPan <> nil) then TriggersTable[b].ShotPanelIdx := TriggersTable[b].shotPan.userPanelId;
     end;
-    *)
 
     // create map grid, init other grids (for monsters, for example)
     e_WriteLog('Creating map grid', MSG_NOTIFY);
@@ -1926,47 +1988,13 @@ begin
         Inc(trignum);
         if (TriggersTable[trignum].texPan <> nil) then b := TriggersTable[trignum].texPan.PanelType else b := 0;
         if (TriggersTable[trignum].shotPan <> nil) then c := TriggersTable[trignum].shotPan.PanelType else c := 0;
-        tgpid := -1;
+        // we can have only one of those
              if (TriggersTable[trignum].LiftPanelIdx <> -1) then tgpid := TriggersTable[trignum].LiftPanelIdx
         else if (TriggersTable[trignum].DoorPanelIdx <> -1) then tgpid := TriggersTable[trignum].DoorPanelIdx
         else if (TriggersTable[trignum].ShotPanelIdx <> -1) then tgpid := TriggersTable[trignum].ShotPanelIdx
         else tgpid := -1;
-        (*
-        if (rec.TexturePanel <> -1) then
-        begin
-          {
-          if (TriggersTable[trignum].TexturePanel < 0) or (TriggersTable[trignum].TexturePanel >= panels.count) then
-          begin
-            e_WriteLog('error loading map: invalid panel index for trigger', MSG_FATALERROR);
-            result := false;
-            exit;
-          end;
-          }
-          //b := panels[TriggersTable[a].TexturePanel].PanelType;
-          //b := mapReader.panel[TriggersTable[trignum].texPanIdx].PanelType;
-          assert(TriggersTable[trignum].texPanIdx >= 0);
-          b := TriggersTable[trignum].texPanIdx;
-        end
-        else
-        begin
-          b := 0;
-        end;
-        e_LogWritefln('trigger #%s: type=%s; texPanIdx=%s; b=%s', [trignum, rec.TriggerType, TriggersTable[trignum].texPanIdx, b]);
-        if (rec.TriggerType = TRIGGER_SHOT) then e_LogWritefln('  SHOT: shotpanidx=%s', [rec.trigRec.trigShotPanelID]);
-        if (rec.TriggerType = TRIGGER_SHOT) and {(rec.trigRec.trigShotPanelID <> -1)} (TriggersTable[trignum].shotPan <> nil) then
-        begin
-          //c := panels[TriggersTable[a].ShotPanel].PanelType;
-          //c := mapReader.panel[TriggersTable[trignum].ShotPanel].PanelType;
-          assert(TriggersTable[trignum].ShotPanelIdx >= 0);
-          c := TriggersTable[trignum].ShotPanelIdx;
-        end
-        else
-        begin
-          c := 0;
-        end;
-        *)
         //e_LogWritefln('creating trigger #%s; texpantype=%s; shotpantype=%s (%d,%d)', [trignum, b, c, TriggersTable[trignum].texPanIdx, TriggersTable[trignum].ShotPanelIdx]);
-        CreateTrigger(rec, TriggersTable[trignum].texPanIdx, tgpid, Word(b), Word(c));
+        CreateTrigger(trignum, rec, TriggersTable[trignum].texPanIdx, tgpid, Word(b), Word(c));
       end;
     end;
 
@@ -2008,8 +2036,8 @@ begin
       for rec in monsters do CreateMonster(rec);
     end;
 
-    //MapReader.Free();
-    gCurrentMap := mapReader;
+    gCurrentMap := mapReader; // this will be our current map now
+    gCurrentMapFileName := Res;
     mapReader := nil;
 
     // Çàãðóçêà íåáà
@@ -2069,7 +2097,7 @@ begin
     if not gLoadGameMode then g_GFX_Init();
 
     // Ñáðîñ ëîêàëüíûõ ìàññèâîâ:
-    _textures := nil;
+    mapTextureList := nil;
     panels := nil;
     items := nil;
     areas := nil;
@@ -2087,23 +2115,24 @@ begin
     begin
       gMusic.SetByName('');
     end;
+
+    stt := curTimeMicro()-stt;
+    e_LogWritefln('map loaded in %s.%s milliseconds', [Integer(stt div 1000), Integer(stt mod 1000)]);
   finally
     sfsGCEnable(); // enable releasing unused volumes
     mapReader.Free();
-    hashTextPan.Free();
-    hashLiftPan.Free();
-    hashDoorPan.Free();
-    hashShotPan.Free();
+    e_ClearInputBuffer(); // why not?
   end;
 
   e_WriteLog('Done loading map.', MSG_NOTIFY);
   Result := True;
 end;
 
+
 function g_Map_GetMapInfo(Res: String): TMapInfo;
 var
   WAD: TWADFile;
-  MapReader: TDynRecord;
+  mapReader: TDynRecord;
   //Header: TMapHeaderRec_1;
   FileName: String;
   Data: Pointer;
@@ -2128,22 +2157,6 @@ begin
 
   WAD.Free();
 
-  {
-  MapReader := TMapReader_1.Create();
-  if not MapReader.LoadMap(Data) then
-    begin
-      g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True);
-      ZeroMemory(@Header, SizeOf(Header));
-      Result.Name := _lc[I_GAME_ERROR_MAP_SELECT];
-      Result.Description := _lc[I_GAME_ERROR_MAP_SELECT];
-    end
-  else
-    begin
-      Header := MapReader.GetMapHeader();
-      Result.Name := Header.MapName;
-      Result.Description := Header.MapDescription;
-    end;
-  }
   try
     mapReader := g_Map_ParseMap(Data, Len);
   except
@@ -2151,10 +2164,6 @@ begin
   end;
 
   FreeMem(Data);
-  //MapReader.Free();
-
-  //if (mapReader <> nil) then Header := GetMapHeader(mapReader) else FillChar(Header, sizeof(Header), 0);
-  //MapReader.Free();
 
   if (mapReader.Width > 0) and (mapReader.Height > 0) then
   begin
@@ -2235,7 +2244,7 @@ begin
     end;
 end;
 
-procedure g_Map_Free();
+procedure g_Map_Free(freeTextures: Boolean=true);
 var
   a: Integer;
 
@@ -2274,17 +2283,42 @@ begin
 
   //gDOMFlags := nil;
 
-  if Textures <> nil then
+  if (Length(gCurrentMapFileName) <> 0) then
   begin
-    for a := 0 to High(Textures) do
-      if not g_Map_IsSpecialTexture(Textures[a].TextureName) then
-        if Textures[a].Anim then
-          g_Frames_DeleteByID(Textures[a].FramesID)
-        else
-          if Textures[a].TextureID <> LongWord(TEXTURE_NONE) then
-            e_DeleteTexture(Textures[a].TextureID);
-
-    Textures := nil;
+    e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]);
+  end
+  else
+  begin
+    e_LogWritefln('g_Map_Free: no previous map.', []);
+  end;
+  if freeTextures then
+  begin
+    e_LogWritefln('g_Map_Free: clearing textures...', []);
+    if (Textures <> nil) then
+    begin
+      for a := 0 to High(Textures) do
+      begin
+        if not g_Map_IsSpecialTexture(Textures[a].TextureName) then
+        begin
+          if Textures[a].Anim then
+          begin
+            g_Frames_DeleteByID(Textures[a].FramesID)
+          end
+          else
+          begin
+            if (Textures[a].TextureID <> LongWord(TEXTURE_NONE)) then
+            begin
+              e_DeleteTexture(Textures[a].TextureID);
+            end;
+          end;
+        end;
+      end;
+      Textures := nil;
+    end;
+    TextNameHash.Free();
+    TextNameHash := nil;
+    BadTextNameHash.Free();
+    BadTextNameHash := nil;
   end;
 
   FreePanelArray(gWalls);
@@ -2296,6 +2330,7 @@ begin
   FreePanelArray(gSteps);
   FreePanelArray(gLifts);
   FreePanelArray(gBlockMon);
+  gMovingWallIds := nil;
 
   if BackID <> DWORD(-1) then
   begin
@@ -2332,9 +2367,7 @@ var
     i: Integer;
 
   begin
-    if panels <> nil then
-      for i := 0 to High(panels) do
-        panels[i].Update();
+    for i := 0 to High(panels) do panels[i].Update();
   end;
 
 begin
@@ -3210,17 +3243,6 @@ begin
 ///// /////
 end;
 
-function g_Map_PanelForPID(PanelID: Integer; var PanelArrayID: Integer): PPanel;
-var
-  Arr: TPanelArray;
-begin
-  Result := nil;
-  if (PanelID < 0) or (PanelID > High(PanelByID)) then Exit;
-  Arr := PanelByID[PanelID].PWhere^;
-  PanelArrayID := PanelByID[PanelID].PArrID;
-  Result := Addr(Arr[PanelByID[PanelID].PArrID]);
-end;
-
 
 // trace liquid, stepping by `dx` and `dy`
 // return last seen liquid coords, and `false` if we're started outside of the liquid