unit prepare;

// initialize and read data, find nif files to use

//============================================================================
// Set NEWFILE to DynDOLOD.esp and do some basic esm/esp checking
function DynDOLODGetFile: boolean;
var
  i: integer;
  s: string;
begin
  Result := True;
  if bCreateMod then begin
    NEWFILE := AddNewFile;
  end
  else begin
    for i := Pred(FileCount) downto 0 do begin
      s := GetFileName(FileByindex(i));
      if SameText(s, 'DynDOLOD.esp') then 
        NEWFILE := FileByindex(i);
      // we might as well add these masters right away because of MATOs and TXSTs used by STATs
      if SameText(s, 'Skyrim.esm') then 
        AddMasterIfMissing(NEWFile, s);
      if SameText(s, 'Dragonborn.esm') then 
        AddMasterIfMissing(NEWFile, s);
      if wbStringListInString(slModNameWarn, s) <> -1 then begin
        MyMessage('!!! Found ' + s + '. Please remove from load order !!!');
        Result := False;
        exit;
      end;
    end;
  end;
  if not Assigned(NEWFile) then begin
    MyMessage('!!! Mod not found !!!');
    Result := False;
    exit;
  end;
  sDynDOLODFileName := GetFileName(GetFile(NEWFile));
  MyMessage(sDynDOLODFileName + ' found');
end;

//============================================================================
// create globals and get vars from ini and form ids from DynDOLOD.esp
procedure DynDOLODPrepare;
begin
  // this wasn't so complex at the beginning when this project was started :)
  slLODFiles := TStringList.Create;
  slLODFiles.StrictDelimiter := True;
  slLODFiles.Duplicates := dupIgnore;
  slDynDOLODFiles := TStringList.Create;
  slDynDOLODFiles.Duplicates := dupIgnore;
  slLODs := TStringList.Create;
  slLODs.Delimiter := ';';
  slLODs.StrictDelimiter := True;
  slLODVanilla := TStringList.Create;

  slTownNames := TStringList.Create;
  slTownNames.StrictDelimiter := True;
  slTownSTATEDIDIgnore := TStringList.Create;
  slTownSTATEDIDIgnore.StrictDelimiter := True;
  slModNameIgnore := TStringList.Create;
  slModNameIgnore.StrictDelimiter := True;
  slModNameWarn := TStringList.Create;
  slModNameWarn.StrictDelimiter := True;
  slIgnoreBSAName := TStringList.Create;
  slIgnoreBSAName.StrictDelimiter := True;
  slFolders := TStringList.Create;
  
  IniLoad('DynDOLOD.ini');
end;

