the Sim Settlements forums!

Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Tutorial Papyrus: Dealing With Arrays

Whisper

Well-Known Member
Patreon Supporter
Community Rockstar
Verified Builder
Vault Librarian
Messages
1,223
As a newbie Papyrus scripter here is the source of much of your initial learning and documentation: Category:Papyrus. It offers general tips on variables, functions, events, etc. There is one special part that may trip you up though: Arrays.

Programming languages have inbuilt literals, declared like this:
Code:
int iCounter
...or...
Code:
int iCounter = 0
Similarly, with a string you can declare:
Code:
string sWords
...or...
Code:
string sWords = "This is the message stored in the string. It is otherwise blank, or empty."
In Papyrus, arrays are different. They are not inbuilt literals (not handled natively in the code) - they are objects that you have to explicitly declare and deal with.

In many modern scripting languages you declare arrays something like this:
Code:
int[] iaNumbers
iaNumbers[] = 42 ; the answer to life, the universe, and everything
And it will create an empty array that can hold numbers - the second line will add the number 42 to the array.

The papyrus way is to explicitly declare the array object:
1/ As an array of numbers (or Forms, or Keywords, or ObjectReferences)
2/ With an initial length (between 0 and 128)
3/ Then add something to it
Code:
int[] iaNumbers = new int[0]
iaNumbers.Add(42)
You can then use code like the following:
Code:
iaNumbers.Length ; gives you how many items are saved in the array - numbers in this case
int iAnswer = iaNumbers[0] ; stores the first item in the new integer variable
if iaNumbers.Find(42)
  Debug.Notification("Yes we found the correct answer to Life, the Universe, and Everything!")
endif
Etcetcetc. If you haven't declared the array properly though - you've forgotten the "= new int[0]" part - the compiler won't complain. It'll happily let you do it, and run various commands upon your nonexistent array.

Without crashing.

And leaving you pulling your hair out while you go not-so-slowly insane.
===================================================
There is one other thing about arrays: If you declared it, you can't have more than 128 items stored in it.

So you can declare an array that will hold 128 items:
Code:
int[] iaNumbers = new int[128]
And they will be held in item-slots 0-127 (looks familiar? it should - think "signed byte" - and yes, all arrays are zero-based). You cannot add to the end of the array though.

There are a couple of caveats to this:
1/ Game functions that return arrays can hold any number of items
2/ Arrays declared in the Creation Kit can hold any number of items

You can also get around the 128-item limit by doing some tricky scripting. That's out of the scope of this tutorial though. What I will show is a use for an array. (Next post.)
 
Last edited:

Whisper

Well-Known Member
Patreon Supporter
Community Rockstar
Verified Builder
Vault Librarian
Messages
1,223
Here is an example script that I wrote to use arrays - this is what tripped me up. The purpose behind it is:

1/ load forms from file (in this case - I wanted to load the forms of shipments)
2/ add the forms (shipments) to the local workshop

I could have done something simple like the following for each shipment:
Code:
; add to workshop silently
nearestWorkshop.AddItem(Game.GetFormFromFile(0x001ec13f, "Fallout4.esm"), 20, true) ; concrete
nearestWorkshop.AddItem(Game.GetFormFromFile(0x0024a067, "Fallout4.esm"), 20, true) ; steel
nearestWorkshop.AddItem(Game.GetFormFromFile(0x0024a068, "Fallout4.esm"), 20, true) ; wood
...etc...
That's fine for specifically adding 20 of whatever to the inventory, yet I wanted something more flexible. I decided that passing an array of integers (the 0x001ec13f is an integer in hexadecimal notation) would let me grab several forms of whatever type - return them as an array of forms - which can then be dealt with however I needed.
========================================
Here is functioning code that builds off of Kinggath's Workshop Framework:
Code:
; import kinggath's API for the workshop
import WorkshopFramework:WSFW_API
Note that LoadForms and AddToWorkshop are two helper functions that I declare elsewhere:
Code:
;=============================================================================
; main functions
;=============================================================================

