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.

QUESTION: If I import a script with a global function that shares the name of a non-global function in my script, what do I have to do to call the local version?

QUESTION: Can a global function be called like a non-global function? A global function cannot be called like a non-global function (on a variable, which is what I assume you were asking).

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.

QUESTION: Do scripts extending Form have access to the "Self" variable outside of functions (remember to include events)? Do other scripts? Self is available to any function or event and always refers to the object that the function or event exists on. You can pass "self" to another script via a function on that script, but they'll use the variable you passed the value into, not the "self" keyword. In fact, passing "self" to someone else is usually what you'll use it for.

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.

QUESTION: Can non-global functions be declared in scripts that don't extend Form? You cannot attach a script that doesn't inherit from Form, Alias, or ActiveMagicEffect to anything in the game, so non-native functions will have nothing to work with. (Though the compiler will not complain)

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.

QUESTION: Can the "parent" special variable be used to call a parent's version of an event? I expect yes, but should confirm. Yes, "parent" is a special keyword that you can call functions on that will call the immediate parent's version of the function/event, bypassing any you may have defined in the child.

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.

Can native functions be overridden? I expect yes, but surely you'd never want to. Yes, native functions can be overridden, but you may sometimes get odd results depending on game initialization order with properties. If a property is set up before your script is attached, then the property may point at the auto-generated script and not your derived script, causing the parent version of the function to be called instead of your derived one.

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

QUESTION: Can a non-global function be called implicitly from within a global function? I expect no, but should confirm. No, non-global functions cannot be implicitly called from global ones, the game has no idea what object you'd want to call it on.

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.

QUESTION: Again(?), can non-global functions be called with implicit "Self" in scripts that don't extend Form? Perhaps they would compile in a non-global function (such as an event), as they would never be called? Can non-global functions be defined in scripts that don't extend Form? You cannot attach a script that doesn't inherit from Form, Alias, or ActiveMagicEffect to anything in the game, so self will not work. (Though the compiler will not complain)

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.

QUESTION: Is it possible to use "Parent" to call a parent's version of an event? I expect yes, but should confirm. Yes, parent works on both events and functions.

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.

QUESTION: Ensure that it's not possible to override anything on an inherited property. Property overloading is not allowed (but if you write your own get/set functions, they can call functions that are overridden)

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.

QUESTION: Do variables have function scope? Variables have block scope, whether that block be a function, if block, while block, or similar.

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

QUESTION: Does cast fail if these are not true? Surely it will. What is the compiler error? The compiler will error if it can detect that the cast will always fail (like Form to Alias), but if the cast might succeed (like from ObjectReference to your TeleportLinkedRef) then the compiler will be fine with it. If the cast fails at runtime there will be no error, the cast operation will simply return "none". This can be useful if you want to see if what you are given is something you expect (especially because None evaluates to false in an if expression)

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.

Find more information on this.

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.

  1. ScriptName TeleportLinkedRef extends Activator
    
    ObjectReference LinkedRef
    
    Event OnActivate(ObjectReference akActionRef)
    	LinkedRef = GetLinkedRef()
    
    	if (akActionRef == Game.GetPlayer())
    		(LinkedRef as TeleportOnBleedout).TeleportAway()
    	endif
    EndEvent
  2. 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.