//============================================================================
// create globals and get vars from ini and form ids from DynDOLOD.esp
function DynDOLODPrepareWorld: boolean;
begin
  Result := False;

  // Check if directory exists
  if not(bCreateMod) and bUpdateDDOL and not DirectoryExists(sSKSEPluginPath) then begin
    MyMessage('Required output path ' + sSKSEPluginPath + ' not found');
    Exit;
  end;

  sWorld := EditorID(LODGenWorld);
  if sWorld = '' then
    Exit;

  if not bCreateMod then
    MyMessage('Doing LOD for ' + sWorld);

  SkyrimWorld.XMin := GetElementNativeValues(LODGenWorld, 'Object Bounds\NAM0\X') - 1;
  SkyrimWorld.YMin := GetElementNativeValues(LODGenWorld, 'Object Bounds\NAM0\Y') - 1;
  SkyrimWorld.XMax := GetElementNativeValues(LODGenWorld, 'Object Bounds\NAM9\X') + 1;
  SkyrimWorld.YMax := GetElementNativeValues(LODGenWorld, 'Object Bounds\NAM9\Y') + 1;

  IniLoadWorld('DynDOLOD.ini');

  olEDIDExists := TwbFastStringList.Create;
  olEDIDFormID := TwbFastStringList.Create;
  olSignatureFormID := TwbFastStringList.Create;

  slBaseSignatures := TStringList.Create;
  if (not bCreateMod) and (not bSTATOnly) then begin
    slBaseSignatures.Add('DOOR');
    slBaseSignatures.Add('ACTI');
    slBaseSignatures.Add('CONT');
    slBaseSignatures.Add('MSTT');
    slBaseSignatures.Add('FURN');
    if not bIgnoreTrees then
      slBaseSignatures.Add('TREE');
  end;
  slBaseSignatures.Add('STAT');

  slRulesCache := TwbFastStringList.Create;
  slRulesCache.Duplicates := dupIgnore;

  // org base element form id to new DynDOLOD base element form id
  olBaseElementDynDOLOD := TwbFastStringList.Create;
  // DOOR/ACTI/MSTT/TREE org base element form id to new temp base element form id
  olSTATDAMT := TStringList.Create;
  // org base element MODL to new DynDOLOD base element form id
  olMODLFileBaseElement := TwbFastStringList.Create;

  // omg more of these
  olREFRDynDOLOD := TwbFastStringList.Create;
  olREFRDynDOLOD.Duplicates := dupIgnore;
  slCellsWithObjects := TwbFastStringList.Create;
  slCellsWithObjects.Duplicates := dupIgnore;
  slCellsWithObjects.Sorted := True;
  slTownsWithFirstborns := TStringList.Create;
  slTownsWithFirstborns.Duplicates := dupIgnore;
  olXCLCCache := TwbFastStringList.Create;
  olREFRTown := TwbFastStringList.Create;
  olREFRTown.Duplicates := dupIgnore;
  olREFRWorld := TwbFastStringList.Create;
  olREFRWorld.Duplicates := dupIgnore;
  olMODLCache := TwbFastStringList.Create;
  olCNAMCache := TwbFastStringList.Create;
  lstSkipDynDOLOD := TwbFastStringList.Create; //TList.Create;
  slREFRToDisable := TwbFastStringList.Create; //TList.Create;
  slREFRTREELOD := TwbFastStringList.Create; //TList.Create;
  olJsonObjectData := TwbFastStringList.Create;
  olFirstborns := TwbFastStringList.Create;
  olMinions := TwbFastStringList.Create;
  slLODMeshes := TwbFastStringList.Create;

  // get NEWFILE
  if DynDOLODGetFile then
    Result := True;

  SkyrimWorldUsed.XMin := 256;
  SkyrimWorldUsed.YMin := 256;
  SkyrimWorldUsed.XMax := 0;
  SkyrimWorldUsed.YMax := 0;

end;

//============================================================================
// free a global list or two, or three, or four, omg look how many
procedure FreeLists;
begin
  slMODLTextureSet.Free;
  slTexturesNames.Free;
  slChildWorldIgnore.Free;
  slLODMeshes.Free;
  olMinions.Free;
  olFirstborns.Free;
  olJsonObjectData.Free;
  slREFRTREELOD.Free;
  slREFRToDisable.Free;
  lstSkipDynDOLOD.Free;
  olCNAMCache.Free;
  olMODLCache.Free;
  olREFRWorld.Free;
  olREFRTown.Free;
  slTownsWithFirstborns.Free;
  olXCLCCache.Free;
  slCellsWithObjects.Free;
  olREFRDynDOLOD.Free;
  olMODLFileBaseElement.Free;
  olSTATDAMT.Free;
  olBaseElementDynDOLOD.Free;
  slRulesCache.Free;
  slBaseSignatures.Free;
  olSignatureFormID.Free;
  olEDIDFormID.Free;
  olEDIDExists.Free;
  slFolders.Free;
  slIgnoreBSAName.Free;
  slModNameWarn.Free;
  slModNameIgnore.Free;
  slTownSTATEDIDIgnore.Free;
  slTownNames.Free;
  slLODVanilla.Free;
  slLODs.Free;
  slDynDOLODFiles.Free;
  slLODFiles.Free;
end;

