27th February 2012
Papyrus for Beginners
Introduction
When modding Skyrim, you will likely find yourself needing to define behaviour that cannot be set up with the Creation Kit, like removing a key from the player when they open a door, or killing a character when a lever is pulled. In order to define this behaviour, you will need to use Skyrim's scripting language, Papyrus.
This tutorial is aimed at people with little or no prior programming experience, and is an introduction to Papyrus that shows you how to use its simpler features without going too in-depth or getting too technical.
The first section of this tutorial is a "My First Script" tutorial, in which you will write a simple script in order to familiarise yourself with the process. After that, I will talk about some of Papyrus' more useful features.
Not every aspect of Papyrus is covered in this tutorial, but if you expect only to have to write simple scripts and you aren't interested in the technical side of things, then it should be enough to get you going. The topics covered in this tutorial are:
More complex features, such as states and loops, will be discussed in a later tutorial, in which I will go into more technical detail. If you expect to do more than very simple scripting, or if you're interested in the technical details of papyrus, then keep a look out for this upcoming tutorial.
In the meantime, if you run into "function not defined" errors and don't know why, take a look at my "Accessing External Functions and Properties in Papyrus" tutorial, which was written to help with exactly that.
It's also worth mentioning that, while they are used commonly in the creation of quests and such, Papyrus fragments are not covered in this tutorial. Almost everything you learn here should be applicable within them, though.
My First Script
In this section of the tutorial, we are going to create a plugin file that uses a Papyrus script to print the text "Hello, World!" to the screen. It will briefly go over a few important aspects of Papyrus, and setting up a script to be used by a data file in the Creation Kit.
Getting Set Up
Before you write any code, you'll want to get a development environment set up. While the Creation Kit does have an internal text editor that you can use, it's lacking in a lot of good features.
Take a look at the Creation Kit wiki's list of text editors for instructions on how to set them up as a Papyrus development environment. Personally, I use Notepad++. My setup highlights my scripts like on the wiki, has function autocompletion, and allows me to compile my scripts with a simple keyboard shortcut.
Script Header
The first part of the first line of any Papyrus script is a ScriptName declaration, which specifies the name that the Creation Kit uses to recognise the script. This name will have to be the same as the filename, when the script is saved. Typically, a ScriptName declaration looks something like this:
ScriptName MyFirstScript
At a minimum, that first line always starts with "ScriptName", followed by the name of the script you're writing. After that, you'll almost always have an extra bit that says "extends <Type>". The type you use here should be the type of object on which this script should run.
ScriptName MyFirstScript extends Quest
This script is going to run on a quest, so after its ScriptName declaration comes "extends Quest". Most commonly, your scripts will extend one of these object types:
After this first line, it's often a good idea to add a documentation comment. Documentation comments are surrounded by curly braces - {} - and are used by the Creation Kit as tooltips for scripts and functions:
ScriptName MyFirstScript extends Quest {This is my first script. It prints the message "Hello, World!" to the screen once.}
Events
In order to have code that runs, you need to put it inside a native event. Native events are a special kind of function that the game calls in reaction to certain events. In your script, you can define the code that will run when the game calls these events. I describe events and how they work in more detail later in this tutorial.
The event we're going to use here is called OnInit. OnInit specifically is an event that is called automatically when the object on which a script runs has the script attached to it, and again whenever that object is reset. There are other events that are called by the game in different situations, but for this script we want to use OnInit:
ScriptName MyFirstScript extends Quest {This is my first script. It prints the message "Hello, World!" to the screen once.} Event OnInit() EndEvent
As you can see, events start with the keyword "Event", followed by the name of the event. After the name comes a pair of brackets. Inside these brackets will go any parameters of this event. Since the OnInit event in particular doesn't have any parameters, these brackets are empty. After all of the code that makes up the body of your event, the "EndEvent" keyword designates the end of the event.
Printing The Message
When writing any script, there are two big questions you need to ask yourself:
- When do I want to do something?
- What do I want to do?
By using the OnInit event, we've set up our script to run some code as soon as the game is loaded (or, at least, that's how it will work once we've also set it up in the Creation Kit). What's left now is "What do I want to do?".
In this script, we want to print a message saying "Hello, World!" to the screen. There are a few ways to do this, but we're going to use the Notification function in this case. This function is a global function (you don't need to worry about what that means for now) that is defined inside the "Debug" script, so it needs to be called like this:
ScriptName MyFirstScript extends Quest {This is my first script. It prints the message "Hello, World!" to the screen once.} Event OnInit() Debug.Notification("Hello, World!") EndEvent
Compiling The Script
Now that our script is ready, save it in the Data/Scripts/Source folder in your Skyrim installation directory as MyFirstScript.psc. Papyrus source files should always be saved with their filename the same as their ScriptName, and the file extension .psc.
Once your script has been saved, it also needs to be compiled. How you do this will depend on how you've set up your environment, but if you've used Notepad++ like I have, and you followed the setup instructions on the Creation Kit Wiki, you should need only to use a keyboard shortcut like Ctrl+F5 to compile your script.
In The Creation Kit
Now that you script is saved, compiled, and ready to run, you need to create a new plugin file to make it run in the game. This script will need to be attached to a new "Quest" object, which we'll create in our plugin.
To create this plugin, first open up the Creation Kit and load both Skyrim.esm and Update.esm files.
Once everything's loaded, navigate to the Quest area in the Object Window (under the Character section), right-click on the right hand panel of the object window and select "New Quest".
When creating this quest, I recommend ticking the "Start Game Enabled" and "Run Once" flags on this quest.
The "Start Game Enabled" flag will mean that your script will start right away, so you don't need to worry about making sure it's running if you need it to handle stuff for which it needs to be running.
The "Run Once" flag prevents your quest from running more than once, which also prevents it from being reset when it's started. If this flag weren't checked, the OnInit event would be called twice in a row instead of just once, as the script is attached to the quest then the quest is reset when it is told to start.
Once the quest has been created, go to its "Script" tab and click the "Add" button. Here, you should be able to find and select the script you just saved.
Testing Your Script
After you've attached your script to your quest, save your plugin, then activate it using your favourite mod manager and load up Skyrim. If everything goes according to plan, the message "Hello, World!" should appear when you load your saved game.
Congratulations, you've now written and set up a working script!
Having a quest with a script that runs as soon as your mod is installed is a useful way to control the setup of your mod. You may find yourself creating a plugin very similar to this one that gives the player a certain perk or item once your mod is installed.
Now that you've worked through the "My First Script" section of this tutorial, the rest of this tutorial will talk about some of Papyrus' simplest and most useful features. You'll have seen a couple of these already, now, but some may still be new to you.
You can expect to use all of the features discussed here a lot, so it's important that you understand them and that you feel comfortable using them. Once you've completed this tutorial, you should try to modify the plugin you created in the "My First Script" section so that it makes use of all the features talked about in the rest of this tutorial.
Variables
Often, you'll find that you need to somehow store information for later use. For this purpose, you will normally use a variable. Variables can be categorised into two types, value variables and object variables.
Value Variables
These variables directly store a value. The only possible types of value variables are:
-
int
Representing "integer", these variables can store whole numbers, both positive and negative.
-
float
Representing "floating point number", these variables can store non-whole numbers, both positive and negative.
-
bool
Representing "boolean", these variables can only store two values: true or false. If you need to store a variable that acts somewhat like an on/off switch, you'll want to use this type.
-
string
These variables store strings of characters. These values are always surrounded by double quotes on either side. For example: "This is an example of a string"
Object Variables
Instead of storing values like value variables, these variables point at an object. There are two differences here - the type of data involved, and the way in which it is stored.
Instead of using values like 2, or -4.53, or true, these variables let you access objects. Objects are things like quests, actors, and spells.
Instead of storing something, object variables point at something. To illustrate the difference, magine you have two people, each of them having been told to remember something, and you are allowed to ask them to tell you what they've remembered or tell them to remember something else.
If you ask Person 1 to tell you what she's remembered, she might tell you "5". She's a value variable of type "int" - she's remembering an integer value. If you tell her that, instead of "5", she should remember "6", then what she remembered before is gone forever and if you ask her again what she's remembering, she'll tell you "6".
If you ask Person 2 to tell you what she's remembered, she might tell you "Go to 221B Baker Street". If you follow her directions to 221B Baker Street, you'll find a person, which in Papyrus would be said to be of type "Actor", called Sherlock Holmes. Using these directions, you could find Sherlock and give him an apple, but Person 2 wouldn't know about that. She is an object variable of type "Actor" - she's remembering where an actor is.
If you tell her that, instead of "Go to 221B Baker Street", she should remember "Go to 4 Privet Drive, and look in the cupboard under the stairs", then her directions now point to Harry Potter instead of Sherlock Holmes. However, unlike before, Sherlock Holmes still exists, and probably still has the apple you gave him. This is what it means to point to information, as opposed to storing it.
As I mentioned earlier, the default value of all object variables is a special value called "None". If an object variable contains this value, then it not pointing at any object. It's important to be aware of this value, as it can be very useful to check whether or not an object variable is set to None.
Creating Variables
In order to create a variable, you need to declare it. The syntax for declaring a variable looks like this:
int foo
That line creates a variable of type "int", which is called "foo". Because we haven't specified a value to give it, it will instead use the appropriate default value for its type.
Variables can be declared anywhere in your script, so long as it's after your ScriptName declaration. If they are declared outside of a function definition, then you can only initialise them (assign a value as they're created) to what is known as a literal.
For value variables, a literal is a value in its simplest form - it cannot be an expression. For example, 2 is a literal, but 1+1 is not. For object variables, the only literal available is None, which is also the default value for object variables, so there's never any need to give them an initial value.
The OnInit event, which we used in the "My First Script" section, is an excellent place to initialise variables that require an initial value that has to be calculated or retrieved somehow.
If a variable is declared within a function, then that variable is only available within that function. The same goes for blocks of other types too, some of which are described later in this tutorial. If a variable is declared within a block, that variable is only available within that block.
Changing A Variable's Value
Once you have declared your variable, it's possible to change its value with an assignment operation. To assign a new value to a variable, you need to use the assignment operator, which is the equals sign: =
Unlike in mathematics, in Papyrus (and other scripting and programming languages) it matters what is on each side of the equals sign. On the left, there must be something in which a value can be stored, i.e. a variable or a property (more on properties later). On the right, there must be a value that can be stored there. This value can take the form of an expression, in which case the expression will be evaluated before the assignment occurs. For example:
foo = 1 + 1
When the line above is executed, the expression is evaluated first. You can visualise this as "1 + 1" being replaced with its result, so now our line of code looks like this in our minds:
foo = 2
Now that the right hand side of our assignment is a literal (remember them?), the assignment itself takes place, and our "foo" variable is now storing the value "2".
Remember, when we declared this variable, we specified that the type of information it is able to store is "int". Now, what would happen if we tried to assign a value to it that is of a different type? The answer depends on the type of the information and the type of the variable.
Casting is the process of changing information of one type to information of another type. For some type conversions, the compiler is able to convert the information automatically. For example, if I try to assign the information 2, which is of type "int", to a variable of type "bool", the compiler will automatically convert that information to the boolean value "true".
Not every type conversion can be done automatically, though. Some type conversions, such as converting "float" to "int", cannot be done automatically but can be done manually. This is done through the use of the keyword as, in the form "<information> as <type>". For example:
foo = 3.41 as int
If you attempt to cast information into an incompatible type, your script will not compile. The Creation Kit Wiki has a page documenting how various type conversions work, including auto-casting done by the compiler in the absence of the as keyword - Cast Reference
Using A Variable's Value
Using the value of a variable is easy. You can use the value of any value variable in an expression in the exact same way as you could use a literal of the same type. For example, imagine we have two value variables of type "int" declared with different initial values:
int foo = 2 int bar = -3
In an expression, such as one you would use when assigning a new value to a variable, value variables can be used just like literals of the same type. For example:
foo = bar
Just like before, you can visualise this as the variable being replaced with the value that it is storing. In our heads, the above line of code now looks like this:
foo = -3
Using object variables in scripts is also easy, although it is not the same as using value variables. Object variables are typically used in function calls, which are described in another section of this tutorial.
Outside of function calls, the only places you're likely to use object variables are in conditional statements, also described in another section of this tutorial, when comparing two object variables with one another or checking that an object variable is not set to None.
Properties
Because scripts are not internal parts of data files, and the compiler is not an integral part of the Creation Kit, you cannot directly refer to information from a data file in a script. If you want to refer to something in your data file, such as a particular actor, then you need to use something else as an intermediate. This "something else" is called a property.
A property allows your script to use information in a data file by providing a special interface that they can both access. From your script's point of view, a property is an arbitrary piece of information of a certain specified type, like "Actor" or "Quest". From the Creation Kit's point of view, it is a point at which information of that type can be inserted into the instance of your script attached to the object you're editing.
Declaring properties is very similar to declaring variables, except there are a couple of extra things you need to do, and properties cannot be declared inside functions. Here's an example of a typical property declaration:
ObjectReference Property foobar auto
As you can see, this is similar to a variable declaration, starting with the type and also specifying the name of the property, but there are a couple of extra keywords here. "Property", as I'm sure you've guessed, specifies that "foobar" is a property, not a variable.
The keyword "auto" basically just means that setting the value of the property, and asking for it, works just like it would for a variable. There's a little more to it than that, but I won't get further into it in this tutorial. Just know that you will practically always want to declare properties in this way, and once you do so you'll be able to treat them as though they were variables.
Once you've defined your property, you can use it in your script as though it points to the object that you want to use. Once you've attached your script to the right object in the Creation Kit, you will then need to use it to associate your property with the right object via a drop-down list of all objects of your property's type.
While this means that you must declare a property for every specific object that you want to use in your script, and set it up in the Creation Kit, it also gives you the great power to build scripts that can be used in multiple similar situations.
If you want to use specific objects in your script, then by naming your properties the same as the editorIDs of the objects at which you want them to point, the Creation Kit will be able to fill them automatically at the press of a button, so it's not quite the hassle it sounds like.
A good example of a script that has been written in such a way that it can be reused in similar situations is the script for the "Transmute" spell, which is called transmuteMineralScript.
This script uses three properties of type MiscObject to identify the three objects altered by the spell. It would be entirely possible to create a copy of the existing "Transmute" spell and, just by changing the values of these 3 properties, enable it to transmute an entirely different set of 3 MiscObjects.
One other important difference between variables and properties, into which I won't go into much detail in this tutorial, is that variables are private, whereas properties are public. What this means is that any variables you declare are accessible only within the script in which they were declared, whereas properties are accessible from any script, so long as you can get a handle on the object on which they exist.
It's also worth noting that it's completely possible to declare properties of value types, but they're only useful if you want to access its value from another script or if you want to tweak the initial value of a non-object type from the Creation Kit, which isn't something you'll need to do often. Using object types to insert information available only to the Creation Kit is a much more common and useful way to use a property.
Conditional Statements
You don't always want your events and other functions to do the same thing every time. Sometimes, you want it to do something different depending on the state of something, like the player's current health, or how strongly a container is locked. In order to do this, you need to use a conditional statement.
A set of conditional statements can consists of four different keywords and at least one expression. Simple sets conditional statements may only use two of these keywords, although more complex ones may use all four - some more than once. These four keywords are:
-
If
This keyword is used to signify the start of a set of conditional statements. It is followed by a literal or an expression, just like when assigning a value to a variable, which can consist of a combination of variables, properties, operators, and calls to functions with return values.
If and only if the result of an expression is true, a non-zero number, a non-empty string, an object that is not None, or an array with at least one element (arrays aren't covered in this tutorial) then the code within its conditional statement will be executed.
More specifically, this will happen if the result of the expression can automatically be cast to the boolean value "true", but you don't need to worry too much about that for now, and I won't be going more in-depth into it in this tutorial. For now, though, I will refer to such expressions as "evaluating to true" for this reason.
-
EndIf
This keyword is used to signify the end of a set of conditional statements.
-
ElseIf
Between If and an EndIf, an ElseIf statement statement can be used with an expression of its own to mean "if none of the preceding conditions in this set of conditional statements evaluated to true, and this expression does evaluate to true, run this code".
-
Else
After all If and ElseIf statements in a set of conditional statements, just before the EndIf, an Else statement can be used, without an expression, to mean "if none of the preceding conditions in this set of conditional statements evaluated to true, run this code". Because of this, if a set of conditional statements includes an Else statement, some code will always run.
Here's an example of a set of conditional statements from a script attached to an actor that demonstrates how you can use them to detect various states in the game:
If (IsDead()) ; This actor is dead ElseIf (GetActorValuePercentage("health") < 0.5) ; This actor is alive and at less than 50% health Else ; This actor is alive and at greater than or equal to 50% health EndIf
Note that the brackets that I've put around the expressions in that example aren't necessary, but I prefer to use them.
Functions
A function is essentially a wrapper for a section of code. When a function is called, the code within it is executed. There are various ways in which functions can be categorised, but for the purposes of this explanation I'm going to split them into 3 groups:
Native Functions
These functions are what allows Papyrus scripts to interact with the game engine. They do not contain Papyrus code, and are declared along with the keyword native. You will never ever need to declare or define native functions, but you should be aware of their existence, as they will be the building blocks of your scripts.
Both IsDead and GetActorValuePercentage, which I used in the example of a set of conditional statements above, are native functions.
Non-Native Functions
These functions contain segments of Papyrus code that can be run by calling the function. They are normally used to make tedious, common things very easy to do. For example, if I often find that I need to find the largest number out of two integers, I could create a custom function to do this:
int Function Max(int a, int b) global {Returns the largest value out of two integers} if (a > b) Return a else Return b endif EndFunction
You can ignore the global keyword for now. Just know that it means my function doesn't need to do anything with the object to which its script is attached. It's not necessary, but it is a good idea to use it when appropriate for reasons I won't get into in this tutorial. Most of your functions probably won't be global functions.
As you can see, in some ways the first line of a function definition is similar to the declaration of a property. It starts with a type - "int" in this case - then the keyword Function, then the function's name.
After the function's name is a set of brackets, which often has what looks like a bunch of variable declarations inside them, separated by commas. These are the function's parameters, which are used to pass information into the function when it is called. Not all functions have parameters, but the brackets are always required, even if they're empty.
NOTE TO SELF: Default values for parameters (not covered in this tutorial)
In this case, after the first line is a documentation comment. These are not necessary, but they are useful to document what the function does, especially what its parameters are for and what its return value represents.
A return value is a value that is the result of a function. The type of a function's return value is specified in the first line of its definition - "int" in this case. If a function has a return value, then the code within it must end with returning a value. Not all functions have a return value - those that don't simply lack a type identifier in their definition.
To return a value, the Return keyword is used, followed by a literal or an expression. In this example, the return value is one of the two values passed into the function as a parameter, depending on which one is the largest.
After the body of the function, the definition is finished with the use of the EndFunction keyword.
There are other, more advanced uses for functions (NOTE TO SELF: providing an externally accessible interface while manipulating privately accessible variables), but I won't get into them in this tutorial.
Events
Events are functions that are pretty much exactly the same as regular non-native functions, except they cannot have return values, there's a slight semantic difference, and for some types of object there are some special events.
Most native object types, such as ObjectReference and Quest, have a list of native events that will be called automatically by the game engine in response to certain things in the game. This is what's special about events - whereas other functions must always be called by Papyrus code, some events are called by the game.
Events are very important. Because other functions must be called by Papyrus code, and Papyrus code that actually runs can only exist inside function definitions, you will need to use native events as the points of entry for your code. Without the game calling these events in response to things happening in the game, none of the code you write could ever run.
Events are defined in pretty much the same way as non-native functions, except they can't have return values so they don't use a type identifier, and instead of the Function and EndFunction keywords they use the Event and EndEvent keywords.
When the game calls a native event, it passes certain information to that event in the form of parameters. Because the game needs to be able to pass this information in a particular format, your definitions for native events must use the same parameters as the game. This information can be found on the Creation Kit Wiki, which contains documentation for each native event. For example, here is its documentation for the OnHit event, which is called by the game for objects of type ObjectReference.
It is possible to define custom events, which use the same syntax as native events and work in exactly the same way as non-native functions without return values. The only potential benefit to this would be that there is a slight semantic difference between functions and events, but there is no functional difference. It will never be necessary for you to create custom events, but it's worth knowing that it is possible as you may see them in others' code.
Calling Functions
In order to run the code within a function definition, the function needs to be called somehow. Calling functions consists of three parts:
FunctionLocation.FunctionName(Parameters)
-
Function Location
The vast majority of functions must be called on an object of a certain type. For example, the native function IsDead must always be called on an Actor. The calling object for these functions is specified before the name of a function, separated by a dot. If it's enclosed in brackets, you can use a complex expression, so long as its end product is of an appropriate type.
If you are calling this function on the object to which the current script is attached, then you do not need to specify a location.
Some functions, known as global functions (remember the "Max" function I defined earlier was a global function), are called on types instead of objects. I won't go into detail on these in this tutorial, but I will say that a very common and useful example of a global function is the GetPlayer function, which returns the player character's Actor object and is called like this:
Game.GetPlayer()
Basically, global functions usually don't require any information about a specific object as input, whereas non-global functions do.
-
Function Name
The name of a function is the simplest part of a function call. It's simply the name of the function as was specified when the function was defined - nothing more to it.
-
Parameters
After the function's name, any parameters from the function's definition need to be passed into the function call, enclosed by a pair of brackets. Like in a function definition, the brackets are required even if the funcion has no parameters.
If the function does have parameters, a value of the correct type (which may be the result of an expression) must be passed into the function call. The order of parameters is the same as in the function definition and, likewise, they must be separated by commas.
For example, the "Max" function I defined earlier could be called like this:
Max(3, 2+2)
You may notice that some native functions have parameters that do not always need to be specified when they're being called. It is possible for your own custom functions to work this way as well, but that's not covered in this tutorial.
That concludes this tutorial. At the beginning of this tutorial, you went through the "My First Script" section to create a plugin that prints out the message "Hello, World!" to the screen. Since then, you've learned about variables, properties, conditional statements and functions.
There's still a lot to learn about Papyrus, but you know enough now to write relatively simple scripts. In order to solidify what you've learned here, you should return to the script you created earlier, and edit it to make use of all of these new features that you've learned about.
One very useful thing to know when it comes to testing scripts is that, because they're not part of any data files, you don't need to save any plugins that use them before you can test changes that you've made - all you need to do is compile them. You will only need to use the Creation Kit to attach them to objects and to set up properties.
If you find yourself stuck anywhere, the official Creation Kit forum is a great place to ask for help, or you can contact me directly.
Good luck, and have fun scripting for Skyrim!