Jump to content
Mattiewagg

[Graduate Thread] Trivia - Matthiaswagg

Recommended Posts

Lesson on Functions and Events

Resources:

All About Functions (highly recommend you read, as I'm going to have the functions part of this shorter since this is very extensive + examples and written by myself before)

Events Reference

 

Functions

As you probably know by now, functions make up a lot of your scripts. They make stuff happen, for the most part. Moving things, changing things, getting values, etc. Those are ALL functions. 

 

A function consists of 5 parts:

Type Function FunctionName(Parameters)
EndFunction 

The function type is used for functions that return values, not functions that only DO things and don't return values (if you have one that returns values and does stuff, it should have a function type). As you know, Show is a returning function. It returns an integer - the button chosen. Thus, it is an Int function.  If it weren't a native function (to be explained later), then it's function declaration would have looked like this:

Int Function Show(float afArg1 = 0, float afArg2 = 0, float afArg3 = 0, float afArg4 = 0, float afArg5 = 0, float afArg6 = 0, float afArg7 = 0, float afArg8 = 0, float afArg9 = 0)
     ;what the function does and returns
EndFunction 

The function part is just a function declaration, telling the code that THIS is a function. It's just like the "Property" part of property declarations. The FunctionName part is just like the PropertyName. The Parameters are variables that are passed into the function - so you can have an Actor parameter, Int parameter, etc. This parameter can be used within the code of the function, no matter WHERE you call it from, unlike with global functions (discussed later); which can only use the parameters and nothing from within the script they were declared in. So if I have the function HealthierActor, and it compares the healths of two actors, then I'd have to pass in the two actors as parameters:

Function HealthierActor(Actor actorA, Actor actorB); parameters can be named whatever you like
     If actorA.GetActorValue("Health") > actorB.GetActorValue("Health")
         ;whatever code you want here
     ElseIf actorA.GetActorValue("Health") < actorB.GetActorValue("Health")
         ;whatever code you want here
     Else; is equal
         ;whatever code you want here
     EndIf
EndFunction 

 

And then, you must close it off with an EndFunction, just like you would close off an event with EndEvent.

 

Now let's talk about Return values. You can have your function Return a value, which you can then use. You could assign an integer to the return value of a function that returns an int (or float, or bool, for that matter - see Casting though we'll talk more about it later if you want). Examples of Returning functions are functions like Game.GetPlayer(), which returns the Actor of the player so you can easily use the player in a script. You must have a function type if you want your function to return a value. So, while this will NOT work:

Function ThisIsAFunction()
     Return 10 + 10
EndFunction 

But this WILL:

Int Function ThisIsAFunction()
     Return 10 + 10
EndFunction 

You can also return the value of a parameter, global, variable declared within a function, or a property within a script (IF it is not a global function).

 

Read the Resource All About Functions for more examples, and info on Native and Global functions. 

 

Note that if you have a convenience function which is just there so you don't have to retype code a lot, it doesn't mean the code isn't still happening. If you call a function with 30 lines of code in it 100 times in a script, that's still 3000 lines of code being called (not necessarily a bad thing). It's just you typing out only 30ish lines. So it's not more performance friendly, but it's also not less so. It's the same as if you had typed out all the lines, except the file size is smaller and you don't have to type as much.

 

Events

Events are a bit simpler than functions. As I've said before, they're called when an event in the game happens. They often, but not always, include parameters. Parameters are variables sent along with the event, which you can manipulate within that event. (If you want to use a parameter throughout a script and not just in the event, then you can assign the value of one variable to the value of a parameter.) They look like this (events):

Event EventName(Parameters)
EndEvent

There's not a much that you don't already know about events, to be honest. If you have any questions about them, feel free to ask. There are a few exceptions/things to note, however:

 

- Don't pile up too much in an OnInit event. It can overload it. It's a rather fragile event, though I know of no others like it. If there's more than a few function/code being called in there, use a RegisterForSingleUpdate(0.1) and then put all that code in the OnUpdate event.