//============================================================================
// load ini settings
procedure SkyrimIniLoad(sFileName: string);
var
  ini: TMemIniFile;
begin
  ini := TMemIniFile.Create('c:\Users\fraggle\Documents\My Games\Skyrim\Skyrim.ini');
  AddMessage(ini.ReadString('Archive', 'sResourceArchiveList', ''));
  AddMessage(ini.ReadString('Archive', 'sResourceArchiveList2', ''));
end;

//============================================================================
// load ini settings
procedure IniLoad(sFileName: string);
var
  ini: TMemIniFile;
  i: integer;
  s, t: string;
  sl: TStringList;
begin
  ini := TMemIniFile.Create(IniPath + sFileName);

  bAdvanced := ini.ReadBool('DynDOLOD', 'Advanced', False);
  bIgnoreTrees := ini.ReadBool('DynDOLOD', 'IgnoreTrees', False);
  bCreateMod := ini.ReadBool('DynDOLOD', 'CreateMod', False);
  bSTATOnly := ini.ReadBool('DynDOLOD', 'STATOnly', False);
  bUpdateWRLD := ini.ReadBool('DynDOLOD', 'UpdateWRLD', True);
  bExecuteLODGenPas := ini.ReadBool('DynDOLOD', 'ExecuteLODGenPas', True);
  bExecuteLODGenExe := ini.ReadBool('DynDOLOD', 'ExecuteLODGenExe', True);

  if bCreateMod then begin
    MyMessage('Creating a mod. Disabled LODGen');
    bExecuteLODGenPas := False;
    bExecuteLODGenExe := False;
  end;

  s := ini.ReadString('DynDOLOD', 'IgnoreModFileName', '');
  if Length(s) > 0 then
    slModNameIgnore.CommaText := StringReplace(s, ', ', ',', [rfReplaceAll]);

  s := ini.ReadString('DynDOLOD', 'WarnModFileName', '');
  if Length(s) > 0 then
    slModNameWarn.CommaText := StringReplace(s, ', ', ',', [rfReplaceAll]);

  i := 1;
  repeat
    s := ini.ReadString('DynDOLOD', 'Folder' + IntToStr(i), '');
    if Length(s) > 0 then
      slFolders.Add(Lowercase(s));
    Inc(i);
  until Length(s) = 0;

  s := ini.ReadString('DynDOLOD', 'IgnoreBSAName', '');

  if Length(s) > 0 then
    slIgnoreBSAName.CommaText := StringReplace(s, ', ', ',', [rfReplaceAll]);

  ini.Free;
end;

//============================================================================
// load ini settings for World
procedure IniLoadWorld(sFileName: string);
var
  ini: TMemIniFile;
  i: integer;
  s, t: string;
  sl: TStringList;
begin
  ini := TMemIniFile.Create(IniPath + sFileName);

  s := ini.ReadString(wbGameName + ' ' + sWorld, 'ChildWorlds', '');
  if Length(s) > 0 then
    slTownNames.CommaText := StringReplace(s, ', ', ',', [rfReplaceAll]);

  s := ini.ReadString(wbGameName + ' ' + sWorld, 'Ignore', '');
  if Length(s) > 0 then
    slTownSTATEDIDIgnore.CommaText := StringReplace(s, ', ', ',', [rfReplaceAll]);

  ini.Free;
end;

//============================================================================
// Load data from some other lists
procedure LoadAdditionalLists;
var
  i, j: integer;
  FormID: cardinal;
  Element: IInterface;
begin
  // list of textures to 'color' names
  slTexturesNames := TStringList.Create;
  LoadOSLFromFile2(slTexturesNames, 'textures_names', False, True);
  if slTexturesNames.Count > 0 then
    MyMessage('Loaded ' + IntToStr(slTexturesNames.Count) + ' textures to names');

  // list of textures sets overrides
  slMODLTextureSet := TStringList.Create;
  LoadOSLFromFile2(slMODLTextureSet, 'texture_set', False, True);
  if slMODLTextureSet.Count > 0 then
    MyMessage('Loaded ' + IntToStr(slMODLTextureSet.Count) + ' texture set indices');

  if bCreateMod then
    Exit;

  // form id list of references to ignore in child towns
  // the difference of x, y, z of some town LOD references compared to their full model counterparts is too high for automatic detection
  slChildWorldIgnore := TStringList.Create;
  LoadOSLFromFile2(slChildWorldIgnore, 'childworldlod_' + sWorld, False, True);
  if slChildWorldIgnore.Count > 0 then
    MyMessage('Loaded ' + IntToStr(slChildWorldIgnore.Count) + ' references to ignore in child worlds');

  // add rules for REFR FormdIDs
  MyMessage('Scanning ' + IntToStr(slLOD.Count) + ' rules for reference FormIDs');
  for i := 0 to Pred(slLOD.Count) do begin
    // FormID?
    try
      FormID := FileNameToLoadOrder(Copy(slLOD[i], 0, Pos(';', slLOD[i]) -1), StrToInt64('$' + Copy(slLOD[i], Pos(';', slLOD[i]) +2, Length(slLOD[i]))));
      if FormID > 0 then
          Element := RecordByLoadOrderFormID(FormID);
          if Assigned(Element) then
            if Signature(Element) = 'REFR' then begin
              j := slLOD.Objects[i];
              MyMessage('  Found reference ' + IntToHex(GetLoadOrderFormID(Element), 8) + ' using base element ' + IntToHex(GetLoadOrderFormID(BaseRecord(Element)), 8) + ' ' + EditorID(BaseRecord(Element)));
              slRulesCache.AddObject(j, FormID);
            end
            else
              MyMessage('  Found ' + IntToHex(FormID, 8) + ' but not a reference');
    except
       // do nothing
    end;
  end;
end;

//============================================================================
// Scan BSAs and game dir for lod files
function BSALODFileList(bVanilla: boolean): TStringList;
var
  slContainers, slAssets, slFiles: TStringList;
  i, c, index: integer;
  lodpath, fulllodpath: string;
