commit
64aecebcba
1 changed files with 233 additions and 0 deletions
@ -0,0 +1,233 @@ |
||||
{ |
||||
ESMify Plugins 1.0 |
||||
by Eddoursul https://eddoursul.win |
||||
|
||||
For proper overriding of temporary records, "Always save ONAM" must be enabled in xEdit. |
||||
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. |
||||
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. |
||||
} |
||||
|
||||
unit ESMify_Plugins; |
||||
|
||||
var |
||||
refsChecked, recordsCounted, flaggedCount, persLocSkipped: integer; |
||||
pluginAnnounced: boolean; |
||||
|
||||
function IsReferencedByNonLocation(rec: IwbMainRecord): boolean; |
||||
var |
||||
masterRecord, referencingRecord: IwbMainRecord; |
||||
i: integer; |
||||
sig: string; |
||||
begin |
||||
Result := False; |
||||
masterRecord := MasterOrSelf(rec); |
||||
|
||||
for i := 0 to Pred(ReferencedByCount(masterRecord)) do begin |
||||
referencingRecord := ReferencedByIndex(masterRecord, i); |
||||
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. |
||||
|
||||
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 Equals(LinksTo(ElementByPath(rec, 'Cell')), LinksTo(ElementByPath(referencingRecord, 'Cell'))) then begin |
||||
continue; |
||||
end; |
||||
end; |
||||
end; |
||||
|
||||
// Refs referencing themselves do not require the flag |
||||
if not Equals(rec, referencingRecord) then begin |
||||
result := True; |
||||
break; |
||||
end; |
||||
|
||||
end; |
||||
end; |
||||
end; |
||||
|
||||
function MarkPersistent(e: IInterface): boolean; |
||||
begin |
||||
AddMessage(' + Marking as persistent: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')'); |
||||
Inc(flaggedCount); |
||||
SetIsPersistent(e, True); |
||||
CheckNonPersistentOverride(e); |
||||
end; |
||||
|
||||
function CheckNonPersistentOverride(e: IInterface): integer; |
||||
begin |
||||
if IsWinningOverride(e) then exit; |
||||
if not GetIsPersistent(WinningOverride(e)) then |
||||
AddMessage(' ! WARNING: the highest override of ' + ShortName(e) + ' is not persistent: ' + PathName(WinningOverride(e))); |
||||
end; |
||||
|
||||
function Process(e: IInterface): integer; |
||||
var |
||||
currentPlugin: IwbFile; |
||||
baseRefRecord, package, refCell, actorLocation: IwbMainRecord; |
||||
i, baseID, packageCount: integer; |
||||
sig, baseSig, packageLoc: string; |
||||
isREFR, isACHR: boolean; |
||||
begin |
||||
if not pluginAnnounced then begin |
||||
currentPlugin := GetFile(e); |
||||
AddMessage(#13#10 + '* Processing ' + Name(currentPlugin)); |
||||
pluginAnnounced := true; |
||||
refsChecked := 0; |
||||
recordsCounted := 0; |
||||
flaggedCount := 0; |
||||
persLocSkipped := 0; |
||||
if not GetIsESM(currentPlugin) then |
||||
setIsESM(currentPlugin, true); |
||||
end; |
||||
|
||||
if (GetLoadOrderFormID(e) = 0) then begin |
||||
AddMessage(#13#10 + ' ' + IntToStr(recordsCounted) + ' records scanned.'); |
||||
AddMessage(' ' + IntToStr(refsChecked) + ' references checked.'); |
||||
AddMessage(' ' + IntToStr(persLocSkipped) + ' actors with Persistent Location skipped.'); |
||||
AddMessage(' ' + IntToStr(flaggedCount) + ' references have been marked as persistent.' + #13#10); |
||||
pluginAnnounced := false; |
||||
exit; |
||||
end; |
||||
|
||||
Inc(recordsCounted); |
||||
|
||||
sig := Signature(e); |
||||
isREFR := (sig = 'REFR'); |
||||
isACHR := (sig = 'ACHR'); |
||||
|
||||
if (not isREFR) and (not isACHR) and (sig <> 'PHZD') then |
||||
exit; |
||||
|
||||
Inc(refsChecked); |
||||
|
||||
if GetIsDeleted(e) then begin |
||||
if GetIsPersistent(e) then begin |
||||
AddMessage(' Removing persistence flag from deleted record: ' + ShortName(e)); |
||||
SetIsPersistent(e, False); |
||||
end; |
||||
exit; |
||||
end; |
||||
|
||||
baseRefRecord := BaseRecord(e); |
||||
|
||||
if not Assigned(baseRefRecord) then begin |
||||
AddMessage(' INVALID RECORD: missing base form: ' + FullPath(e)); |
||||
exit; |
||||
end; |
||||
|
||||
if GetIsPersistent(e) then begin |
||||
CheckNonPersistentOverride(e); |
||||
exit; |
||||
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; |
||||
|
||||
baseSig := Signature(baseRefRecord); |
||||
if (baseSig = 'TXST') or ((baseSig = 'ACTI') and ElementExists(baseRefRecord, 'WNAM')) then begin |
||||
MarkPersistent(e); |
||||
exit; |
||||
end; |
||||
end; |
||||
|
||||
// Flag all records with non-locational references |
||||
if IsReferencedByNonLocation(e) then begin |
||||
MarkPersistent(e); |
||||
exit; |
||||
end; |
||||
|
||||
if not isACHR then |
||||
exit; |
||||
|
||||
// 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')); |
||||
if ReferencedByCount(actorLocation) > 1 then begin |
||||
Inc(persLocSkipped); |
||||
//AddMessage(' Skipping actor with Persistent Location: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')'); |
||||
exit; |
||||
end; |
||||
end; |
||||
|
||||
// Skip non-persistent, non-unique actors. Multi-package bandits will probably be late on their schedules. |
||||
if GetElementNativeValues(baseRefRecord, 'ACBS\Flags\Unique') = 0 then |
||||
exit; |
||||
|
||||
// Skip Starts Dead NPCs |
||||
if GetElementNativeValues(e, 'Record Header\Record Flags\Starts Dead') <> 0 then |
||||
exit; |
||||
|
||||
// Skip simple actors (chicken and such) |
||||
if GetElementNativeValues(baseRefRecord, 'ACBS\Flags\Simple Actor') <> 0 then |
||||
exit; |
||||
|
||||
packageCount := ElementCount(ElementByPath(baseRefRecord, 'Packages')); |
||||
|
||||
// Skip unique actors without packages (probably, flagged erroneously) |
||||
if packageCount = 0 then |
||||
exit; |
||||
|
||||
if packageCount = 1 then begin |
||||
|
||||
// Skip actors, having a single package revolving around their editor location or themselves |
||||
package := LinksTo(ElementByIndex(ElementByPath(baseRefRecord, 'Packages'), 0)); |
||||
|
||||
if not Assigned(package) then |
||||
exit; |
||||
|
||||
packageLoc := GetElementEditValues(ElementByIndex(ElementByPath(package, 'Package Data\Data Input Values'), 0), 'PLDT\Type'); |
||||
|
||||
if (Pos(packageLoc, 'Near editor location|Near self') <> 0) then begin |
||||
AddMessage(' Skipping editor location actor: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')'); |
||||
exit; |
||||
end; |
||||
|
||||
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 Equals(refCell, LinksTo(ElementByPath(e, 'Cell'))) then begin |
||||
AddMessage(' Skipping actor, staying in one cell: ' + GetElementEditValues(e, 'NAME') + ' - (' + Name(e) + ')'); |
||||
exit; |
||||
end; |
||||
end; |
||||
end; |
||||
|
||||
end; |
||||
|
||||
MarkPersistent(e); |
||||
|
||||
end; |
||||
|
||||
function Finalize: integer; |
||||
begin |
||||
AddMessage('All done.'); |
||||
end; |
||||
|
||||
end. |
Loading…
Reference in new issue