- You can call an event directly. So, you could just call OnUpdate() like it were a function, rather than RegisterForSingleUpdate, if you wanted. Or, you could call any other event and pass in it's parameters. Etc. I've not experimented with this much but I've heard from others more experienced than I, that it is possible.

 

That's all I can think of for now. If you have any questions whatsoever, want me to clarify on anything, etc. don't hesitate to ask. :D 

 

Also, I'll probably have you be making your own first script next, but if you'd prefer to have a lesson on say, Casting (how a Boolean turns into an Integer, etc.) or on Advanced Properties (accessing properties from other scripts, properties that can be set but not "got" - value of the property gotten) then we could do that instead. Whichever you prefer. We'd end up doing those lessons later on anyway and then making another or improving your first script to use those concepts. After that we'll do Arrays/Formlists and maybe a few other lessons on advanced concepts with you creating scripts scattered in between.

 

EDIT: Don't ask me why I used that color. That yellow is... not easy on the eyes. Sorry.

Share this post


Link to post
Share on other sites

Okay, I've got one.

Try your hand at a script that will be placed on a miscellaneous object (this is created under Items>MiscObject). When the object is equipped, it will create an enemy where the player is (a little offset so it's not uh... INSIDE the player). It will only create this enemy IF a random integer between 0 and 100 is bigger than or equal to 50. Otherwise, it will damage the player's health by 50. You can choose the enemy. Just one enemy, for now.

=========================

If you have any questions, don't hesitate to ask. If you're looking for functions, I recommend having a look at this page on the CK Wiki. I'll give you two hints here:

http://www.creationkit.com/DamageActorValue_-_Actor

http://www.creationkit.com/RandomInt_-_Utility

And when you get a chance, read this:

=========================

Once you're all done, show me the script. I'll point a few things out (if necessary) until it gets to a state where it should work. Then you can attach it and test it out in game. If it doesn't work, I'll help you troubleshoot.

Few common problems/mistakes:

  • Make sure you fill in your properties

Erm. That's about it. ;) Good luck! Take your time and don't stress about it. I recommend laying out what you think you will need based on what I've said first, without worrying about the details. It'll help you write it out in code form.

Share this post


Link to post
Share on other sites
Okay, I think I got it:
 
Scriptname TriviaCursedSkullScript extends ObjectReference  
 
ActorBase Property EnemySummoned Auto
 
Event OnEquipped(Actor akActor)
if Utility.RandomInt() >= 50
akActor.PlaceActorAtMe(EnemySummoned, 4, None)
else
akActor.DamageAV("Health", 50.0)
endif
EndEvent

Share this post


Link to post
Share on other sites
Okay, I think I got it:
 
Scriptname TriviaCursedSkullScript extends ObjectReference  
 
ActorBase Property EnemySummoned Auto
 
Event OnEquipped(Actor akActor)
if Utility.RandomInt() >= 50
akActor.PlaceActorAtMe(EnemySummoned, 4, None)
else
akActor.DamageAV("Health", 50.0)
endif
EndEvent

Nice! Well done. You even found PlaceActorAtMe, which, I admit, I didn't even know was a function. :P So fill the properties, and try it out in-game. Feel free to take a video or just report back.

Perfect.

Share this post


Link to post
Share on other sites

Fantastic. I'll write up the next lesson, Advanced Properties, within a few days. It'll discuss the full capabilities of properties, and it WILL get a little more advanced. I'll try to be as detailed as possible in explanations, however.

And the whole "Can't be equipped" message is, unfortunately, not removable in any sensible way. It might not bother you but in case you were wondering. ;)

  • reaction_title_1 1

Share this post


Link to post
Share on other sites

Advanced Properties pt. 1

This will go over accessing properties from other scripts.

As you already know properties need to be set externally. Or rather, from within the Creation Kit. This means that they're the only way to reference items, objects, or references that are in game. They can also contain values of integers, floats, bools, etc. However, so can variables, and they do not need to be set externally and they take up (slightly) more memory. 

There's a reason for this. Properties can be accessed from other scripts, unlike variables. This way, you can share information between scripts, as well as changing the values of properties from other scripts. You can even set the value of a property from scriptB with a variable or another property from scriptA. We have 2 ways of doing this, though both are not always viable.