begin
  Result := TStringList.Create;
  Result.Duplicates := dupIgnore;

  if (bVanilla) then begin
    MyMessage('Building a list of original LOD files');
    slLODVanilla := LoadStringList('default_lod');
    if slLODVanilla.Count > 0 then begin
      MyMessage('  Loaded ' + IntToStr(slLODVanilla.Count) + ' original LOD files from cache');
      exit;
    end;
  end;

  // wbFilterStrings uses Pos()
  lodpath := '_lod';
  //*_lod*.nif

  slFiles := TwbFastStringList.Create;
  slFiles.Sorted := True;
  slFiles.Duplicates := dupIgnore;
  slContainers := TStringList.Create;
  slAssets := TStringList.Create;

  try
    ResourceContainerList(slContainers);
    for i := 0 to Pred(slContainers.Count) do begin
      if not bVanilla or (bVanilla and (ExtractFileExt(Lowercase(slContainers[i])) = '.bsa') and (wbStringListInString(slIgnoreBSAName, Lowercase(slContainers[i])) <> -1)) then begin
        MyMessage('  Scanning ' + slContainers[i]);
        ResourceList(slContainers[i], slAssets);
        wbFilterStrings(slAssets, slFiles, lodpath);
      end;
    end;
  finally
    slAssets.Free;
    slContainers.Free;
  end;

  for i := Pred(slFiles.Count) downto 0 do begin
    // nif files only
    if bVanilla and (ExtractFileExt(Lowercase(slFiles[i])) = '.nif') and (Pos('\lod\', Lowercase(slFiles[i])) > 0) then
      slLODVanilla.Add(slFiles[i]);
    // nif files only
    if (ExtractFileExt(Lowercase(slFiles[i])) = '.nif') then
      Result.Add(slFiles[i]);
  end;

  slFiles.Free;

  if bVanilla then begin
    MyMessage('  Found and saved ' + IntToStr(slLODVanilla.Count) + ' original LOD files');
    slLODVanilla.SaveToFile(AdvancedPath + 'default_lod' + '.txt');
  end;

end;

//============================================================================
// initialize vars and call nif file lookup
procedure ScanFiles;
var
  sFileName: string;
begin
  iTotalFiles := 0;

  sFileName := CachePath + 'meshes.txt';
  if bUseCache and FileExists(sFileName) then
    slLODMeshes.LoadFromFile(sFileName);

  if bUseCache and (slLODMeshes.Count > 0) then begin
    MyMessage('Loaded ' + IntToStr(slLODMeshes.Count) + ' LOD meshes from cache');
  end
  else begin
    slLODMeshes.Clear;
    MyMessage('Looking for LOD nif files in BSA archives and in ' + sDataPath);
    slLODMeshes.AddStrings(BSALODFileList(False));
    slLODMeshes.SaveToFile(sFileName);
  end;

  ProcessLODFiles(slLODMeshes);

  // is not cleared here atlas will contain all textures
  slLODMeshes.Clear;

  MyMessage('  Found ' + IntToStr(iTotalFiles) + ' LOD files for ' + IntToStr(slLODFiles.Count) + ' unique LOD objects');
end;

//============================================================================
Function PathInPath(start: integer; s: string): boolean;
var
  i: integer;
begin
  Result := False;
  if Pos(slFolders[start], s) = 0 then begin
    Result := True;
    exit;
  end;
  inc(start);
  for i := start to Pred(slFolders.Count) do begin
    if Pos(slFolders[i], s) > 0 then begin
      Result := True;
      exit;
    end;
  end;
end;

//============================================================================
// Process all LOD files found in BSA and game directory
procedure ProcessLODFiles(sl: TStringList);
var
  s, sPath, sSearchPath, sThisFileName, sShortName, sFileName, sCurrentFileName: string;
  i, idx, index: integer;
begin

  for index := 0 to Pred(slFolders.Count) do begin
    for idx := 0 to Pred(sl.Count) do begin
      //AddMessage('Process '+ slFolders[index] + ' = ' + sl[idx]);

      if PathInPath(index, Lowercase(ExtractRelativePath(sDataPath, sl[idx]))) then
        Continue;

      sThisFileName := Lowercase(ExtractFileName(sl[idx]));
      sSearchPath := Lowercase(ExtractFilePath(sl[idx]));

      // skip *h.nif for HeavySnow mountains, they will be looked up by editor ID 'manually'
      // *l.nif will be used
      if Pos('h.nif', sThisFileName) > 0 then
        Continue;

      sShortName := Copy(sThisFileName, 0, Pos('_lod', sThisFileName) - 1);

      // dyndolod_lod
      if Pos('_dyndolod', sShortName) > 0 then
        sShortName := Copy(sThisFileName, 0, Pos('_dyndolod', sThisFileName) - 1);

      // Immersive College of Winterhold
      if sShortName = 'cwidomemainexterior' then
        sShortName := 'cwidomemain';

      sFileName := sSearchPath + sThisFileName;

      inc(iTotalFiles);

      // super special animated lod only for DynDOLOD
      if Pos('dyndolod_lod', Lowercase(sFileName)) > 0 then
  
        slDynDOLODFiles.Values[sShortName] := sFileName

      else begin

        // LOD is 4; 8; 16
        // _0 -> level  4
        // _1 -> level  8, fill level 4 if empty
        // _2 -> level 16, fill level 4, 8 if empty
        // no number in filename -> fill level 4, 8, 16 if empty

        if slLODFiles.Values[sShortName] = '' then
        slLODFiles.Values[sShortName] := ';;';

        slLODs.Clear;
        slLODs.DelimitedText := slLODFiles.Values[sShortName];

        sCurrentFileName := '';
        for i := 3 downto 0 do begin

          // lod number
          if Pos(IntToStr(i) + '.nif', sFileName) > 0 then
            sCurrentFileName := sFileName;

          // vanilla static LOD comes with 3 in filenames, but then there is none with 2
          if i >= 3 then
            Continue;

          // no numbering fallback
          if (i = 0) and (sCurrentFileName = '') then
            slLODs[i] := sFileName;

          if (sCurrentFileName <> '') then
            slLODs[i] := sCurrentFileName;

        end;

        s := slLODs[0];
        // add vanilla LOD with no numbers to all levels if they are empty or overwrite if no number
        for i := 1 to 2 do begin
          if ((slLODs[i] = '') and (Pos('_lod.nif', slLODs[Pred(i)]) > 0) and ((Pos('dyndolod', slLODs[Pred(i)]) = 0)))
	  then
            slLODs[i] := slLODs[Pred(i)];
          s := s + ';' + slLODs[i];
        end;
        slLODFiles.Values[sShortName] := s;

      end;
    end;
  end;
end;

//============================================================================
// read stuff from DynDOLOD.esp
function ReadMasterFile: Boolean;
var
  Element, TempElement, group: IInterface;
  i, iElement, interval, count: integer;
  sEDID: string;
  slNames, slRemove: TStringList;
  bCounter: boolean;
begin
  Result := True;

  // create some groups that may be needed
  for i := 0 to Pred(slBaseSignatures.Count) do
    If not Assigned(GroupBySignature(NEWFile, slBaseSignatures[i])) then
      Add(NEWFile, slBaseSignatures[i], True);

  if bCreateMod then
    Exit;

  Result := False;

  MyMessage('Reading ' + GetFileName(NEWFILE) + ' with ' + IntToStr(RecordCount(NEWFILE)) + ' records');

  slNames := TStringList.Create;
  slRemove := TStringList.Create;

  try
    slNames.Add('DynDOLOD_MasterActivator');
    slNames.Add('DynDOLOD_FirstbornActivator');
    slNames.Add('DynDOLOD_MinionActivator');
    slNames.Add('DynDOLOD_LODObject');
    slNames.Add('DynDOLOD_FirstbornObject');
    slNames.Add('DynDOLOD_MinionObject');
    slNames.Add('DynDOLOD_ShesonObject');
    slNames.Add('DynDOLOD_MyLastObjectList');

    iFirstbornCounter := 0;
    iMinionCounter := 0;
    iDynDOLODCounter := 0;

    if RecordCount(NEWFILE) < 100 then
      interval := RecordCount(NEWFILE)
    else
      interval := RecordCount(NEWFILE) / 10;
    if RecordCount(NEWFILE) > 2000 then
      bCounter := True
    else
      bCounter := False;

    for iElement := Pred(RecordCount(NEWFILE)) downto 0 do begin
      if bCounter then
        if iElement mod interval = 0 then
          MyMessage('    ' + IntToStr(RecordCount(NEWFILE) - iElement) + ' of ' + IntToStr(RecordCount(NEWFILE)) + ' done');

      Element := RecordByIndex(NEWFILE, iElement);
      if ElementType(Element) <> etMainRecord then
        Continue;
      sEDID := EditorID(Element);

      // build a remove list, do not remove things from index while still inerating through the whole index :)
      if not bUpdateWRLD and (((Signature(Element) = 'WRLD') or (Signature(Element) = 'CELL') or (Signature(Element) = 'ACTI')) and ((Pos(sWorld, sEDID) > 0) or (wbStringListInString(slTownNames, sEDID) <> -1) or (Pos(sWorld, GetElementEditValues(Element, 'Worldspace')) > 0))) then begin
        slRemove.Add(GetLoadOrderFormID(Element));
        Continue;
      end;

      // add editor id of some existing elements to list
      if wbStringListInString(slNames, sEDID) <> -1 then begin
        olEDIDFormID.AddObject(Copy(sEDID, 10, Length(sEDID)), GetLoadOrderFormID(Element));
        Continue;
      end;

      // read all existing firstborns and minions and fill the lists
      if bUpdateWRLD then
        if Signature(Element) = 'REFR' then begin
          if EDIDToID(sWorld + '_Firstborn', sEDID) <> '' then begin
            olFirstborns.AddObject(EDIDToID(sWorld + '_Firstborn', sEDID), GetLoadOrderFormID(Element));
            inc(iFirstbornCounter);
          end;
          if EDIDToID(sWorld + '_Minion', sEDID) <> '' then begin
            olMinions.AddObject(EDIDToID(sWorld + '_Minion', sEDID), GetLoadOrderFormID(Element));
            inc(iMinionCounter);
          end;
          if (Pos('_DynDOLOD_LOD', sEDID) > 0) or (Pos('_DynDOLOD_Manual', sEDID) > 0) or (Pos('_DynDOLOD_STAGE', sEDID) > 0) then begin
            // the WRLD element this reference is in
            TempElement := ChildrenOf(GetContainer(GetContainer(GetContainer(Element))));
            if Signature(TempElement) <> 'WRLD' then
              MyMessage('  No WRLD record. Need help! ' + Name(TempElement));
            if ((sWorld = EditorID(TempElement)) and (Pos(sWorld, EditorID(LinksTo(ElementByPath(Element, 'Activate Parents\Activate Parent Refs\XAPR - Activate Parent Ref\Reference'))))) = 1) or 
               ((sWorld = EditorID(LinksTo(ElementByPath(TempElement, 'Parent\WNAM')))) and (GetElementNativeValues(TempElement, 'Parent\PNAM\Flags') and $2 = $2))
            then begin
              AddToFirstbornListElement(Element, False);
              inc(iDynDOLODCounter);
            end;
          end;
          olEDIDExists.AddObject(sEDID, GetLoadOrderFormID(Element));
          Continue;
        end;

      // add all DynDOLOD base elements to list
      if (Signature(Element) = 'WRLD') or 
         (Signature(Element) = 'ACTI') or 
         ((Signature(Element) = 'STAT') and (Pos('DynDOLOD_LOD', sEDID) > 0))
      then
        olEDIDExists.AddObject(sEDID, GetLoadOrderFormID(Element));

    end;

    // removing things is safe now
    if slRemove.Count > 0 then begin
      for i := 0 to Pred(slRemove.Count) do begin
        if Assigned(RecordByLoadOrderFormID(slRemove[i])) then begin
          MyMessage('  Removing ' + Name(RecordByLoadOrderFormID(slRemove[i])));
          RemoveNode(RecordByLoadOrderFormID(slRemove[i]))
        end;
      end;`
    end;

    if olEDIDFormID.Count <> slNames.Count then begin
      MyMessage('  Something wrong with ' + sDynDOLODFileName + IntToStr(olEDIDFormID.Count) + '/' + IntToStr(i) + '. Please restart TES5Edit or get a default file');
      exit;
    end;

    if olEDIDExists.Count > 0 then
      MyMessage('  Read ' + IntToStr(olEDIDExists.Count) + ' existing records')
    else
      MyMessage('  Found default ' + sDynDOLODFileName);

    iCellsWithObjectsCounter := slCellsWithObjects.Count;

    if iDynDOLODCounter > 0 then
      MyMessage('  Found ' + IntToStr(iDynDOLODCounter) + ' dynamic LOD objects in ' + sWorld);
    if iCellsWithObjectsCounter > 0 then
      MyMessage('  Found ' + IntToStr(iCellsWithObjectsCounter) + ' active cells in ' + sWorld);
    if iFirstbornCounter > 0 then
      MyMessage('  Found ' + IntToStr(iFirstbornCounter) + ' FarGrids cell entries in ' + sWorld);
    if iMinionCounter > 0 then
      MyMessage('  Found ' + IntToStr(iMinionCounter) + ' NearGrids cell entries in ' + sWorld);

  finally
    slRemove.Free;
    slNames.Free;
  end;
  Result := True;

end;

end.
