28th October 2014
Accessing External Functions and Properties in Papyrus
Global Functions
The easiest types of functions to call are global functions. These functions can be identified by the use of the keyword "global" in their declaration. For example:
ScriptName ExampleLibrary int Function SumTwo (int a, int b) global {Returns the sum of the two inputs} Return (a + b) EndFunction
Global functions are easy to call because, unlike regular functions, they don't need to be called on an in-game object. Calling a global function in the same script as its declaration is very simple:
int Three = SumTwo(1, 2)
Calling a global function that was declared in a different script is slightly more complicated, but only slightly. In order for the compiler to recognise the function, you need to specify its location. For global functions, this means specifying the script in which they were declared. There are two ways in which you can do this:
int Three = ExampleLibrary.SumTwo(1, 2)
Import ExampleLibrary int Three = SumTwo(1, 2)
By using the "Import" keyword, you can make all global functions defined in a script easily accessible in another script. This is very useful if you call a lot of global functions from a certain script within your script. Importing "Math", for example, is very common.
Even if you're importing a script, however, if there is any ambiguity in the name of a function then you need to be explicit about its location. If, for example, I import two scripts with a global function called "SumTwo", then I'd have to call the function as though it weren't imported.
Usually, functions will not be declared as "global". This means they will run on an in-game object, and will have access to a special variable called "Self" that can be used as a reference to the object on which they are running. These functions must be called in a different way.
Inheritance
As I mentioned earlier, non-global functions are run on in-game objects. When calling a non-global function on an object, you can only call functions that are available in that object's script.
Whether or not a script has been attached to an object in the Creation Kit, certain scripts are attached automatically by the game engine, depending on the object's type. For example, the Actor script is automatically attached to all references to actors.
As an example, I'm going to create a new script for an Actor that teleports him to a marker once he's been beaten up a bit:
ScriptName TeleportOnBleedout extends Actor ObjectReference property TeleportMarker auto Event OnEnterBleedout() MoveTo(TeleportMarker) EndEvent
It might not look like much, but there is quite a lot going on in this script in terms of inheritance.
The first clue here is in the ScriptName declaration. In plain English, "extends Actor" means "inherit all of Actor's properties and functions (including events)". If all I had in this script was that single line, then it would act as a duplicate of the Actor script.
This means that, without declaring them myself, I have access to all of Actor's properties and functions, without having to add any extra identifiers to tell the compiler where to look.
As well as being able to call any of Actor's functions directly, I can override them by defining them again in my script. For example, the OnEnterBleedout event (remember that events are functions) is defined in the Actor script like this:
Event OnEnterBleedout() EndEvent
(OnEnterBleedout is a native event - the game calls it on Actor objects when they enter "bleedout" mode)
As you can see, had I not overridden it, nothing would happen when this event was called. However, I have overridden it, so when the game calls the OnEnterBleedout event on this actor, the code defined in TeleportOnBleedout's version will run.
Now, speaking of that code, this is an example of how easy it is to call a function on the object to which its script is attached.
Although MoveTo is not declared in this script, it is still available because it is declared in an ancestor of this script. TeleportOnBleedout extends Actor, and Actor extends ObjectReference, and MoveTo is declared within the ObjectReference script.
Functions can be inherited through any number of levels in this way, and the lowest overridden version will always take precedence. You won't want to override native functions like MoveTo, even though it's possible, as they are defined by the game engine and overriding them may produce odd results.
Remember how I mentioned earlier that non-global functions are run on an object in the game? MoveTo is no exception, yet we haven't specified an object on which it should run. When a non-global script is called in this way, it is called on the object on which the current function is running (functions can only be called from within other functions. Remember that your entry point is always the game calling a native event, like OnEnterBleedout).
It is possible to directly use this object within a non-global function by using the special variable "Self". This is usually only useful when you need to pass this information as the parameter of a function.
Another special variable that is available within non-global functions is "Parent". With this variable, it is possible to call the parent's version of a function, including events, if it has been overridden in the current script.
Properties and Variables
When you extend a script, you also inherit its properties. As with functions, you can use these properties as though they were declared in your own script. However, unlike functions, you cannot override properties. You will have to use them in whichever way they were declared in your script's ancestor.
When extending a script, you do not inherit its variables. Variables are entirely private, and cannot be accessed from outside the block in which they are declared (this is known as having "block scope"). Examples of blocks are functions, if blocks, and while blocks.
Calling Functions on External Objects
When calling a function on an object other than the one to which your script is attached, the syntax can get more difficult. The most important thing to remember here is that the compiler can travel up the trail of inheritance, but it cannot travel down. Functions declared in a certain script are only accessible from that script or its descendants (i.e. scripts that extend it).
Usually, this isn't a problem. However, when you're trying to call a function from a script which you've written after getting the object's handle from some other function, there is an extra step that you will probably need to take. I'll tell you about that now.
For example, let's make a small modification to TeleportOnBleedout:
ScriptName TeleportOnBleedout extends Actor ObjectReference property TeleportMarker auto Function TeleportAway() MoveTo(TeleportMarker) EndFunction Event OnEnterBleedout() TeleportAway() EndEvent
Now, imagine I have another script that I will attach to an activator. When the player activates this activator, it will find my actor and force them to teleport away. The teleporting actor will be set up as the linked reference of my activator, so I will get a handle on them by using GetLinkedRef:
ScriptName TeleportLinkedRef extends ObjectReference ObjectReference LinkedRef Event OnActivate(ObjectReference akActionRef) LinkedRef = GetLinkedRef() if (akActionRef == Game.GetPlayer()) LinkedRef.TeleportAway() endif EndEvent
GetLinkedRef returns an object of type ObjectReference, so I assign its return value to a variable of that type. Then, when the player activates the scripted activator (hopefully you recognise that GetPlayer is a global function from the Game script) this script will call the TeleportAway function that I've defined.
There's a problem, though. This all might look correct at first glance, but the compiler complains that it can't find the TeleportAway function. The problem is that, because the variable LinkedRef is of type ObjectReference, the compiler is looking in the ObjectReference script for functions (including inherited functions, remember) called TeleportAway. There aren't any, though, so it won't compile.
The solution involves something called casting, which is something that lets you turn an object of one type into an object of another type.
With the exception of the basic types, like "int" and "bool", an object of type A can be cast to an object of type B if either...
- type A is an ancestor of type B
- type B is an ancestor of type A
Remember that a function is available in a script if it is defined in that script or one of its ancestors. This means that, if I want the compiler to be able to find my TeleportAway function, I'll need to cast LinkedRef to type TeleportOnBleedout or one of its descendants.
Now, TeleportOnBleedout doesn't have any descendants, so my only choice is to cast as type TeleportOnBleedout. However, if it did have descendants, I could not cast LinkedRef to a type more derived than the script attached to it.
For example, if I created a script extending TeleportOnBleedout called TeleportOnBleedoutChild, but didn't attach it to my actor that I'm using in this example, then I wouldn't be able to cast it successfully to the type TeleportOnBleedoutChild. However, the compiler doesn't know this, so my script would still compile. If a cast fails for a reason like this, then the result of the cast will be None.
In order to cast an object of one type to another, you need to use the as keyword. There are a couple of ways in which I could solve my current problem with casting.
ScriptName TeleportLinkedRef extends Activator ObjectReference LinkedRef Event OnActivate(ObjectReference akActionRef) LinkedRef = GetLinkedRef() if (akActionRef == Game.GetPlayer()) (LinkedRef as TeleportOnBleedout).TeleportAway() endif EndEvent
ScriptName TeleportLinkedRef extends Activator TeleportOnBleedout LinkedRef Event OnActivate(ObjectReference akActionRef) LinkedRef = GetLinkedRef() as TeleportOnBleedout if (akActionRef == Game.GetPlayer()) LinkedRef.TeleportAway() endif EndEvent
Each of these methods has its own pros and cons. The first method - casting just for the function call - is useful if you need to cast LinkedRef to another derivative type elsewhere, as you can't cast directly between sibling types. The following is possible, but it's also a bit inelegant:
ScriptName TeleportLinkedRef extends Activator TeleportOnBleedout LinkedRef Event OnActivate(ObjectReference akActionRef) LinkedRef = GetLinkedRef() as TeleportOnBleedout if (akActionRef == Game.GetPlayer()) LinkedRef.TeleportAway() ((LinkedRef as Actor) as OtherNewActorChild).OtherFunction() endif EndEvent
The second method - casting the return value of GetLinkedRef and storing the value as the derivative type - is useful if you need to treat the value as the derivative type most of the time, and never (or almost never) need to use it as a sibling type.
In these examples, I have used a variable to store the value returned by GetLinkedRef. However, if the value is something that you don't need to reuse, you could use the return value directly in your function call, like this:
ScriptName TeleportLinkedRef extends Activator Event OnActivate(ObjectReference akActionRef) if (akActionRef == Game.GetPlayer()) (GetLinkedRef() as TeleportOnBleedout).TeleportAway() endif EndEvent
When deciding whether or not to store something in a variable, it's also important to realise that having a variable point to a certain object will force that object to remain persistent. If you want to use a variable, but you don't want to keep this object persistent, you can "free" it (assuming that nothing else is keeping it persistent) by assigning the value of your variable to be None.