Through script property You can create a property for another script, and then reference that property to reference the other script through it. That sounds rather confusing, but you're essentially creating a property that is filled with the value of another script. So, like how you can fill a Weapon property with a Weapon entry, you can create a property that is the TYPE (a type is something like ObjectReference) of your script. Then you fill the property with your script, and it allows you to access any properties and functions in the other script, just like how you can access and set information on an ObjectReference.

Here's an example:

Scriptname scriptA extends ObjectReference; can extend anything, it can be any type of script
Int Property myAccessibleProperty Auto; could be any type of property
Event OnInit()
;codey code
EndEvent
Function SillyFunction()
;silly function stuff
EndFunction
Event OnActivate(ObjectReference akActionRef)
;moar code
EndEvent
Scriptname scriptB extends ActiveMagicEffect; again, can extend anything
scriptA Property anythingHere Auto; the TYPE of property is the exact name of the script, and anythingHere is just like any property name - it can be ANYTHING
Event OnEffectStart(Actor akTarget, Actor akCaster)
anythingHere.myAccessibleProperty = 29; setting the value of myAccessibleProperty in scriptA from scriptB
​     anythingHere.SillyFunction()
EndEvent

Whenever doing this, you have to make sure you use the proper name for the properties in the other script. Sometimes, you will run into compilation errors when referencing another script, but you can't find anything wrong in scriptB (or the equivalent of it). It always pays to check the other script. Every time you compile scriptB, it will check over scriptA, since scriptB is using info from scriptA.

You DO need to fill the property. There will be a few choices in the dropdown, or just one. Each one is for the object the script is on. So if scriptA is on 100 different references in the game - 100 different weapon entries, for example - you'll have 100 choices in the dropdown. The property refers to a SPECIFIC script each time, so you have to choose a specific one. Just like any other property, leaving it unfilled will ensure the property doesn't work.

By getting the reference the script is on Alternatively, you can get the reference your script is on through function or property, and CAST to your script from that object. So, if you have two scripts on myObjectReference, objScriptA and objScriptB, you could reference objScriptA from objScriptB like so:

Scriptname objScriptA Extends ObjectReference
Bool Property LetSetIt Auto
Event OnActivate(ObjectReference akActionRef)
     ;do some stuff
EndEvent
Scriptname objScriptB Extends ObjectReference; script is on the object that objScriptA is on
Event OnInit()
     (Self as objScriptA).LetSetIt = True
EndEvent

Self is not a function, but sort of like a variable. It automatically contains the reference that the script it is called in is on.

So, what you do is you call Self, and then you CAST to the value of your script. It's a little confusing, but simple in practice. You use a function (other examples are GetOwningQuest(), which we'll use for a lot of quests and returns the value of the quest that a dialogue option or alias is contained within) or "special function" like Self to get the value of the object that has the script to reference on it. Then, you cast to the exact name of your script, and call functions from within that script, or set properties, etc.

MAKE SURE YOU USE THOSE PARENTHESES WITH THIS METHOD! 

Self as objScriptA

Will NOT work. 

(Self as objScriptA)

WILL work.

This method is not always possible if you can't use a function to get the reference of the object the script you're accessing is on. However, it stops you from needing to fill properties, which can save you time. It really depends when you should use one or the other, but you can generally figure it out.

Part 2 will be coming soon and going over the other functions of properties. Part 2 is less useful stuff, but definitely important to know about anyway. I really wanted to get this one out because you've been waiting a while. Tell me if you have any questions.  ;)

  • reaction_title_1 1

Share this post


Link to post
Share on other sites

We're going to begin moving onto more advanced things now. First, Importing and Inheritance, which is a key concept for Papyrus. Casting next, which is another very important concept. Then we'll have a lesson on something you'll put into use far more often - Arrays. I'll have you write a short sample function/script to put Arrays into practice, as well as a little bit of Casting, which is relatively simple.

