Jump to content
Mattiewagg

[Student Thread] Treeaza - Matthiaswagg

Recommended Posts

I had all that done.  I'll play around with the setup a bit more and see if I can get it to work.  I knew about the semicolons, that's just a habit from Java and C#.  Sometimes I accidentally put them after sentences too;

Edited by Treeaza
  • reaction_title_1 1

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:
  1. WhAtiSthisVarEvENdoiNGTomeEYES - You think that's readable? Think again. Please. It hurts to look at.
  2. ALLTHECAPSEVERALLTHETIMEYEEES - Meh. Maybe you're angry, but there's no need to yell at your script.
  3. 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.
  4. CapsForEachWord - It looks nice, makes sense. Clean and simple.
  5. 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

Share this post


Link to post
Share on other sites

I'll stick with the way I did things in Java for spacing and naming.  That is, using camel case for everything except object types, where I use all caps.  As for spacing, it drives me crazy when things aren't spaced correctly.  For a while I didn't use the Visual Studio auto-format because it didn't indent empty lines.  Then I realized you can change that, so now all my code is uniform.  I'm still working on my commenting habits.  Once I had a little game I was working on in Java with (*opens Eclipse to count*) 1481 lines of code and one comment.

Edited by Treeaza
  • 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.  ;)

Share this post


Link to post
Share on other sites

Here you go:

Scriptname SummonableEnemy extends Actor
Float Property healthWhenSummoned Auto

Function Summon(Float x, Float y, Float z)
	; Summons this enemy
endFunction
	

Scriptname OnHitSummonEnemy extends ObjectReference
SummonableEnemy Property enemyToSummon
	
Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
	enemyToSummon.healthWhenSummoned = 10.0
	enemyToSummon.Summon(self.x + 1.0, self.y + 1.0, self.z)
endEvent
	

 

I forget what my original intention was with these scripts, and they don't function, but one does access the other.

Edited by Treeaza

Share this post


Link to post
Share on other sites

Okay, yep, that would work technically. The scripts themselves don't because:

10 is not a float and HealthWhenSummoned is. 10.0 - it can cast itself but why make it when it doesn't have to, as it's a slight optimization to make it 10.0. self.x +  1 won't necessarily be a float, though likely will, so make that 1.0. And I'm not certain self.x is a function but maybe. However, there are functions for getting x, y, and z, hence my hesitation about whether it IS one.

Also the scripts have no function. :P But if the above were fixed they would compile.

But yes - it would work for communicating between scripts.

Share this post


Link to post
Share on other sites

I've fixed that now.  I completely forgot about the Int and Float type difference.  I would have made a functional script, but I couldn't think of what to do.

​No worries. I have the same problem when trying to make you write a script.

Speaking of which - would you like to write another script or keep going with lessons first? We still have to cover things like Arrays and Advanced Properties II, but you're relatively close to the end.

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. 

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

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

×