; add resource shipments to the workshop
; parameters:
; shipmentType - string, valid codes are common (default), uncommon, rare
; iAmount - number (any up to 65535 - big numbers aren't recommended)
Function AddShipments(string shipmentType = "common", int iAmount = 20)
  ; get the nearest workshop (courtesy of WSFW)
  WorkshopScript nearestWorkshop = GetNearestWorkshop(Game.GetPlayer())
 
  ; short-circuit if no workshop or not owned by player
  If (nearestWorkshop == None || nearestWorkshop.GetActorOwner() != Game.GetPlayer().GetActorBase())
    ; tell the player we failed
    Debug.Notification("Could not add " + shipmentType + " resource shipments to workshop")
 
    ; return nothing at all
    return
  EndIf
 
  ; short-circuit if wrong shipmentType parameter passed
  If (shipmentType != "common" && shipmentType != "uncommon" && shipmentType != "rare")
    ; tell the player we failed
    Debug.Notification("Wrong shipmentType passed to function (use common/uncommon/rare)")
 
    ; return nothing at all
    return
  EndIf
 
  ; array to hold shipment hex numbers
  int[] iaShipments = new int[0]
 
  ; add appropriate shipment hex numbers to array
  ; NOTE: basic shipments here - check for WW/FH DLC elsewhere and drag the biggest shipments from them in specially
  if (shipmentType == "common")
 
    ; add common shipments (biggest shipment varieties)
    iaShipments.Add(0x001ec13b) ; ceramic
    iaShipments.Add(0x001ec13e) ; cloth
    iaShipments.Add(0x001ec13f) ; concrete
    iaShipments.Add(0x001ec14a) ; leather
    iaShipments.Add(0x001ec14d) ; plastic
    iaShipments.Add(0x001ec14e) ; rubber
    iaShipments.Add(0x0024a069) ; plastic
    iaShipments.Add(0x0024a067) ; steel
    iaShipments.Add(0x0024a068) ; wood
  elseif (shipmentType == "uncommon")
    ; add uncommon shipments (biggest shipment varieties)
    iaShipments.Add(0x001ec134) ; adhesive
    iaShipments.Add(0x001ec136) ; aluminum
    iaShipments.Add(0x0024a06c) ; copper
    iaShipments.Add(0x001ec141) ; cork
    iaShipments.Add(0x001ec143) ; fertilizer
    iaShipments.Add(0x001ec144) ; fiberglass
    iaShipments.Add(0x001ec146) ; gears
    iaShipments.Add(0x001ec147) ; glass
    iaShipments.Add(0x001ec149) ; lead
    iaShipments.Add(0x001ec14f) ; screw
    iaShipments.Add(0x001ec150) ; silver
    iaShipments.Add(0x001ec151) ; spring
  else
    ; add rare shipments (biggest shipment varieties)
    iaShipments.Add(0x001ec133) ; acid
    iaShipments.Add(0x001ec139) ; antiseptic
    iaShipments.Add(0x001ec13a) ; asbestos
    iaShipments.Add(0x001ec138) ; ballistic fiber
    iaShipments.Add(0x001ec13d) ; circuitry
    iaShipments.Add(0x001ec142) ; crystal
    iaShipments.Add(0x001ec145) ; fiber optics
    iaShipments.Add(0x001ec148) ; gold
    iaShipments.Add(0x001ec14b) ; nuclear material
  endif
 
  ; load desired forms from file
  Form[] fShipments = LoadForms(iaShipments)

  ; add them to the workshop
  AddToWorkshop(nearestWorkshop, fShipments, iAmount)
 
  ; if we are adding common items and we have the Wasteland Workshop installed
  if (shipmentType == "common" && Game.IsPluginInstalled("DLCworkshop01.esm"))
    ; add one of the biggest concrete to the workshop (only a single item to add)
    nearestWorkshop.AddItem(Game.GetFormFromFile(0x00000e80, "DLCworkshop01.esm"), iAmount, true) ; add big concrete
  endif
 
  ; tell the player we are finished
  Debug.Notification("Shipments added to workshop")
EndFunction
Here are the two helper functions:
Code:
; load desired forms from a game file
; parameters:
; iaLoad - array of integers referencing the basic forms
; sFile - file that we want to load the forms from
Form[] Function LoadForms(int[] iaLoad, string sFile = "Fallout4.esm")
  ; local array of forms
  Form[] lForms = new Form[0]
 
  ; loop through array
  int countForm = 0
  while (countForm < iaLoad.Length)
    ; add into array from disk
    lForms.Add(Game.GetFormFromFile(iaLoad[countForm], sFile))
 
    ; increment through
    countForm += 1
  endwhile
 
  ; return the forms
  return lForms
EndFunction
...and...
Code:
; add passed forms to the workshop
; parameters:
; nearestWorkshop - natch
; faForms - array of forms (resource, shipment)
; iAmount - integer amount to add to workshop
Function AddToWorkshop(WorkshopScript nearestWorkshop, Form[] faForms, int iAmount)
  ; loop through array
  int countForm = 0
  while (countForm < faForms.Length)
    ; add to workshop silently
    nearestWorkshop.AddItem(faForms[countForm], iAmount, true)
 
    ; increment through
    countForm += 1
  endwhile
EndFunction
From this point, the mod-author can use:
Code:
AddShipments("common", 20)
AddShipments("uncommon", 10)
AddShipments("rare", 5)
And have them added to the workshop silently. Extra hex numbers can be added to the arrays and the whole recompiled to add extra items.
 
Last edited:

Whisper

Well-Known Member
Patreon Supporter
Community Rockstar
Verified Builder
Vault Librarian
Messages
1,223
Some more information regarding declaring arrays in the Creation Kit, courtesy of @Ssmb_92 in discord:
You can fill an array property beyond 128 in the CK, but if it is not declared as Const and you try to manipulate it, you'll usually be greeted with Out-of-range errors in the logs. If you intend to have a pre-defined array > 128 members, mark it as Const to save any potential issues. I have no idea how this plays out in Skyrim, I have only tested it in F4.
Many thanks!
 

Whisper

Well-Known Member
Patreon Supporter
Community Rockstar
Verified Builder
Vault Librarian
Messages
1,223
Extra thought regarding array properties that are declared in the Creation Kit:

1/ properties are kept persistently in memory and your save
2/ if your array needs to be different between script calls, you need to empty it out yourself
3/ ditto #2 if you don’t want the info in the array to remain between saves
 

msalaba

Well-Known Member
Community Rockstar
Messages
1,554
Extra thought regarding array properties that are declared in the Creation Kit:

1/ properties are kept persistently in memory and your save
2/ if your array needs to be different between script calls, you need to empty it out yourself
3/ ditto #2 if you don’t want the info in the array to remain between saves
Using your example code above, to zero out int Array Property, would it look something like:
Code:
int[] iaNumbers = new int[0]
iaNumbers[0] = 0
Or is the first line of code no longer needed as the Property is already declared?
Or is it simply:
Code:
iaNumbers.Clear()
 
Last edited:

Whisper

Well-Known Member
Patreon Supporter
Community Rockstar
Verified Builder
Vault Librarian
Messages
1,223
Using your example code above, to zero out int Array Property, would it look something like:
Code:
int[] iaNumbers = new int[0]
iaNumbers[0] = 0
Or is the first line of code no longer needed as the Property is already declared?
Or is it simply:
Code:
iaNumbers.Clear()
In this case the property array already exists - so you would use Clear()
 

msalaba

Well-Known Member
Community Rockstar
Messages
1,554
Form[] fShipments = LoadForms(iaShipments)
Is this the same as:
Code:
Form fShipments = new Form[0]
int i = 0
    while i < iaShipments.length
        fShipments.add(LoadForms(iaShipments[i]))
        i += 1
    Endwhile

The next question is does this apply to your example? (from the CK wiki, Skyrim side)

Creating a FormID Array​


Int variables and properties can be set to hex FormIDs. This can be particularly enabling when using GetFormID, GetForm, and GetFormFromFile. The below will work, but you will find you cannot set an Int[] element as a FormID from within the Creation Kit.


Code:
Int iFormID = 0x00000BEE
Game.GetPlayer().PlaceActorAtMe(Game.GetFormFromFile(iFormID, "Killer Bees.ESM") As ActorBase)

The "0x" notation is specific to the Papyrus compiler which internally converts the hex to decimal for you, thus the above works as intended. If, however, you want to store an array of FormIDs with the elements predefined in the Creation Kit, you'll need to first convert them from hex to decimal. Windows' Calculator in "Programmer" mode allows easy conversion. BEE, for instance, is 3054.


Code:
Int[] Property iFormIDArray Auto ; Filled with bee actor FormIDs converted from hex to decimal
Game.GetPlayer().PlaceActorAtMe(Game.GetFormFromFile(iFormIDArray[0], "Killer Bees.ESM") As ActorBase)

Or does this not apply as you declared each form as a variable and used a function to create the array at run time? If that's the case, how do you declare a filled array? I do not see any way of doing this in the CK wiki. Your described method seems the only way other than being a property and filled in the editor.

The next question is "what the heck is the X" in the following:
Code:
objectXPos = ObjectArray[currentObject].X
I have seen this a few times looking through scripts, but can't seem to wrap my head around it. What is the use scenario? I could understand if X was a Function, but it is not.

The last thing is an addition. @1000101 taught me that you can create an array of Global Variables. You have to create all the individual Global Variables and then you can create the array. It looks more like a formlist of Global Variables in the editor. This is very useful if you need a bunch of Global Variables for condition switches for things like terminals. It greatly cleans up the code, I was able to remove over 200 lines of code and I wasn't even done with my function yet!
Thank you! :giver:
 

Whisper

Well-Known Member
Patreon Supporter
Community Rockstar
Verified Builder
Vault Librarian
Messages
1,223
Hiya,

* Form[] fShipments is an array declaration.

* Form fShipments is a singular declaration.

The two are different and you cannot use them the same way.

This means that:
Code:
Form fShipments = new Form[0]
Will cause the compiler to fail, since you’re attempting to put an array - a special object structure - into a single form structure.

I personally use the singular/plural versions fShipment and fShipments to make it clearer in my code that one applies to a single object and the other to multiple objects.
 

Whisper

Well-Known Member
Patreon Supporter
Community Rockstar
Verified Builder
Vault Librarian
Messages
1,223
Code:
Int[] Property iFormIDArray Auto ; Filled with bee actor FormIDs converted from hex to decimal
Game.GetPlayer().PlaceActorAtMe(Game.GetFormFromFile(iFormIDArray[0], "Killer Bees.ESM") As ActorBase)

Or does this not apply as you declared each form as a variable and used a function to create the array at run time? If that's the case, how do you declare a filled array? I do not see any way of doing this in the CK wiki. Your described method seems the only way other than being a property and filled in the editor.
This does apply, so long as the iFormIDArray is filled with valid integer values.

Technically you can probably fill an array of strings with the hex version - I don’t think there’s a strtohex function in the game though.
 

Whisper

Well-Known Member
Patreon Supporter
Community Rockstar
Verified Builder
Vault Librarian
Messages
1,223
The next question is "what the heck is the X" in the following:
Code:
objectXPos = ObjectArray[currentObject].X
I have seen this a few times looking through scripts, but can't seem to wrap my head around it. What is the use scenario? I could understand if X was a Function, but it is not.
On an ObjectReference - literally an object that exists in the world - X/Y/Z are the cartesian coordinates of the objects location in the world.
 
Top