Jump to content
Mattiewagg

[Student Thread] Treeaza - Matthiaswagg

Recommended Posts

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

Let's see if I can get this one right on the first go:

Int Function FloatToInt(Float toChange)
    return toChange as Int
endFunction

 

​Goal accomplished. Perfect.

Write a quick script (doesn't have to have a reasonable function, but it should be one that would feasibly compile properly) using that function.

Share this post


Link to post
Share on other sites

I made this script for a spell that tells you how much health you have:

 

Scriptname HealthDisplaySpellEffect extends activemagiceffect  

Event OnEffectStart(Actor akTarget, Actor akCaster)
	Float health = akCaster.GetActorValue("Health")
	Debug.Notification("You have " + FloatToInt(health) + " health.")
EndEvent

Int Function FloatToInt(Float toChange)
	return toChange as Int
endFunction

 

Edited by Treeaza

Share this post


Link to post
Share on other sites

Fantastic. Couple notes:

Try to avoid use of convenience functions. I haven't told you this so that's my fault. Convenience functions are things like "GetAV" "GetRef" or "GetActorRef". They, stupid as papyrus sometimes is, call the lengthened version of them self, which in turn calls the actual code, rather than calling the code immediately:

GetAV>GetActorValue>Actual stuff happens

GetActorRef>GetActorReference>GetReference as Actor>Actual stuff happens, cast to actor

GetRef>GetReference>Actual stuff happens

Not only is this (almost imperceptibly) slower, and less efficient, it also means more function calls. Papyrus is a threaded language, so extra function calls COULD, feasibly, slow down your script speed more than expected.

you named your var Heath instead of Health, though it's technically just fine.

The way you wrote the notification debug would, for example, actually notify the player as:

"You have2health".

change it to:

"You have " + floatToInt(health) + " health".

Share this post


Link to post
Share on other sites

Arrays

Arrays are an incredibly useful tool  that can save you time, many lines of code, and allow for more dynamic usage of scripts.

An array is a type of variable or property - not in the way an Int is a type. It's a modifier of them, essentially.

  1. Int Variable > Int Variable Array

  2. Weapon Property > Weapon Property Arrar
  3. Float Property > Float Property Array
  4. Bool Variable > Bool Variable Array

Etc. An array is a collection of properties/variables. There are various functions to cycle through them and do something to each element.

Arrays can be declared two ways. One, they can be declared by creating a property in the Properties window of your script and checking the Array box when doing so. Or, the variable/property can be declared like so:

Bool[] BoolArray
;or
ObjectReference[] Property ObjRefArray Auto

You can change any property or variable into an array by adding those brackets.

You can't do things to an array like you normally would for a property or variable, since they consist of more than one property/variable (again, elements). Each element is assigned an index - a number (integer) - which is used to do things to that element. The first element in an array is 0, and counts up by 1 from there. This means that the highest element will have an index 1 less than the length of the array. You call upon elements like so:

ObjectReference[] Property ObjRefArray Auto
 
ObjRefArray[0].Disable(); disables the first ObjectReference element in the array

You can put any number in there and have it work, provided there is an element at that index (else, nothing happens and Papyrus will be sad and possibly angry, but it won't break). You can also put in a variable - so let's say I have a list of random potions I want to give the player when my script is initialized (doesn't matter what the script is on, so I'll leave the scriptname part out). I fill an array with all the potions, and then:

Potion[] Property PotionsArray Auto
 
Event OnInit()
    Int x = Utility.RandomInt(0, 20); don't leave out 0, it's an index in the array. I'll just pretend there's 20 elements
    Game.GetPlayer().AddItem(PotionsArray[x], 1)
EndEvent

There is another function that will allow you to manipulate arrays. It's not exactly a function in that there's no parameter and you don't even have to use parentheses. It's sort of like Self. It's called Length, and is used to get the number of elements in an array.

PotionsArray.Length

You leave out the brackets on the array because you're just calling it on the property itself, not an element. Length is a function/special function for the whole array.

One way to use Length would be something to improve that little script with the potion array we wrote earlier. Rather than picking a range ending at 20 for our integer, which has the problem of needing to be changed each time I add or remove from the array and me needing to know there are that many elements in the array, which I might not always, I can just use PotionsArray.Length and use that as the parameter for the end of the range:

Int x = Utility.RandomInt(0, PotionsArray.Length)

You can combine this method with a While statement to do something to every element in an array with only a few lines of code.

Whiles

I'm not sure if I've mentioned Whiles to you before, but they're sort of like Ifs. They have blocks (begun by While and ended by EndWhile, like If/EndIf). There needs to be a condition in the While declaration - While x > 10, for example. Then, all code within that While block will be run until that condition is no longer true. Be sure to avoid Whiles that will never end. These are called infinite loops and are quite likely to crash the game. Check your conditions, basically. Don't do something like "If 1 < 2†or anything that will always evaluate to true and never change.

Back to Arrays

So, we can combine Whiles with Length to do something to EVERY element in an array with minimal effort. Let's say we have 100 guards that we want to enable. It'd be a pain to have 100 lines of code that "Guard1.Enable()†"Guard2.Enable()†"Guard3.Enable()â€. Even if copy pasting, you'd still need to change the name for each and it would still be 100 lines of code that you could have achieved in 5:

Actor[] Property Guards Auto
Event OnInit(); can be any event or function
    Int index = Guards.Length; doesn't have to be called index but I prefer that
    While index; while index doesn't = 0
          Index -= 1; take 1 away from index - remember, the highest element in an array is 1 less than the length, so we have to start with 1 less than the length to begin with the last element
          Guards[index].Enable()
    EndWhile
EndEvent

As long as the index isn't at 0 (done), the while will keep looping - subtract from index, enable guard at the new index number. Repeat.

Whole lot better than 100 lines of code, isn't it?

Write me a script extending anything you like which will Kill every actor in an array (however long you like, in whatever event) UNLESS that actor has health bigger than or equal to 50. I'll leave it up to you to find the appropriate functions - I recommend the CK Wiki, as always.

Hint: (Read after you submit your script, but don't change it once

 

Health is an actor value. Look for functions that do things to actor values. If you can't find it, search the wiki for List of Papyrus Functions and Ctrl + F it for ActorValue.

Hint2:

 

There should be an If block inside your While, after the index -= part, checking If the actor in the array has a health LESS than 50 and killing him if so. Remember - unnecessary Elses aren't good. Elses should only be used if you have two situations you want. So, you're only doing something if the health is NOT bigger than or equal to 50, so you check If it's lower than that and then kill.

 

Edited by Mattiewagg

Share this post


Link to post
Share on other sites

Here we go:

 

Scriptname KillArrayOfActorsEffect extends activemagiceffect  

Actor[] Property actorsToKill Auto

Event OnEffectStart(Actor akTarget, Actor akCaster)
	Int on = 0
	while on < actorsToKill.Length
		if actorsToKill[on].GetActorValue("Health") < 50 ; We wouldn't want to kill healthy people!
			actorsToKill[on].Kill(akCaster) ; Kill them, with the caster as the guilty murderer.
		EndIf
		
		on += 1
	EndWhile
EndEvent

 

Just one quick question.  Does Papyrus have for loops, or is a while loop on a counter your only option for doing something several times?

Edited by Treeaza
I forgot to use -= instead of --. Old Java/C# habits.

Share this post


Link to post
Share on other sites

Here we go:

 

Scriptname KillArrayOfActorsEffect extends activemagiceffect  

Actor[] Property actorsToKill Auto

Event OnEffectStart(Actor akTarget, Actor akCaster)
	Int on = 0
	while on < actorsToKill.Length
		if actorsToKill[on].GetActorValue("Health") < 50 ; We wouldn't want to kill healthy people!
			actorsToKill[on].Kill(akCaster) ; Kill them, with the caster as the guilty murderer.
		EndIf
		
		on += 1
	EndWhile
EndEvent

 

Just one quick question.  Does Papyrus have for loops, or is a while loop on a counter your only option for doing something several times?

​Only While loops.

That is a perfect functioning script. :) One thing though - in terms of efficiency. You can totally count UP through the array if you like, but you can also count down and remove the necessity of having that Length part every time.

Make on = actorsToKill.Length, just do While on (which will run it While on isn't 0 - it casts the int to a bool there). and then change the on += 1 to on -= 1 and move it to the top, like so:

Event OnEffectStart(Actor akTarget, Actor akCaster)
	Int on = actorsToKill.Length
	while on
		on -= 1
		if actorsToKill[on].GetActorValue("Health") < 50 ; We wouldn't want to kill healthy people!
			actorsToKill[on].Kill(akCaster) ; Kill them, with the caster as the guilty murderer.
		EndIf
	EndWhile
EndEvent

The reason you move the -= part up is because Length is 1 more than the highest element in the array (since 0 is an element), so you want to get to the highest element before starting to avoid a useless loop. You could also make on = actorsToKill.Length - 1 and keep the -= 1 down there if you like.

Share this post


Link to post
Share on other sites

Now, you're going to learn about States. And I'm just going to link a few tutorials for you to read through, because I have never gotten around to making an actual lesson for this one.

http://www.creationkit.com/States_(Papyrus) - Read first

http://www.cipscis.com/skyrim/tutorials/states.aspx 

Tell me when you're done.

Share this post


Link to post
Share on other sites

Information on states has been read.

​Grand. Now - we have script deciphering. It's practice, I give you a script with no names or comments, and you try to figure out what it does (tell me) and then, at the end, tell me what you think a possible use could be.

Hint:

Read up on GetCurrentGameTime, figure out why I'd want to use floor on it. 

It gives you just the day, since the integer part of the global GameDaysPassed is the days, whereas the decimals correspond to hours/minutes/etc.

Scriptname ascript Extends ObjectReference

Spell Property someSpell Auto 
ObjectReference Property marker Auto 
;this IS an enable marker, which is linked to other objects, and when the marker is enabled, so are the other objects, and vice versa
;this is linked to a sound emitter, and a particle effect (on top of a different object, which is not linked)
Actor Property PlayerREF Auto 
{The player, should autofill}
GlobalVariable Property aglobal Auto 

 
Auto State statea
 
 	Event OnBeginState()
 		marker.Enable(true)
 	EndEvent

	Event OnActivate(ObjectReference akActionRef)

		If PlayerREF.HasSpell(someSpell)
			PlayerREF.RemoveSpell(someSpell)
			Utility.Wait(0.1)
		EndIf
		PlayerREF.AddSpell(someSpell, true)
		GoToState("stateb")
	EndEvent
 	
EndState
 
State stateb
 
 	Event OnActivate(ObjectReference akActionRef)
 	
 	EndEvent

	Event OnBeginState()
		marker.Disable()
		RegisterForSingleUpdateGameTimeAt(0.0)
	EndEvent
 
	Event OnUpdateGameTime()
		GoToState("statea")
	EndEvent
 
EndState

Function iAmAFunction(float c)
 
	float foo = otherFunction()
	If (c < foo)
		c += 24
	EndIf
 
	RegisterForSingleUpdateGameTime(c - foo)
 
EndFunction

float Function OtherFunction() global
 
	float z = Utility.GetCurrentGameTime()
	z -= Math.Floor(z) 
	z *= 24 
	Return z
 
EndFunction

So yeah. Tell me, part by part (state by state, function by function/event if you wish), what it does, and then at the end, tell me what you think it is intended to do as a greater whole/

Share this post


Link to post
Share on other sites

Okay, let's see.

 

The script extends ObjectReference.  We have a Spell property, an ObjectReference enable property, the player, and some global variable.

Auto State statea
 
 	Event OnBeginState()
 		marker.Enable(true)
 	EndEvent

	Event OnActivate(ObjectReference akActionRef)

		If PlayerREF.HasSpell(someSpell)
			PlayerREF.RemoveSpell(someSpell)
			Utility.Wait(0.1)
		EndIf
		PlayerREF.AddSpell(someSpell, true)
		GoToState("stateb")
	EndEvent
 	
EndState

Now, we have a state, statea, that we are in by default.  In the OnBeginState() event, we enable the ObjectReference property from before.  When the OnActivate() event is called in this state, we check if the player has the spell stored in our spell property, and if so remove the spell and wait 0.1 seconds, presumably for some effect or something else.  Then we add the spell and go to stateb.

 


 

State stateb
 
 	Event OnActivate(ObjectReference akActionRef)
 	
 	EndEvent

	Event OnBeginState()
		marker.Disable()
		RegisterForSingleUpdateGameTimeAt(0.0)
	EndEvent
 
	Event OnUpdateGameTime()
		GoToState("statea")
	EndEvent
 
EndState

stateb's OnActivate() is empty, possibly to disable it.  In OnBeginState(), we disable the ObjectReference, and  register for an immediate game update.  Then we have the OnUpdateGameTime() event, which will be called shortly after because we registered for the update, which brings us back to statea.


Function iAmAFunction(float c)
 
	float foo = otherFunction()
	If (c < foo)
		c += 24
	EndIf
 
	RegisterForSingleUpdateGameTime(c - foo)
 
EndFunction

float Function OtherFunction() global
 
	float z = Utility.GetCurrentGameTime()
	z -= Math.Floor(z) 
	z *= 24 
	Return z and fall in a big hole!
 
EndFunction

 

In iAmAFunction(float) we first get another float from OtherFunction(), foo,  check if the float passed to the function is less than that, if it is, add 24 to the float passed in, then register for an update in that float less foo.

 

Finally OtherFunction().  It gets the game time, then runs floor on it.  This gives it the number of days passed as a whole number.  By multiplying it by 24 we get hours, then this number is returned.

 

Now, what do I think this does.  I think this is for some sort of device to give players spells.  When it is activated, it clears the spell if they have it, then gives it to them, waits a moment, then goes to a new state which deactivates the sound and particle emitters the objectreference is linked to, then goes back and reactivates them , preparing the device for use again.  OtherFunction at the end returns the number of days that have fully passed as hours.  The purpose of iAmAFunction seems to be registering for an update when the value of c hours have passed since the start of the game, but I can't really tell.

 

Well, that;s my analysis.  We will see how correct it is.

 

PS.  Sorry this took me a while to write.  I had exams, and then the Steam Summer Sale started.

  • reaction_title_1 1

Share this post


Link to post
Share on other sites

Steam Summer Sale ends all.

You're pretty on point here. A few things:

  • Whenever an event is defined an a state, and is EMPTY (or JUST has a comment in it), it is most definitely disabling that event. That way, when you're in stateb, it'll call an OnActivate with nothing in it. If you just didn't have it defined there, as you learned in the states link, then it would default to the one in stateA, which would do something. So yeah - disabling activation doing anything during this time, as you thought.
  • I messed up one thing. The function RegisterForSingleUpdateGameTimeAt was supposed to be renamed iAmAFunction, and that probably would have cleared up a decent amount for you. You can read about OtherFunction and iAmAFunction (GetCurrentHourOfDay and RegisterForSingleUpdateGameTimeAt respectivelyl) here: http://www.creationkit.com/Light_Switch#Script_Explanation They're two incredibly useful functions, though in order to use RegisterForSingleUpdateTimeAt you must have GetCurrentHourOfDay defined in the same script. They're custom functions. That said, we do have a function I put in CYRMasterScript (not sure if you'll be working in CYR) called HoursUntilTimeOnDOW, which will return a float value that is the hours until a certain time on a certain day of the week. Then, you can RegisterForSingleUpdateGameTime, say, the next monday at 9 AM. Quite useful. You can find the function written out here, for reference: http://pastebin.com/78wc3vQw.
  • I'm not sure how "Return z and fall in a big hole" got in there, but that obviously wouldn't compile, just Return Z.

Now, you were pretty close with the guesstimate of what it was. You can only get so specific with a vague, weirdly named script like this. It's the script I wrote for the Ayleid Wells in Cyrodiil, and it has been tested so it does work. It starts in a state where the particle effect and sound marker for the Ayleid well effect is on, when activated, it turns it off (OnBeginState in StateB). If you already have the Ayleid well effect on you, it removes it. It will then add it - restarting the timer on the Ayleid well effect (which lasts 60 secs or something, I didn't make the spell). It will then go to the next state, turning off the marker and the linked sound marker and particle, as I mentioned. At midnight, it will turn back on and be on again.

Incidentally, this was my first script for Beyond Skyrim. I intended to just be freelancing, working on my own project, and doing the occasional scripts for them. Look where I am now! :o 

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

×