Last active
June 5, 2018 17:41
-
-
Save cehoffman/4750614 to your computer and use it in GitHub Desktop.
Skyrim Redone ReProccer Fixes for TES5Edit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
Fix Armor BODT fields erroneously added from ReProccer | |
Fix keywords and fields lost from USKP due to mods between USKP and ReProccer | |
not including USKP fixes. | |
Fix infiltration keywords that are missing on obviously Bandit, Thalmor, | |
Stormcloak, Imperial, Forsworn. Also remove infiltration keywords from cloaks, | |
because it doesn't make sense for you to infiltrate wearing a cloak instead | |
of a chest piece | |
Fix Guard Dialogue Overhaul keywords that are lost in ReProccer setup. | |
Fix alternate texture settings that are incorrectly copied in Reproccer for | |
armors. | |
} | |
unit UserScript; | |
var | |
armors, summaryList, hasMaster: TStringList; | |
{ Used to get an idea of what the Name, Path, and Signature for elements in a | |
container are in order to know what to use for coding } | |
procedure PrintContainedElements(container: IInterface); | |
var | |
i: integer; | |
el: IInterface; | |
begin | |
for i := 0 to ElementCount(container) - 1 do begin | |
el := ElementByIndex(container, i); | |
AddMessage(Name(el) + ' | ' + Signature(el) + ' | ' + GetEditValue(el)); | |
end; | |
end; | |
{ It it assumed that main is the top level record, e.g an esm or esp. | |
The form_id is the form_id as seen in TES5Edit or when inspecting the element | |
on the record it originates from, i.e. the top 2 bytes reference that record. | |
The keyword is also given in a form equivalent to form_id. } | |
function RemoveKeywordOnFormID(main: IInterface; form_id: integer; keyword: integer): boolean; | |
var | |
rec: IInterface; | |
begin | |
form_id := LoadOrderFormIDToFileFormID(main, form_id); | |
rec := RecordByFormID(main, form_id, True); | |
result := false; | |
if Assigned(rec) then begin | |
result := RemoveKeyword(rec, keyword); | |
end; | |
end; | |
{ Similar to RemoveKeywordOnFormID but adds instead of removes } | |
function AddKeywordOnFormID(main: IInterface; form_id: integer; keyword: integer): boolean; | |
var | |
rec: IInterface; | |
begin | |
form_id := LoadOrderFormIDToFileFormID(main, form_id); | |
rec := RecordByFormID(main, form_id, True); | |
result := false; | |
if Assigned(rec) then begin | |
result := AddKeyword(rec, keyword); | |
end; | |
end; | |
{ Simply for debugging, prints the keyword with EDID and FormID } | |
procedure PrintKeywords(keywords: IInterface); | |
var | |
i: integer; | |
container, key: IInterface; | |
begin | |
if Signature(keywords) = 'KWDA' then begin | |
container := GetContainer(keywords); | |
end else begin | |
container := keywords; | |
keywords := ElementBySignature(container, 'KWDA'); | |
end; | |
for i := 0 to ElementCount(keywords) - 1 do begin | |
key := ElementByIndex(keywords, i); | |
AddMessage(IntToHex64(GetNativeValue(key), 8) + ' ' + GetEditValue(key)); | |
end; | |
end; | |
{ The form id is in the form seen in TES5Edit interface } | |
function RemoveKeyword(keywords: IInterface; keyword: integer): boolean; | |
var | |
i: integer; | |
container, key: IInterface; | |
begin | |
if Signature(keywords) = 'KWDA' then begin | |
container := GetContainer(keywords); | |
end else begin | |
container := keywords; | |
keywords := ElementBySignature(container, 'KWDA'); | |
end; | |
result := false; | |
// It assumed all form ids for keywords are given in load order form which | |
// is the form id you see in the TES5Edit Interface. It is converted to the | |
// FormID needed for this file before setting. | |
keyword := LoadOrderFormIDToFileFormID(GetFile(keywords), keyword); | |
for i := 0 to ElementCount(keywords) - 1 do begin | |
key := ElementByIndex(keywords, i); | |
if GetNativeValue(key) = keyword then begin | |
AddMessage('Removing keyword ' + GetEditValue(key) + ' on record ' + | |
GetEditValue(ElementBySignature(container, 'EDID'))); | |
Remove(key); | |
result := true; | |
end; | |
end; | |
end; | |
{ Removes keywords that are seen twice on a record } | |
procedure RemoveDuplicateKeywords(keywords: IInterface); | |
var | |
i, j, max: integer; | |
edited: boolean; | |
keyword: string; | |
container: IInterface; | |
begin | |
if Signature(keywords) = 'KWDA' then begin | |
container := GetContainer(keywords); | |
end else begin | |
container := keywords; | |
keywords := ElementBySignature(container, 'KWDA'); | |
end; | |
edited := false; | |
max := ElementCount(keywords) - 2; | |
for i := 0 to max do begin | |
keyword := GetEditValue(ElementByIndex(keywords, i)); | |
for j := ElementCount(keywords) - 1 downto i + 1 do begin | |
if GetEditValue(ElementByIndex(keywords, j)) = keyword then begin | |
Remove(ElementByIndex(keywords, j)); | |
AddMessage('Removing keyword ' + keyword + ' on record ' + | |
GetEditValue(ElementBySignature(container, 'EDID'))); | |
max := max - 1; | |
edited := true; | |
end; | |
end; | |
end; | |
if edited then SetNativeValue(ElementBySignature(container, 'KSIZ'), ElementCount(keywords)); | |
end; | |
{ Helper to copy classes of keywords based on a common substring in EDID of keyword } | |
procedure CopyKeywordsContainingStrToRecord(keywords: IInterface; keyword: string; dest: IInterface); | |
var | |
i, form_id: integer; | |
container, key, main: IInterface; | |
begin | |
if Signature(keywords) = 'KWDA' then begin | |
container := GetContainer(keywords); | |
end else begin | |
container := keywords; | |
keywords := ElementBySignature(container, 'KWDA'); | |
end; | |
main := GetFile(container); | |
for i := 0 to ElementCount(keywords) - 1 do begin | |
key := ElementByIndex(keywords, i); | |
if Pos(keyword, GetEditValue(key)) > 0 then begin | |
form_id := FileFormIDToLoadOrderFormID(main, GetNativeValue(key)); | |
AddKeyword(dest, form_id); | |
end; | |
end; | |
end; | |
function HasKeyword(keywords: IInterface; keyword: integer): boolean; | |
var | |
i: integer; | |
container: IInterface; | |
begin | |
if Signature(keywords) = 'KWDA' then begin | |
container := GetContainer(keywords); | |
end else begin | |
container := keywords; | |
keywords := ElementBySignature(container, 'KWDA'); | |
end; | |
result := false; | |
for i := 0 to ElementCount(keywords) - 1 do begin | |
if GetNativeValue(ElementByIndex(keywords, i)) = keyword then begin | |
result := true; | |
break; | |
end; | |
end; | |
end; | |
function AddKeyword(keywords: IInterface; keyword: integer): boolean; | |
var | |
i: integer; | |
container, ksize, el: IInterface; | |
begin | |
if Signature(keywords) = 'KWDA' then begin | |
container := GetContainer(keywords); | |
end else begin | |
container := keywords; | |
keywords := ElementBySignature(container, 'KWDA'); | |
end; | |
if not ElementExists(container, 'KSIZ') then begin | |
Add(container, 'KSIZ', True); | |
end; | |
ksize := ElementBySignature(container, 'KSIZ'); | |
// It assumed all form ids for keywords are given in load order form which | |
// is the form id you see in the TES5Edit Interface. It is converted to the | |
// FormID needed for this file before setting. | |
keyword := LoadOrderFormIDToFileFormID(GetFile(keywords), keyword); | |
if not HasKeyword(keywords, keyword) then begin | |
el := ElementAssign(keywords, HighInteger, nil, False); | |
SetNativeValue(el, keyword); | |
AddMessage('Adding keyword ' + GetEditValue(el) + ' to record ' + | |
GetEditValue(ElementBySignature(container, 'EDID'))); | |
SetNativeValue(ksize, ElementCount(keywords)); | |
result := true; | |
end else begin | |
result := false; | |
end; | |
end; | |
{ Unused right now, but don't want to delete since it could be useful } | |
function HasKeywordContaining(keywords: IInterface; keyword: string): boolean; | |
var | |
i: integer; | |
begin | |
if Signature(keywords) <> 'KWDA' then begin | |
keywords := ElementBySignature(keywords, 'KWDA'); | |
end; | |
if Signature(keywords) <> 'KWDA' then begin | |
result := false; | |
end; | |
for i := 0 to ElementCount(keywords) - 1 do begin | |
if Pos(keyword, GetEditValue(ElementByIndex(keywords, i))) > 0 then begin | |
result := true; | |
Exit; | |
end; | |
end; | |
end; | |
function GetLoadFile(rec: string): IInterface; | |
var | |
i: integer; | |
begin | |
result := nil; | |
for i := 0 to FileCount - 1 do begin | |
if SameText(rec, GetFileName(FileByIndex(i))) then begin | |
result := FileByIndex(i); | |
exit; | |
end; | |
end; | |
end; | |
function MyHasMaster(main: IInterface; filename: string): boolean; | |
var | |
i, j: integer; | |
store: string; | |
begin | |
main := GetFile(main); | |
store := GetFileName(main) + '-' + filename; | |
result := false; | |
if Assigned(hasMaster.Values[store]) then begin | |
// AddMessage('Saved ' + store + ' ' + hasMaster.Values[store]); | |
if hasMaster.Values[store] = 't' then result := true; | |
// result := hasMaster.Values[store] <> 't'; | |
// if result and (hasMaster.Values[store] = 'f') then begin | |
// AddMessage('Setting master shortcut failed'); | |
// end; | |
exit; | |
end else begin | |
hasMaster.Values[store] := 'f'; | |
end; | |
for i := 0 to MasterCount(main) - 1 do begin | |
for j := 0 to FileCount - 1 do begin | |
if SameText(GetFileName(FileByIndex(j)), GetFileName(MasterByIndex(main, i))) then begin | |
result := true; | |
hasMaster.Values[store] := 't'; | |
exit; | |
end; | |
end; | |
end; | |
end; | |
function WillBeRedundantLevelList(main: IInterface; rec: IInterface; a: TStringList): boolean; | |
var | |
i, j, form_id: integer; | |
tmp, el1, el2: IInterface; | |
b: TStringList; | |
str: string; | |
begin | |
result := false; | |
form_id := FileFormIDToLoadOrderFormID(GetFile(rec), FormID(rec)); | |
for i := FileCount - 1 downto 0 do begin | |
if not MyHasMaster(main, GetFileName(FileByIndex(i))) then continue; | |
tmp := FileByIndex(i); | |
el1 := tmp; | |
try | |
tmp := RecordByFormID(tmp, LoadOrderFormIDToFileFormID(tmp, form_id), false); | |
except on E : Exception do | |
// A continue statement seems to fail at jumping to next for loop | |
tmp := FileByIndex(0); | |
end; | |
if GetFileName(GetFile(tmp)) <> GetFileName(el1) then continue; | |
el1 := ElementByPath(tmp, 'EDID'); | |
el2 := ElementByPath(rec, 'EDID'); | |
if GetEditValue(el1) <> GetEditValue(el2) then begin | |
AddMessage('Abort on EDID'); | |
exit; | |
end; | |
el1 := ElementByPath(tmp, 'OBND'); | |
el2 := ElementByPath(rec, 'OBND'); | |
if GetEditValue(el1) <> GetEditValue(el2) then begin | |
AddMessage('Abort on OBND'); | |
exit; | |
end; | |
el1 := ElementByPath(tmp, 'LVLD'); | |
el2 := ElementByPath(rec, 'LVLD'); | |
if GetEditValue(el1) <> GetEditValue(el2) then begin | |
AddMessage('Abort on LVLD'); | |
exit; | |
end; | |
el1 := ElementByPath(tmp, 'LVLG'); | |
el2 := ElementByPath(rec, 'LVLG'); | |
if GetEditValue(el1) <> GetEditValue(el2) then begin | |
AddMessage('Abort on LVLG'); | |
exit; | |
end; | |
b := SummarizeLevelList(tmp); | |
try | |
if a.Count <> b.Count then begin | |
AddMessage('Abort on count mismatch'); | |
exit; | |
end; | |
for j := 0 to a.Count - 1 do begin | |
str := a.Strings[j]; | |
i := b.IndexOf(str); | |
if i < 0 then begin | |
AddMessage('Abort on b not having entity'); | |
exit; | |
end; | |
if integer(b.Objects[i]) <> integer(a.Objects[j]) then begin | |
AddMessage('Abort on Entity Matches'); | |
exit; | |
end; | |
end; | |
finally | |
b.Free; | |
end; | |
result := true; | |
exit; | |
end; | |
end; | |
function IsRedundantLevelList(rec: IInterface): boolean; | |
var | |
i, j, form_id: integer; | |
main, tmp, alt_rec: IInterface; | |
a, b: TStringList; | |
str: string; | |
begin | |
result := false; | |
main := GetFile(rec); | |
form_id := FileFormIDToLoadOrderFormID(main, FormID(rec)); | |
for i := MasterCount(main) - 1 downto 0 do begin | |
try | |
tmp := MasterByIndex(main, i); | |
tmp := RecordByFormID(tmp, LoadOrderFormIDToFileFormID(tmp, form_id), true); | |
if not Assigned(tmp) then begin | |
Continue; | |
end; | |
except | |
on E : Exception do | |
Continue; | |
end; | |
result := true; | |
for j := 0 to ElementCount(tmp) - 1 do begin | |
alt_rec := ElementByIndex(tmp, j); | |
if GetEditValue(ElementByName(rec, Name(alt_rec))) <> GetEditValue(alt_rec) then begin | |
result := false; | |
break; | |
end; | |
end; | |
a := SummarizeLevelList(rec); | |
b := SummarizeLevelList(tmp); | |
try | |
if a.Count <> b.Count then exit; | |
for j := 0 to a.Count - 1 do begin | |
str := a.Strings[j]; | |
i := b.IndexOf(str); | |
if i < 0 or integer(b.Objects[i]) <> integer(a.Objects[j]) then exit; | |
end; | |
finally | |
a.Free; | |
b.Free; | |
end | |
xit; | |
end; | |
end; | |
function SummarizeLevelList(rec: IInterface): TStringList; | |
var | |
summary, tmpList: TStringList; | |
str: string; | |
i, j, form_id: integer; | |
tmp, lli, alt_rec: IInterface; | |
begin | |
tmpList := TStringList.Create; | |
str := GetFileName(GetFile(rec)) + '-' + IntToStr(FormID(rec)); | |
i := summaryList.IndexOf(str); | |
if i >= 0 then begin | |
tmpList.Assign(summaryList.Objects[i]); | |
result := tmpList; | |
exit; | |
end else begin | |
summaryList.AddObject(form_id, tmpList); | |
end | |
// Sum up the number of times each item appears in this record only | |
summary := TStringList.Create; | |
result := summary; | |
alt_rec := ElementByName(rec, 'Leveled List Entries'); | |
for i := 0 to ElementCount(alt_rec) - 1 do begin | |
lli := ElementByIndex(alt_rec, i); | |
form_id := GetNativeValue(ElementByPath(lli, 'LVLO\Reference')); | |
form_id := FileFormIDtoLoadOrderFormID(GetFile(lli), form_id); | |
// Create a unique reference to this entry, e.g. reference, level and | |
// count and separate each by a delimiter for easy separation later | |
str := IntToStr(form_id) + ';' + | |
GetEditValue(ElementByPath(lli, 'LVLO\Level')) + ';' + | |
GetEditValue(ElementByPath(lli, 'LVLO\Count')); | |
j := summary.IndexOf(str); | |
if j >= 0 then begin | |
// if Assigned(summary.Values[str]) then begin | |
summary.Objects[j] := integer(summary.Objects[j]) + 1; | |
// summary.Values[str] := IntToStr(StrToInt(summary.Values[str]) + 1); | |
end else begin | |
summary.AddObject(str, TOBject(1)); | |
// summary.Values[str] := '1'; | |
end; | |
end; | |
tmpList.Assign(summary); | |
end; | |
procedure RunningSummaryOfLevelLists(base: IInterface; | |
root: TStringList; | |
lists: TStringList); | |
var | |
i, j, k, form_id: integer; | |
str: string; | |
rec: IInterface; | |
temp_list, summary: TStringList; | |
begin | |
for i := 0 to ElementCount(base) - 1 do begin | |
rec := ElementByIndex(base, i); | |
form_id := FileFormIDtoLoadOrderFormID(GetFile(base), FormID(rec)); | |
str := IntToHex64(form_id, 8); | |
// Create a reference holder for this specific list | |
j := root.IndexOf(str); | |
if j >= 0 then begin | |
temp_list := root.Objects[j]; | |
end else begin | |
temp_list := TStringList.Create; | |
temp_list.Duplicates := dupError; | |
root.AddObject(str, temp_list); | |
end; | |
// Track the latest mod needed for level list | |
if lists <> nil then lists.Values[str] := GetFileName(GetFile(base)); | |
// Sum up the number of times each item appears in this record only | |
summary := SummarizeLevelList(rec); | |
// Go over the references found and add them to a running summary of stats. | |
for j := 0 to summary.Count - 1 do begin | |
str := summary.Strings[j]; | |
k := temp_list.IndexOf(str); | |
if k >= 0 then begin | |
if integer(temp_list.Objects[k]) < integer(summary.Objects[j]) then begin | |
temp_list.Objects[k] := summary.Objects[j]; | |
// temp_list.Objects[k] := TObject(integer(temp_list.Objects[k]) + | |
// integer(summary.Objects[j])); | |
end; | |
end else begin | |
temp_list.AddObject(str, summary.Objects[j]); | |
end; | |
end; | |
end; | |
end; | |
procedure PrintLeveledListSummary(summary: TStringList); | |
var | |
i, count: integer; | |
format: TStringList; | |
begin | |
format := TStringList.Create; | |
format.Delimiter := ';'; | |
for i := 0 to summary.Count - 1 do begin | |
count := integer(summary.Objects[i]); | |
format.DelimitedText := summary.Strings[i]; | |
format[0] := IntToHex64(StrToInt(format[0]), 8); | |
AddMessage(format[0] + ' lvl: ' + format[1] + ' count: ' + format[2] + | |
' total: ' + IntToStr(count)); | |
end; | |
format.Free; | |
end; | |
procedure RemoveItemsFromLevelList(main: IInterface; | |
llid: integer; | |
llref: integer; | |
lllvl: integer; | |
llcount: integer; | |
removals: integer); | |
var | |
form_id, i: integer; | |
rec, lle: IInterface; | |
begin | |
try | |
rec := RecordByFormID(main, LoadOrderFormIDToFileFormID(main, llid), true); | |
form_id := LoadOrderFormIDToFileFormID(main, llref); | |
except | |
on E : Exception do begin | |
AddMessage('Unable to find ' + IntToHex64(llid, 8) + ' for cleaning'); | |
exit; | |
end; | |
end; | |
AddMessage(IntToHex64(llid, 8) + ' removing ' + IntToHex64(llref, 8) + | |
' completely (' + IntToStr(removals) + ')'); | |
// if FileFormIDToLoadOrderFormID(main, FormID(rec)) <> StrToInt('$' + str) then continue; | |
lle := ElementByName(rec, 'Leveled List Entries'); | |
for i := ElementCount(lle) - 1 downto 0 do begin | |
rec := ElementByIndex(lle, i); | |
// Seems like crap I have to wrap each part of statement in parens | |
if (form_id = GetNativeValue(ElementByPath(rec, 'LVLO\Reference'))) and | |
(lllvl = GetNativeValue(ElementByPath(rec, 'LVLO\Level'))) and | |
(llcount = GetNativeValue(ElementByPath(rec, 'LVLO\Count'))) then begin | |
// AddMessage('Removing ' + IntToHex64(llref, 8) + ' lvl: ' + IntToStr(lllvl) + ' count: ' + IntToStr(llcount)); | |
removals := removals - 1; | |
Remove(rec); | |
if removals <= 0 then exit; | |
end; | |
end; | |
end; | |
{ Merging Leveled Lists follows a basic algorithm. The last LL from | |
Skyrim.esm, Dawnguard.esm, Hearthfires.esm, Dragonborn.esm, USKP, USKP | |
- Dawnguard, USKP - Hearthfire, USKP - Dragonborn. From here items are | |
added to the leveled list from other mods. } | |
procedure CompactLeveledList(patch: IInterface; llist: string); | |
var | |
str: string; | |
exists, identical: boolean; | |
i, j, k, form_id: integer; | |
tmp, rec, alt_rec, lli: IInterface; | |
skll, ll, mll, tmpll, dll, spliter, a, b: TStringList; | |
begin | |
skll := TStringList.Create; | |
ll := TStringList.Create; | |
mll := TSTringList.Create; | |
dll := TStringList.Create; | |
AddMessage('Working on ' + llist); | |
try | |
for i := 0 to FileCount - 1 do begin | |
tmp := FileByIndex(i); | |
// Don't include ourself in the data accumulation or the main skyrim folder | |
// because if a main skyrim load list is changed the change is absolutely | |
// desired. | |
if SameText(GetFileName(tmp), GetFileName(patch)) or | |
SameText(GetFileName(tmp), 'Skyrim.esm') then begin | |
continue; | |
end; | |
if SameText(GetFileName(tmp), 'Update.esm') or | |
SameText(GetFileName(tmp), 'Dawnguard.esm') or | |
SameText(GetFileName(tmp), 'HearthFires.esm') or | |
SameText(GetFileName(tmp), 'Dragonborn.esm') or | |
SameText(GetFileName(tmp), 'Unofficial Skyrim Patch.esp') or | |
SameText(GetFileName(tmp), 'Unofficial Dawnguard Patch.esp') or | |
SameText(GetFileName(tmp), 'Unofficial Hearthfire Patch.esp') or | |
SameText(GetFileName(tmp), 'Unofficial Dragonborn Patch.esp') or | |
SameText(GetFileName(tmp), 'SkyRe_Main.esp') then begin | |
// Iterate over all the level lists of the specified type and store | |
// that the given file contains the level list. Also gather stats on | |
// this module outside of accumulation between mods | |
RunningSummaryOfLevelLists(GroupBySignature(tmp, llist), dll, skll); | |
end; | |
RunningSummaryOfLevelLists(GroupBySignature(tmp, llist), ll, mll); | |
end; | |
CleanMasters(patch); | |
// Get mods that need to be in master list | |
for i := 0 to mll.Count - 1 do begin | |
AddMasterIfMissing(patch, mll.Values[mll.Names[i]]) | |
end; | |
// Cleanup old consolidation of lists in preparation of new combination | |
Remove(GroupBySignature(patch, llist)); | |
// At this point skll contains a reference to the final level list from the | |
// official sources (including the unofficial patches). ll contains a list of | |
// all leveled lists which other mods included with all entries combined into | |
// single instances that specify how many times that entry should exist in | |
// the list | |
// for k := 0 to MasterCount(patch) - 1 do begin | |
// AddMessage(GetFileName(MasterByIndex(patch, k))); | |
// end; | |
// Loop over the level lists and recreate them from the summation of all mods | |
for i := 0 to ll.Count - 1 do begin | |
str := ll.Strings[i]; | |
j := StrToInt('$' + str); | |
// AddMessage('Working on ' + str); | |
// Use one of the good masters as base if it is overriden, otherwise use | |
// the last mod in the load order as base | |
if Assigned(skll.Values[str]) then begin | |
lli := GetLoadFile(skll.Values[str]); | |
end else begin | |
lli := GetLoadFile(mll.Values[str]); | |
end; | |
try | |
form_id := LoadOrderFormIDToFileFormID(lli, j); | |
lli := RecordByFormID(lli, form_id, True); | |
except on E : Exception do | |
AddMessage('Got an exception on ' E.Message); | |
continue; | |
end; | |
// Check if this will be redudant before doing the actual copy | |
if WillBeRedundantLevelList(patch, lli, ll.Objects[i]) then begin | |
// AddMessage('Skipping would be redundant record ' + ll.Strings[i]); | |
continue; | |
end; | |
// Use the winning master record, e.g. one with most correct stats for | |
// level list for base of new list; | |
rec := wbCopyElementToFile(lli, patch, False, True); | |
alt_rec := ElementByName(rec, 'Leveled List Entries'); | |
// Remove all the entires for a fresh start to number of entries | |
for j := ElementCount(alt_rec) - 1 downto 0 do begin | |
Remove(ElementByIndex(alt_rec, j)); | |
end; | |
// Copy over references data from accumulated mods | |
tmpll := ll.Objects[i]; | |
spliter := TStringList.Create; | |
try | |
spliter.Delimiter := ';'; | |
for j := 0 to tmpll.Count - 1 do begin | |
spliter.DelimitedText := tmpll.Strings[j]; | |
form_id := LoadOrderFormIDToFileFormID(patch, StrToInt64(spliter[0])); | |
// Create the number of entries for this reference | |
for k := integer(tmpll.Objects[j]) - 1 downto 0 do begin | |
lli := ElementAssign(alt_rec, HighInteger, nil, False); | |
SetNativeValue(ElementByPath(lli, 'LVLO\Reference'), form_id); | |
SetEditValue(ElementByPath(lli, 'LVLO\Level'), spliter[1]); | |
SetEditValue(ElementByPath(lli, 'LVLO\Count'), spliter[2]); | |
end; | |
end; | |
finally | |
spliter.Free | |
end; | |
SetNativeValue(ElementByPath(rec, 'LLCT'), ElementCount(alt_rec)); | |
// Go backward through the load order and if we find a list equal to the | |
// one we made, delete the one we made. I wish I can a better way of | |
// going through this but overrides doesn't seem to work for what I want | |
// if IsRedundantLevelList(rec) then begin | |
// AddMessage('Removing the redundant record ' + ll.Strings[i]); | |
// Remove(rec); | |
// end; | |
end; | |
// SyncLevelListRemovals(patch, llist, 'Dragonborn.esm', 'SkyRe_Main.esp'); | |
SyncLevelListRemovals(patch, llist, 'HearthFires.esm', 'SkyRe_Main.esp'); | |
SyncLevelListRemovals(patch, llist, 'Dawnguard.esm', 'SkyRe_Main.esp'); | |
SyncLevelListRemovals(patch, llist, 'Skyrim.esm', 'SkyRe_Main.esp'); | |
SyncLevelListRemovals(patch, llist, 'Skyrim.esm', 'Unofficial Skyrim Patch.esp'); | |
SyncLevelListRemovals(patch, llist, 'Dawnguard.esm', 'Unofficial Dawnguard Patch.esp'); | |
SyncLevelListRemovals(patch, llist, 'HearthFires.esm', 'Unofficial Hearthfire Patch.esp'); | |
SyncLevelListRemovals(patch, llist, 'Dragonborn.esm', 'Unofficial Dragonborn Patch.esp'); | |
rec := GroupBySignature(patch, 'LVLI'); | |
for i := 0 to ElementCount(rec) - 1 do begin | |
alt_rec := ElementByIndex(rec, i); | |
tmp := ElementByName(alt_rec, 'Leveled List Entries'); | |
if ElementCount(tmp) < GetElementNativeValues(alt_rec, 'LLCT') then begin | |
SetElementNativeValues(alt_rec, 'LLCT', ElementCount(tmp)); | |
end; | |
end; | |
finally | |
for i := 0 to ll.Count - 1 do begin | |
ll.Objects[i].Free; | |
end; | |
ll.Free; | |
for i := 0 to dll.Count - 1 do begin | |
dll.Objects[i].Free; | |
end; | |
dll.Free; | |
skll.Free; | |
mll.Free; | |
end; | |
end; | |
procedure SyncLevelListRemovals(main: IInterface; llist: string; base: string; master: string); | |
var | |
i, j, k, form_id: integer; | |
str: string; | |
rec: IInterface; | |
dll, ll, tmpll, spliter, reproc, format: TStringList; | |
begin | |
dll := TStringList.Create; | |
ll := TSTringList.Create; | |
reproc := TStringList.Create; | |
format := TStringList.Create; | |
format.Delimiter := ';'; | |
AddMessage('Cleaning ' + GetFileName(GetFile(main)) + ' ' + llist + | |
' using base ' + base + ' and master ' + master); | |
try | |
RunningSummaryOfLevelLists(GroupBySignature(GetLoadFile(base), llist), dll, nil); | |
RunningSummaryOfLevelLists(GroupBySignature(GetLoadFile(master), llist), ll, nil); | |
RunningSummaryOfLevelLists(GroupBySignature(main, llist), reproc, nil); | |
for i := 0 to ll.Count - 1 do begin | |
str := ll.Strings[i]; | |
// AddMessage('Looking for ' + str); | |
// Skip this if it isn't in the final mod file | |
if reproc.IndexOf(str) < 0 then continue; | |
// form_id := LoadOrderFormIDToFileFormID(rec, str); | |
j := dll.IndexOf(str); | |
if j >= 0 then begin | |
// See which records from dll do not exist on the ll version, e.g. ll | |
// has removed them and the patch object should have them removed as | |
// well. This needs to look at the count value for each set of | |
// reference/level/count values and use the smaller one in ll if found. | |
tmpll := dll.Objects[j]; | |
spliter := ll.Objects[i]; | |
for j := 0 to tmpll.Count - 1 do begin | |
// Get the ref/level/count object and see if ll has it | |
k := spliter.IndexOf(tmpll.Strings[j]); | |
if k >= 0 then begin | |
// If it has it, then make sure there was not a decrease in total | |
// occurances. | |
if integer(tmpll.Objects[j]) > integer(spliter.Objects[k]) then begin | |
format.DelimitedText := tmpll.Strings[j]; | |
RemoveItemsFromLevelList(main, StrToInt('$' + str), StrToInt(format[0]), | |
StrToInt(format[1]), StrToInt(format[2]), | |
integer(tmpll.Objects[j]) - integer(spliter.Objects[k])); | |
end; | |
end else begin | |
// If it doesn't have it then it should be removed | |
format.DelimitedText := tmpll.Strings[j]; | |
RemoveItemsFromLevelList(main, StrToInt('$' + str), StrToInt(format[0]), | |
StrToInt(format[1]), StrToInt(format[2]), | |
integer(tmpll.Objects[j])); | |
end; | |
end; | |
end; | |
end; | |
except | |
on E : Exception do begin | |
AddMessage(E.Message); | |
end; | |
finally | |
for i := 0 to ll.Count - 1 do begin | |
ll.Objects[i].Free; | |
end; | |
ll.Free; | |
for i := 0 to dll.Count - 1 do begin | |
dll.Objects[i].Free; | |
end; | |
dll.Free; | |
for i := 0 to reproc.Count - 1 do begin | |
reproc.Objects[i].Free; | |
end; | |
reproc.Free; | |
format.Free; | |
end; | |
end; | |
function Initialize: integer; | |
var | |
i, j, k: integer; | |
form_id: LongWord; | |
str: string; | |
has_mod2, has_mod4: boolean; | |
reproccer, uskp, sic, gdo, imm_wep: IInterface; | |
tmp, rec, alt_rec: IInterface; | |
lli: IInterface; | |
a: TStringList; | |
begin | |
for i := 0 to FileCount - 1 do begin | |
tmp := FileByIndex(i); | |
str := GetFileName(tmp); | |
if SameText(str, 'ReProccer.esp') then begin | |
reproccer := tmp; | |
end else if SameText(str, 'Unofficial Skyrim Patch.esp') then begin | |
uskp := tmp; | |
end else if SameText(str, 'Skyrim Immersive Creatures.esm') then begin | |
sic := tmp; | |
end else if SameText(str, 'Guard Dialogue Overhaul.esp') then begin | |
gdo := tmp; | |
end else if SameText(str, 'Immersive Weapons.esp') then begin | |
imm_wep := tmp; | |
end; | |
end; | |
{ This all require at least ReProccer.esp for fixes } | |
if not Assigned(reproccer) then begin | |
MessageDlg('You must have ReProccer.esp loaded', mtConfirmation, [mbOk], 0); | |
result := 1; | |
Exit; | |
end; | |
// Store the form id of armors in reproccer for easy lookup when searching | |
// for dreamcloth version of armors in MOD4/MOD2 fix step | |
armors := TStringList.Create; | |
summaryList := TStringList.Create; | |
hasMaster := TStringList.Create; | |
CompactLeveledList(reproccer, 'LVLI'); | |
CompactLeveledList(reproccer, 'LVLN'); | |
CompactLeveledList(reproccer, 'LVSP'); | |
// ReProccer has a bug that adds BODT records even when there is a BOD2 one | |
// which is an unwanted side effect. This goes through all armors and removes | |
// the BODT field when a BOD2 one exists. | |
tmp := GroupBySignature(reproccer, 'ARMO'); | |
for i := 0 to ElementCount(tmp) - 1 do begin | |
rec := ElementByIndex(tmp, i); | |
armors.AddObject(GetEditValue(ElementBySignature(rec, 'FULL')), TObject(FormID(rec))); | |
if ElementExists(rec, 'BOD2') then begin | |
RemoveElement(rec, 'BODT'); | |
end; | |
RemoveDuplicateKeywords(rec); | |
// Add Thalmor Infiltration to Armors that have Thalmor in the name | |
if Pos('Thalmor', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin | |
AddKeyword(rec, $69037d2b); | |
end; | |
// Add Forsworn Infiltration to Armors that have Forsworn in the name | |
if Pos('Forsworn', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin | |
AddKeyword(rec, $6903a8a9); | |
end; | |
// Add Stormcloak Infiltration to Armors that have Stormcloak in the name | |
if Pos('Stormcloak', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin | |
AddKeyword(rec, $69037d2f); | |
end; | |
// Add Bandit Infiltration to Armors that have Bandit in the name | |
if Pos('Bandit', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin | |
AddKeyword(rec, $6903a8aa); | |
end; | |
// Add Imperial Infiltration to Armors that have Imperial in the name | |
if Pos('Imperial', GetEditValue(ElementBySignature(rec, 'EDID'))) > 0 then begin | |
AddKeyword(rec, $69037d31); | |
end; | |
// Since it isn't always possible to create load order form ids, e. g. the | |
// item comes later in the load order than the requested esp it is being | |
// made for it is necessary to wrap this in a try/except block | |
if Assigned(uskp) then try | |
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec)); | |
form_id := LoadOrderFormIDToFileFormID(uskp, form_id); | |
alt_rec := RecordByFormID(uskp, form_id, True); | |
if Assigned(alt_rec) then begin | |
CopyKeywordsContainingStrToRecord(alt_rec, 'USKP', rec); | |
end; | |
except | |
on E : Exception | |
// AddMessage('Got Exception ' + E.Message); | |
end; | |
if Assigned(gdo) then try | |
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec)); | |
form_id := LoadOrderFormIDToFileFormID(gdo, form_id); | |
alt_rec := RecordByFormID(gdo, form_id, True); | |
if Assigned(alt_rec) then begin | |
CopyKeywordsContainingStrToRecord(alt_rec, 'GDO', rec); | |
end; | |
except | |
on E : Exception | |
end; | |
end; | |
// Need to fix MO4S and MO2S entries in Reproccer and since the OverrideCount | |
// function appears to be broken we need to iterate over all masters of | |
// ReProccer and then all armors of those to see which ones have the | |
// MOD4/MOD2 fields that need correction. | |
for i := 0 to MasterCount(reproccer) do begin | |
tmp := GroupBySignature(MasterByIndex(reproccer, i), 'ARMO'); | |
if not Assigned(tmp) then Continue; | |
for j := 0 to ElementCount(tmp) - 1 do begin | |
rec := ElementByIndex(tmp, j); | |
try | |
form_id := FileFormIDtoLoadOrderFormID(MasterByIndex(reproccer, i), FormID(rec)); | |
form_id := LoadOrderFormIDToFileFormID(reproccer, form_id); | |
alt_rec := RecordByFormID(reproccer, form_id, True); | |
if not Assigned(alt_rec) then Continue; | |
has_mod2 := ElementExists(rec, 'MOD2\MO2S'); | |
has_mod4 := ElementExists(rec, 'MOD4\MO4S'); | |
if has_mod2 then begin | |
// AddMessage('Updating MOD2 on ' + IntToHex64(form_id, 8) + | |
// ' ' + GetEditValue(ElementBySignature(rec, 'EDID'))); | |
wbCopyElementToRecord(ElementBySignature(rec, 'MOD2'), alt_rec, False, True); | |
end; | |
if has_mod4 then begin | |
// AddMessage('Updating MOD4 on ' + IntToHex64(form_id, 8) + | |
// ' ' + GetEditValue(ElementBySignature(rec, 'EDID'))); | |
wbCopyElementToRecord(ElementBySignature(rec, 'MOD4'), alt_rec, False, True); | |
end; | |
if not has_mod2 and not has_mod4 then Continue; | |
// Need to also fix the dreamcloth version of armors created | |
str := GetEditValue(ElementBySignature(rec, 'FULL')) + ' [Dreamcloth]'; | |
k := armors.IndexOf(str); | |
if k >= 0 then begin | |
alt_rec := RecordByFormId(reproccer, Integer(armors.Objects[k]), True); | |
if has_mod2 then begin | |
// AddMessage('Updating MOD2 on ' + | |
// GetEditValue(ElementBySignature(alt_rec, 'EDID'))); | |
wbCopyElementToRecord(ElementBySignature(rec, 'MOD2'), alt_rec, False, True); | |
end; | |
if has_mod4 then begin | |
// AddMessage('Updating MOD4 on ' + | |
// GetEditValue(ElementBySignature(alt_rec, 'EDID'))); | |
wbCopyElementToRecord(ElementBySignature(rec, 'MOD4'), alt_rec, False, True); | |
end; | |
end; | |
except | |
on E : Exception do | |
AddMessage('Got Exception ' + E.Message); | |
end; | |
end; | |
end; | |
{ UKSP fixes numerous sounds for weapons and a few critical multipliers for | |
weapons and those values are lost because of other mods in load order, like | |
SkyRe itself } | |
tmp := GroupBySignature(reproccer, 'WEAP'); | |
for i := 0 to ElementCount(tmp) - 1 do begin | |
rec := ElementByIndex(tmp, i); | |
RemoveDuplicateKeywords(rec); | |
if Assigned(uskp) then try | |
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec)); | |
form_id := LoadOrderFormIDToFileFormID(uskp, form_id); | |
alt_rec := RecordByFormID(uskp, form_id, True); | |
if Assigned(alt_rec) then begin | |
CopyKeywordsContainingStrToRecord(alt_rec, 'USKP', rec); | |
end; | |
if ElementExists(alt_rec, 'VNAM') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'VNAM'), rec, False, True); | |
end; | |
if ElementExists(alt_rec, 'BIDS') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'BIDS'), rec, False, True); | |
end; | |
if ElementExists(alt_rec, 'BAMT') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'BAMT'), rec, False, True); | |
end; | |
if ElementExists(alt_rec, 'TNAM') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'TNAM'), rec, False, True); | |
end; | |
if ElementExists(alt_rec, 'INAM') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'INAM'), rec, False, True); | |
end; | |
if ElementExists(alt_rec, 'NAM9') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'NAM9'), rec, False, True); | |
end; | |
if ElementExists(alt_rec, 'NAM8') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'NAM8'), rec, False, True); | |
end; | |
if ElementExists(alt_rec, 'CNAM') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'CNAM'), rec, False, True); | |
end; | |
if ElementExists(alt_rec, 'CRDT\% Mult') then begin | |
SetNativeValue(ElementByPath(rec, 'CRDT\% Mult'), | |
GetNativeValue(ElementByPath(alt_rec, 'CRDT\% Mult'))); | |
end; | |
if ElementExists(alt_rec, 'CRDT\Damage') then begin | |
SetNativeValue(ElementByPath(rec, 'CRDT\Damage'), | |
GetNativeValue(ElementByPath(alt_rec, 'CRDT\Damage'))); | |
end; | |
except | |
on E : Exception | |
// AddMessage('Got Exception while fixing weapons from USKP: ' + E.Message); | |
end; | |
// Guard Dialogue Overhaul uses keywords on weapons to trigger some | |
// dialogue options | |
if Assigned(gdo) then try | |
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec)); | |
form_id := LoadOrderFormIDToFileFormID(gdo, form_id); | |
alt_rec := RecordByFormID(gdo, form_id, True); | |
if Assigned(alt_rec) then begin | |
CopyKeywordsContainingStrToRecord(alt_rec, 'GDO', rec); | |
end; | |
except | |
on E : Exception | |
// AddMessage('Got Exception while fixing weapons from GDO: ' + E.Message); | |
end; | |
// Immersive Weapons has a few first person models that are not copied | |
// correctly by ReProccer | |
if Assigned(imm_wep) then try | |
form_id := FileFormIDtoLoadOrderFormID(reproccer, FormID(rec)); | |
form_id := LoadOrderFormIDToFileFormID(imm_wep, form_id); | |
alt_rec := RecordByFormID(imm_wep, form_id, True); | |
if Assigned(alt_rec) then begin | |
if ElementExists(alt_rec, 'WNAM') then begin | |
wbCopyElementToRecord(ElementBySignature(alt_rec, 'WNAM'), rec, False, True); | |
end; | |
end; | |
except | |
on E : Exception | |
end; | |
end; | |
{ Fix Frostfall armors that get wrong infiltration } | |
// Remove Bandit infiltration keyword | |
RemoveKeywordOnFormID(reproccer, $140415c8, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $140415c9, $6903a8aa); | |
// Add Stormcloak infiltration keyword | |
AddKeywordOnFormID(reproccer, $140415c8, $69037d2f); | |
AddKeywordOnFormID(reproccer, $140415c9, $69037d2f); | |
{ Fix Winter is Coming Cloaks } | |
// Remove Bandit infiltration keyword | |
// Leather Fur Trim cloaks | |
RemoveKeywordOnFormID(reproccer, $35013203, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $35013204, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $35013205, $6903a8aa); | |
// Leather Rare Fur cloaks | |
RemoveKeywordOnFormID(reproccer, $35016302, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $35016303, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $35016304, $6903a8aa); | |
// Steel Fur Trim cloaks | |
RemoveKeywordOnFormID(reproccer, $3501aed6, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3501aed7, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3501aed8, $6903a8aa); | |
// Steel Rare Fur cloaks | |
RemoveKeywordOnFormID(reproccer, $3501BA03, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3501BA04, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3501BA05, $6903a8aa); | |
{ Fix Immersive Armors } | |
// Armored hoods, either remove from these or add to none armored fur hoods | |
RemoveKeywordOnFormID(reproccer, $3b001d8f, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0022f5, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0022fd, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0048da, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0048da, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0048db, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0048dc, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0048dd, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0048de, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0048df, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0ba99b, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b0ba99c, $6903a8aa); | |
// Einherjar Armor - Found on many bandits on my playthrough | |
RemoveKeywordOnFormID(reproccer, $3b00803e, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b00803f, $6903a8aa); | |
RemoveKeywordOnFormID(reproccer, $3b008040, $6903a8aa); | |
// Copy bounds of book from USKP not inlcuded in SkyRe_Main | |
// $10fd60 - OBND | |
// Need to copy record from SkyRe_Main first (VenderItemCluter | |
// AddKeywordOnFormID(reproccer, $4c3c6, $914e9) | |
// AddKeywordOnFormID(reproccer, $4c3c8, $914e9) | |
// AddKeywordOnFormID(reproccer, $8632c, $914e9) | |
// AddKeywordOnFormID(reproccer, $98620, $914e9) | |
// AddKeywordOnFormID(reproccer, $98621, $914e9) | |
// AddKeywordOnFormID(reproccer, $98623, $914e9) | |
// AddKeywordOnFormID(reproccer, $98624, $914e9) | |
// AddKeywordOnFormID(reproccer, $98625, $914e9) | |
// AddKeywordOnFormID(reproccer, $98626, $914e9) | |
// AddKeywordOnFormID(reproccer, $98627, $914e9) | |
// AddKeywordOnFormID(reproccer, $b9bd0, $914e9) | |
// AddKeywordOnFormID(reproccer, $b9bd2, $914e9) | |
// AddKeywordOnFormID(reproccer, $b9bd4, $914e9) | |
// AddKeywordOnFormID(reproccer, $b9bd6, $914e9) | |
// AddKeywordOnFormID(reproccer, $b9bd8, $914e9) // copy over OBND | |
// AddKeywordOnFormID(reproccer, $b9bde, $914e9) | |
// AddKeywordOnFormID(reproccer, $f08f1, $914e9) | |
// AddKeywordOnFormID(reproccer, $f08f3, $914e9) | |
// AddKeywordOnFormID(reproccer, $f08f5, $914e9) | |
// AddKeywordOnFormID(reproccer, $f08f6, $914e9) | |
// AddKeywordOnFormID(reproccer, $f08f8, $914e9) | |
// AddKeywordOnFormID(reproccer, $f08f9, $914e9) | |
// AddKeywordOnFormID(reproccer, $f08fa, $914e9) // OBND | |
// AddKeywordOnFormID(reproccer, $f08fb, $914e9) // OBND | |
// AddKeywordOnFormID(reproccer, $f2012, $106e1f) // OBND | |
// AddKeywordOnFormID(reproccer, $f2012, $106e20) | |
// AddKeywordOnFormID(reproccer, $f2013, $106e1f) // OBND | |
// AddKeywordOnFormID(reproccer, $f2013, $106e20) | |
// AddKeywordOnFormID(reproccer, $f2014, $106e1f) // OBND | |
// AddKeywordOnFormID(reproccer, $f2014, $106e20) | |
// AddKeywordOnFormID(reproccer, $f2015, $106e1f) // OBND | |
// AddKeywordOnFormID(reproccer, $f2015, $106e20) | |
// AddKeywordOnFormID(reproccer, $f5d07, $914e9) | |
// AddKeywordOnFormID(reproccer, $f5d08, $914e9) // OBND | |
// AddKeywordOnFormID(reproccer, $fed17, $106e1f) // OBND | |
// AddKeywordOnFormID(reproccer, $fed17, $106e20) | |
result := 0; | |
end; | |
function Process(e: IInterface): integer; | |
begin | |
// everything is done in initialization, terminate | |
result := 0; | |
end; | |
function Finalize: integer; | |
var | |
i: integer; | |
begin | |
armors.Free; | |
for i := 0 to summaryList.Count - 1 do begin | |
summaryList.Objects[i].Free | |
end; | |
summaryList.Free; | |
hasMaster.Free; | |
end; | |
end. | |
// vim: set ft=delphi commentstring=//\ %s : |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment