Talk:Arrays (Papyrus)

Active discussions
Revision as of 08:33, 6 August 2017 by imported>Lisselli (→‎Additional examples)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

I appreciate that this page has been created because I never really understood this Arrays thing... but I still barely get it. I think that this page needs a rewrite and it needs to be dumbed down.

First, when you write something "for begginers" don't give them a long lecture about how computer memory works. I know that you wanted people to understand the whole concept but it's really hard for newbies. And if you do something like this please, simply try to avoid sentences like "...since you'd need to construct identifiers at runtime by concatenating the descripting name with the changing index". Sometimes I just didn't know what it was about. You're talking about arrays and then suddenly: "Arrays are, memory-wise, just a bunch of bytes stacked together, continuous in memory (virtual memory, that is, physical memory can get messy). From now on, I'll just refer to it as memory."

Second, when you get to the technical part of the page, scripts and all, could you make it more orderly? Like, start with how you define an array, then tell how to define its value, then how do some other stuff... I mean, get from the simplest stuff to the most complicated. Right now I think there's everything in this article but I still don't understand it. Try to incorporate new things gradually. I know that this isn't a tutorial but still, many people are going to learn from this. Domius 14:16, 17 February 2012 (EST)

Side-note - Very good! Regarding the "beginner's page intended for masters", I must underline a false and confusing assertion : "A rather simple formal representation: <identifier> <index_or_count>"... It's an index, not a count which could be confused with the number of items (with standard compilers, using such "count" would provoke an array overflow or an in-game CTD). The sole place where one would imperatively use the "Count" would be for initializing the array, which should be underlined outside of the formalization itself. Of course some algorithm could count the number of items and then use that number to calculate an index, or use some variable named "Count" for whatever purpose like incrementing it within a While loop for traversing the array, but that's not the point : we should keep in mind that it's essentially an index and that's it, nothing to do with counting anything.. e.g. when you have a seat number in the Magic Plane (the array), you just go there directly and sit, there's no counting nor "count" involved : you don't care about such a "count" number since you have your ticket (your own index). I guess that the original text wanted to insinuate that one must use the count so as to initialize the Array ; or that an array is ordered, meaning that the last index will always be (number of items -1) or (length of Array - 1), both representing the "Count - 1" (since indexes begin at 0). Actually, when talking about Arrays, the term "Count" is more understandable as being the length of the array, its number of indexes : the number of seats in the Magic Plane. The doc should be careful when addressing beginners : it should not insinuate anything as if some foreknowledge of what it's trying to explain was actually required. It should state it clearly, especially within its formalization. --HawkFest (talk) 2013-02-10T08:54:00 (EST)

For beginners who didn't major in CSEdit

No offense meant to the person who wrote the For Beginners section, but I agree it's still far too technical for beginners. Here's my attempt.

Arrays are how programmers store lists of related things. Compare a normal person's grocery list:

Bread
Milk
Eggs
Bananas

Some people are a little more organized:

Grocery List
------------
1. Bread
2. Milk
3. Eggs
4. Bananas

Programmers have to be even more obsessive because computes are dumb and need every little detail written down.

Defining arraysEdit

First the computer needs to know what kind of things will be in the list, that it will be a list, the name of the list.

FoodItem[] GroceryList

Declaring arraysEdit

But many times we need to also tell the computer to make enough storage space to hold the number of items we plan to store.

FoodItem[] GroceryList = new Fooditem[4]

Setting individual valuesEdit

Then the items are stored in the numbered locations, but it's easier for the computer to start counting at zero instead of one, and has to be reminded of which list every single time.

GroceryList[0] = "Bread"
GroceryList[1] = "Milk"
GroceryList[2] = "Eggs"
GroceryList[3] = "Bananas"

Checking the lengthEdit

Finding out how many items are in the list is easy. (Actually it's how many slots are available, even if some are blank.)

GroceryList.length

Getting individual valuesEdit

Getting the values back isn't bad as long as you remember that the computer starts counting at zero.

FoodItem TheThirdItem = GroceryList[2]

Creating functions that expect arraysEdit

When you need to tell the computer to expect a list, you put the square brackets after the name of the type of items the list will hold.

Function BuyThese(FoodItem[] ShoppingListForToday)

Sending whole arrays to a functionEdit

But to do something with an entire list you would just use it's name.

BuyThese(GroceryList)

Examples of working with every item in the arrayEdit

The real power of lists only appears when you need to do the same thing with every item in the list. We can loop through all of the items in the list by their index position.

Function BuyThese(FoodItem[] ShoppingListForToday)
   int itemNumber = 0
   while itemNumber < ShoppingListForToday.Length
      FindTheItemAndPutItInYourCart(ShoppingListForToday[itemNumber])
      itemNumber += 1
   endwhile
   GoThroughCheckoutAndPay()
EndFunction

Making it a little more like the game's context for tracking items will make it more complex. So programmers usually shorten those names and might even give the list a name that makes more sense based on how it's used just to compensate.

Function BuyThese(FoodItem[] item)           ; item is a list of fooditems
   int totalCost = 0                         ; we'll want to know this at the end
   int n = 0                                 ; start at the beginning
   while n < list.Length                     ; and go to the end of the list
      if TheStore.GetItemCount(item[n]) > 0  ; if item is in stock
         TheStore.RemoveItem(item[n], 1)     ; take one from the shelf
         TheCart.AddItem(item[n], 1)         ; put it in our cart
         totalCost += item[n].GetGoldValue() ; record the price
         item[n] = None                      ; mark it off of our list
      endif
      n += 1                                 ; go on to the next item
   endwhile
   GoThroughCheckoutAndPay()                 ; hopefully we'll have enough money
EndFunction

Other considerationsEdit

But passing that list into the BuyThese function really is like handing someone a list. If code inside that function changes or removes some of the items, then they are changed everywhere that list is referenced. We aren't making copies of the list, just letting different parts of the code see it (and some may call it different things, but it's the same list). So after we've called "BuyThese(GroceryList)" we'll be able to see if anything wasn't available.

   int i = 0
   while i < GroceryList.Length
      if GroceryList[i]  ; it wasn't changed to None
         Debug.Notification("We weren't able to buy the " + GroceryList[i].GetName())
      endif
      i += 1
   endwhile

Some special limitations are that each list can only have up to 128 items, you must use a literal number with the "new" command, and that you can't have an array of arrays.

--Cdcooley 23:24, 25 May 2012 (EDT)

I just wrote a function that passes in an array of forms and populates it with the currently equipped gear of the specified actor. When attempting to access the array outside of the function, it is still empty. To me this implies that arrays are passed by value and any changes do not stick outside of the function. I am a CS major and understand how arrays work, so I am fairly sure that this is not a misunderstanding.
Form[] property playerEquipArray auto;

Function GetEquipment(Actor akActor, Form[] akEquipList, int aiArraySize)

	int count = 0;
	Form tempSlot;
	
	tempSlot = akActor.GetWornForm(1);  This is an skse function that gets the worn form if given a slot
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)  ;Ensures that something was found and that it is not already in the array
		akEquipList[count] = tempSlot;  Assigns currently worn form to the current slot in the array
		count += 1;  Increments the array index
	endif
	tempSlot = akActor.GetWornForm(2);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(4);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(8);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(16);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(32);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(64);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(128);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(256);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(512);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	tempSlot = akActor.GetWornForm(4096);	
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)
		akEquipList[count] = tempSlot;
		count += 1;
	endif
	while (count < aiArraySize)
		akEquipList[count] = None;
		count += 1;
	endWhile

endFunction

Event OnActivate(ObjectReference akActionRef)
	GetEquipment(Game.GetPlayer(), playerEquipArray, 11);  Player is definitely wearing something when tested
	debug.messagebox(playerEquipArray[0].GetName());  Outputs no text
endEvent

--JaxFirehart (talk) 16:16, 30 August 2012 (EDT)

I modified this script as thus:
Form[] property playerEquipArray auto;

Form[] Function GetEquipment(Actor akActor)

	int count = 0;
	Form[] akEquipList = new Form[11];
	Form tempSlot;
	
	tempSlot = akActor.GetWornForm(1);  This is an skse function that gets the worn form if given a slot
	if (tempSlot != None) && (akEquipList.find(tempSlot) < 0)  ;Ensures that something was found and that it is not already in the array
		akEquipList[count] = tempSlot;  Assigns currently worn form to the current slot in the array
		count += 1;  Increments the array index
	endif

	;SNIP

	while (count < 11)
		akEquipList[count] = None;
		count += 1;
	endWhile

endFunction

Event OnActivate(ObjectReference akActionRef)
	playerEquipArray = GetEquipment(Game.GetPlayer());  Player is definitely wearing something when tested
	debug.messagebox(playerEquipArray[0].GetName());  Outputs "Shrowded Cowl" as expected
endEvent
It now works as expected, this confirms to me that arrays are passed by value unless someone can explain how I am wrong

--JaxFirehart (talk) 16:41, 30 August 2012 (EDT)