Finally, we'll wrap up with Advanced Properties pt. II because it's pretty boring and will rarely be used - I've never used the method we'll discuss in there once, but it's still good to know as it will allow you to manipulate properties to a much greater extent.

After that, I'll let you loose on writing a few scripts so I can help you figure out things you don't understand and make sure you've got a good grasp on the concepts. I'll have you go through some vanilla scripts and tell me what they mean, and let you ask any questions you may have. Once we're all through with that, we'll move onto quest design.

(This will be a barrage of lessons because I wrote them on two 14 hour plane flights. 28 hours of time dawdling tends to be good motivation. Just not for the Advanced Properties one because I had no internet and couldn't properly remember it, having never used it.)

Importing and Inheritance

Important Links:

INHERITANCE HIERARCHY

Extending Scripts

Inheritance

As you may know, functions are INHERITED. That little part after "extends†in a scriptname says that a script is a descendant of that extended script. There is a script called ObjectReference in the game, and when you extend ObjectReference, you're really saying that yourScript inherits all functions from ObjectReference.

Generally, you're extending a native script. As you learned in the functions lesson, native functions are defined in C++, and just declared in scripts. These scripts are ones like ObjectReference.psc, or Form.psc.

ObjectReference inherits functions from Form, so it has access to ALL Form functions and ALL ObjectReference functions. This means that yourScript inherits all ObjectReference and Form functions, as well as any functions declared within yourScript.

You could then create yourSecondScript which extends yourScript (would look like Scriptname yourSecondScript extends yourScript in the declaration), and has access to all Form, ObjectReference, yourScript, and yourSecondScript functions. You won't need to use this often, but it's good to know. As seen in Advanced Properties pt. I, you can use properties to access functions as well. You shouldn't inherit from a script if it isn't the right "typeâ€. Like, I shouldn't have a script that would normally extend Quest extend yourScript instead, since yourScript extends ObjectReference.

Importing

You can also import from scripts. Importing is a convenience tool used with global functions (learned about in the functions lesson).

Normally, when you call a global function, you call it like so:

functionScript.functionName()
functionScript being the script the global function is contained in, and functionName being the name of the global function. Two common examples of global functions are Wait and GetPlayer. They are usually called like:
Utility.Wait(float afSeconds); replacing the parameter appropriately
Game.GetPlayer(); returning the player actor
Game and Utility are two scripts in the game with functions declared in non-Papyrus code (C++). Wait and GetPlayer are just two of many globalnative functions declared within them. If you're using Game.GetPlayer() a bazillion times in your script (which you shouldn't do for efficiency reasons - will discuss later), it would save you a decent amount of time to forego that "Game.†part, wouldn't it?

So, you could can instead Import a script, like Game or Utility, and then not need that prefix. You import like so:

Import Game;event or function here, all functions must be called inside them of course
GetPlayer()
With Wait:
Import Utility;event or function here, all functions must be called inside of them
Wait(1.0)
There is absolutely no difference in performance for either method. As said, it's entirely a convenience function.

Tell me when you're ready for the next or if you had any questions on this.

Share this post


Link to post
Share on other sites

Okay, so inheritance is sort of like the scripting equivalent of making an .esp dependent on an .esm, right? Except you can only make it dependent on one other script, unless of course that script is extending another one and so on?

And importing just saves some typing if you're calling functions from the same script frequently. (And the imported script does not need to be the same as the parent script, right?)

  • reaction_title_1 1

Share this post


Link to post
Share on other sites

Okay, so inheritance is sort of like the scripting equivalent of making an .esp dependent on an .esm, right? Except you can only make it dependent on one other script, unless of course that script is extending another one and so on?

And importing just saves some typing if you're calling functions from the same script frequently. (And the imported script does not need to be the same as the parent script, right?)

Exactly. I suck at analogies but that's exactly right.

Right on importing. And no, it doesn't. Keep in mind that some functions have the prefix cuz they're global, but if you're inheriting and its not global there's no prefix. Importing is just for the global stuff. Hence why Kill() in Actor script needs no prefix but RandomFloaf() in Utility script does. It's becausw they're not meant to be inherited - they're misc functions for use EVERYWHERE, not just on actors or quests.

