Comprehensive Tutorial on Custom Functions

From the CreationKit Wiki
Jump to navigation Jump to search

Introduction[edit | edit source]

Original tutorial can be found here.

I'm going to talk a bit about creating custom functions, calling them, and some other stuff. Custom functions have been touched upon in numerous places like the Creation Kit Wiki and Cipcis' site, but I'd like to go into some slightly more complex functions, as well as the basics, parameters, and what global functions are. So if you're interested, here we go.

What Are Functions?[edit | edit source]

Functions are very similar to events. They're little bits of code that you can "call" on, or activate. Once you've done that, the code inside of the function is processed, and whatever is necessary is done. Unlike events, however, you must call them manually (actually you can call events too, but that's a subject for another day), and they aren't activated by anything else unless you explicitly declare they must be.

Essentially, functions are processes that you declare, enclosed within function tags, like so:

Function MyFunctionName(Parameters)

     ;code within function, this will be processed when you "call" the function

EndFunction

As you can see, their structure is pretty similar to an event. Just like an event, you must declare the beginning and end of an event. Just like an event, you may declare parameters (for example, the OnDeath event has the parameter Actor akKiller). However, function parameters generally function a bit differently than events. Event parameters are usually pre-declared or set to a certain value, which can then be used within the event. An example, again using the OnDeath event:

Event OnDeath(Actor akKiller); akKiller is pre-defined as the actor who killed the actor who died

     If akKiller == Game.GetPlayer(); a common use of akKiller - using it to get the value of the actor who killed the actor the script is on

          ;other stuff

     EndIf

EndEvent

Where's function parameters usually have to be defined when calling the function, like so:

Event OnInit()

     MyFunction(10, Game.GetPlayer())

EndEvent

Function MyFunction(Int Param1, Actor Param2)

     ;do stuff

EndFunction

Now lets talk about creating your own functions.

Basic Custom Functions[edit | edit source]

As you may have noticed earlier, function declarations are fairly simple. The structure looks like this:

Function FunctionName(Type Parameter)

     ;function content

EndFunction

So let's break it up really quick. You have to start and end the function with two words - Function to begin it, and EndFunction to end the function. Then, much like a property name, you have the function name. This is how you refer to the function within scripts. Then, you have any parameters enclosed within parentheses, in variable form (since they are variables) - Type VariableName. If you have no parameters, then you simply end the function with parentheses. When you call the function, you'll call it like this:

FunctionName(Parameter)

But you won't put in the Type Parameter for parameters. You'll put in what you want the parameter to be. Yet another example, where I have a function called WhatFunction and a string parameter:

Scriptname MyFuncScript Extends Quest; or whatever extension

;calling the function, must be within an event or function
Event OnInit()

     WhatFunction("MyString")

EndEvent

;declaring the function, may be declared anywhere in the script but not within events or other functions
Function WhatFunction(String Param1)

     ;do functiony stuff

EndFunction

So some of you might be wondering what the purpose of this is. Well, the most basic of custom functions can be used to enclose certain bits of code that you use often in a script, so you can later call on them. So instead of typing this needlessly long script:

Scriptname ThatScript Extends Quest
{This script is soo much longer than it should be.}

Int Count

Event OnActivate()

     If Count == 0

          Debug.Notification("Don't use debugs in actual releases, use messages and message properties!"
          Count += 1

     ElseIf Count == 1

          Debug.Notification("Be descriptive with names! This script has an awful name!")
          Count += 1

     ElseIf Count == 2

          Debug.Notification("Other useful stuff...")
          Count += 1

     EndIf

     RegisterForSingleUpdate(1.0)

EndEvent

Event OnUpdate()

     If Count == 0

          Debug.Notification("Don't use debugs in actual releases, use messages and message properties!"
          Count += 1

     ElseIf Count == 1

          Debug.Notification("Be descriptive with names! This script has an awful name!")
          Count += 1

     ElseIf Count == 2

          Debug.Notification("Other useful stuff...")
          Count += 1

     EndIf

EndEvent

You can just add a function to the script to shorten it significantly:

Scriptname ThatOtherScript Extends Quest

Int Count

Event OnActivate()

     CountFunction()

     RegisterForSingleUpdate(1.0)

EndEvent

Event OnUpdate()

     CountFunction()

EndEvent

Function CountFunction(); no parameters necessary for this type of function

          If Count == 0

          Debug.Notification("Don't use debugs in actual releases, use messages and message properties!"
          Count += 1

     ElseIf Count == 1

          Debug.Notification("Be descriptive with names! This script has an awful name!")
          Count += 1

     ElseIf Count == 2

          Debug.Notification("Other useful stuff...")
          Count += 1

     EndIf

EndFunction

The size of that is significantly reduced, as you can see. However, you could use some parameters to make it more custom. So if you wanted something different to be printed to the screen based on a variable, you could try something like this:

Scriptname ThatBetterScript Extends Quest

Int Count

Event OnActivate()

     CountFunction(1)

     RegisterForSingleUpdate(1.0)

EndEvent

Event OnUpdate()

     CountFunction(2)

EndEvent

Function CountFunction(Int PrintWhat); parameters we can set so we can define what the function will do

     If PrintWhat == 1

          If Count == 0

               Debug.Notification("PrintWhat is 1. Don't use debugs in actual releases, use messages and message properties!"
               Count += 1

          ElseIf Count == 1

               Debug.Notification("Be descriptive with names! This script has an awful name!")
               Count += 1

          ElseIf Count == 2

               Debug.Notification("Other useful stuff...")
               Count += 1

          EndIf

     ElseIf PrintWhat == 2

          If Count == 0

               Debug.Notification("PrintWhat is 2. This means that we called CountFunction with its parameter as 2."
               Count += 1

          ElseIf Count == 1

               Debug.Notification("See how this could be useful? While it still processes the same amount of code, it's much cleaner.")
               Count += 1

          ElseIf Count == 2

               Debug.Notification("I recommend putting functions at the bottom of your code. Much cleaner.")
               Count += 1

          EndIf

EndFunction

Hopefully you understand how this works. When you call the new CountFunction, then you can define the parameter for CountFunction. It exists when you declare it, but it doesn't really have any meaning. When you call CountFunction and set the parameter, then the parameter will equal what you called for that function call. Then, within the function, it checks for the value of PrintWhat, and depending on what you inputted (a word?), it will print a different notification to your game. So that's how you might use the functions - simply to clean up your scripts and save yourself some time. But there are a lot of other ways to use them too.

Returning Functions[edit | edit source]

One of the functions you may see more often is a type I call "returning functions". These functions return a value you can use, be it an integer, a form, or even a string. Their structure is a tad different from the ones before. They look like this:

ReturnType Function FunctionName(Parameters)

     ;Function content, and return

EndFunction

Everything is identical to the prior function structure, except for the ReturnType bit you see. Here, you'll put in the type of value the function will return - the value you'll "get". For example, a function like GetStage would have a ReturnType of Int, since it returns an integer value. Likewise, IsBeingRidden is a bool function, since it returns a value of true or false. Here's one of the examples I seem to love:

Bool Function IsActorCool(Actor WhatActor);tell the function which actor you're calling the function on 

     If WhatActor.GetRelationshipRank(Game.GetPlayer()) == 4

          ;WhatActor likes the player
          Return True

     Else

          Return False

EndFunction

The function IsActorCool will return true if the actor I'm calling it on is the player's lover, and false if they are not. This is a pretty common application of returning functions. (No, not getting if an actor is the player's lover, but a bool function like this is pretty common.) Now I'm going to give an example of a more complex function. This function will enable every ObjectReference in an array if it is called at night time (anywhere between 10 PM and 6 AM), and disable all of the ObjectReferences in the array if it is called at day time (anywhere within the range of 6 AM-10 PM). It returns true if it was day time, and false if it was night time. The most common use for this function would be to turn on and off lights, but it could be applied however you want it:

Bool Function LightSwitch(ObjectReference[] kArray)

      Int kIndex = kArray.Length

      If GameHour.GetValueInt() >= 22 && GameHour.GetValueInt() < 6; if the time is bigger than or equal to 10 PM and less than 6 - 24 hour clock

     	While kIndex; while we haven't counted through the entire array

     		kArray[kIndex].Enable(true); enable the entry in the array, fading in
     		kIndex -= 1; move onto the next entry

     	EndWhile

     	Return False; it was nighttime

     Else; time is bigger than or equal to 6, and less than 10 - 24 hour clock

     	While kIndex; while we haven't counted through the entire array

     		kArray[kIndex].Disable(true); disable the current entry in the array, fading out
     		kIndex -= 1; move onto the next entry

     	EndWhile

     	Return True; it was day time

     EndIf 

EndFunction

So hopefully you can see how this works. You call the function, declare the parameter as an ObjectReference array, and the function commences. If the current value of GameHour (a global - you'll have to declare a property for it somewhere else in the script for this function) is more than or equal to 22, which is the equivalent of 10 PM on a 24 hour clock, and if GameHour is less than 6, then it will enable all the lights, fading them in. It does this by cycling through an array. It then returns False, since it was night time. If the global GameHour doesn't meet those conditions, then the lights are disabled, fading out. This is done with the same method as in the other If statement. It then returns True, since it was day time.

Global and Native Functions[edit | edit source]

There are two other tags you can add to functions. The first is "native". It's added like this:

ReturnType FunctionName(Parameters) native

;or, if it's a non-returning function

FunctionName(Parameters) native

A native function means that the function is hard-coded into the game. Functions like GetStage, Kill, and BlockActivation are all native, along with hundreds of others. A native function also means that you can't see the content of the function, because it was hard-coded in and defined elsewhere. The only way to create your own native functions is by making your own SKSE plugin.

Then, there is a global function. A global function is one that can be called from any script, without having to define a property or cast to the script the function is in. For example, EnablePlayerControls is a global function, as is RandomInt. However, you do have to prefix global functions with the name of the script they came from, and a dot when calling them, like so:

;how you call EnablePlayerControls

Game.EnablePlayerControls()

EnablePlayerControls is a global (and native) function in the Game script. Therefore, you have to call EnablePlayerControls in Game to use it. However, you don't have to define a property for the Game script. Alternatively, you can import the owning script of the function, like so:

;how you call EnablePlayerControls, with Game imported
Import Game

EnablePlayerControls()

I don't recommend importing an outside script unless you are constantly using global functions in that script, since it's not really worth it. As far as I know there's no real speed impact or anything like that when importing, I just think it's only necessary to import when you've got several global functions or multiple instances of one global function in your script. The structure of global functions is like so:

ReturnType FunctionName(Parameters) global; you can tack on a native flag after the global, but it wouldn't have a function body then

     ;function content

EndFunction

Now, there's one important caveat to global functions: All of the variables used in the function must be defined within the global function. You cannot access any properties from the script that declares the global function, or from any other script. Since you cannot declare properties inside functions, you are limited to using variables from within the function and the parameters of the function. So if we made our LightSwitch function from earlier global, it wouldn't work correctly, since it relies on GameHour being defined outside of the function. There are two ways to rectify this:

1. Add GameHour as a parameter you have to define. 2. Use GetFormFromFile within the function. We're not going to get into this. It's not a method I would recommend, given the fact that this is a pretty simple function.


So here's how we would achieve method one - just add a very simple parameter so the function declaration looks like this:

Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameHour) global

Everything else would be the same. Then, when you call the function, it would look something like this:

GlobalVariable Property GameHour Auto; fill this property
ObjectReference[] Property MyObjects Auto; fill this array property

Event OnInit()

     LightSwitch(MyObjects[], GameHour)

EndEvent

Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameHour) global

  Int kIndex = kArray.Length

      If kGameHour.GetValueInt() >= 22 && kGameHour.GetValueInt() < 6; if the time is bigger than or equal to 10 PM and less than 6 - 24 hour clock

           While kIndex; while we haven't counted through the entire array

                 kArray[kIndex].Enable(true); enable the entry in the array, fading in
                kIndex -= 1; move onto the next entry

            EndWhile

           Return False; it was nighttime

      Else; time is bigger than or equal to 6, and less than 10 - 24 hour clock

           While kIndex; while we haven't counted through the entire array

                kArray[kIndex].Disable(true); disable the current entry in the array, fading out
                kIndex -= 1; move onto the next entry

           EndWhile

           Return True; it was day time

     EndIf 

EndFunction

Function Design Tips[edit | edit source]

I'd like to touch upon one last thing before I finish: It's important to be as all-encompassing with your functions as possible. Properties are great because you can have the same script and have the property values differ for that script depending on what it is attached to. Therefore, doing this:

Quest Property MyQuest Auto
Int Property StageToSet Auto

Event OnActivate()

     MyQuest.SetStage(StageToSet)

EndEvent

Is better than doing this:

Quest Property MyQuest Auto

Event OnActivate()

     MyQuest.SetStage(10)

EndEvent

Why? Because then, rather than creating dozens of scripts almost identical to the second script with different stages that are being set, you can use one script (the first script) dozens of times with the property StageToSet at a different value for each.

The same applies to functions, especially global ones that you intend to use in lots of places. So, for example, I'd probably change LightSwitch to a far more versatile version that looks like this:

Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameTime, Int LowTime, Int HighTime) global

	Int kIndex = kArray.Length

	If kGameTime.GetValueInt() >= HighTime && kGameTime.GetValueInt() < LowTime; if the time is bigger than or equal to hightime and less than lowtime - 24 hour clock

		While kIndex; while we haven't counted through the entire array

			kArray[kIndex].Enable(true); enable the entry in the array, fading in
			kIndex -= 1; move onto the next entry

		EndWhile

		Return False; it was nighttime

	Else; time is bigger than or equal to lowtime, and less than hightime - 24 hour clock

		While kIndex; while we haven't counted through the entire array

			kArray[kIndex].Disable(true); disable the current entry in the array, fading out
			kIndex -= 1; move onto the next entry

		EndWhile

		Return True; it was day time

	EndIf 

EndFunction

Now, rather than having a set range that the lights or objects will be switched on and off with, or even a set time interval, you can now choose between many different options. You can change the GameHour to GameMonth if you like, or GameYear. Or even any other global, if you felt like it. You could change it to PlayerFollowerCount, though that would be pretty unproductive. And, you can change the hours (or years, or months, etc.) range at which the lights are disabled and enabled. Furthermore, remember that it doesn't have to be lights. Any ObjectReference array will be proccessed.

There's one last thing: You can have default parameters. For example, the Disable function has a default parameter:

Function Disable(bool abFadeOut = False) native

; the parameter checks if you want the object to fade out of existence rather than just blink out

Usually when disabling objects, you simply call Disable on them like so:

MyObject.Disable()

This will automatically execute your code as if you had called the function like this:

MyObject.Disable(false)

It's pretty simple. You just set the variable to a certain value in the parameters like in the above example, and then that will be the default value of that parameter. The structure of the function:

ReturnType FunctionName(Parameter = DefaultValue); tack on the global and native flags here if you want

;if there is no return type

FunctionName(Parameter = DefaultValue)

And remember that not all of the parameters have to have default values, you can decide which ones when you declare the function. Example:

Bool Function LightSwitch(ObjectReference[] kArray, GlobalVariable kGameTime, Int LowTime = 6, Int HighTime = 10)

Some Common Functions[edit | edit source]

Here are a few common functions you might want to use (the first two are just random ones that came to mind, not necessarily the most common). If you have a function you'd like to see, just ask and I'll add it here if possible.

Disable Array[edit | edit source]

This is a DisableArray function. It will disable all objects an array of your choice, and fade them out depending on what you input in place of kFadeObject, though by default it won't fade out. It's a global function, though you can delete the global flag, so whichever script you put this function in will be the prefix to the function (i.e. you need to call it like OwningScriptName.DisableArray(MyArray, true) to disable all the objects in MyArray, fading them out.) You could also replace all instances where I used Disable and instead replace it with Enable, if you want this to enable objects rather than disable them.

Function DisableArray(ObjectReference[] kArray, bool kFadeObject = false) global

     Int kIndex = kArray.Length

     While kIndex

     	kArray[kIndex].Disable(kFadeObject)
     	kIndex -= 1

     EndWhile

EndFunction

Set Hostile[edit | edit source]

This is a SetHostile function. While you can usually use StartCombat, or SetRelationshipRank, sometimes individually they won't work. So SetHostile will set all of these to something that should make an actor target and attack another actor. This is also a global function, so whichever script you put this function in will be the prefix to the function (i.e. you need to call it like OwningScriptName.SetHostile(Actor1, Actor2) to make Actor1 hostile towards Actor2).

Function SetHostile(Actor kActor, Actor kTarget) global

	kActor.StartCombat(kTarget)
	kActor.SetRelationshipRank(kTarget, -4)

EndFunction