xEdit - Copy Unenchanted Armor Records

Modding guides & discussions for Skyrim SE
Post Reply
User avatar
FiftyTifty
Site Admin
Posts: 31
Joined: 27 Jun 2023, 12:50

xEdit - Copy Unenchanted Armor Records

Post by FiftyTifty »

This tutorial assumes 3 things.

1. You have the Creation Kit installed
2. You have SSEEdit installed
3. You have created an .esp or .esm plugin

The cool thing here is that the syntax for each xEdit version is the same. All that differs between them (TES4Edit, FO3Edit, FNVEdit, etc.) is that some records are different and/or don't exist. Such as Oblivion not having records for Volumetric Rays, but Skyrim SE & Fallout 4 do.

For reference later in the tutorial, here is a non-exhaustive list of the scripting functions available in xEdit: https://tes5edit.github.io/docs/13-Scri ... tions.html

--

The first thing we have to do, is figure out what we want to do. The best approach, when you don't know what you are doing, is to break down the process into each individual "thing" you want to automate. So let's outline that.

Goal
Copy over armour records to my .esp plugin

Steps
  • Have an existing esp file
  • Find all armours
  • Get all armours that have armour resistance
  • But only if they aren't enchanted versions, as those use the armour values of the base unenchanted version
Sounds easy, right? It is if you know how to do it. If you don't, It sounds intimidating as heck.

Let's begin!
User avatar
FiftyTifty
Site Admin
Posts: 31
Joined: 27 Jun 2023, 12:50

Re: xEdit - Copy Unenchanted Armor Records

Post by FiftyTifty »

Let's have a look in the CK what we're trying to get from the game's data, and see if there is anything different between various armor records.

Image

Let's take a look at the Hide Helmet record:

Image

That's pretty cool. We can edit all the values, and see what's there. Lots of cool editable parts.

Now let's take a look at one of the many enchanted variants:

Image

Since this is an enchanted variant, it's using the data of the base Hide Helmet record, and we cannot change those values. That's good, as we can make changes to Hide Helmet, and those will be passed on to all of the enchanted variants.

Before we open xEdit, first thing we should do is get the FormID of this enchanted version. That way, we can actually see the data, which is what our script will read/modify.

Initially, the Form ID column is hidden. Click-drag the right edge of the Editor ID column to the right, and you'll see the Form ID column appear

Image

Let's note it down for reference: 0010DFB8

Now let's open xEdit, and use that FormID to go straight to it. Select all of the usual Skyrim master files along with your plugin:

Image

Once it's loaded, you'll see something similar to the following:

Image
User avatar
FiftyTifty
Site Admin
Posts: 31
Joined: 27 Jun 2023, 12:50

Re: xEdit - Copy Unenchanted Armor Records

Post by FiftyTifty »

Near the top left of the window, you will see two text entry boxes: FormID, and Editor ID. FormID is MUCH faster and should always be used when possible.

Since we have that FormID from earlier 10DFB8, type or paste it into that FormID entry box and then hit enter:

Image

Image

That has taken us straight to the EnchArmorHideShieldResistMagic03 record in Skyrim.esm. And we see the actual data stored inside that record, which can be edited (!AFTER COPYING TO YOUR MOD'S PLUGIN!) manually or through scripting. Notice how everything is laid out as a column of trees, with the hierarchy being from left to right.

Now let's go to the original Hide Shield record, which holds the actual data. See that entry at the bottom called TNAM - Template Armor? That is set to the Full FormID (which is: Editor ID + "Name" + [Signature:FormID] ) of the record.

That's handy, as we can jump straight to it by ctrl+clicking on any entry that has a Full FormID value. So ctrl+click on it:

Image

Image

Wow, that took us to the referenced Hide Shield record. And what do we see right off the bat?

Image

It doesn't have an entry for the TNAM - Template Armor field. This is awesome, as we can check if this exists or not. If it does, we'll skip copying it over. And if it doesn't, we'll copy that armor record to our plugin, as it's the base unenchanted version.
User avatar
FiftyTifty
Site Admin
Posts: 31
Joined: 27 Jun 2023, 12:50

Re: xEdit - Copy Unenchanted Armor Records

Post by FiftyTifty »

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?

Image

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.

Image

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...

Image

A new window will pop up, with a drop-down bar called Script. Click on that, and select the script we just made:

Image

After selecting it, it will show our code. Make sure you got the right script selected if it doesn't! And then, click OK.

Image

If you did everything properly, you'll see something like this, with a bunch of messages in the xEdit window:

Image

Let's open our plugin, and take a look at what is now inside there:

Image

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.

Image

Big scary window appeared! Wait 3 seconds, and then click the nice green button. And click yes to the next message that pops up.

Image

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.
User avatar
FiftyTifty
Site Admin
Posts: 31
Joined: 27 Jun 2023, 12:50

Re: xEdit - Copy Unenchanted Armor Records

Post by FiftyTifty »

reserved
Post Reply