Share this post


Link to post
Share on other sites

Script Habits

For those of you getting this little lessony thing later into their students thread, I apologize. I should've written this first so you settled your habits. Hopefully you're not too set in your ways now. ;)

This will be covering good script habits for NAMING, COMMENTING, and SPACING.

Naming

Naming is very important everywhere, not just in scripting. When you make a new record in the CK, when you make a new variable, function, or property - it's important that you have a consistent and clear naming convention. Records in the CK should have prefixes relative to your mod (Beyond Skyrim has one of these - CYR for Cyrodiil is an example, you'll have to check with your provincial lead to find out theirs) so you can find them with quick searches.

For scripts, prefixes are not as important. Generally, unless editing a vanilla script, you should know what mod properties belong to as they're all contained in ONE (or a few) scripts, which you can prefix. You SHOULD indeed prefix these scriptnames, but the functions/properties/variables do not need to have prefixes.

However, they should clearly and concisely describe what they're doing. If I make an integer variable that I intend to use to store the health of the player's current enemy, "duckytoy†isn't a good name for it. Even if you'll provide yourself a laugh when looking back at your scripts, that'll quickly become an annoyance when you want to understand what that variable's purpose was at a glance. And no - you don't always or even usually remember these things. You'll be making a lot of scripts. So, in this case, enemyHealth would be a preferable name, or something along those lines.

I know some people who just name their variables and don't give a rat's ass what they're called - resulting in something like a Bool called Shitgoesboom (Darren, I'm lookin' at you!). You'll regret it. Unless you're insane. And even then, it'll be harder to be insane because you'll have no IDEA what Shitgoesboom even does. Only to find out after hours of sifting through your script and CK that it really was just used to check if the duckytoy was enabled yet.

I made that way longer then it needed to be, apologies. For naming, aside from clear and sensible names, you'll want to decide how they look. There are 5 choices for you:

  • WhAtiSthisVarEvENdoiNGTomeEYES - You think that's readable? Think again. Please. It hurts to look at.
  • ALLTHECAPSEVERALLTHETIMEYEEES - Meh. Maybe you're angry, but there's no need to yell at your script.
  • camelCase - First word uncapitalized, the rest capitalized. A bit strange but also pleasing to the eye and easy to look at. It LOOKS codey too. Makes you feel more sophisticated. That's probably why I use it.
  • CapsForEachWord - It looks nice, makes sense. Clean and simple.
  • allthelowercases - Please, spare me. The human eye can't comprehend when a word begins and another ends without explicitly knowing the word. What if you made up a word, or there was a name? How would you know when the name began and when it ended?!
PLEASE don't do 1, 2, or 5. I will personally kick you out of the program. Not really. But I will pay less attention to your scripts. ;) After that, it's personal preference between 3 and 4. I like 3, though I used to use 4. Whatever you like. The difference is minimal - duckyToy vs DuckyToy; playerHealth vs PlayerHealth. Capitalization has no effect in Papyrus so type away. Prettily.

Commenting

Jesus, how did I not make it to this section faster? Commenting can be done in one of two ways:

{Comment in here}

; comment over here

The first CAN be used anywhere but is special in the sense that, when underneath Properties and your Scriptname declaration, it will show as a tooltip in the CK. So, if I create a comment underneath the declaration of exampleScript:

