In your
Skyrim\Edit Scripts\ folder, you'll find a lot of
.pas files. These are text files that contain Pascal source code, which is the coding language that xEdit uses to automate actions via script. You can open these with any text editor, like the basic Windows Notepad, or the better software Notepad++. You should get Notepad++ to make things easier, as it has syntax highlighting.
Look for the
_newscript_.pas file. Create a copy of it, and rename it to something you can easily identify. I'll call my copy
AAAFyTy - Copy Base Armor Records.pas
Here's the template we'll use for this script:
Code: Select all
unit userscript;
//Called before processing
function Initialize: integer;
begin
end;
//Called for each selected record
function Process(e: IInterface): integer;
begin
AddMessage('Processing: ' + FullPath(e));
end;
//Called after processing
function Finalize: integer;
begin
end;
end.
This may look intimidating, but it's very simple. We have 3 default functions that will always be run by xEdit. Inside these is where we put our code, including our own functions.
We need to do 2 things. First, we need to actually find our destination plugin. Let's do that by creating a global variable (variables outside of functions, which can be accessed by any function) of the
IInterface type, and a function to find our file and assign it to that variable. We will put this inside the Initialize function.
And we'll use a constant variable to hold the name of our plugin, which we will look for in the code.
For future reference,
IInterface is a type that is any piece of actual data (not the data value, but the data entry itself). If you can select it in xEdit, it's an
IInterface.
To declare a variable, we have to make a line before it called
var and we declare our variables after it. Let's refer to our plugin as
fileDest.
Code: Select all
unit userscript;
var
fileDest: IInterface;
//Called before processing
function Initialize: integer;
begin
end;
Now, how do we access the list of files loaded by xEdit?
By using the
FileByIndex &
FileCount functions inside a
For loop. You can see their documentation here (use ctrl+f to go to those specific functions):
https://tes5edit.github.io/docs/13-Scri ... tions.html
We'll also need to make a variable that will act as our counter. As we will make code that does the following:
1. For the number of files loaded
2. Get each file's name
3. If the file's name matches our plugin's
4. Set the
fileDest variable to that file
5. And stop looking at each file
Here's how that code now looks:
Code: Select all
unit userscript;
const //Constant variables that cannot be changed later on in the script. Use = and not := to set constant variables.
strDest = 'FyTy Damage Overhaul.esp'; //Constant variables don't need their variable type declared
var //Global variables (not contained inside a function)
fileDest: IInterface;
//Called before processing
function Initialize: integer;
var //Local variables contained inside the Initialize function
iCounter: integer; //Local variable used in our for loop
fileCurrent: IInterface; //Local variable to access the current file we're working with
begin
//We start iCounter at 0, as Pascal is 0 based: the first entry in a collection is at index 0, the 2nd is at index 1, etc.
//FileCount is a function which returns the number of loaded files, INCLUDING the game's executable
//We subtract 1 from it, as we are given the total number of files, nut we are working with file indexes (which start at 0).
for iCounter := 0 to FileCount - 1 do begin
//By using iCounter's value, and the FileByIndex function, we can get the file at that specific place in the xEdit load order
fileCurrent := FileByIndex(iCounter);
if GetFileName(fileCurrent) = strDest then begin
fileDest := fileCurrent;
exit;
end;
end;
end;
Now we can move on to our code to actually do stuff with what we selected in xEdit. Here's what we need to do:
1. Check if our file has been found (if not, don't run any code!)
2. Check if the current record being processed is an armor record (if not, don't run any code!)
3. Check if the current armor record isn't an enchanted variant (if it is, don't run any code!)
4. If all checks pass, copy that armor record to our file
For each of the functions being used in this code, refer to the docs to see how and why they're being used here. There's lots of cool things we can use in xEdit's scripting language!
Here's what the above 4 steps look like in code, complete with comments. If it's daunting, don't worry. Read the tutorial, look at the comments, and ctrl+f the various function calls to the xEdit scripting docs. And after that, it'll sink in as you do more scripting on your own.
Code: Select all
unit userscript;
const //Constant variables that cannot be changed later on in the script. Use = and not := to set constant variables.
strDest = 'FyTy Damage Overhaul.esp'; //Constant variables don't need their variable type declared
var //Global variables (not contained inside a function)
fileDest: IInterface;
//Called before processing
function Initialize: integer;
var //Local variables contained inside the Initialize function
iCounter: integer; //Local variable used in our for loop
fileCurrent: IInterface; //Local variable to access the current file we're working with
begin
//We start iCounter at 0, as Pascal is 0 based: the first entry in a collection is at index 0, the 2nd is at index 1, etc.
//FileCount is a function which returns the number of loaded files, INCLUDING the game's executable
//We subtract 1 from it, as we are given the total number of files, nut we are working with file indexes (which start at 0).
for iCounter := 0 to FileCount - 1 do begin
//By using iCounter's value, and the FileByIndex function, we can get the file at that specific place in the xEdit load order
fileCurrent := FileByIndex(iCounter);
if GetFileName(fileCurrent) = strDest then begin
//Set our GLOBAL variable to the same file as our LOCAL variable
fileDest := fileCurrent;
//And since we found the file, we can exit the Initialize function!
exit;
end;
end;
end;
//Called for each selected record after the Initialize function
//The current record being processed is accessed by the variable e as declared in the next line
function Process(e: IInterface): integer;
begin
//A safety check. If the code didn't find our file, fileDest will not hold any data.
//Hopefully xEdit will throw a big fat error at you whent rying to use an unfilled variable.
//But if it doesn't, let's play it safe and make sure to avoid running any code.
if GetFileName(fileDest) = '' then
exit;
//Now check the record signature of the current record, to make sure we're only working with armor records
//If the signature isn't 'ARMO', stop the code here
if Signature(e) <> 'ARMO' then
exit;
//Check to make sure there is no Template Armor entry, which means we're working with the base armor record.
if ElementExists(e, 'TNAM - Template Armor') then
exit;
//If we got this far, you'll see code in the xEdit message window on the right.
//That means our file has been found, we're running the code on an ARMO record
//And it isn't an enchanted variant, but the base armor record!
AddMessage('Processing: ' + FullPath(e));
//Now let's copy it over to our plugin file!
//The four following values mean the following:
//e = The currently selected element
//fileDest = our plugin file
//false = don't make the copy a new standalone record (new FormID)
//true = copy over all the data from the original
wbCopyElementToFile(e, fileDest, false, true);
end;
//Called after processing
function Finalize: integer;
begin
end;
end.
end.
And here's the code without comments:
Code: Select all
unit userscript;
const
strDest = 'FyTy Damage Overhaul.esp';
var
fileDest: IInterface;
function Initialize: integer;
var
iCounter: integer;
fileCurrent: IInterface;
begin
for iCounter := 0 to FileCount - 1 do begin
fileCurrent := FileByIndex(iCounter);
if GetFileName(fileCurrent) = strDest then begin
fileDest := fileCurrent;
exit;
end;
end;
end;
function Process(e: IInterface): integer;
begin
if GetFileName(fileDest) = '' then
exit;
if Signature(e) <> 'ARMO' then
exit;
if ElementExists(e, 'TNAM - Template Armor') then
exit;
AddMessage('Processing: ' + FullPath(e));
wbCopyElementToFile(e, fileDest, false, true);
end;
function Finalize: integer;
begin
end;
end
Now you will be wondering, what is
ARMO? Why is that being checked? How do you know that's what you should check for?
Every record in Bethesda's games (except maybe Starfield, but that's not a game it's a pile of ew) has a thing called a
Signature. A 4 character (letter/number) long sequence. Remember that armour record we looked at? Let's expand the Record Header entry in xEdit, and see what's there.
See that entry called
Signature? It has the value
ARMO, which is the same for every single armor in the game. And since we only want to copy over armor records, we make sure to check for that.
Now let's run our script on all the armour records in
Skyrim.esm. To do that, go to
Skyrim.esm, expand it, and right-click the category called
Armor and then choose
Apply Script...
A new window will pop up, with a drop-down bar called Script. Click on that, and select the script we just made:
After selecting it, it will show our code. Make sure you got the right script selected if it doesn't! And then, click OK.
If you did everything properly, you'll see something like this, with a bunch of messages in the xEdit window:
Let's open our plugin, and take a look at what is now inside there:
Hot darn! Look at all those armour records we just copied over. We didn't have to do it one by one! And we skipped all the enchanted variants!! All in our own script!!! In less than a second!!!!
Of course, since we copied over *every* armor record that met our criteria, some things slipped through. We probably don't want to have the SkinNaked armour record in there, so let's just delete that. Click on it, then press the Delete key.
Big scary window appeared! Wait 3 seconds, and then click the nice green button. And click yes to the next message that pops up.
No more SkinNaked!
But there's a few things that happened here. We just copied over the original ARMO records from Skyrim.esm. What about the other files that modified them, like Update.esm? That'd be good to have. Or what if we wanted to make changes to the ARMO records as well?
Let's get onto that below.