unit create;

// create Grids for DynDOLOD Mod
// add REFR for DynDOLOD Mod
// export json formlist

//============================================================================
// main
procedure DynDOLODCreate;
begin
  slFirstbornsFormList := TwbFastStringList.Create;
  slMinionsFormList := TwbFastStringList.Create;
  olFirstbornsIndex := TwbFastStringList.Create;
  olMinionsIndex := TwbFastStringList.Create;
  olBrotherIndex := TwbFastStringList.Create;

  DoManualDynDOLODFirstborns;

  if iCellsWithObjectsCounter = slCellsWithObjects.Count then
    MyMessage('No new active cells. No need to update NearGrids or FarGrids')
  else begin
    CreateMasters;
    CreateFirstborns2;
  end;

  DoDynDOLOD;

  DoManualDynDOLODREFR;

  SaveFormlists1;

  if iCellsWithObjectsCounter = slCellsWithObjects.Count then
   MyMessage('No new active cells. No need to update NearGrids or FarGrids form lists')
  else begin
    AddFirstbornsTown;
    SaveFormlists2;
  end;

  SaveFormlists3;

  olBrotherIndex.Free;
  olMinionsIndex.Free;
  olFirstbornsIndex.Free;
  slMinionsFormList.Free;
  slFirstbornsFormList.Free;
end;

//============================================================================
//Ripped from LODGen.pas and bend to my devilish needs
procedure CreateAtlas(bNew: boolean);
var
  i, j, width, height: integer;
  slLODTextures, sl: TwbFastStringList;
  s, AtlasName, AtlasMapName: string;