Btw you forgot the last line in your function : Return akEquipList. And yes function parameters are passed by value. Which is somewhat expected considering that the compiler does not allow dynamic arrays (we must provide its absolute length at compile time), more globally does not allow the manipulation of pointers (either to memory addresses and/or function stacks). However, in the context of Papyrus, it's not necessary : all one must do is to declare a variable outside any state, event or function (the default script), which can then be used within any function of the same script, without the need to pass it by address. e.g. in your example, your function could've used playerEquipArray directly instead of declaring a variable local to it (akEquipList), thus using less memory (here doubled) and less processes. Doing this with your other script above would make it work. Note that this would require some locking mechanism if you know that another script could externally access that function while it's in use by your script (e.g. the function could return an unexpected playerEquipArray property to your script if it's called at the same time by another script, since the external calls within your function will allow other scripts to unlock the thread - Read more). --HawkFest (talk) 2013-09-09T00:08:38 (EDT)
It may not be necessary, but that doesn't mean it's not useful. Loops aren't necessary either, but try doing any half serious coding without them and you'll be wanting to hang yourself from a tree. In any case, the explanation in the main page is misleading, since it states that arrays are passed by reference, but as we know this isn't always the case. I had to learn it the hard way.. Anyway, I am editing the article to reflect this information.--Five Alpha Reductase 2015-04-09T00:13:54 (EDT)

Avoiding "Array Index out of range"Edit

This can occur if the number of references you're adding to an array at run-time exceeds the length of the array.

Changing number of elements in Array propertiesEdit

From testing, it seems rather unfortunate that changing the number of elements in a property also are subject to the previous length of the array being baked into the save. Changing the integer that is assigned to the length and setting it to 0 and then assigning it back to the length as a way of "resetting" the length of the array to the new length does not work.--Terra Nova2 (talk) 2015-02-27T07:20:35 (EST)

There is a work around for this. Source --Terra Nova2 (talk) 2016-01-24T09:29:12 (EST)

Additional examplesEdit

Attempting my hand at explaining arrays. My examples have been tested. I explain what they do in comments in the code, and try not to present their functions in a confusing way. Various methods of getting information with arrays will be used, and these will also be using floats. This is intended for beginners(like me) who do not have extensive programming knowledge.

Let's say you want to return the highest value of an index.

Float Function GetNumbers()
	Float[] fNums = new Float[5]                 ; This is the number of available slots.(0-4)
	fNums[0] = 100.0
	fNums[1] = 100.0
	fNums[2] = 85.0
	fNums[3] = 97.0
	fNums[4] = 66.0
	
	Int i = 0
	Int index = fNums.Length
	Float fHighestValue = -999.0 ; a large negative number so you can handle negative values.
	
	While index
		index -= 1                            ; Start from the last index iterate to the first
		Float CurrentValue = fNums[index]     
		if CurrentValue >= fHighestValue      ; if the value of this index is greater than the highestvalue.
			fHighestValue = CurrentValue  ; store that value as the highest value
		endif				      ; repeat process until it returns
	EndWhile
	
	return fHighestValue                          ; return highest value
			
EndFunction

Let's say you want to get how many skills a player has that are maxed.

Int Function GetNumMaxSkills()
	; returns the number of skills that are capped.
	Actor Player = Game.GetPlayer()
	Float[] fSkills = new Float[18]                            ; Available slots(0-17)
	fSkills[0] = Player.GetActorValue("Alteration")
	fSkills[1] = Player.GetActorValue("Alchemy")
	fSkills[2] = Player.GetActorValue("Block")
	fSkills[3] = Player.GetActorValue("Conjuration")
	fSkills[4] = Player.GetActorValue("Destruction")
	fSkills[5] = Player.GetActorValue("Enchanting")
	fSkills[6] = Player.GetActorValue("HeavyArmor")
	fSkills[7] = Player.GetActorValue("Illusion")
	fSkills[8] = Player.GetActorValue("Lockpicking")
	fSkills[9] = Player.GetActorValue("LightArmor")
	fSkills[10] = Player.GetActorValue("Marksman")
	fSkills[11] = Player.GetActorValue("OneHanded")
	fSkills[12] = Player.GetActorValue("Pickpocket")
	fSkills[13] = Player.GetActorValue("TwoHanded")
	fSkills[14] = Player.GetActorValue("Restoration")
	fSkills[15] = Player.GetActorValue("Sneak")
	fSkills[16] = Player.GetActorValue("Smithing")
	fSkills[17] = Player.GetActorValue("Speechcraft")
	
	Int index = fSkills.Length
	Int iCount
	
	While index
		index -= 1                  ; Start from the last index.
		if fSkills[index] == 100.0  ; Find any skill that is lvl 100.
			iCount += 1         ; Record how many were found.
		endif
	EndWhile
	
	return iCount
EndFunction

Now let's say you want the sum of all skills.

Float Function GetTotalCombinedSkillLevels()
	; returns the total number of all skills combined. (for example 100 x 18 = 1800)
	Actor Player = Game.GetPlayer()
	Float[] fSkills = new Float[18]
	fSkills[0] = Player.GetActorValue("Alteration")
	fSkills[1] = Player.GetActorValue("Alchemy")
	fSkills[2] = Player.GetActorValue("Block")
	fSkills[3] = Player.GetActorValue("Conjuration")
	fSkills[4] = Player.GetActorValue("Destruction")
	fSkills[5] = Player.GetActorValue("Enchanting")
	fSkills[6] = Player.GetActorValue("HeavyArmor")
	fSkills[7] = Player.GetActorValue("Illusion")
	fSkills[8] = Player.GetActorValue("Lockpicking")
	fSkills[9] = Player.GetActorValue("LightArmor")
	fSkills[10] = Player.GetActorValue("Marksman")
	fSkills[11] = Player.GetActorValue("OneHanded")
	fSkills[12] = Player.GetActorValue("Pickpocket")
	fSkills[13] = Player.GetActorValue("TwoHanded")
	fSkills[14] = Player.GetActorValue("Restoration")
	fSkills[15] = Player.GetActorValue("Sneak")
	fSkills[16] = Player.GetActorValue("Smithing")
	fSkills[17] = Player.GetActorValue("Speechcraft")
	
	Int i
	Int index = fSkills.Length
	Float fTotal
	
	While i < index              ; i starts at index 0 and keeps looping til it reaches the same number of available slots(index).
		fTotal += fSkills[i] ; Add the value of the index to the variable.
		i += 1               ; go to the next index.
	EndWhile
	
	return fTotal
EndFunction

There's another way to do that as well by changing the while loop to this:

While index                    ; Number of available slots(even if blank)
    index -= 1                 ; Start from the last index and loop up to the first.
    fTotal += fSkills[index]   ; Add the values for each index to the variable.
EndWhile

The following will zero out values that are not 100.

Int Function RemoveSkillNotMaxed()
	; returns the number of skills that aren't capped that have been removed from the array(zero'd out in the case of Floats)
	Actor Player = Game.GetPlayer()
	Float[] fSkills = new Float[18]
	fSkills[0] = Player.GetActorValue("Alteration")
	fSkills[1] = Player.GetActorValue("Alchemy")
	fSkills[2] = Player.GetActorValue("Block")
	fSkills[3] = Player.GetActorValue("Conjuration")
	fSkills[4] = Player.GetActorValue("Destruction")
	fSkills[5] = Player.GetActorValue("Enchanting")
	fSkills[6] = Player.GetActorValue("HeavyArmor")
	fSkills[7] = Player.GetActorValue("Illusion")
	fSkills[8] = Player.GetActorValue("Lockpicking")
	fSkills[9] = Player.GetActorValue("LightArmor")
	fSkills[10] = Player.GetActorValue("Marksman")
	fSkills[11] = Player.GetActorValue("OneHanded")
	fSkills[12] = Player.GetActorValue("Pickpocket")
	fSkills[13] = Player.GetActorValue("TwoHanded")
	fSkills[14] = Player.GetActorValue("Restoration")
	fSkills[15] = Player.GetActorValue("Sneak")
	fSkills[16] = Player.GetActorValue("Smithing")
	fSkills[17] = Player.GetActorValue("Speechcraft")
	
	int i
	int iCount 
	int index = fSkills.Length
	
	While i < index				; Start at first index, increment up til reaches the same number of available slots(index).
		if fSkills[i] < 100.0   ; If this skill value for this index is less than 100.
			fSkills[i] = 0.0    ; zero it out.
			iCount += 1         ; record the number of times one has been found not to be capped.
			i += 1              ; move to the next index.
		else                        ; if a skill is 100..
			i += 1              ; Move to the next index.
		endif
	EndWhile
	
	return iCount
	
EndFunction

You might wonder why I didn't just change an element that isn't 100 to "none". This is because Floats and Ints can't have a "none" type. They can only be 0.0 or 0. I will add more complex ways(with details of course!) when I come up with some. These functions were tested with my current character using an older save. The return values for function 2(function was used random numbers) was 5, the return value for function 3 was 1125, and the return value for function 4 was 13. I hope this helps someone out there. I had trouble with arrays for a very long time and am pleased that I'm now getting somewhere with them. --Lisselli (talk) 2017-08-06T03:34:54 (EDT)

Function PickEveryFlowerInCell(Actor akFlowerPicker) Global
	Cell kCell = akFlowerPicker.GetParentCell()                  
	Int iIndex = kCell.GetNumRefs(39) ; kFlora = 39              ; Total number of forms in this cell that are type flora.
	While iIndex
		iIndex -= 1                                          ; Loop from the last index.
		kCell.GetNthRef(iIndex, 39).Activate(akFlowerPicker) ; Activate all the forms in this cell that are type flora 
	EndWhile
EndFunction

This one is sligtly different since it uses a SKSE function but the basics behind this array remains the same as you can see.

Return to "Arrays (Papyrus)" page.