Trying to learn Papyrus scripting...

Post » Thu Jun 21, 2012 1:06 pm

I want to learn the scripting language so I can make cool mods. So I have been going through the creation kit and looking through scripts along with the creation kit wiki to learn the various functions... While I am not totally new to programming, there is a lot of new stuff that I am trying to get used to. I was hoping someone could help walk me through a small default script and make sure I am understanding this stuff right. The script I am going through is called WIDeadBodyCleanupScript:

Spoiler

Scriptname WIDeadBodyCleanupScript extends Actor;**** This should ONLY BE USED ON UNIQUE ACTORS!!! ****;This script cleans up dead actors, enables a container (ie grave/crypt) and moves all their inventory, after it Loads/unloads after a certain period of time.;If you need to temporarily stop someone from being cleaned up, put them in the WINoBodyCleanupFaction faction.;please do not modify this script without talking to jduvallWIFunctionsScript Property WI Auto ;(10){Pointer to WIFunctionsScript attached to WI quest. You MUST set this or things will be broken.}float Property DaysBeforeCleanUp = 0.5 Auto{Default = 0.5: What's the minimum amount of time (in days) that the body should stick around before being cleaned up. (Clean up happens during while loop.)}ObjectReference Property DeathContainer Auto{Container to move all the items the actor has in his inventory when cleaned up.}actor SelfRef  ;used to keep me persistent so I get UnLoad events while I exist; (20)state Dead	 ;do nothing	 Event OnDeath(Actor akKiller)	 EndEventEndStateEvent OnDeath(Actor akKiller); (30)	 GoToState("Dead")	 if DeathContainer		  bool cleanedUp = false		  while cleanedUp == false;			  debug.trace("WIDeadBodyCleanupScript" + self + "OnDeath() In While Loop, will try cleaning up after waiting " + DaysBeforeCleanUp)			   utility.waitGameTime(DaysBeforeCleanUp * 24) ; (40)			   cleanedUp = checkForCleanup()		  endWhile	 else;		 debug.trace("WIDeadBodyCleanupScript" + self + " WARNING: NO DeathContainer!", 2)	 endif; (50)EndEventbool function checkForCleanup()	 if IsInFaction(WI.WINoBodyCleanupFaction);		  debug.trace("WIDeadBodyCleanupScript" + self + "In Faction WINoBodyCleanupFaction so NOT cleaning up body.", 1)		   ;do nothing		   return true ;bail out of while loop; (60)	 Elseif Is3DLoaded() == False;		  debug.trace("WIDeadBodyCleanupScript" + self + "Cleaning up body.")		   cleanUpBody()		   return true	 Else;		  debug.trace("WIDeadBodyCleanupScript" + self + "Not cleaning up body, because my 3D is loaded.")	 EndIf	 return false; (70)endfunctionfunction cleanUpBody();	 debug.trace("WIDeadBodyCleanupScript" + self + "cleanUpBody() moving to WIDeadBodyCleanupCellMarker in WIDeadBodyCleanupCell and Calling RemoveAllItems() to DeathContainer, and enabling it:" + DeathContainer)	  ;Disable()	  ;*** It has been decided it's safer to move them to a holding cell, for quests that might be filling allowing for dead actors but not allowing checking for disabled actors	  MoveTo(WI.WIDeadBodyCleanupCellMarker); (80)	  DeathContainer.SetActorOwner(GetActorBase())	  DeathContainer.Enable()	  RemoveAllItems(DeathContainer)EndFunction

I added the ; (10) through ; (80) to make it easier to find the lines I am talking about, they were not a part of the original script.

Line 1 starts with - Scriptname WIDeadBodyCleanupScript extends Actor - From my understanding of the wiki, this is how one must start each script were WIDeadBodyCleanupScript is the name of the script and Actor is the type of script it is used on.

Line 3 starts with a semicolon. I am guessing (though have no confirmation) that lines starting with semicolons are comment lines.

In all honesty I have no idea what Line 10 is. My guess is that it is declaring a variable called "WI" of type WIFunctionsScript and the it is a special type of variable used in papyrus called a property and the property is an "auto". This doesn't make much sense to me because I am used to working with variables like int, float, long, bool and so forth, not WIFunctionsScript. Also I don't competely understand properties.

I have to assume that Line 11 is also a comment, and that you can comment stuff out by putting them in braces.

Line 13 is easier to understand than Line 10 but similar. It is declaring a float variable called DaysBeforeCleanUp and initializing it to .5. It is also a property of type auto.

Line 16 is also easier to understand. It declares another variable called DeathContainer of type ObjectReference. I assume that we are going to set it to the Form ID of the container we want to use? It is also a property of type auto.

Line 19 also confuses me. It looks like you are declaring an actor called "SelfRef" or maybe its calling a function called "SelfRef" but it doesn't use the actor and I wasn't able to find the function on the wiki (I assume it would be in either this script or the script it extends, the actor script).

Line 21-26 looks like it does nothing. It creates a state called "Dead" (but doesn't set the actor to the state?), and then does nothing when the actor is killed. Why do we need this?

Line 29-51 is a special type of function called an event. It receives data for an actor an sets it to the actor variable akKiller (which this function doesn't use). The event starts on the death of the actor the script is attached to. It puts the actor into the new state called "Dead". It then starts an if statement that is unlike what I usually see. I assume it reads a non zero value as true, but we haven't set "DeathContainer" to anything yet (so I was under the impression it can be anything since it hasn't be initialized). Assuming that "DeathContainer" is true, it creates a bool variable called "cleanedUp" and sets it to false. Then starts a while statement that while "cleanedUp" is equal to false, it launches a function from the utility script called waitGameTime (found on the wiki, pauses script for the arguments time in hours). The argument is 24 times a float variable we set to .5 earlier in the script. Line 42 sets cleanedUp to the return of the function checkForCleanup() in order to get out of the loop. Assuming that "DeathContainer" is false, nothing happens.

Line 38, 47, 57, 62, 66 and 74 also confuse me. If a semi colon marks a comment line, why is the comment written codelike?

Line 54-71 defines a function called checkForCleanup() that wants a bool in return. (do functions not need prototypes?). First it checks if the actor attached to this script is in the faction "WINoBodyCleanupFaction" (why do they use WI.WINoBodyCleanupFaction and not just WINoBodyCleanupFaction as the argument). If true it returns true. Else it checks that the actor is not loaded. If true it calls the function cleanUpBody() and returns true. Else it returns false.

Line 73-85 looks like it moves the actor to a marker defined by "WIDeadBodyCleanupCellMarker", that is probably in another script called WI? Then it uses DeathContainer (its still undefined right?) and sets its owner to the actor attached to this script (SetActorOwner and GetActorBase are on the wiki). It then Enables the DeathContainer, and removes all items from DeathContainer (we never added anything to it though right? How is this removing the items from the actor?)

That is my basic understanding of this script. If anyone could help clear up some of my confusion and tell me what I am getting right and help me out, I would be grateful. Thanks for your time!
User avatar
Annika Marziniak
 
Posts: 3416
Joined: Wed Apr 18, 2007 6:22 am

Post » Thu Jun 21, 2012 7:47 pm

Hi lisaburlew,
I've gone through the script and your post, and I think I can help you. Hopefully what I have to say will help your understanding of this script and of Papyurs.

Line 1 starts with - Scriptname WIDeadBodyCleanupScript extends Actor - From my understanding of the wiki, this is how one must start each script were WIDeadBodyCleanupScript is the name of the script and Actor is the type of script it is used on.
That's correct, all scripts have to start with a http://www.creationkit.com/Script_File_Structure#Header_Line, and any scripts that you write will also have to use the http://www.creationkit.com/Extending_Scripts_(Papyrus) keyword to extend an existing script.

Line 3 starts with a semicolon. I am guessing (though have no confirmation) that lines starting with semicolons are comment lines.
You're right there - semicolons are used in Papyrus to denote line comments. There are 2 other types of comment available as well. A typical block comment, though not commonly seen, start with ;/ and end with /; like so:
;/This is ablock comment/;

Also available are documentation comments, which have to go immediately after a ScriptName declaration or a property of function definition. They are enclosed in curly brackets - {} - and appear in the Creation Kit as hover text. Here's an example:
ScriptName MyScript extends ObjectReference{This script is for doing things}
The wiki documents comment syntax on the http://www.creationkit.com/Script_File_Structure#Comments page.

In all honesty I have no idea what Line 10 is. My guess is that it is declaring a variable called "WI" of type WIFunctionsScript and the it is a special type of variable used in papyrus called a property and the property is an "auto". This doesn't make much sense to me because I am used to working with variables like int, float, long, bool and so forth, not WIFunctionsScript. Also I don't competely understand properties.
Line 10 is a property definition. Properties are usually very much like variables, with the main difference being that they are accessible to other scripts and to the Creation Kit. This is the case if they are declared with the http://www.creationkit.com/Variables_and_Properties#Auto_Properties keyword. There are other ways in which properties can be declared that you can read about on the wiki - http://www.creationkit.com/Variables_and_Properties#Declaring_Properties

That the type of property is WIFunctionsScript reveals an important aspect of Papyrus, which is that each script is an object type definition. Some pre-existing scripts are native scripts, such as Form and Actor. Some of these scripts do not extend any other scripts, such as Form, and if you follow the trail of inheritance you will always end up at such a script. Native scripts are also automatically attached to objects of the correct type, unlike other scripts which are attached in a data file.

Using the extends keyword basically means 2 things: "This script can be attached to objects of the type this script is extending" and "This script contains everything defined in the script it's extending".

I have to assume that Line 11 is also a comment, and that you can comment stuff out by putting them in braces.
As I mentioned before, that's a documentation comment. In this case, its contents would appear as hover text when the cursor hovers over this property in the Creation Kit

Apparently I can't post all that I want to say in one go, as the forum seems to have trouble handling so many quotes in one post. The second part of my post is just below...

Cipscis
User avatar
Katie Pollard
 
Posts: 3460
Joined: Thu Nov 09, 2006 11:23 pm

Post » Thu Jun 21, 2012 3:53 pm

Line 13 is easier to understand than Line 10 but similar. It is declaring a float variable called DaysBeforeCleanUp and initializing it to .5. It is also a property of type auto.
You've pretty much got this one. The main difference, as you've noticed, is that this property is initialised in the script. It's worth knowing that all auto properties can be given default values via the Creation Kit, although it's not necessary for ones like this. The type of this property, unlike the other one, is a native type. Native types are (sort of) listed on the wiki here - http://www.creationkit.com/Literals_Reference

Line 16 is also easier to understand. It declares another variable called DeathContainer of type ObjectReference. I assume that we are going to set it to the Form ID of the container we want to use? It is also a property of type auto.
Your assumption sounds good to me. The value of this property is never set in this script, so it must be set externally. It's most likely that it is specified via the Creation Kit, although since it's a property it's also accessible to other scripts.

Line 19 also confuses me. It looks like you are declaring an actor called "SelfRef" or maybe its calling a function called "SelfRef" but it doesn't use the actor and I wasn't able to find the function on the wiki (I assume it would be in either this script or the script it extends, the actor script).
This line is a variable declaration, defining an http://www.creationkit.com/Variable_Reference#Object_Variables of type Actor. Variables are similar to auto properties, except they are not accessible outside of this script.

Line 21-26 looks like it does nothing. It creates a state called "Dead" (but doesn't set the actor to the state?), and then does nothing when the actor is killed. Why do we need this?
This state is probably most confusing because it appears before it's useful. If you look to the section from lines 29-51, you'll see that the http://www.creationkit.com/OnDeath_-_Actor event is defined. Because this definition is not in any state (technically the state of not being in the state is sort of like specifically being in the no-state, if that makes sense) it will be used by default. However, when this event is called, it calls http://www.creationkit.com/GotoState_-_All_Scripts to send the script into the "Dead" state.

What this means is that, for any functions (including events) that are defined both in no states (or, as I said, in the no-state) and in the "Dead" state, the definition in the "Dead" state will now take precedence, so if the OnDeath event is called again, nothing will happen.

Line 29-51 is a special type of function called an event. It receives data for an actor an sets it to the actor variable akKiller (which this function doesn't use). The event starts on the death of the actor the script is attached to. It puts the actor into the new state called "Dead". It then starts an if statement that is unlike what I usually see. I assume it reads a non zero value as true, but we haven't set "DeathContainer" to anything yet (so I was under the impression it can be anything since it hasn't be initialized). Assuming that "DeathContainer" is true, it creates a bool variable called "cleanedUp" and sets it to false. Then starts a while statement that while "cleanedUp" is equal to false, it launches a function from the utility script called waitGameTime (found on the wiki, pauses script for the arguments time in hours). The argument is 24 times a float variable we set to .5 earlier in the script. Line 42 sets cleanedUp to the return of the function checkForCleanup() in order to get out of the loop. Assuming that "DeathContainer" is false, nothing happens.
You've pretty much got this sorted out, I think. Your assumption about the conditional statement is correct. DeathContainer is an object type, and the only object value that can be http://www.creationkit.com/Cast_Reference#Cast_to_Boolean is http://www.creationkit.com/Literals_Reference#None_Literal, so this conditional statement is essentially the same as this:
if DeathContainer != None
I'm not sure if the Papyrus compiler does enough optimisation to make that version as efficient as the one in the actual script, but ignoring that they do the same thing.

Also remember that, as I said earlier, the value for DeathContainer will likely be set via the Creation Kit, although it also may be altered in another script.

Line 38, 47, 57, 62, 66 and 74 also confuse me. If a semi colon marks a comment line, why is the comment written codelike?
That's just commented out code. The http://www.creationkit.com/Trace_-_Debug function logs a message to the Papyrus log on disk, which is useful in debugging. However, it is not necessary for the script to run, and would potentially spam the Papyrus log, so it's been commented out to stop it from running.

Line 54-71 defines a function called checkForCleanup() that wants a bool in return. (do functions not need prototypes?). First it checks if the actor attached to this script is in the faction "WINoBodyCleanupFaction" (why do they use WI.WINoBodyCleanupFaction and not just WINoBodyCleanupFaction as the argument). If true it returns true. Else it checks that the actor is not loaded. If true it calls the function cleanUpBody() and returns true. Else it returns false.
No, functions don't need prototypes in Papyrus. In fact, I don't believe Papyrus allows for function prototypes.

WI.WINoBodyCleanupFaction means the property called WINoBodyCleanupFaction on the object stored in the WI property of this script. If WINoBodyCleanupFaction alone were used, compiler would complain that no such object exists. It's important to remember that the Papyrus compiler cannot look inside any data files, so any time a script needs to refer to something in a data file, a property must be used. In this case, the property required is on the object stored in the WI property, as the value stored there is the central controlling quest for various related scripts, and of course storing the value there once is easier than storing it multiple times in different scripts.

Line 73-85 looks like it moves the actor to a marker defined by "WIDeadBodyCleanupCellMarker", that is probably in another script called WI? Then it uses DeathContainer (its still undefined right?) and sets its owner to the actor attached to this script (SetActorOwner and GetActorBase are on the wiki). It then Enables the DeathContainer, and removes all items from DeathContainer (we never added anything to it though right? How is this removing the items from the actor?)
Line 83 doesn't remove any items from DeathContainer. Instead, it calls http://www.creationkit.com/RemoveAllItems_-_ObjectReference on the Actor to which this script is attached, and passes DeathContainer as its akTransferTo parameter. In other words, it moves all items in the Actor's inventory into DeathContainer.

That is my basic understanding of this script. If anyone could help clear up some of my confusion and tell me what I am getting right and help me out, I would be grateful. Thanks for your time!
I hope what I've said has been helpful for you. Let me know if anything I said was unclear.

Cipscis
User avatar
The Time Car
 
Posts: 3435
Joined: Sat Oct 27, 2007 7:13 pm

Post » Thu Jun 21, 2012 7:45 am

Thank you so much for your help! Your explanations were clear and thorough, and it will definitively help me on my quest of learning the papyrus language. I am going to keep going through default scripts in the editor and using this as a base, I should start to understand the language more and more. Thanks again!
User avatar
Louise
 
Posts: 3407
Joined: Wed Nov 01, 2006 1:06 pm


Return to V - Skyrim