begin
  slLODTextures := TwbFastStringList.Create;

  AtlasMapName := CachePath + 'atlas_map_' + Lowercase(sWorld) + '.txt';
  AtlasName := sDestination + '\textures\DynDOLOD\LOD\DynDOLOD_Atlas_' + sWorld + '.dds';

  MyMessage('Gathering textures for atlas creation');

  if bNew then begin
      // go through export file to get a list of used LOD meshes
    if slLODMeshes.Count = 0 then begin
      slLODMeshes.Sorted := True;
      slLODMeshes.Duplicates := dupIgnore;
      sl := TStringList.Create;
      sl.Delimiter := ',';
      sl.StrictDelimiter := True;
      for i := 0 to Pred(slExport.Count) do begin
        s := StringReplace(slExport[i], #9, ',', [rfReplaceAll]);
        sl.CommaText := s;
        if sl.Count >= 14 then
          for j := 13 to Pred(sl.Count) do
            if sl[j] <> '' then
              slLODMeshes.Add(sl[j]);
      end;
      sl.Free;
    end;

    // get used textures
    slLODTextures.Sorted := True;
    slLODTextures.Duplicates := dupIgnore;
    wbGetUVRangeTexturesList(slLODMeshes, slLODTextures, 10);
  end
  else begin
    if FileExists(AtlasMapName) then begin
	  slLODTextures.LoadFromFile(AtlasMapName);
      sl := TStringList.Create;
      sl.Delimiter := ',';
      sl.StrictDelimiter := True;
	  for i := 0 to Pred(slLODTextures.Count) do begin
	    s := StringReplace(slLODTextures[i], #9, ',', [rfReplaceAll]);
		sl.CommaText := s;
		if sl.Count > 0 then
		  slLODTextures[i] := sl[0];
	  end;
	  sl.Free;
	end
	else
	  MyMessage('No atlas data at ' + AtlasMapName);
  end;

  if slLODTextures.Count > 1 then begin
      
    // remove any texture that isn't LOD
    for i := Pred(slLODTextures.Count) downto 0 do
      if Pos('\lod\', Lowercase(slLODTextures[i])) = 0 then
        slLODTextures.Delete(i);

    // make sure atlas folder exists
    if not DirectoryExists(ExtractFilePath(AtlasName)) then
      if not ForceDirectories(ExtractFilePath(AtlasName)) then begin
        MyMessage('Error: can not create output folder for atlas ' + ExtractFilePath(AtlasName));
        Exit;
      end;
    MyMessage('Creating atlas textures ' + AtlasName);
    wbBuildAtlasFromTexturesList(
      slLODTextures,
      iAtlasTextureSize,
      iAtlasMaxWidth,
      iAtlasMaxHeight,
      AtlasName,
      AtlasMapName
    );
	if not bNew then
	  MyMessage('Done');
  end;
  slLODTextures.Free;
end;

//============================================================================
procedure CreateMinion(xfirstborn, yfirstborn : int64);
var
  TempElement : IInterface;
  sEDID: string;
begin
  sEDID := RemoveNonAlphaNum(StringReplace(sWorld + '_Minion_' + CellsToString(xfirstborn, yfirstborn), '-', '%', [rfReplaceAll]));

  // done before
  if olEDIDExists.IndexOf(sEDID) <> -1 then
    TempElement := RecordByLoadOrderFormID(olEDIDExists.Objects[olEDIDExists.IndexOf(sEDID)])
  else begin
    TempElement := Add(CopyOverrideToFile(GetCellFromWorldspace(LODGenWorld, 0, 0, True), NEWFILE), 'REFR', True);
    SetElementNativeValues(TempElement, 'Record Header\Record Flags', $40000600);
    SetElementNativeValues(TempElement, 'EDID', sEDID);
    SetElementNativeValues(TempElement, 'NAME', olEDIDFormID.Objects[olEDIDFormID.IndexOf('MinionActivator')]);
    SetElementNativeValues(TempElement, 'DATA\Position\X', CellToPos(xfirstborn));
    SetElementNativeValues(TempElement, 'DATA\Position\Y', CellToPos(yfirstborn));
  end;
  olMinions.AddObject(CellsToID(xfirstborn, yfirstborn), GetLoadOrderFormID(TempElement));
  // for json list
  olSignatureFormID.AddObject(Signature(BaseRecord(TempElement)), GetLoadOrderFormID(TempElement));
end;

//============================================================================
procedure CreateFirstborn(xfirstborn, yfirstborn : int64);
var
  Element, TempElement : IInterface;
  sEDID: string;
begin
  sEDID := RemoveNonAlphaNum(StringReplace(sWorld + '_Firstborn_' + CellsToString(xfirstborn, yfirstborn), '-', '%', [rfReplaceAll]));

  // done before
  if olEDIDExists.IndexOf(sEDID) <> -1 then
    TempElement := RecordByLoadOrderFormID(olEDIDExists.Objects[olEDIDExists.IndexOf(sEDID)])
  else begin
    Element := RecordByLoadOrderFormID(olEDIDFormID.Objects[olEDIDFormID.IndexOf('FirstbornObject')]);
    TempElement := Add(CopyOverrideToFile(GetCellFromWorldspace(LODGenWorld, 0, 0, True), NEWFILE), 'REFR', True);
    SetElementNativeValues(TempElement, 'Record Header\Record Flags', $40000600);
    SetElementNativeValues(TempElement, 'EDID', sEDID);
    SetElementNativeValues(TempElement, 'NAME', olEDIDExists.Objects[olEDIDExists.IndexOf(sWorld + '_FirstbornActivator')]);
    SetElementNativeValues(TempElement, 'DATA\Position\X', CellToPos(xfirstborn));
    SetElementNativeValues(TempElement, 'DATA\Position\Y', CellToPos(yfirstborn));
    SetElementNativeValues(TempElement, 'DATA\Position\Z', 16384);
    // because of reasons
    SetElementNativeValues(TempElement, 'DATA\Rotation\Z', 90);
    wbCopyElementToRecord(ElementByPath(Element, 'XPRM'), TempElement, True, True);
  end;
  olFirstborns.AddObject(CellsToID(xfirstborn, yfirstborn), GetLoadOrderFormID(TempElement));
  // for json list
  olSignatureFormID.AddObject(Signature(BaseRecord(TempElement)), GetLoadOrderFormID(TempElement));
end;

//============================================================================
// create master in worldspace and a copy of the Firstborn ACTI base element
procedure CreateMasters;
var
  TempElement, BaseElement: IInterface;
  s: string;
begin
  s := sWorld + '_ShesonObject';
  sThisWorld := sWorld;
  if olEDIDExists.IndexOf(s) = -1 then begin
    MyMessage('Adding Master into ' + sWorld);
    // create a new ShesonObject so the Firstborns have something to look up to
    TempElement := Add(CopyOverrideToFile(GetCellFromWorldspace(LODGenWorld, 0, 0, True), NEWFILE), 'REFR', True);
    // persistant, neverfade, norespawn
    SetElementNativeValues(TempElement, 'Record Header\Record Flags', $40010400);
    SetElementNativeValues(TempElement, 'EDID', s);
    SetElementNativeValues(TempElement, 'NAME', olEDIDFormID.Objects[olEDIDFormID.IndexOf('MasterActivator')]);
    olEDIDFormID.AddObject(s, GetLoadOrderFormID(TempElement));
    olEDIDExists.AddObject(s, GetLoadOrderFormID(TempElement));
  end
  else begin
    MyMessage('Master in ' + sWorld + ' found');
    TempElement := RecordByLoadOrderFormID(olEDIDExists.Objects[olEDIDExists.IndexOf(s)]);
  end;
  // for json list
  olSignatureFormID.AddObject(Signature(BaseRecord(TempElement)), GetLoadOrderFormID(TempElement));
  
  // create a new FirstbornActivator for this ShesonObject
  s := sWorld + '_FirstbornActivator';
  if olEDIDExists.IndexOf(s) = -1 then begin
    BaseElement := wbCopyElementToFile(RecordByLoadOrderFormID(olEDIDFormID.Objects[olEDIDFormID.IndexOf('FirstbornActivator')]), NEWFILE, True, True);
    SetElementNativeValues(BaseElement, 'EDID', s);
    olEDIDExists.AddObject(sWorld + '_FirstbornActivator', GetLoadOrderFormID(BaseElement));
  end;
end;

//============================================================================
// needs a form list for each Firstborn that points to all other Firstborns of the MasterGrid that have DynDOLOD objects
// same for Minions / MinionGrids, Minions only exist for cells that have DynDOLOD objects
// the whole point is to have as few lists as possible so they can be shared across Firstborns/Minions
procedure CreateFirstborns2;
var
  xfirstborn, yfirstborn, xfirstbornMin, yfirstbornMin, xfirstbornMax, yfirstbornMax, xWidth, x, xw, xMN, xwMN, y, ymin, ymax, yMNmin, yMNmax, i: int64;
  ThisID, total, interval, count: int64;
  MasterRadius, MinionRadius, xGridsDiff, yGridsDiff: Cardinal;
  s, sFB, sMN, sGrids, ID: string;
begin

  sThisWorld := sWorld;

  SkyrimWorldUsed.XMin := SkyrimWorldUsed.XMin - 128;
  SkyrimWorldUsed.YMin := SkyrimWorldUsed.YMin - 128;
  SkyrimWorldUsed.XMax := SkyrimWorldUsed.XMax - 128;
  SkyrimWorldUsed.YMax := SkyrimWorldUsed.YMax - 128;

  if SkyrimWorldUsed.XMin < SkyrimWorld.XMin then
    SkyrimWorldUsed.XMin := SkyrimWorld.XMin;
  if SkyrimWorldUsed.YMin < SkyrimWorld.YMin then
    SkyrimWorldUsed.YMin := SkyrimWorld.YMin;
  if SkyrimWorldUsed.XMax > SkyrimWorld.XMax then
    SkyrimWorldUsed.XMax := SkyrimWorld.XMax;
  if SkyrimWorldUsed.YMax > SkyrimWorld.YMax then
    SkyrimWorldUsed.YMax := SkyrimWorld.YMax;

  MasterRadius := MasterGridsToLoad div 2;
  MinionRadius := MinionGridsToLoad div 2;

  total := (SkyrimWorldUsed.XMax - SkyrimWorldUsed.XMin + MasterGridsToLoad) * (SkyrimWorldUsed.YMax - SkyrimWorldUsed.YMin + MasterGridsToLoad);

  xfirstbornMin := SkyrimWorldUsed.XMin - MasterRadius;
  xfirstbornMax := SkyrimWorldUsed.XMax + MasterRadius;
  yfirstbornMin := SkyrimWorldUsed.YMin - MasterRadius;
  yfirstbornMax := SkyrimWorldUsed.YMax + MasterRadius;
  xWidth := (xfirstbornMax - xfirstbornMin + 1) * 5;
  xGridsDiff := (MasterRadius - MinionRadius) * 5;
  yGridsDiff := (MasterRadius - MinionRadius) * xWidth;

  //MyMessage('Preparing '+IntToStr(total)+' cells with ' + IntToStr(slCellsWithObjects.Count) + ' active cells for ' + IntToStr(olREFRDynDOLOD.Count) + s + ' dynamic LOD objects for DynDOLOD');

  // quickly create a very long string with 4 digits for coordinates/id for each cell, seperated by ;
  sGrids := '';
  yfirstborn := yfirstbornMin;
  while yfirstborn <= yfirstbornMax do begin
    xfirstborn := xfirstbornMin;
    while xfirstborn <= xfirstbornMax do begin
      // CellsToID, not using function for speed
      ThisID := ((xfirstborn + 128) * 256) + (yfirstborn + 128);
      if slCellsWithObjects.IndexOf(ThisID) <> -1 then begin
        sGrids := sGrids + IntToHex(ThisID, 4) + ';';
        CreateMinion(xfirstborn, yfirstborn);
      end
      else begin
        sGrids := sGrids + '0000;'
      end;
      inc(xfirstborn);
    end;
    inc(yfirstborn);      
  end;

  if iDynDOLODCounter > 0 then
    s := ' new and ' + IntToStr(iDynDOLODCounter) + ' existing '
  else
    s := '';
  MyMessage('Setting up '+IntToStr(total)+' cells with ' + IntToStr(slCellsWithObjects.Count) + ' active cells for ' + IntToStr(olREFRDynDOLOD.Count) + s + ' dynamic LOD objects for DynDOLOD');

  if total < 100 then
    interval := total
  else
    interval := total / 10;
  if total > 1000 then
    interval := total / 20;
  count := 0;

  // iterate over the very long string and copy chunks the width of the grids out of it
  // put a ; seperated list of coordinates into stringlist, using stringlist index for its hash function to check for doubles
  // the stringlist object becomes the ID for a list
  yfirstborn := yfirstbornMin;
  while yfirstborn <= yfirstbornMax do begin

    ymin := 0 - MasterRadius;
    ymax := MasterRadius;

    if (yfirstborn + ymin < yfirstbornMin) then
      ymin := 0;
    if (yfirstborn + ymax > yfirstbornMax + 1) then
      ymax := yfirstbornMax - yfirstborn;

    ymin := (yfirstborn - yfirstbornMin + ymin) * xWidth;
    ymax := (yfirstborn - yfirstbornMin + ymax) * xWidth;
    yMNmin := ymin + yGridsDiff;
    yMNmax := ymax - yGridsDiff;

    xfirstborn := xfirstbornMin;
    while xfirstborn <= xfirstbornMax do begin

      sFB := '';
      sMN := '';
      x := xfirstborn - MasterRadius;
      xw := MasterGridsToLoad;
      if x < xfirstbornMin then begin
        xw := xw - (xfirstbornMin - x);
        x :=  xfirstbornMin;
      end;
      if x + xw > xfirstbornMax + 1 then
        xw :=  xfirstbornMax - x + 1;

      x :=  ((x - xfirstbornMin) * 5) + 1;
      xw := (xw * 5);

      xMN := x + xGridsDiff;
      xwMN := xw - (xGridsDiff * 2);

      i := ymin;
      repeat
        sFB := sFB + Copy(sGrids, x + i, xw);
        if (i >= yMNmin) and (i <= yMNmax) then
          sMN := sMN + Copy(sGrids, xMN + i, xwMN);
        i := i + xWidth;
      until i > ymax;

      sFB := StringReplace(sFB, '0000;', '', [rfReplaceAll]);
      sMN := StringReplace(sMN, '0000;', '', [rfReplaceAll]);

      // CellsToID, not using function for speed
      ThisID := ((xfirstborn + 128) * 256) + (yfirstborn + 128);

      if Length(sFB) > 0 then begin
        i := slFirstbornsFormList.IndexOf(sFB);
        if i = -1 then begin
          slFirstbornsFormList.AddObject(sFB, ThisID);
          ID := IntToStr(ThisID);
        end
        else
          ID := IntToStr(slFirstbornsFormList.Objects[i]);
        CreateFirstborn(xfirstborn, yfirstborn);
        // Set MyFirstbornList string on this Firstborn
        olFirstbornsIndex.AddObject('f' + ID, olFirstborns.Objects[olFirstborns.IndexOf(ThisID)]);
      end;

      if Length(sMN) > 0 then begin
        i := slMinionsFormList.IndexOf(sMN);
        if i = -1 then begin
          slMinionsFormList.AddObject(sMN, ThisID);
          ID := IntToStr(ThisID);
        end
        else
          ID := intToStr(slMinionsFormList.Objects[i]);
        // Set MyMinionList string on this Firstborn
        olMinionsIndex.AddObject('m' + ID, olFirstborns.Objects[olFirstborns.IndexOf(ThisID)]);
      end;

      inc(count);
      if count mod interval = 0 then
        MyMessage('    ' + IntToStr(count + 1) + ' of ' + IntToStr(total) + ' cells done');
      inc(xfirstborn);
    end;
    inc(yfirstborn);      
  end;

end;

//============================================================================
// process DynDOLOD_manual_refr.txt
procedure DoManualDynDOLODREFR;
var
  Element, TempElement, BaseElement, BaseElementOrg: IInterface;
  i, idx: integer;
  slImport, sl, sl2: TStringList;
  FormID, STATFid: cardinal;
  s: string;
begin

  sThisWorld := sWorld;

  slImport := TStringList.Create;
  slImport := LoadStringList('manual_refr_' + sWorld);

  if slImport.Count > 0 then begin
    MyMessage('Add ' + IntToStr(slImport.Count) + ' manual references for DynDOLOD');

    sl := TStringList.Create;
    sl.Delimiter := ',';
    sl.StrictDelimiter := True;

    sl2 := TStringList.Create;
    sl2.Delimiter := ',';
    sl2.StrictDelimiter := True;

    for i := 0 to Pred(slImport.Count) do begin
      sl.CommaText := StringReplace(slImport[i], ', ', ',', [rfReplaceAll]);
      //   0 = EDID
      //   1 = element file
      //   2 = element form id
      //   3 = initially enabled
      //   4 = baseelement file
      //   5 = baseelement form id
      //   6 = pos x
      //   7 = pos y
      //   8 = pos z
      //   9 = scale
      //  10 = rot x
      //  11 = rot y
      //  12 = rot z

      // org REFR for enable parent
      FormID := FileNameToLoadOrder(sl[1], StrToInt64('$' + sl[2]));
      Element := RecordByLoadOrderFormID(FormID);

      // the STAT to point to
      STATFid := FileNameToLoadOrder(sl[4], StrToInt64('$' + sl[5]));
      BaseElementOrg := RecordByLoadOrderFormID(STATFid);
      if olBaseElementDynDOLOD.IndexOfObject(STATFid) <> -1 then
        BaseElement := RecordByLoadOrderFormID(StrToInt64(olBaseElementDynDOLOD[olBaseElementDynDOLOD.IndexOfObject(STATFid)]))
      else begin
        MyMessage('  no DynDOLOD for ' + sl[0] + IntToHex(STATFid, 8) + '. Add a rule');
        Continue;
      end;

      s := RemoveNonAlphaNum(sl[0]) + '_DynDOLOD_Manual';
      if olEDIDExists.IndexOf(s) <> -1 then
        // this reference was already added
        TempElement := RecordByLoadOrderFormID(olEDIDExists.Objects[olEDIDExists.IndexOf(s)])
      else
        // add a new element to the persistant cell
        TempElement := Add(CopyOverrideToFile(GetCellFromWorldspace(LODGenWorld, 0, 0, True), NEWFILE), 'REFR', True);
 
      // play with yourself if there is no org reference
      if not Assigned(Element) then
        Element := TempElement;

      if StrToFloat(sl[9]) <> 1 then
        SetElementNativeValues(TempElement, 'XSCL', StrToFloat(sl[9]));
      SetElementNativeValues(TempElement, 'DATA\Position\X', StrToFloat(sl[6]));
      SetElementNativeValues(TempElement, 'DATA\Position\Y', StrToFloat(sl[7]));
      SetElementNativeValues(TempElement, 'DATA\Position\Z', StrToFloat(sl[8]));
      SetElementNativeValues(TempElement, 'DATA\Rotation\X', StrToFloat(sl[10]));
      SetElementNativeValues(TempElement, 'DATA\Rotation\Y', StrToFloat(sl[11]));
      SetElementNativeValues(TempElement, 'DATA\Rotation\Z', StrToFloat(sl[12]));

      // persistant, initially disabled, neverfade, norespawn
      SetElementNativeValues(TempElement, 'Record Header\Record Flags', $40010C00);
      SetElementNativeValues(TempElement, 'EDID', s);
      SetElementNativeValues(TempElement, 'NAME', STATFID);

      AddDynDOLODREFERValues(Element, TempElement, BaseElement, BaseElementOrg);

      idx := olJsonObjectData.IndexOfObject(GetLoadOrderFormID(TempElement));
      sl2.CommaText := olJsonObjectData[idx];

      // isInitiallyEnabled
      if Lowercase(sl[3]) = 'True' then
        sl2[2] := '1'
      else
        sl2[2] := '';

      // isSwitch
      sl2[3] := '1';

      olJsonObjectData[idx] := '"' + sl2[0] + '","' + sl2[1] + '","' + sl2[2] + '","' + sl2[3] + '","' + sl2[4] + '","' + sl2[5] + '","' + sl2[6] + '","' + sl2[7] + '","' + sl2[8] +'"';

      // for json list
      olSignatureFormID.AddObject(Signature(BaseRecord(TempElement)), GetLoadOrderFormID(TempElement));
    end;

    // try to tell papyrus scripts about update
    if GetLoadOrderFormID(TempElement) <> 0 then
      SetElementNativeValues(RecordByLoadOrderFormID(olEDIDFormID.Objects[olEDIDFormID.IndexOf('MyLastObjectList')]), 'FormIDs\LNAM - FormID', GetLoadOrderFormID(TempElement));

    sl2.Free;
    sl.Free;
  end;
  slImport.Free;
end;

//============================================================================
// process disable list slREFRToDisable
procedure DoDisables;
var
  TempElement: IInterface;
  i: integer;
begin
  if slREFRToDisable.Count > 0 then begin
    MyMessage('Disabling ' + IntToStr(slREFRToDisable.Count) + ' REFRs');
    for i := 0 to Pred(slREFRToDisable.Count) do begin
      // disable and set opposite player and below ground
      TempElement := CopyOverrideToFile(RecordByLoadOrderFormID(slREFRToDisable[i]), NEWFile);
      SetIsInitiallyDisabled(TempElement, True);
      if not ElementExists(TempElement, 'XESP') then
        Add(TempElement, 'XESP', True);
      SetElementNativeValues(TempElement, 'XESP\Reference', 20);
      SetElementNativeValues(TempElement, 'XESP\Flags', 1);
      if not GetIsPersistent(TempElement) then
        SetElementNativeValues(TempElement, 'DATA\Position\Z', -30000);
    end;
  end;
end;

//============================================================================
// process olREFRDynDOLOD
procedure DoDynDOLOD;
var
  Element, TempElement, BaseElement, BaseElementOrg: IInterface;
  i, interval: integer;
begin
  if olREFRDynDOLOD.Count > 0 then begin
    MyMessage('Add ' + IntToStr(olREFRDynDOLOD.Count) + ' references for DynDOLOD');

    if olREFRDynDOLOD.Count < 100 then
      interval := olREFRDynDOLOD.Count
    else
      interval := olREFRDynDOLOD.Count / 10;

    for i := 0 to Pred(olREFRDynDOLOD.Count) do begin

      if i mod interval = 0 then
        MyMessage('    ' + IntToStr(i + 1) + ' of ' + IntToStr(olREFRDynDOLOD.Count) + ' done');

      // the original REFR
      Element := WinningOverride(RecordByLoadOrderFormID(olREFRDynDOLOD.Objects[i]));

      // this is disabled for good, do nothing
      if ElementExists(Element, 'XESP') then
        if (GetElementNativeValues(Element, 'XESP\Reference') = 20) and (GetElementNativeValues(Element, 'XESP\Flags') and 1 = 1) then
          Continue;

      // BaseRecord() not working every time, using LinksTo() instead 
      //BaseElementOrg := WinningOverride(BaseRecord(Element));
      BaseElementOrg := WinningOverride(LinksTo(ElementByPath(Element, 'NAME')));
      // the new base element
      BaseElement := RecordByLoadOrderFormID(StrToInt64(olREFRDynDOLOD[i]));

      // this should not happen here
      if GetElementNativeValues(Element, 'Record Header\Record Flags') and $10000 = $10000 then begin
        MyMessage('neverfade happened ' + Name(Element));
      end;

      TempElement := AddDynDOLODREFRElement(Element, BaseElement, BaseElementOrg, 'LOD');

      //MyMessage('  ' + Name(TempElement));
    end;

    // try to tell papyrus scripts about update
    if GetLoadOrderFormID(TempElement) <> 0 then
      SetElementNativeValues(RecordByLoadOrderFormID(olEDIDFormID.Objects[olEDIDFormID.IndexOf('MyLastObjectList')]), 'FormIDs\LNAM - FormID', GetLoadOrderFormID(TempElement));
  end;
end;

//============================================================================
// Sort and Save formlists
// 0 isDistantFade
// 1 isFadeOut
// 2 IsInitiallyEnabled
// 3 IsSwitch
// 4 IsOppositeParent
// 5 MyEnableParentFormID
// 6 MyEnableParentString
// 7 MyStageIndex
// 8 MyStageType
procedure SaveFormlists1;
var
  i, MaxList, interval, count, FormID: integer;
  slDataFileFormlists: TwbFastStringList;
  s: string;
  isPapyrusUtil: boolean;
  Element: IInterface;
begin

  isPapyrusUtil := True;

  slDataFileFormlists := TwbFastStringList.Create;
  slDataFileFormlists.Add('{');

  if isPapyrusUtil then
    slDataFileFormlists.Add('"stringList":{');

  MaxList := Pred(olJsonObjectData.Count);
  MyMessage('Adding LOD objects data with ' + IntToStr(MaxList) + ' entries');
  for i := 0 to MaxList do begin
    FormID := olJsonObjectData.Objects[i];
    s:= '"'+ IntToStr(FormID and $00FFFFFF) + '":[' + olJsonObjectData[i];
    if i <> MaxList then
      s := s + '],'
    else
      s := s + ']';
    slDataFileFormlists.Add(s);
  end;

//  if isPapyrusUtil then
//    slDataFileFormlists.Add('},');
//
//  if isPapyrusUtil then
//    slDataFileFormlists.Add('"floatList":{');
//
//  MaxList := Pred(olSignatureFormID.Count);
//  MyMessage('  Adding position index with ' + IntToStr(MaxList) + ' entries');
//  for i := 0 to MaxList do begin
//    FormID := olSignatureFormID.Objects[i];
//    Element := RecordByLoadOrderFormID(FormID);
//    s := '"' + IntToStr(FormID and $00FFFFFF) + '":[';
//    if ElementExists(Element, 'XSCL') then
//      s := s + GetElementEditValues(Element, 'XSCL') + ','
//    else
//      s := s + '1.0,';
//    s := s + GetElementEditValues(Element, 'DATA\Position\X') + ',';
//    s := s + GetElementEditValues(Element, 'DATA\Position\Y') + ',';
//    s := s + GetElementEditValues(Element, 'DATA\Position\Z') + ',';
//    s := s + GetElementEditValues(Element, 'DATA\Rotation\X') + ',';
//    s := s + GetElementEditValues(Element, 'DATA\Rotation\Y') + ',';
//    s := s + GetElementEditValues(Element, 'DATA\Rotation\Z') + ']';
//    if i <> MaxList then
//      s := s + ',';
//  slDataFileFormlists.Add(s);
//  end;
//
  if isPapyrusUtil then
    slDataFileFormlists.Add('}');

  slDataFileFormlists.Add('}');
  slDataFileFormlists.SaveToFile(sSKSEPluginPath + 'DynDOLOD_' + sWorld + '_Objects.json');

  MyMessage('  Saved ' + sSKSEPluginPath + 'DynDOLOD_' + sWorld + '_Objects.json');

  slDataFileFormlists.Free;

end;

//============================================================================
// Sort and Save formlists
procedure SaveFormlists2;
var
  i, j, MaxList, MaxIDs, interval, count, idx, FormID: integer;
  slIDs, slDataFileFormlists: TwbFastStringList;
  s, ID: string;
  isPapyrusUtil: boolean;
begin

  MyMessage('Sorting Formlists');

  isPapyrusUtil := True;

  slIDs := TwbFastStringList.Create;
  slIDs.Delimiter := ';';
  slIDs.StrictDelimiter := True;
  slIDs.Sorted := True;

  slDataFileFormlists := TwbFastStringList.Create;
  slDataFileFormlists.Add('{');

  if isPapyrusUtil then
    slDataFileFormlists.Add('"stringList":{');

  MaxList := Pred(olFirstbornsIndex.Count);
  MyMessage('  Adding Grids index with ' + IntToStr(MaxList) + ' entries');
  for i := 0 to MaxList do begin
    FormID := olFirstbornsIndex.Objects[i];
    s:= '"'+ IntToStr(FormID and $00FFFFFF) + '":["' + olFirstbornsIndex[i] + '"';
    idx := olMinionsIndex.IndexOfObject(FormID);
    if idx <> -1 then
      s := s + ',"' + olMinionsIndex[idx] + '"';
    //else
    //  s := s + ',"none"';
    if i <> MaxList then
      s := s + '],'
    else
      s := s + ']';
    slDataFileFormlists.Add(s);
  end;

  if isPapyrusUtil then
    slDataFileFormlists.Add('},');

  if isPapyrusUtil then
    slDataFileFormlists.Add('"intList":{');

  FormID := olEDIDExists.Objects[olEDIDExists.IndexOf(sWorld + '_ShesonObject')];
  slDataFileFormlists.Add('"master":[' + IntToStr(FormID and $00FFFFFF) + '],');

  MaxList := Pred(olBrotherIndex.Count);
  if MaxList > 0 then begin
    MyMessage('  Adding child world Grids list with ' + IntToStr(MaxList) + ' entries');
    for i := 0 to MaxList do begin
      FormID := olBrotherIndex.Objects[i];
      slDataFileFormlists.Add('"' + IntToStr(FormID and $00FFFFFF) + '":[' + olBrotherIndex[i] + '],');
    end;
  end;

  MyMessage('  Adding ' + IntToStr(slFirstbornsFormList.Count) + ' FarGrids lists');

  if slFirstbornsFormList.Count < 100 then
    interval := slFirstbornsFormList.Count
  else
    interval := slFirstbornsFormList.Count / 10;

  for i := 0 to Pred(slFirstbornsFormList.Count) do begin

    if (i + 1) mod interval = 0 then
      MyMessage('    ' + IntToStr(i + 1) + ' of ' + IntToStr(slFirstbornsFormList.Count) + ' lists done');

    // the id of the neighbours formlist
    ID := IntToStr(slFirstbornsFormList.Objects[i]);

    // neighbours formlist
    s := Lowercase('"f' + ID + '":[');
    slIDs.DelimitedText := slFirstbornsFormList[i];
    MaxIDs :=Pred(slIDs.Count);
    // skip the first one
    for j := 1 to MaxIDs do begin
      FormID := olFirstborns.Objects[olFirstborns.IndexOf(StrToInt64('$' + slIDs[j]))];
      if isPapyrusUtil then begin
        if j <> MaxIDs then
          s := s + IntToStr(FormID and $00FFFFFF) + ','
        else
          s := s + IntToStr(FormID and $00FFFFFF);
      end
      else begin
        if j = MaxIDs then
          s := s + '"__formData|' + sDynDOLODFileName + '|' + IntToHex((FormID and $00FFFFFF), 6) + '"'
        else
          s := s + '"__formData|' + sDynDOLODFileName + '|' + IntToHex((FormID and $00FFFFFF), 6) + '",';
      end;
    end;
    s := s + '],';
    slDataFileFormlists.Add(s);
  end;

  // Wow someone really looking at the code?

  MyMessage('  Adding ' + IntToStr(slMinionsFormList.Count) + ' NearGrids lists');

  if slMinionsFormList.Count < 100 then
    interval := slMinionsFormList.Count
  else
    interval := slMinionsFormList.Count / 10;

  MaxList := Pred(slMinionsFormList.Count);
  for i := 0 to MaxList do begin

    if (i + 1) mod interval = 0 then
      MyMessage('    ' + IntToStr(i + 1) + ' of ' + IntToStr(slMinionsFormList.Count) + ' lists done');

    // the id of the neighbours formlist
    ID := IntToStr(slMinionsFormList.Objects[i]);

    // neighbours formlist
    s := Lowercase('"m' + ID + '":[');
    slIDs.DelimitedText := slMinionsFormList[i];
    MaxIDs :=Pred(slIDs.Count);
    // skip the first one
    for j := 1 to MaxIDs do begin
      FormID := olMinions.Objects[olMinions.IndexOf(StrToInt64('$' + slIDs[j]))];
      if isPapyrusUtil then begin
        if j <> MaxIDs then
          s := s + IntToStr(FormID and $00FFFFFF) + ','
        else
          s := s + IntToStr(FormID and $00FFFFFF);
      end
      else begin
        if j = MaxIDs then
          s := s + '"__formData|' + sDynDOLODFileName + '|' + IntToHex((FormID and $00FFFFFF), 6) + '"'
        else
          s := s + '"__formData|' + sDynDOLODFileName + '|' + IntToHex((FormID and $00FFFFFF), 6) + '",';
      end;
    end;
    if i <> MaxList then
      s := s + '],'
    else
      s := s + ']';
    slDataFileFormlists.Add(s);
  end;

  if isPapyrusUtil then
    slDataFileFormlists.Add('}');
  slDataFileFormlists.Add('}');
  slDataFileFormlists.SaveToFile(sSKSEPluginPath + 'DynDOLOD_' + sWorld + '.json');

  MyMessage('  Saved ' + sSKSEPluginPath + 'DynDOLOD_' + sWorld + '.json');

  slIDs.Free;
  slDataFileFormlists.Free;

end;

procedure SaveFormlists3;
var
  i, j, MaxList, MaxIDs, interval, count: integer;
  group, Element: IInterface;
  sl, slDataFileFormlists: TwbFastStringList;
  s: string;
  isPapyrusUtil: boolean;
begin
  isPapyrusUtil := True;

  sl := TwbFastStringList.Create;

  slDataFileFormlists := TwbFastStringList.Create;
  slDataFileFormlists.Add('{');

  if isPapyrusUtil then
    slDataFileFormlists.Add('"string":{');

  for i := 0 to Pred(FileCount) do begin
    if not SameText(GetFileName(FileByIndex(i)), sDynDOLODFileName) then
      Continue;
    // go through each wrld to find child worlds
    group := GroupBySignature(FileByIndex(i), 'WRLD');
    for j := 0 to Pred(ElementCount(group)) do begin
      Element := ElementByIndex(group, j);
      if ElementType(Element) <> etMainRecord then
        Continue;
      s := '"' + LowerCase(EditorID(Element)) + '":"';
      if ElementExists(Element, 'Parent\WNAM') and (GetElementNativeValues(Element, 'Parent\PNAM\Flags') and $2 = $2) then
        s := s + EditorID(LinksTo(ElementByPath(Element, 'Parent\WNAM')))
      else
        s := s + EditorID(Element);
      sl.Add(s + '"');
    end;
  end;

  MaxList := sl.Count;
  MyMessage('  Adding world index with ' + IntToStr(MaxList) + ' entries');
  for i := 0 to Pred(MaxList) do begin
    if i <> Pred(MaxList) then
      slDataFileFormlists.Add(sl[i] + ',')
    else
      slDataFileFormlists.Add(sl[i]);
  end;

  if isPapyrusUtil then
    slDataFileFormlists.Add('}');
  slDataFileFormlists.Add('}');
  slDataFileFormlists.SaveToFile(sSKSEPluginPath + 'DynDOLOD_Worlds.json');

  MyMessage('  Saved ' + sSKSEPluginPath + 'DynDOLOD_Worlds.json');

  sl.Free;
end;

//============================================================================
procedure CreateFirstbornTown(xfirstborn, yfirstborn : int64);
var
  TempElement, Element : IInterface;
  idx, FormID: integer;
  s: string;
begin

  // nothing to copy, eh
  idx := olFirstborns.IndexOf(CellsToID(xfirstborn, yfirstborn));
  if idx = -1 then
    Exit;

  s := RemoveNonAlphaNum(StringReplace(sThisWorld + '_Firstborn_' + CellsToString(xfirstborn, yfirstborn), '-', '%', [rfReplaceAll]));

  // firstborn already exists
  if olEDIDExists.IndexOf(s) <> -1 then begin
    TempElement := RecordByLoadOrderFormID(olEDIDExists.Objects[olEDIDExists.IndexOf(s)]);
    // for json list
    olSignatureFormID.AddObject(Signature(BaseRecord(TempElement)), GetLoadOrderFormID(TempElement));
    Exit;
  end;

  // Firstborn to copy from world to town
  Element := RecordByLoadOrderFormID(olFirstborns.Objects[idx]);
  // the cell to copy to
  TempElement := Add(CopyOverrideToFile(GetCellFromWorldspace(LODGenWorld, 0, 0, True), NEWFILE), 'REFR', True);
  SetElementNativeValues(TempElement, 'Record Header\Record Flags', $40000600);
  SetElementNativeValues(TempElement, 'EDID', s);
  SetElementNativeValues(TempElement, 'NAME', olEDIDExists.Objects[olEDIDExists.IndexOf(sWorld + '_FirstbornActivator')]);
  SetElementNativeValues(TempElement, 'DATA\Position\X', CellToPos(xfirstborn));
  SetElementNativeValues(TempElement, 'DATA\Position\Y', CellToPos(yfirstborn));
  SetElementNativeValues(TempElement, 'DATA\Position\Z', 16384);
  // because of reasons
  SetElementNativeValues(TempElement, 'DATA\Rotation\Z', 90);
  wbCopyElementToRecord(ElementByPath(Element, 'XPRM'), TempElement, True, True);
  // copy lists ID from Firstborn in the real world
  s := IntToStr(GetLoadOrderFormID(Element) and $00FFFFFF);
  idx := olMinions.IndexOf(CellsToID(xfirstborn, yfirstborn));
  if idx <> -1 then begin
    FormID := olMinions.Objects[idx];
    s := s + ',' + IntToStr(FormID and $00FFFFFF);
  end;
  olBrotherIndex.AddObject(s, GetLoadOrderFormID(TempElement));
  olFirstbornsIndex.AddObject(olFirstbornsIndex[olFirstbornsIndex.IndexOfObject(GetLoadOrderFormID(Element))], GetLoadOrderFormID(TempElement));
  idx := olMinionsIndex.IndexOfObject(GetLoadOrderFormID(Element));
  if idx <> -1 then
    olMinionsIndex.AddObject(olMinionsIndex[idx], GetLoadOrderFormID(TempElement));
  // for json list
  olSignatureFormID.AddObject(Signature(BaseRecord(TempElement)), GetLoadOrderFormID(TempElement));
end;

//============================================================================
procedure AddFirstbornsTown;
var
  i, j, x, y, xmin, ymin, xmax, ymax: integer;
  sEdid: string;
  Element, group: IInterface;
begin
  if slTownsWithFirstborns.Count > 0 then begin
    MyMessage('Adding ' + IntToStr(slTownsWithFirstborns.Count) + ' child worlds using ' + sWorld + ' for LOD');
    for i:= 0 to Pred(slTownsWithFirstborns.Count) do begin
      sThisWorld := slTownsWithFirstborns[i];
      if sThisWorld = sWorld then
        Continue;
      MyMessage('  ' + sThisWORLD);
      Element := RecordByLoadOrderFormID(olEDIDFormID.Objects[olEDIDFormID.IndexOf(sThisWorld)]);
      xmin := GetElementNativeValues(Element, 'Object Bounds\NAM0\X');
      ymin := GetElementNativeValues(Element, 'Object Bounds\NAM0\Y');
      xmax := GetElementNativeValues(Element, 'Object Bounds\NAM9\X');
      ymax := GetElementNativeValues(Element, 'Object Bounds\NAM9\Y');
      for x := xmin to xmax do begin
        for y := ymin to ymax do begin
          CreateFirstbornTown(x, y);
        end;
      end;
    end;
  end;
end;

end.

