Compare commits

...

4 Commits

  1. 208
      ESMify_Plugins.pas

@ -6,7 +6,7 @@
This script does not process bi-directional location references. Resave plugin in CK to rebuild them.
Running it on a plugin will set the ESM flag as well. Running it on a node will only attempt to fix broken persistence.
Normally, NPCs must have assigned Persistent Location. If they don't, their packages will break after conversion to ESM.
Normally, NPCs must have assigned Persistent Location. If they don't, their packages may break after conversion to ESM.
Persistent Location can't be reliably set by a script, so instead we scan NPC attributes and packages, try to determine
if they potentially need persistence, and set the global Persistent flag. This is a band-aid! To fix them properly,
assign valid locations to actors via the Persistent Location field.
@ -17,6 +17,8 @@ unit ESMify_Plugins;
var
refsChecked, recordsCounted, flaggedCount, persLocSkipped: integer;
pluginShown: boolean;
hardcodedStatForms: TStringList;
dragonCZMarker, dragonLZMarker: IwbMainRecord;
function IsReferencedByNonLocation(rec: IwbMainRecord): boolean;
var
@ -32,22 +34,17 @@ begin
sig := Signature(referencingRecord);
// Locational links are bi-directional, this script does not process them. To fix them, resave plugin in CK.
// WRLD records may refer to REFRs if they are large references. This does not imply necessity of the persistence flag.
// TES4 may refer to REFRs if they are listed in ONAM. To keep ONAM up to date, enable "Always save ONAM" in xEdit.
// WRLD records usually refer to large reference REFRs. Being a large reference does not imply necessity of the persistence flag.
// TES4 refers to REFRs if they are listed in ONAM. To keep ONAM up to date, enable "Always save ONAM" in xEdit.
if (sig = 'LCTN') or (sig = 'WRLD') or (sig = 'TES4') then
continue;
// Do not consider unused formlist a reference
if (sig = 'FLST') and (ReferencedByCount(referencingRecord) = 0) then
continue;
// Only check plugins higher in the load order.
// This will include non-masters as well, and will not take references from plugins lower in the load order into consideration.
if GetLoadOrder(GetFile(referencingRecord)) <= GetLoadOrder(GetFile(rec)) then begin
// When referencing record is not referenced and it's in the same cell, the ref does not need persistence.
// Does not cover long chains of linked refs.
if (ReferencedByCount(referencingRecord) = 0) then begin
if (sig = 'REFR') then begin
if InSameCell(rec, referencingRecord) then continue;
@ -109,24 +106,6 @@ begin
result := (coords.x = otherCoords.x) and (coords.y = otherCoords.y);
end;
function HasScripts(e: IwbMainRecord; targetScripts: string): boolean;
var
scripts, currentItem: IwbElement;
linkedCount, i: integer;
begin
if not ElementExists(e, 'VMAD') then exit;
scripts := ElementByPath(e, 'VMAD\Scripts');
for i := 0 to Pred(ElementCount(scripts)) do begin
currentItem := ElementByIndex(scripts, i);
if Pos(GetElementEditValues(currentItem, 'ScriptName'), targetScripts) <> 0 then begin
result := true;
break;
end;
end;
end;
function MarkPersistent(e: IwbMainRecord): boolean;
begin
AddMessage(' + Marking as persistent: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')');
@ -201,13 +180,87 @@ begin
end;
end;
function IsSameCellPackage(inputValues: IwbElement): boolean;
var
packageLoc: string;
inputVal: IwbElement;
refCell, linkedRefKeyword, linkedRef: IwbMainRecord;
i: integer;
begin
result := True;
for i := 0 to Pred(ElementCount(inputValues)) do begin
inputVal := ElementByIndex(inputValues, i);
packageLoc := GetElementEditValues(inputVal, 'PLDT\Type');
if packageLoc <> '' then begin
if (packageLoc = 'Near editor location') or (packageLoc = 'Near self') then begin
//AddMessage(' Skipping editor location actor: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')');
continue;
end
else if (packageLoc = 'In cell') then begin
refCell := LinksTo(ElementByPath(inputVal, 'PLDT\Cell'));
if Assigned(refCell) then begin
if SameRecord(refCell, LinksTo(ElementByPath(e, 'Cell'))) then continue;
end;
end
else if (packageLoc = 'Near linked reference') then begin
linkedRefKeyword := LinksTo(ElementByPath(inputVal, 'PLDT\Keyword'));
if not IsLinkedRefRemote(e, linkedRefKeyword) then continue;
end
else if (packageLoc = 'Near reference') then begin
if InSameCell(e, LinksTo(ElementByPath(inputVal, 'PLDT\Reference'))) then continue;
end;
end
else begin
packageLoc := GetElementEditValues(inputVal, 'PTDA\Target Data\Type');
if (packageLoc = 'Linked Reference') then begin
linkedRefKeyword := LinksTo(ElementByPath(inputVal, 'PTDA\Target Data\Reference'));
if not IsLinkedRefRemote(e, linkedRefKeyword) then continue;
end
else if (packageLoc = 'Specific Reference') then begin
linkedRef := LinksTo(ElementByPath(inputVal, 'PTDA\Target Data\Reference'));
if InSameCell(e, linkedRef) then continue;
end;
end;
result := False;
break;
end;
end;
function Initialize: integer;
var dobj: IwbMainRecord;
begin
AddMessage('ESMify Plugins is starting...');
hardcodedStatForms := TStringList.Create;
hardcodedStatForms.Add($5); // DivineMarker
hardcodedStatForms.Add($6); // TempleMarker
hardcodedStatForms.Add($10); // MapMarker
hardcodedStatForms.Add($12); // HorseMarker
hardcodedStatForms.Add($15); // MultiBoundMarker
hardcodedStatForms.Add($1F); // RoomMarker
hardcodedStatForms.Add($34); // XMarkerHeading
hardcodedStatForms.Add($3B); // XMarker
// DLZM and DCZM Default Objects.
// For simplicity sake, I assume they are unchanged, which is true in 99.999% cases.
// If you ever find a mod, changing these objects, update this to retrieve actual values.
hardcodedStatForms.Add($138C0); // DragonMarker
hardcodedStatForms.Add($3DF55); // DragonMarkerCrashStrip
end;
function Process(e: IInterface): integer;
var
currentPlugin: IwbFile;
baseRefRecord, package, refCell, actorLocation, linkedRef, linkedRefKeyword: IwbMainRecord;
baseRefRecord, package, actorLocation: IwbMainRecord;
packages: IwbElement;
i, baseID, packageCount, typeId: integer;
sig, baseSig, packageLoc, packageType: string;
i, j, baseID, packageCount, typeId: integer;
sig, baseSig: string;
isREFR, isACHR, skip: boolean;
begin
if not pluginShown then begin
@ -247,7 +300,7 @@ begin
baseRefRecord := BaseRecord(e);
if not Assigned(baseRefRecord) then begin
AddMessage(' INVALID RECORD: missing base form: ' + FullPath(e));
AddMessage(' INVALID RECORD: Missing base form: ' + FullPath(e));
exit;
end;
@ -257,18 +310,43 @@ begin
end;
if isREFR then begin
// Water, texture sets, and markers always get flagged by CK
baseID := FormID(baseRefRecord);
if (baseID = $3B) or (baseID = $4) or (baseID = $34) or (baseID = $1F) or (baseID = $15) or (baseID = $12) or (baseID = $10) or (baseID = $6) or (baseID = $5) or (baseID = $138C0) or (baseID = $3DF55) then begin
MarkPersistent(e);
exit;
end;
// Certain types of references are always flagged as persistent by the CK. See DavidJCobb's post:
// https://discord.com/channels/535508975626747927/535530099475480596/1129026688077013084
baseSig := Signature(baseRefRecord);
if (baseSig = 'TXST') or ((baseSig = 'ACTI') and ElementExists(baseRefRecord, 'WNAM')) then begin
if baseSig = 'TXST' then begin
// Flag texture sets
MarkPersistent(e);
exit;
end
else if baseSig = 'STAT' then begin
// Flag hardcoded static forms (markers)
if hardcodedStatForms.indexOf(FormID(baseRefRecord)) > -1 then begin
MarkPersistent(e);
exit;
end;
end
else if baseSig = 'ACTI' then begin
// Flag water activators
if ElementExists(baseRefRecord, 'WNAM')) then begin
MarkPersistent(e);
exit;
end;
end
else if baseSig = 'LIGH' then begin
// Flag Never Fades lights
if (GetElementNativeValues(e, 'Record Header\Record Flags\Never Fades') > 0) then begin
MarkPersistent(e);
exit;
end;
end
else if baseSig = 'DOOR' then begin
// Flag PrisonMarker refs and any doors with teleport data
if (FormID(baseRefRecord) = $4) or (ElementExists(e, 'XTEL')) then begin // PrisonMarker
MarkPersistent(e);
exit;
end;
end;
end;
@ -284,9 +362,16 @@ begin
// This NPC uses Persistent Location, should work fine if the location was assigned correctly
if ElementExists(e, 'XLCN') then begin
actorLocation := LinksTo(ElementByPath(e, 'XLCN'));
// CK flags refs with the PersistAll location
if FormID(actorLocation) = $216A7 then begin // PersistAll
MarkPersistent(e);
exit;
end;
if ReferencedByCount(actorLocation) > 1 then begin
Inc(persLocSkipped);
//AddMessage(' Skipping actor with Persistent Location: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')');
AddMessage(' Skipping actor with Persistent Location: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')');
exit;
end;
end;
@ -302,8 +387,8 @@ begin
if packageCount = 0 then
exit;
// Scan packages and try to determine if their range extends beyond the NPC's starting cell.
skip := true;
for i := 0 to Pred(packageCount) do begin
package := LinksTo(ElementByIndex(packages, i));
@ -312,48 +397,14 @@ begin
continue;
end;
packageLoc := GetElementEditValues(ElementByIndex(ElementByPath(package, 'Package Data\Data Input Values'), 0), 'PLDT\Type');
if packageLoc <> '' then begin
if (packageLoc = 'Near editor location') or (packageLoc = 'Near self') then begin
//AddMessage(' Skipping editor location actor: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')');
continue;
end
else if (packageLoc = 'In cell') then begin
refCell := LinksTo(ElementByPath(ElementByIndex(ElementByPath(package, 'Package Data\Data Input Values'), 0), 'PLDT\Cell'));
if Assigned(refCell) then begin
if SameRecord(refCell, LinksTo(ElementByPath(e, 'Cell'))) then continue;
end;
end
else if (packageLoc = 'Near linked reference') then begin
linkedRefKeyword := LinksTo(ElementByPath(ElementByIndex(ElementByPath(package, 'Package Data\Data Input Values'), 0), 'PLDT\Keyword'));
if not IsLinkedRefRemote(e, linkedRefKeyword) then continue;
end
else if (packageLoc = 'Near reference') then begin
if InSameCell(e, LinksTo(ElementByPath(ElementByIndex(ElementByPath(package, 'Package Data\Data Input Values'), 0), 'PLDT\Reference'))) then continue;
end;
end
else begin
packageLoc := GetElementEditValues(ElementByIndex(ElementByPath(package, 'Package Data\Data Input Values'), 0), 'PTDA\Target Data\Type');
if (packageLoc = 'Linked Reference') then begin
linkedRefKeyword := LinksTo(ElementByPath(ElementByIndex(ElementByPath(package, 'Package Data\Data Input Values'), 0), 'PTDA\Target Data\Reference'));
if not IsLinkedRefRemote(e, linkedRefKeyword) then continue;
end
else if (packageLoc = 'Specific Reference') then begin
linkedRef := LinksTo(ElementByPath(ElementByIndex(ElementByPath(package, 'Package Data\Data Input Values'), 0), 'PTDA\Target Data\Reference'));
if InSameCell(e, linkedRef) then continue;
end;
end;
if (IsSameCellPackage(ElementByPath(package, 'Package Data\Data Input Values'))) then continue;
skip := false;
break;
end;
if skip then begin
//AddMessage(' Skipping actor, staying in one cell: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')');
AddMessage(' Skipping actor, staying in the same cell: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')');
exit;
end;
@ -363,6 +414,7 @@ end;
function Finalize: integer;
begin
hardcodedStatForms.Clear;
AddMessage('All done.');
end;

Loading…
Cancel
Save