Compare commits
4 Commits
dfde75ef4e
...
fae731a9dd
Author | SHA1 | Date | |
---|---|---|---|
fae731a9dd | |||
ce2345d7aa | |||
76af8d0a4a | |||
5d02a8a4b6 |
|
@ -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…
Reference in New Issue
Block a user