boozehoundblue Posted May 2, 2018 Share Posted May 2, 2018 I'm having a hard time getting a script together that will both compile in Delphi's Starter IDE and run in FO4Edit. It's typically one or the other, due to enums. For example, I can operate on DefType or ElementType returns, and have created a script that runs correctly, but it raises compile errors in IDE, which makes it difficult to check other syntax errors I may be making while trying to learn the language. That's a general problem I'm having. (But maybe I can add 'wbDefinitionsFO4' from source to uses to compile, then remove? Idk.) Now I have a script which doesn't (yet) utilize those type returns and which does compile correctly but raises "Class declaration expected but '(' found", due to my apparently otherwise seemingly correct implementation of my own enumeration type, based off Delphi's online and TES5Edit's source examples. The TES5 Scripting Functions Wiki notes that the 'type' keyword is unsupported but it can be used to "alias a class name" (whatever that means). I don't know how to define this enumeration otherwise, or if I'm even allowed to use it within the confines of the API. (It's all magic and a bunch of := and ; but not += to me. Yeesh.) Anyway, I'm trying to write a script that will write a collection of records to json(s). In order to do that, I first want to remove the numbering from things like 'Value #1' and otherwise put elements that are described as arrays (in 'wbDefinitionsFO4') into arrays (because ElementType doesn't tell the whole story in that regard). This entails pre-compiling lists (or delimited strings) of Names for each record type to search against as I cycle through the container(s) in order to format the json correctly. To do the search, I thought I'd grab the signature of the record and run it through a case statement, so I know which particular strings to search for. Can't do that for strings, and I don't want to write a bunch of if else statements if I can help it. So I figured I'd change to signature to its unique integer and run it through a case of similarly pre-determined signature enum integers and fork from there. Smart? Idk.(A timeit test in Python showed that, even with conversion cost, 'int in [ints]' was 2x+ faster than 'string in [strings]'. Python doesn't have cases, and from the Wiki, Edits scripts don't support 'in'. Yeesh.) I can't get my script to run in Editor, let alone set up some bench comparisons. (It did work before I added the enum/case stuff, and output in same format as an xDump, since my Write functions are adapted directly from it.) Rant over. Code with snipped enum below. Any insight/guidance would be appreciated. Thanks! unit myEditScript; interface uses xEditAPI, Classes, SysUtils, StrUtils, Windows, Math; type TSig = ( _AACT = 65656784, _NPC_ = 78806795 ); // Global variables var List : TStringList; var aIndent : string; procedure WriteElement(e : IInterface; aIndent: string = ''); procedure WriteContainer(e : IInterface; aIndent: string = ''); function RemoveNumbering(str : string): string; function SigToTSig(SigStr: string): TSig; function SigSwitch(Sig: TSig): integer; implementation function RemoveNumbering(str : string): string; var i : integer; begin Result := ''; i := Pos(' #', str); if (i = 0) then Result := str else Result := copy(str, 1, i-1); end; //idk if this works yet, different than my Python test function function SigToTSig(SigStr: string): TSig; var i, ch, e, Sig : integer; begin e := 3; for i := 1 to 4 do ch := ord(SigStr[i]); Sig := Sig + Round(ch*IntPower(100,e)); e := e - 1; Result := TSig(Sig); end; function SigSwitch(Sig: TSig): integer; begin Result :=0; Case Sig of _NPC_: Result:=1; End; end; procedure WriteContainer(e : IInterface; aIndent: string = ''); var i : Integer; begin for i := 0 to Pred(ElementCount(e)) do WriteElement(ElementByIndex(e, i), aIndent); end; procedure WriteElement(e : IInterface; aIndent: string = ''); var Name : string; Value : string; Str : string; defTypeStr : string; eleTypeStr : string; SigStr : string; begin Name := ShortName(e); Value := GetEditValue(e); SigStr := Signature(e); //first if just a test to see if it will catch if (SigSwitch(SigToTSig(SigStr)) = 1) then List.Add('Match'); if (Name <> 'Unused') then begin if (Name <> '')then Str := aIndent + RemoveNumbering(Name); if (Name <> '') or (Value <> '') then aIndent := aIndent + ' '; if (Value <> '') then begin Str := Str +': ' + Value; List.Add(Str) end else begin if (Name <> '') then List.Add(Str) end; end; WriteContainer(e, aIndent); end; // Called when the script starts function Initialize : integer; begin List := TStringList.Create; end; // Called for each selected record in the TES5Edit tree // If an entire plugin is selected then all records in the plugin will be processed function Process(e : IInterface) : integer; begin aIndent := ''; WriteContainer(e, aIndent); end; // Called after the script has finished processing every record function Finalize : integer; var filename : string; begin filename := ProgramPath + 'Edit Scripts\Test.txt'; AddMessage('Saving to ' + filename); List.SaveToFile(filename); List.Free; end; end. Link to comment Share on other sites More sharing options...
boozehoundblue Posted May 3, 2018 Author Share Posted May 3, 2018 (edited) Removed TSig type entirely. Compiles, but execution in Editor now raises 'Array type required' when attempting to get a character from a string (i.e. ch := SigStr in SigtoInt function). Idk how to get a char otherwise. unit myEditScript; interface uses xEditAPI, Classes, SysUtils, StrUtils, Windows; // Global variables var List : TStringList; var aIndent : string; var Sig : integer; procedure WriteElement(e : IInterface; Sig : integer; aIndent: string = ''); procedure WriteContainer(e : IInterface; Sig : integer; aIndent: string = ''); function RemoveNumbering(str : string): string; function SigToInt(SigStr: string): integer; function SigSwitch(Sig: integer): integer; implementation function RemoveNumbering(str : string): string; var i : integer; begin Result := ''; i := Pos(' #', str); if (i = 0) then Result := str else Result := copy(str, 1, i-1); end; function SigToInt(SigStr: string): integer; var i, e, Sig : integer; c : string; ch : Char; begin e := 3; for i := 1 to 4 do ch := SigStr[i]; Case e of 0: Sig := Sig + ord(ch); 1: Sig := Sig + ord(ch)*100; 2: Sig := Sig + ord(ch)*10000; 3: Sig := Sig + ord(ch)*1000000; End; e := e - 1; Result := Sig; end; function SigSwitch(Sig: integer): integer; begin Result :=0; Case Sig of 78806795: begin Result := 1; List.Add('NPC_ found'); end; End; end; procedure WriteContainer(e : IInterface; Sig : integer; aIndent: string = ''); var i : Integer; begin for i := 0 to Pred(ElementCount(e)) do WriteElement(ElementByIndex(e, i), Sig, aIndent); end; procedure WriteElement(e : IInterface; Sig : integer; aIndent: string = ''); var Name : string; Value : string; Str : string; defTypeStr : string; eleTypeStr : string; SigStr : string; begin Name := ShortName(e); Value := GetEditValue(e); SigSwitch(Sig); if (Name <> 'Unused') then begin if (Name <> '')then Str := aIndent + RemoveNumbering(Name); if (Name <> '') or (Value <> '') then aIndent := aIndent + ' '; if (Value <> '') then begin Str := Str +': ' + Value; List.Add(Str) end else begin if (Name <> '') then List.Add(Str) end; end; WriteContainer(e, Sig, aIndent); end; // Called when the script starts function Initialize : integer; begin List := TStringList.Create; end; // Called for each selected record in the TES5Edit tree // If an entire plugin is selected then all records in the plugin will be processed function Process(e : IInterface) : integer; begin aIndent := ''; Sig := SigToInt(Signature(e)); WriteContainer(e, Sig, aIndent); end; // Called after the script has finished processing every record function Finalize : integer; var filename : string; begin filename := ProgramPath + 'Edit Scripts\Test.txt'; AddMessage('Saving to ' + filename); List.SaveToFile(filename); List.Free; end; end. Edited May 3, 2018 by boozehoundblue Link to comment Share on other sites More sharing options...
zilav Posted May 5, 2018 Share Posted May 5, 2018 Just use sig := Signature(e); if sig = 'NPC_' then ... else if sig = 'ABCD' then ... else if sig = 'DCBA' then ...; instead of "case" and you won't need sig2int. Link to comment Share on other sites More sharing options...
boozehoundblue Posted May 6, 2018 Author Share Posted May 6, 2018 (edited) True, I just thought an integer case would ultimately be more performant than comparing strings, as there's hundreds of unique signatures defined. (I also thought I'd have to nest those else ifs). Still, why can't I get a char from string? From my understanding of the Wiki and JvInterpreter, this functionality was made possible in 1.51.2(?) Edited May 6, 2018 by boozehoundblue Link to comment Share on other sites More sharing options...
zilav Posted May 6, 2018 Share Posted May 6, 2018 xEdit uses scripting interpreter with pascal syntax, it has limited capabilties and quite slow no matter what you do. Your issue is caused by how it deals internally with types and storing all variables in Variant type. Signature() returns not a string, but an array of 4 chars. Interperter has some problems when passing types around in functions, so SigToInt(Siganture(e)) doesn't work even though function's papameter is declared as a string, but manually assigning to a string does work because it explicitely converts Variant array value into a string var s: string; s := Signature(e); SigToInt(s); And in real Pascal you will simply do var s: array [0..3] of AnsiChar; s := Signature(e); case PCardinal(@s)^ of ... end; without any char conversions because both signature and integer/cardinal are 4 bytes. But as i said this is not a real Pascal and you don't need to use such constructs in scripts. I don't even understand the purpose of "case" in your script, is it some kind of a logging or statistics gathering? Link to comment Share on other sites More sharing options...
boozehoundblue Posted May 7, 2018 Author Share Posted May 7, 2018 Thanks for clearing that up! Got it working. According to the Wiki, Signature(e) is defined as returning a string. As far as the Case statement's purpose, I intend it as a means to operate on records individually. No, it doesn't serve a particular purpose in my posted script, but I plan to incorporate field exclusions from an ini to ultimately produce json (and other) files for different record types. The generic Write procedure(s) were mostly for me to have something to study. Since both 'Case int of' and 'if SigStr = 'ABCD' accomplish the same task, the choice comes down to which looks/performs better. Maybe the performance difference isn't worth the hassle or is negated by the cost of conversion. I see what you mean about being quite slow now, having run a working version over Fallout4.esm's Npcs. Yikes. Link to comment Share on other sites More sharing options...
zilav Posted May 7, 2018 Share Posted May 7, 2018 The purpose of scripting in xEdit is to automate manual editing tasks, so it is always magnitudes faster than doing by hands even with relative slowness of scripts. If you need real performance, then get the free Delphi Starter edition and compile from the source code of xEdit. Link to comment Share on other sites More sharing options...
boozehoundblue Posted May 8, 2018 Author Share Posted May 8, 2018 Yes, I've compiled a modified xDump.exe. The advantage of an xEdit script in my case is the visual aspect and ability to arbitrarily select records. If nothing else, perhaps I can use an xEdit script to create an ini file the xDump can utilize. Thanks again for the pointers. Link to comment Share on other sites More sharing options...
Recommended Posts