Scriptname exampleScript extends duckyToy
{It's an example!}
 

Then when I hover over exampleScript in the CK, a tooltip will appear telling me It's an example!. This can be helpful to keep track of things and remember what things are without ever entering the sometimes ugly, sometimes beautiful world that is your script.

The usual comment is just past the semicolon, like so:

Event onActivate(ObjectReference akActivator); well, that's a perfectly respectable event
 

Anything past that semi colon will be ignored by the compiler. It's strictly for the benefit of the reader or writer of the script.

But comments are IMPORTANT. You need to get into a habit of leaving them, ASAP. You should write comments everywhere in your scripts, to say what blocks of code are doing, what a property is supposed to do or what its respective values may mean. Even past a small line of code, it's good to say WHAT that line of code does if it's not extremely obvious (I mean to anyone - not YOU at the time). That way, if you go back and look into your script, you don't need to redo everything in your head because you're thinking "WTF was this doing here?â€. It can keep your script more organized as well.

Spacing

Spacing is also quite important to the readability of your script. By spacing, I mean when to use TABs (usually 4-5 spaces) and line breaks (hitting ENTER). Tell me - how easy is it for you to decipher this script?

scriptname thisScript extends objectReference
int dumbo
bool lock
event onactivate(objectreference akactionref)
if dumbo == 20
if (akactionref as actor); btw, this will evaluate to true if akactionref is an actor. Handy condition check
if !(lock); if lock is not true
akactionref.kill()
else
akactionref.disable()
endif
else
akactionref.enable()
endif
elseif dumbo == 30
if lock
while dumbo < 50
dumbo += 1
endwhile
endif
endif
endevent
 

Because it compiles (I think). It does. All the endifs are there, all the endwhiles. Even the endevent. But… The lack of space (tab) makes it incredibly difficult to read. Here's a comparison example, two fragments:

int smarty
 
Event onActivate(ObjectReference akActionRef)
    If smarty >= 30
         If (akActionRef as Actor)
              While akActionRef == Game.GetPlayer(); if this was the player then this would be an endless loop - don't use this code ever
    Smarty += 40
EndWhile
         EndIf
    EndIf
EndEvent
 

VS

int smarty
Event onActivate(ObjectReference akActionRef)
If smarty >= 30
If (akActionRef as Actor)
While akActionRef == Game.GetPlayer(); if this was the player then this would be an endless loop - don't use this code ever
  Smarty += 40
  EndWhile
            EndIf
EndIf
EndEvent
 

So don't be the second person. Please. Decide how you want your code formatted, and keep it consistent. More line breaks (like one between Ifs, stuff inside If blocks, Whiles, stuff inside Whiles, Events, stuff inside Events, etc.) does mean a larger file size for your script - BARELY. We're talking a couple KB or less for your source file, depending on the number of lines in your script, and the compiled .pex is smaller than the source. But if you freak out about that, just note it and adjust the way you want to write your code.

My advice - never sacrifice the tabs. :D

Edited by Matthiaswagg

Share this post


Link to post
Share on other sites

Casting

Casting is converting one type of property or variable to another. Here are some examples of types:

  1. Int

  2. Bool

  3. String

  4. ObjectReference

  5. Weapon

The list goes on. These are all TYPES and can be used for properties, integers, or as function return types (detailed in the functions lesson).

Some functions require certain type inputs. For example, the command Wait (which is a global native function in Utility, so you'll need to call Utility.Wait or import Utility to use it) requires the float parameter afSeconds to be used:

Utility.Wait(float afSeconds); to be called:
Utility.Wait(1.0); since 1.0 is a float, it is a valid input

However, you could also put in an integer value instead of a float:

Utility.Wait(1); same as 1.0)

This is automatic casting. A float can be automatically cast to an int without any extra input required from you. It IS (incredibly slightly) less efficient, so you should use a float instead of an int if it's asked for, since it's less to process. But, it does work. There are other examples of automatic casting - bool to int and vice versa, stuff like that. However, to cast you generally need to do so "manuallyâ€. I'll use an example - GetValue(), called on a variable/property of type GlobalVariable, returns the float value of  that global. This means that you CANNOT do this:

Int foo = myGlobal.GetValue()

Without compiler errors. You CAN, however, do:

Int foo = myGlobal.GetValue() as int

That as is how you cast. Not everything can be cast - that can generally be found out by using common sense and the compiler (it'll say types aren't compatible if it won't cast). Sometimes, if you try to cast something that isn't "castableâ€, it'll just cast to NONE. So casting a Weapon to a Bool will just make that Weapon variable = NONE, which is as if the property or variable did not have a value, because it won't.

Casting can be a little tricky, but you should be able to get the hang of it. I'd like you to write me a returning function (see the functions lesson if you need reference) which will return the integer version of a float inputted as that function's parameter.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×