It seems like this should be straightforward, but maybe it's not. Full disclosure: I am not a programmer by any stretch. I just know enough to muddle my way through hobby projects, and Pascal is brand new for me as of today. So, if anything is just really terrible code, I'm open to improvements. I'm probably only going to use this script once, though, so I'm not terribly concerned about optimization, just reliable operation.
I'm writing a script for xEdit, aka SSEEDit - the tool most people use for mucking around with Skyrim and Fallout 4 mods that don't make changes to rich game assets. The goal is to dump the raw text content of the DESC property for every BOOK object in the game (vanilla + original DLC). I've gotten pretty far and have a decent understanding of what I'm doing, but I cannot for the life of me get this part to work.
I have a blacklist of strings which should be skipped when iterating through all the BOOK objects. I've tried using pos()
several different ways, and it never comes back with anything other than zero.
The weird thing is, even if I can get a correct result from pos()
in testing, every book is a positive match when I run my actual script. It also doesn't seem like I'm actually iterating past the first entry in the blacklist.
Here's an example of what's returned when I search for a blacklist entry:
EDID: DLC2dunFahlbtharzJournal02
looking in DLC2dunFahlbtharzJournal02
for DLC1ElderScroll...
skipped DLC2dunFahlbtharzJournal02
This shouldn't be returning 0 from pos('DLC1ElderScroll','DLC2dunFahlbtharzJournal02')
...but it is.
(btw - If you have Skyrim SE, you just need SSEEdit to test this.)
Here's my complete script:
{
Dump book text
Dumps "book text" property of every vanilla book
Source for most copypasta:
https://github.com/AngryAndConflict/skyrim-utils/
}
unit UserScript;
uses mteFunctions;
// uses RegExpr;
function Initialize: integer;
var
f, group, e: IInterface;
i, j: integer;
btext,fname: String;
slItems: TStringList;
FileInfo: File_Content;
begin
AddMessage('Dumping books.....');
slItems := TStringList.Create;
// load item and perk stringlists
for i := 0 to FileCount - 1 do begin
f := FileByIndex(i);
group := GroupBySignature(f, 'BOOK');
for j := 0 to ElementCount(group) - 1 do begin
e := ElementByIndex(group, j);
slItems.Add(geev(e, 'EDID'));
// if it's a book, do things
if (isBook(e) = True) then begin
// this it the raw text content of each BOOK's DESC attribute
btext := geev(e,'DESC');
if (Length(btext) > 0) then begin
fname := ProgramPath + 'Output\' + (geev(e, 'EDID')) + '.txt';
// AddMessage('Successfully saved!');
// AddMessage(#9 + fname);
// AddMessage(#20);
// AddMessage('===ERROR=== Unable to save ' + fname + '!');
end;
end;
end;
end;
//AddMessage(btext)
end;
// check if item is book
// exclude blacklisted books and spell tomes
function isBook(item: IInterface): boolean;
var
formID,test: String;
blacklist: TStringList;
ignore: Boolean;
i,t: integer;
begin
Result := false;
ignore := false;
blacklist := TStringList.Create;
// blacklist to exclude - these are can be FormIDs or friendly names
blacklist.Add('DLC1ElderScroll');
blacklist.Add('DLC1FVBook01Falmer');
blacklist.Add('DLC1FVBook02Falmer');
blacklist.Add('DLC1FVBook03Falmer');
blacklist.Add('DLC1FVBook04Falmer');
blacklist.Add('DLC2BlackBook');
blacklist.Add('DA04ElderScroll');
blacklist.Add('ExpSpiderCrftBook');
blacklist.Add('QA');
blacklist.Add('Recipe');
formID := geev(item,'Record Header\FormID');
t := pos(blacklist(1),'Elder');
test := IntToStr(t);
AddMessage(test);
for i := 0 to (blacklist.Count - 1) do begin
if (pos(formID, blacklist(i)) >= 0) then begin
AddMessage('====' + formID + '====');
AddMessage(blacklist(i));
// AddMessage('skipped ' + formID);
ignore := true;
Result := false;
break;
end;
end;
// has Keyword excludes all spell tomes
if (Signature(item) = 'BOOK') and not (hasKeyword(item,'000937A5')) and not ignore then begin
Result := true;
end;
end;
end.{
Dump book text
Dumps "book text" property of every vanilla book
Source for most copypasta:
https://github.com/AngryAndConflict/skyrim-utils/
}
unit UserScript;
uses mteFunctions;
// uses RegExpr;
function Initialize: integer;
var
f, group, e: IInterface;
i, j: integer;
btext,fname: TString;
slItems: TStringList;
FileInfo: File_Content;
blacklist: TStringList;
begin
AddMessage('Dumping books.....');
slItems := TStringList.Create;
// load item and perk stringlists
for i := 0 to FileCount - 1 do begin
f := FileByIndex(i);
group := GroupBySignature(f, 'BOOK');
for j := 0 to ElementCount(group) - 1 do begin
e := ElementByIndex(group, j);
slItems.Add(geev(e, 'EDID'));
// if it's a book, do things
if (isBook(e) = True) then begin
// this it the raw text content of each BOOK's DESC attribute
btext := geev(e,'DESC');
if (Length(btext) > 0) then begin
fname := ProgramPath + 'Output\' + (geev(e, 'EDID')) + '.txt';
// AddMessage('Successfully saved!');
// AddMessage(#9 + fname);
// AddMessage(#20);
// AddMessage('===ERROR=== Unable to save ' + fname + '!');
end;
end;
// AddMessage('----------')
end;
end;
//AddMessage(btext)
end;
// check if item is book
// exclude blacklisted books and spell tomes
function isBook(item: IInterface): boolean;
var
formID,needle,haystack,test: TString;
blacklist: TStringList;
ignore: Boolean;
i,t,p: integer;
begin
Result := false;
ignore := false;
blacklist := TStringList.Create;
// blacklist to exclude - these are can be FormIDs or friendly names
blacklist.Add('DLC1ElderScroll');
blacklist.Add('DLC1FVBook01Falmer');
blacklist.Add('DLC1FVBook02Falmer');
blacklist.Add('DLC1FVBook03Falmer');
blacklist.Add('DLC1FVBook04Falmer');
blacklist.Add('DLC2BlackBook');
blacklist.Add('DA04ElderScroll');
blacklist.Add('ExpSpiderCrftBook');
blacklist.Add('QA');
blacklist.Add('Recipe');
formID := geev(item,'EDID');
for i := 0 to (blacklist.Count - 1) do begin
needle := blacklist[i];
haystack := formID;
// AddMessage('====' + formID + '====');
// AddMessage('looking in ' + haystack);
// AddMessage(#9 + ' for ' + needle + '...');
t := pos(haystack, needle);
test := IntToStr(t);
// AddMessage(test);
if (pos(blacklist[i], formID) >= 0) then begin
// AddMessage(blacklist[i]);
AddMessage('skipped ' + formID);
ignore := true;
break;
end;
end;
// hasKeyword excludes all spell tomes
if (not (Signature(item) = 'BOOK')) or (hasKeyword(item,'000937A5')) then begin
ignore := true;
end;
if ignore then begin
Result := false;
end;
if not ignore then begin
Result := true;
end;
end;
end.
Below is a rewrite of your script, set to only print messages without writing any file: if you're happy with how it works, just change line 14 to
DRY_RUN = False;
to have it actually dump the books content into the Output subfolder.
I started from the latter of the two scripts you concatenated, I assumed that to be your latest version.
Major changes:
blacklist
only once inside Initialize
, no need to do it for every record;BOOK
group, so I reversed the logic and only check if the editor ID contains any string in blacklist
;mteFunctions
, so I removed it and used the existing logic to blacklist records with SpellTome
in their editor ID - if you ever need to check for keywords again, you can enable mteFunctions
back and use its utilities;IsMaster
and then only work once on its WinningOverride
- this avoids processing the same record multiple times in case is overridden by official DLCs or mods, reference: https://tes5edit.github.io/docs/13-Scripting-Functions.html#IwbMainRecordMinor changes:
Free
after using any TStringList
;uses
clauses for system units - not actually needed to run xEdit scripts, but useful to catch errors if you use a linter for your Delphi Pascal;Pred
instead of Count - 1
- the result should be the same, but looks like the standard way to do iterations in Delphi world;begin
and other similar changes following the style guide at: https://docwiki.embarcadero.com/RADStudio/Sydney/en/Delphi_Statements{
Dump book text
Dumps "book text" property of every book in loaded plugins
Source for most copypasta:
https://github.com/AngryAndConflict/skyrim-utils/
}
unit UserScript;
interface
const
// change this to False to actually write the text files
DRY_RUN = True;
// change this to False if you want to continue after the first error
STOP_ON_ERROR = True;
implementation
uses
//mteFunctions,
System.Classes, // TStringList
System.SysUtils, // CreateDir, IntToStr
xEditAPI;
// check if rec editor ID contains any string in blacklist
function IsBlacklisted(rec: IwbMainRecord; blacklist: TStringList): Boolean;
var
i: Cardinal;
edid: string;
//needle, haystack: string;
begin
Result := False;
edid := EditorID(rec);
for i := 0 to Pred(blacklist.Count) do
begin
//needle := blacklist[i];
//haystack := edid;
//AddMessage('====' + edid + '====');
//AddMessage('looking in ' + haystack);
//AddMessage(#9 + ' for ' + needle + '...');
//AddMessage(' test: ' + IntToStr(Pos(needle, haystack));
if (Pos(blacklist[i], edid) > 0) then
begin
Result := True;
//AddMessage(blacklist[i]);
AddMessage('Skipping: ' + edid);
Break;
end;
end;
end;
function Initialize: Integer;
var
i, j: Cardinal;
f: IwbFile;
bookGroup: IwbGroupRecord;
book: IwbMainRecord;
btext, outputPath, fname: string;
blacklist, output: TStringList;
begin
Result := 0;
outputPath := ProgramPath + 'Output\';
if not DRY_RUN then
begin
CreateDir(outputPath);
end;
blacklist := TStringList.Create;
try
// match editor IDs containing any of following
blacklist.Add('DLC1ElderScroll');
blacklist.Add('DLC1FVBook01Falmer');
blacklist.Add('DLC1FVBook02Falmer');
blacklist.Add('DLC1FVBook03Falmer');
blacklist.Add('DLC1FVBook04Falmer');
blacklist.Add('DLC2BlackBook');
blacklist.Add('DA04ElderScroll');
blacklist.Add('ExpSpiderCrftBook');
blacklist.Add('QA');
blacklist.Add('Recipe');
blacklist.Add('SpellTome');
AddMessage('Dumping books...');
for i := 0 to Pred(FileCount) do
begin
f := FileByIndex(i);
bookGroup := GroupBySignature(f, 'BOOK');
for j := 0 to Pred(ElementCount(bookGroup)) do
begin
book := ElementByIndex(bookGroup, j);
if IsMaster(book) then
begin
book := WinningOverride(book);
// if it's not blacklisted, do things
if (not IsBlacklisted(book, blacklist)) then
begin
// this it the raw text content of each BOOK's DESC attribute
btext := GetElementEditValues(book, 'DESC');
if (Length(btext) > 0) then
begin
fname := outputPath + GetElementEditValues(book, 'EDID') + '.txt';
AddMessage('Outputting to: ' + fname);
if not DRY_RUN then
begin
output := TStringList.Create;
try
try
output.Add(btext);
output.SaveToFile(fname);
//AddMessage('Successfully saved!');
//AddMessage(#9 + fname);
//AddMessage(#20);
except
AddMessage('===ERROR=== Unable to save ' + fname + '!');
if STOP_ON_ERROR then
begin
raise;
end;
end;
finally
output.Free;
end;
end;
end;
end;
end;
//AddMessage('----------')
end;
end;
//AddMessage(btext)
finally
blacklist.Free;
end;
end;
end.