Jump to content
DoctaSax

Tutorial: Nvse4+ Part 4: Array Variables

Recommended Posts

1. Whut? Terminology, persistence
2. Types of arrays, keys and values
3. Declaring and initializing array vars
4. Retrieving and storing elements
5. Populating your arrays a little more quickly
6. Inspecting arrays: Walking arrays, TypeOf, Dumping arrays to console
7. Array functions: a quick run-through
8. "Regular array"-only functions



1. WHUT? TERMINOLOGY, PERSISTENCE

Array variables refer to arrays.
Arrays are lists - imagine them as 2-column tables that you'd use in Word or some such. See for instance the examples given here.

Arrays are kept by NVSE for you, and whichever ones exist when a savegame is made will be stored in the .nvse file that corresponds to your save. They're the same as string vars that way.

Unlike with string vars, NVSE cleans up after you all the time: whenever an array is no longer referred to by anything - an array variable in an active script, or another array holding your array - it is removed.
In general, you'd use an array to either construct and store information long-term, in which case you'd park it in a quest array var, or short-term, in which case you should uninitialize the corresponding variable as soon as you don't need it anymore to avoid bloat in the .nvse file.




2. TYPES OF ARRAYS, KEYS AND VALUES

There are different types of arrays we can use.
The first, which we'll call a 'regular' array (array-type array, 'array' array, are also used, but sound a bit silly imo), looks a lot like the formlists that you'll find in the geck:

NonAlcholicDrinks formlist:
0    MS05NukaColaQstm
1    MS05IceNukaCola
2    NukaCola
3    WaterUnpurified
4    WaterPurified

Like formlists, regular arrays are indexed by positive consecutive integer numbers running from 0 to (Total Size - 1). Those index numbers are called keys.

The main difference, and the main advantage that all array types have, is that what's associated with those keys - the values - can be anything: not just forms and refs, but also numbers, strings and even other arrays. (When we talk about a key-value combination, we use the term "element".)

Some regular array:
0    NukaCola                                       ; form
1    3                                                     ; number
2    "You can't beat the real thing"        ; string
3    RecipesInvolvingNukaColaArray   ; array

As with formlists, the indexes will always be consecutive; if you remove any element, the others with a higher index will move up - gaps are not allowed, and neither are floats or negative numbers:

0    NukaCola
1    "You can't beat the real thing"
2    RecipesInvolvingNukaColaArray

This is a limitation sometimes, so for a different kind of numbered indexing, we have maps, which are arrays that can take any number as key, and allow for gaps (they have to because they allow decimals). So we can convert the "agenda" example from the CS wiki to this map, with the time converted to decimal and the activities held in strings:

8.5     "Sign in, coffee"  ; arrays don't store the quotation marks, I just put them there to remind you they're strings
9        "Introduction"
9.5     "Presentation A"
10.5   "Coffee break"
11      "Presentation B"

Here's another one that stores some exterior cells as values with their X coordinates as keys, some of them are negative:
-21      MojaveOutpost
-9        MojaveDriveIn
7         188TradingPost    
12       BoulderCity
17       BitterSprings

Finally, sometimes numbers of whichever kind just don't cut it, so we have stringmaps, which take strings as keys:
"Name"                    "Prudencia"
"Age"                       27
"Body"                     "T6M"
"Profession"            "Test character"
"Time played (hrs)" 106.5
"Cells visited"          CellsVisitedArray ; sadly, holding only GSDocMitchellHouse for all those hours, at index 0



3. DECLARING AND INITIALIZING ARRAY VARS

You declare an array var of whichever type like this:

array_var somearrayvarname

 
And just like string vars, you can't do anything with them until they're initialized and refer to an actual array in NVSE. You initialize arrays by:

- explicitly constructing them, by letting them to the ar_Construct function, with the array type as parameter

        let somearrayvarname := ar_construct array
        let somearrayvarname := ar_construct map
        let somearrayvarname := ar_construct stringmap

   
       'Array', 'map' and 'stringmap' are string parameters to the ar_construct function, like 'Health' is to GetAV in GetAV Health. Especially if you toggle the script compiler override on, you will see a warning about such strings, in which GeckPU will basically say 'I'm assuming these are strings but you can still change your mind so I'm not compiling yet'. If you ignore that warning and compile anyway, things'll work fine in-game.
        


- letting them to another array var, which makes both array vars refer to the same array
 

let somearrayvar2 := somearrayvar1
let somearrayvar1[2] := something             --> somearrayvar2 now also has the new element, because it's the same array

   
        If 2 array vars refer to the same array like that, you can break one free by ar_constructing it, which will erase everything in it.

 

let somearrayvar2 := ar_construct array       --> somearrayvar2 now refers to brand new, empty array         

 
       If you just want to copy an array to another, use ar_copy or ar_deepcopy (see chapter 7).
 
        
- letting them to the result of a function that returns an array (including UDFs that return an array with SetFunctionValue)

 

        let somearrayvar := sv_Split "Chop up this string." " ."
        will end up with a regular array containing the string's fragments as strings:                          '
        0    Chop
        1    up
        2    this
        3    string
let somearrayvar := someActorRef.NX_GetEVFlAr "SomeNXKey"
; will return a stringmap containing all nx EVFL keys that are on someActorREf containing or equalling "SomeNXKey" as the stringmap's keys, with the nx values as the stringmap's values

 
A lot of the array-specific functions (ar_copy, ar_range, ar_map etc.) are functions that return arrays and so intialize the array vars that are "let" to them.

You uninitialize an array var by letting it to ar_Null:

let somearrayvar := ar_Null

 
From that point on it's pretty much like a null ref var. If you want to know if an array is initialized or not before you try and do something with it, you can just check it as a bool, or use LogicalNot (!) to check its being uninitialized.

if somearrayvar
   ; arrayvar is initialized
else
   ; uninitialized
endif

if eval !(somearrayvar)
   ; array var is not initialized
endif

If you already know it's initialized, there's no point.



4. RETRIEVING AND STORING ELEMENTS
    
If you know under what key a value is stored you can look it up directly like this:

 

let somevar := somearrayvar[key]   ; note the brackets

 
applied to the examples in the previous chapter:
 

let sv_somestring := NukaColaArray[2]                         
                                ; sv_somestring now refers to a copy of the "You can't beat the real thing." string stored in our array.
let rSomeCellForm := CellMap[-9]                              
                                ; rSomeCellForm is now the MojaveDriveIn cell
let iSomeInt := CharacterStringMap["Age"]
                                ; iSomeInt is now 27
let fSomeFloat := CharacterStringMap["Time played (hrs)"]    
                               ; fSomeFloat is 106.5
let rSomeCellForm := CharacterStringMap["Cells visited"][0] 
                               ; rSomeCellForm is now GSDocMitchellHouse (the first cell held as value in the regular array under the "Cells visited" key in our character stringmap)

 
You don't strictly have to pass the values from an array to a local script variable everytime you want to do something with them; if you remember from the section on the script compiler override in the syntax tutorial, once you turn that on, you can make this work:

let fSomeFloat := 3 * (somearrayvar[key] - iSomeInt) ; if the value held in that array is a number
someRef.call someUDF fSomeFloat somearrayvar[key]    ; if the UDF expects a variable type like what's in that array as second parameter

and a variety of other combinations like that - it's up to you to test what works and what doesn't.


And you add or replace elements to an array like this:
 

let somearrayvar[index] := something
let NukaColaArray[2] := "Everything goes better with Nuka." 
                        ; replaces the old string as value, which is destroyed if no longer referred to by anything else.
let NukaColaArray[2] := sv_someStringVar
                        ; same, just different
let CellMap[-18] := GoodSprings
                        ; adds the GoodSprings exterior cell's form under index -18
let CharacterStringMap["Race"] := PlayerRef.GetRace
                        ; adds the Hispanic race form under the "Race" key because that's what she is
let CharacterStringMap["Cells visited"][1] := GoodSprings    
                        ; adds the Goodsprings exterior cell as the second element (key = 1) of the array held under the "Cells Visited" key in our stringmap

 
Note that there are times when letting an array element to the result of a function directly, like with the race example, can crap out on you and you need to store it to a local script var first. Don't expect getitemcount to be reliable for instance. Also, yes, whether you're retrieving from or storing to a stringmap, you can use string vars in place of the actual key string.

No matter in what order you add elements to an array, it'll automatically sort itself in ascending order - numerically for regular arrays and maps, alphabetically for stringmaps:

let somearray[1] := value1            let somestringmap["B"] := valueB
let somearray[0] := value0            let somestringmap["A"] := valueA

will be stored as
0     value0                                                            A     valueA
1     value1                                                            B    valueB



5. POPULATING YOUR ARRAYS A LITTLE MORE QUICKLY

Doesn't take a genius to tell that sticking stuff in your arrays like this:

let somearrayvar[index] := Value

 
can be something of a typing chore if you need to put a lot of elements in it, and it also takes up precious script lines. We have a few shortcuts though.

For regular arrays, you can use the ar_List function, which lets you create an array with up to 20 elements in a single line, usually separated by commas:

 

let somearrayvar := ar_List value1, value2, value3, value4, ... value20

let NukaColaArray := ar_List NukaCola, 2, "You can't beat the real thing.", RecipesInvolvingNukaColaArrayVar

 
The order in which you add the elements will be how the array is indexed (from 0 to 19).
0   NukaCola
1   2
2   "You can't beat the real thing."
3   RecipesInvolvingNukaColaArrayVar
 
Ar_list is an array-returning function, so it also immediately initializes the array you "let" it to.


If the elements you want to add to a regular array are a range of integers, consider the ar_Range function:

let someArrayVar := ar_range FirstInt LastInt StepInt   ; StepInt is optional, if you leave it out it's assumed to be 1

let someArrayVar := ar_range 1 9 2                      ; is the same as         let someArrayVar := ar_List 1, 3, 5, 7, 9
let someArrayVar := ar_range 1 4                        ; is the same as         let someArrayVar := ar_List 1, 2, 3, 4

 

For maps and stringmaps, you can use the ar_Map function, again with up to 20 elements you define, but of course the keys can be anything so you need to define them for each element yourself, associating each key with the value by using a double colon:

let somemap := ar_Map someKeyNumber::someValue someKeyNumber::someValue someKeyNumber::someValue ... the20thKeyNumber::the20thValue

let somestringmap := ar_Map someKeyString::someValue someKeyString::someValue ... the 20thKeyString::the20thValue
let CellMap := ar_Map -21::MojaveOutpost -9::MojaveDrivein 7::188TradingPost 12::BoulderCity 17::Bittersprings

let CharacterStringmap := ar_Map "Name"::"Prudencia" "Age"::27 "Body"::"T6M" "Profession"::"Test character" "Time Played (hrs)"::106.5 "Cells visited"::CellsVisitedArray

 
Maps and stringmaps will again auto-sort themselves in ascending order when you're done.

Okay, but, ehm, what if we have more than 20 elements to add, or simply don't know how many we'll add?  



6. INSPECTING ARRAYS: WALKING ARRAYS, TYPEOF, DUMPING ARRAYS TO CONSOLE

In that case, we'd probably want some way of parking a chunk in a temporary array with ar_list, and adding each element to another array that already contains a bunch of elements.


Actually, we could just use ar_InsertRange for that (see chapter 8). But for the sake of argument, let's assume we don't have that one (and it only works on regular arrays anyway).



So we'll need a method to select each element of an array in turn and do stuff with it.

With regular arrays, because the keys are consecutive integers, we could use the ar_size function (see chapter 7) and use that to build a while loop in which we add each value held in array2 to array1 with the ar_append function (see chapter 8):
 

let array1 := ar_list value1, value2, value3, value4, ... value20    ; the values could be anything - in obse docs, that is called a 'multi'
let array2 := ar_list value1, value2, value3, value4, ... value20

let iSize := ar_size array2         ; we could skip this step
let iNum := -1
while (iNum += 1) < iSize           ; the lowest index is 0 and the highest possible index is total size - 1
    let someVar := array2[iNum]
    ar_append array1 someVar
loop

 
And that'll work fine, as long as each value in array2 is the same type (forms, numbers, strings or arrays) so that we know what 'someVar' is supposed to be declared as. The trouble is that arrays can hold any combination of such values. Strictly speaking, we could use the handy TypeOf function to let us know the type of value in the form of a string:
 

let iNum := -1
while (iNum += 1) < (ar_size array2)
    let sv_somestring := TypeOf array2[iNum] ; TypeOf determines the type of the value under array2[iNum] and passes it to our string var
    if eval sv_somestring == "Form"          ; TypeOf returns "Form" for forms and refs
        let rRefVar := array2[iNum]
        ar_append array1 rRefVar
        continue                             ; we got what we need, let us skip the rest of the body and move on with our loop
    elseif eval sv_somestring == "Number"    ; TypeOf returns "Number" for floats and ints
        let fFloatVar := array2[iNum]
        ar_append array1 fFloatVar
        continue
    elseif eval sv_somestring == "String"    ; TypeOf returns "String" for strings
        let sv_StringVar := array2[iNum]
        ar_append array1 svStringVar
        continue
    else                                    ; TypeOf  returns either "Array", "Map" or "StringMap" for arrays - in this case we just need to catch them all
        let ar_ArrayVar := array2[iNum]     ; remember this makes ar_ArrayVar be a [u]reference [/u]to the array under array2[iNum], not a copy
        ar_append array1 ar_ArrayVar
        continue
    endif
loop

 
But that's a rather roundabout way isn't it, and either way a while loop won't work to walk maps (gaps, floats) or stringmaps (string keys).
Enter the foreach loop.

Foreach loops select each element of a collection type in a forwards loop, and like while - loop you need to see foreach - loop as a block. For strings they select each character, for containers each inventory reference, and for any type of array they select each array element. As with while loops, you interrupt a foreach loop with the "break" command, and skip an instance of the loop body with the "continue" command.

When you use a foreach loop on an array, it'll copy each element to a stringmap, the iterator, that you need to declare - the key of the element being inspected will be found under stringmap["key"], the value under stringmap["value"]:

 

"key"      thekeyoftheelement
"value"    thevalueoftheelement

The iterator, ie the temporary stringmap, will only ever have those 2 elements in it: "key"::KeyOfTheInspectedElement and "value"::ValueOfTheInspectedElement. You don't need to initialize it - the foreach command does that - and when the loop is done it is immediately nullified/uninitialized.
 

array_var tempstringmap                            ; for iterators, I like to use 'entry' or 'ar_entry' as the variable name, like you're leafing through an encyclopaedia
array_var arraytowalk

foreach tempstringmap <- arraytowalk               ; you need that arrow-like symbol there
    ; check out or do something with tempstringmap["key"] and/or tempstringmap["value"]
loop

 
so that lengthy while loop could become this short foreach loop:
 

array_var array1
array_var array2
array_var ar_entry

let array1 := ar_List Value1, Value2, ... Value20
let array2 := ar_List someRef, someFloat, "someString", someStringVar, someArray, ... Value20

foreach ar_entry <- array2
    ar_append array1 ar_entry["value"]    ; no matter what type each value in array2 is, we can refer to each one with ar_entry["value"]
loop

An example with our map that's holding some exterior cells by their X coordinates: let's say we want to split it up into a map holding western cells (-X) and one with eastern cells (+X).
 

array_var CellMap
array_var CellMapWest
array_var CellMapEast
array_var entry
float fXPos

let CellMap := ar_Map -21::MojaveOutPost -9::MojaveDriveIn 7::188TradingPost 12::BoulderCity 17::BitterSprings
let CellMapWest := ar_construct Map
let CellMapEast := ar_construct Map

foreach entry <- CellMap
    let fXPos := entry["key"]                       ; passing the key, the X position, to a float
    if 0 > fXPos                                    ; float is negative, so the cell is west
        let CellMapWest[fXPos] := entry["value"]    ; storing the cell - entry["value"] - under the fXPos key in the West map
    else
        let CellMapEast[fXPos] := entry["value"]
    endif
loop

 
But do we really need fXPos? No.

foreach entry <- CellMap
    if eval 0 > entry["key"]
        let CellMapWest[entry["key"]] := entry["value"]
    else
        let CellMapEast[entry["key"]] := entry["value"]
    endif
loop

 
Lastly, as said way back when in the syntax tutorial, you'll want to test a bunch of things out in game, so you'll need console readouts of what's in your arrays. You get them with the ar_Dump function:

ar_dump someArrayVar

will print out

** Dumping Array #1 **
Refs: 1 Owner 3D: DSTest36.esp    
[ 0.000000 ] : Value0
[ 1.000000 ] : Value1
etc.

Stringmaps will obviously have strings between those brackets, where the keys are printed.
Refs: 1 means there is only one reference to this array, in this case the array var in the script I called the ar_dump function on.

The owner is the mod that created the array, DSTest36.esp, and you also get its LO number.
The array ID is the number assigned to the array when it is created. You can call an array dump by specifying the ID rather than calling it on an array var:
 

ar_DumpID someIDInt

But know that as arrays are removed, being no longer referred to, their IDs get recycled. ar_DumpID is mostly useful when you call it from the console; you just need to be sure of the ID number.

Both ar_dump functions work like printc; they don't depend on debugmode. It's really easy though to create a debugmode-dependent version in a UDF:
 

 





call DBArDump somearrayvar
scn DBArDump

array_var a        ; parameter

Begin Function {a}

if GetDebugMode    ; or some quest var int that you set to 1 when people toggle debugmode on in MCM
    ar_dump a
endif

End

 



Actually reading your console readouts in the console can be a drag, so we have the handy con_scof function that dumps everything that appears in your console to a text file in your root folder:

con_scof "somefilename.txt"

 




7. ARRAY FUNCTIONS: A QUICK RUN-THROUGH

Ar_Size will return the size of the array in an int. -1 means it's not initialized yet, 0 that it is but it's empty.

 

let someInt := ar_size someArrayVar

 
    Finding stuff:

Ar_Find will return the key for the value you wish to find.

let iSomeInt := ar_Find ElementToSearchFor ArrayToSearch     
let fSomeFloat := ar_Find ElementToSearchFor MapToSearch
let sv_SomeStringVar := ar_find ElementToSearchFor StringMapToSearch

If the value's nowhere to be found, it'll return -99999.0 for regular arrays and maps, and an empty string (sv_length sv_somestringvar == 0) for stringmaps.

Ar_HasKey will check if an element exists with the key you specify, returning a simple bool.

if ar_HasKey ArrayToSearch someKey
    ; found!
endif

Note that if you have equivalent key-value elements (they're both numbers or both strings), it takes a bit less time/cpu power to find what you're looking for with ar_HasKey than with ar_Find, so if one side is something you think you'll need to search for more than the other, use them as keys.

 

ar_Keys returns a regular array containing all the keys of the source array/map/stringmap:

let somearray := ar_Keys sourcearray

The following should be pretty handy with maps and stringmaps (entirely unnecessary with regular arrays though):

ar_First returns the first key.                                                 let someInt/Float/StringVar := ar_First SomeMapVar
ar_Last returns the last key.                                                 let someInt/Float/StringVar := ar_Last SomeMapVar
ar_Next returns the next key from the one specified.           let someInt/Float/StringVar := ar_Next SomeMapVar someKey
ar_Prev returns the previous key from the one specified.    let someInt/Float/StringVar := ar_Prev SomeMapVar someKey

Ar_Next and ar_Prev return a 'bad index' value when they don't find anything. You need to request a 'bad index' to compare, using ar_BadNumericIndex or ar_BadStringIndex depending on your array type:

let fFloat := ar_Next SomeMapVar someFloatKey
if eval fFloat != ar_BadNumericIndex
    ; do stuff, you've got a valid key there
endif

let svStringVar := ar_Prev someStringMapVar someStringKey
if eval svStringVar != ar_BadStringIndex
    ; do stuff, you've got a valid key there
endif

 
    Copying:

Ar_Copy copies the elements from one array to another:

 

let someArray2 := ar_Copy someArray1

If someArray1 in turn holds a few arrays as values, someArray2 will contain references to the same arrays, not copies.
In order to copy sub-arrays 'cleanly', you use ar_DeepCopy:
 

let someArray2 := ar_DeepCopy someArray1

 
I can't advise you to choose one over the other - this really depends on your mod structure, which type of scripts you're in (persistence-wise), and what you're trying to do, ie whether or not you want to allow changes you make to those subarrays via someArray1 affect those subarrays under someArray2. Your choice, just be clear on which does what.

    Erasing elements:
    
Ar_Erase - fancy that it should be named this way - does the trick:

ar_erase yourarrayVar KeyOfTheElementToErase

It can also erase a range of elements if you specify that range in slice notation, with the first key and last key to erase separated by a colon:
 

ar_erase YourArrayVar FirstKey:LastKey

 
If you erase an element or a range of elements from a regular array, the elements with higher key numbers will shift down automatically.

If you erase an element from an array from within a foreach loop when that element is the one under iteration, that will most likely break the loop. With regular arrays, it doesn't always, but it does if you for instance pass the array as parameter to a UDF from within the loop. It'll always break with maps and stringmaps. The thing to do here is do your erasing when the loop is done, by for instance adding elements to erase to a new array during the loop and erase them from the original afterwards, or add the elements to keep to a new array and let the old one be a copy of that one afterwards.

    Sorting:

Ar_Sort returns a new array with the values of the old one sorted in a specific order. This can only be done if they're all the same type (numbers, objects or strings), or it'll just return an empty array. Strings are sort alphabetically, numbers numerically, and forms according to their formID. The resulting array is a 'regular' array, no matter what the array to sort was:

let array2 := ar_sort array1 descendingSortBool        ; if you don't specify that bool, it'll be in ascending order

 
Ar_SortAlpha does something similar, but the values don't have to be the same type - they're all converted to strings as if you used ToString and sorted alphabetically: strings stay strings, numbers become string representations of numbers, forms become their names if they have them (otherwise, it's their FormID strings).
 

let array2 := ar_sortalpha array1 descendingSortBool

If you need some more specific sorting, you can use Ar_CustomSort, for which you need to write a UDF.
Ar_CustomSort will repeatedly take 2 elements from the array to sort, and pass them to the UDF as parameters in the form of arrays. Your UDF  determines how the values of the 2 array var parameters should be sorted in relation to each other.
This looks to be especially handy if you're sorting forms according to anything other than their name/formID (eg weight, size, skills etc), or sub-arrays. You let the function know that one element should be considered 'earlier than' or 'less than' in the resulting array's order by letting the UDF return 'true', ie: setting a function value.

let array2 := ar_customsort array1 SortingUDF descendingSortbool

   Let's try this out, ok? 


    I want to sort a bunch of Goodsprings residents according to their medicine skill, with those with higher skill earlier in the array, ie 'less than'.
 

    scn MyComparisonUDF
    
    array_var a        ; parameters
    array_var b
    ref rA            ; local ref vars
    ref rB

    Begin Function {a b}
        let rA := a[0]
        let rB := b[0]
        if rA.GetAV Medicine > rB.GetAV Medicine
            SetFunctionValue 1                  ; SetFunctionValue anyInt marks the comparison as returning: element value a < element value b
        endif
    End
    scn MyCallingScript
    
    array_var array1
    array_var array2
    
    let array1 := ar_List TrudyRef, SunnyRef, DocMitchellRef, EasyPeteRef, GSJoeCobbRef, GSChetRef
    let array2 := ar_CustomSort array1 MyComparisonUDF

    now array2 is a regular array sorted like in ascending order, which in our case means in descending order of medicine skill:
    0    DocMitchellRef    ; Doc has 33 medicine
    1    GSChetRef          ; Chet has 15
    2    TrudyRef              ; all these have 12
    3    SunnyRef
    4    EasyPeteRef
    5    GSJoeCobbRef   ; Joe Cobb has 10
    
    If I use the ar_customsort function with the descendingbool, it'll sort the array in descending order, which in our case means in ascending order of medicine skill:
    0    GSJoeCobbRef
    1    EasyPeteRef
    2    SunnyRef
    3    TrudyRef
    4    GSChetRef
    5    DocMitchellRef   


    
    
    

8. "REGULAR ARRAY"-ONLY FUNCTIONS

As said a number of times already, regular arrays can only have positive, consecutive ints as keys, starting at 0. This is a limitation, but also something of a blessing because we can automate some things that way, which we can't do with maps and stringmaps.

Ar_Append, for instance, adds an element to an array at the next available key number.
 

Ar_append ArrayVar ValueToAdd.

 
    which is the same as:

let SomeInt := ar_size ArrayVar
let arrayVar[SomeInt] := ValueToAdd

 
If we had to do this with maps and stringmaps, and had no clue which keys they already had, we'd have to pick a random key, check whether the (string)map already had it with ar_HasKey and only then add it or not, picking another. Or use ar_last and add something to that to create a new key.
    
Ar_Insert adds a value to an array at a key number that you specify, shifting up the one that's already there, as well as those with a higher key. You can also ar_insert to the total size of the array, in fact doing the same like ar_append. You can't insert at an index higher than the total size though, because you'd be missing a piece.

    ar_insert ArrayToAddToVar KeyToAddAt ValueToAdd
    let array1 := ar_list "value1", "value2", "value3", "value4"
    ar_insert array1 2 "value2.5"

    will be:
    0  "value1"
    1  "value2"
    2  "value2.5"
    3  "value3"
    4  "value4"
    
Ar_InsertRange takes that one step further, and lets you insert a range of values in the middle or at the end of an array. You specify the range as yet another array.

    ar_InsertRange ArrayToAddToVar KeyToStartAddingAt ArrayToAddVar

  
    So this is where we pick up our earlier example from chapter 6, adding array2 to the end of array1:
    

    let array1 := ar_list value1, value2, value3, value4, ... value20    ; who knows what type or how many
    let array2 := ar_list value1, value2, value3, value4, ... value20    ; who knows what type or how many
    
    let iSize := ar_size array1               ; don't strictly need this, how about:
    ar_InsertRange array1 iSize array2        ; ar_InsertRange array1 (ar_size array1) array2

    will produce
    0    value1fromarray1
    ...
    39    value20fromarray2
    if there were 20 each.
    
Finally, Ar_Resize lets you cut an array down to a size you specify, or expand it to a size you specify.

 

    ar_resize ArrayToResizeVar NewSizeInt PaddingMulti(opt)

Wait, what's a paddingMulti? Well, a Multi is an array element value - could be whatever. So with 'paddingMulti", we mean any given value that you want the new values to be if you're scaling the array up. They're padding.
   

let arrayVar := Ar_Range 1 20           
;--> creates a regular array with 20 elements, with the ints from 1-20 as values, and obviously the keys being 0-19
ar_resize arrayVar 5                    
;--> our array now only has 5 elements, with the ints 1-5 as values, and the keys being 0-4
ar_resize arrayVar 10 "SomeString"        
;--> our array now has 15 elements, with the keys 0-9, the first 5 values being 1-5, and the last 5 having "SomeString" as a value

 
Ar_insert, ar_insertrange, and ar_resize can also return a bool that tells you if they've done their jobs - you can check that in the same line as commanding them to do it:

    if ar_resize arrayVar 10 "SomeString"
        ; do something with your resized array
    endif

Share this post


Link to post

Checking init state of an array is possible with something like if (array) in oblivion.

(and in the same manner if (string) too, because uninitialized string ID is zero.)

I'm not sure about nvse though..

Share this post


Link to post

I'll check that out.

 

Edit: yep, it works on both. Changed that over, because it's a more straightforward way of doing it and doesn't get ahead of itself referring to ar_size already. Also added a way of checking un-init status with LogicalNot. Added both to the string var tut too.

Share this post


Link to post

Very cool!

Although this was written for fallout, I think most of them can be applied to oblivion safely. Except very few fallout specific functions like Buildref.

Share this post


Link to post

Added a few tidbits (mentioning break & continue under foreach loops, referring to while; and that you can use string vars to refer to a stringmap's keys).

 

Cleaning up now, and moving the whole series to the tutorials section.

 

Edit: oh and yeah, if someone wants to adapt these for Oblivion, or for another forum that's a little less hot on adult examples etc - then that's alright, as long as my nick's on there somewhere (it did take some doing after all).

Share this post


Link to post

I feel Arrays can give nice possibilities, but I really can't focus in which way. Is it possible for you to find me a concrete example? I mean no need to code it, only something like "for example, let's think that we want to do blablabla".

Share this post


Link to post

Mostly arrays are useful for managing diverse data. They're lists, just lists that can hold much more than formlists: numbers, strings, and other arrays. In Sexout Spunk, the mod I made to learn about all this, they're used all over the place:
- a global table associating skeleton filepaths (string keys) with each species type (string values), so that when I look up the skeleton filepath I detect on the actor, I know what species an actor is (with a little finetuning afterwards)
- similarly, semen and sperm multipliers (number values) per species (string keys)
- library of cum shaders, structured according to volumes, species, texture sets, so that I can look up a match for the specified data when creating the effect
- library containing information (string value) about mutual positioning between actors and their relative position versus the ground, organized per sex animation (number key)
- creating an ejaculation stringmap holding information such as species (string), race (form), race category (string), ejaculator (ref), location where it landed (string), volume (float) etc.
- spreading an ejaculation over several areas of the body (copying that stringmap, and adjusting volume and location), holding and tracking the data of several ejaculations on the body
- calculating total volumes by adding readings from a loop to new stringmaps, parking those as nx vars
- lots of other internal handling mechanisms

I can probably find some ways of using them in other situations, if you can give me a hint about what you'd like to use them for.

Share this post


Link to post

Well I... still have to figure it. I mean, your example is quite clear, but it doesn't light any idea in my mind on a possible application. I've read your other tutorials about NVSE4 and if on a side I can feel these new functions are very useful, on the other side (and for the first time) I really feel a big gap from my plain scripting and this new syntax. I suppose it's just a matter of "habit", but it is quite abstruse for me. Right now I'm trying to replace a scanner I did following a very clear example from Halstrom, with a scanner you did in another very clear example: it's really hard to be read for me. The previous scripts were more like "common speech": "if this happens then change this value and allow that actor to do this action"; these new functions really require a bigger level of knowledge I still need to achieve, I find so hard to read them.

 

Anyway, without digressing too much, the first example I'm wondering if it's possible to do is this one:

- I write a list of items in an array:

>>> Item 1: dress A

>>> Item 2: dress B

>>> Item 3: dress C

etc.

- I tell the GECK "ok, now check my inventory, if you see some of those items then create a MenuMode using them (if GetItemCount [item corresponding to the key] >=1) (if I have that item in the inventory)

- Now, allow me to interact to some code (i.e. RemoveItem [item corresponding to the key]) when I click on it.

 

Do you think it's possible? Doing that and referring the rest of the script always using its key, and not the Base ID, would allow me to change the list only on top, where it's declared.

Share this post


Link to post

For your particular example, I'd actually use a formlist. All you do is look up forms, and GetItemCount can directly take formlists as parameter (which is the one advantage formlists still have over arrays).

 

if GetItemcount FormListID

  ; you have something from that formlist in your inventory

endif

 

and then maybe use a while loop to find what exactly you have, what to remove (or PickOneOf)

 

The main advantages of arrays/maps/stringmaps:

- multiple value types (forms, strings, numbers, other arrays/maps/stringmaps)

- parking arrays under arrays means being able to create tiered information structures

- associating information with other information (ie map and stringmap keys --> multi values), like with my species thing:

  if NX_IsUsingSkeleton $someskeletonfilepathstringpart

     let sv_speciesstring := myspeciesstringmap[$skeletonstring]

     NX_SetEVST "Spunk:Species" $sv_speciesstring

  endif

  let sv_species := NX_GetEVST "Spunk:Species"

  let fCumMult := mycummultiplierstringmap[$sv_species]

  and so on

- they're script-bound, script-created; no need to add entries to the geck's object window, destroyed when you don't need them anymore

- much more editing/manipulation possible

- very handy to combine with string var manipulation, NX variables, UDFs etc. (But of course, to get the most out of it, you need to learn about everything at once too.)

 

Of course, if you don't see a use for it in your project, well, you don't have to use it - just know that it's there when you can think of a use for it. And as with all tutorials, things really aren't as difficult when you actually put it in practice as how they look when you're just reading about them ;)

Share this post


Link to post

You're right. Actually the only things I could find a use are UDFs and the new way you wrote a scanner. I suppose I just need to find some pratical way to apply them in a mod. And then, that day, I'll spam this thread with all my mistakes and "why it doesn't work!" ;)

 

But one thing is certain: reading these tutorials at night doesn't make the things easier... :D

Share this post


Link to post

 

 

(modified example)

For maps and stringmaps, you can use the ar_Map function, again with up to 20 elements you define, but of course the keys can be anything so you need to define them for each element yourself, associating each key with the value by using a double colon:

let somemap := +ar_Map+ someKeyNumber::someValue someKeyNumber::someValue someKeyNumber::someValue ... the20thKeyNumber::the20thValue



let somestringmap := someKeyString::someValue someKeyString::someValue ... the 20thKeyString::the20thValue

 

 

I think you made a mistake in the 'ar_Map' example code- you left the actual function out of it. (section 5)

 

Thanks a lot for the guides.. I am having great fun with NVSE4 now

Share this post


Link to post

I think you made a mistake in the 'ar_Map' example code- you left the actual function out of it. (section 5)

 

Thanks a lot for the guides.. I am having great fun with NVSE4 now

Oops, correcting. :blush:

Share this post


Link to post
let rSomeCellForm := CharacterStringMap["Cells visited"][0] 
                               ; rSomeCellForm is now GSDocMitchellHouse (the first cell held as value in the regular array under the "Cells visited" key in our character stringmap)

 

This is an example of how to refer to an array within an array (2d array), yes?  Or did I miss it somewhere else?

Share this post


Link to post

Exactly.

let rSomeCellForm := CharacterStringMap["Cells visited"][0]

is the same as

let temparray := CharacterStringMap["Cells visited"]
let rSomeCellForm := temparray[0]

2d, 3d, I don't really know of a maximum limit to how many levels you can go down. (Common sense is good.)
Also, I don't know if it's stated enough in the tut, but for a stringmap you can pass the keys as string vars, which is what I do a lot in spunk:
let sv_species := NX_GetEVST "Spunk:Species"
let fSpeciesMult := somestringmap[sv_species]

And with the CO on, you can pass keys that are the result of functions directly, depends a little.
like
let somearray[abs(somefunction someparameter)] := something
or
let somearray[(call someUDF)] := something
Haven't tried that enough though to point out any pitfalls.

Share this post


Link to post

I'm a little confused about keys, cause I thought that they could only be numbers (integers or floats for maps).

 

Okay...I give up.  CO?

 

Also, you've got great tuts. *wolf whistle*

Share this post


Link to post

Keys can be strings, if you use a stringmap, a map that has strings for keys.

 

CO = compiler override, see NVSE4 part 1.

 

Oi, my face is up here!

Share this post


Link to post

Funny.  #1 is the one that I haven't read.  Thanks for the help/knowledge.

Share this post


Link to post

At the point where I'm trying to use a 2d array.

 

Array 1:  Should have 5 cells/fields/vars/whatever you want to call them.

Array 2:  Each cell/etc. has an Array1 in it.

 

This is for keeping track of sex acts.

 

So:

 

avSexActs[Which sex act it is][avSexData - the array with the info on actors, anim, etc. associate with that act number]

 

How do I implement this?  I thought that I was doing it correctly, although without CO, which is probably part of the problem.  But I want to understand how it should be done.

 

This is what I tried in a UDF:

Let SexoutBangMain.avSexActs[SexoutBangMain.iSexActCount][SexoutBangMain.avSexData["ActorA"]] : = rPartner

Is this legit, or should I instead capture all the vars in the avSexData array. copy the array avSexData to avSexActs[number of the act] around sex start, and then empty the values in avSexData for the next run?

 

I has a confuse.  : 3

 

 

Share this post


Link to post

I think you're probably looking for a

 

avSexActs[actIndex]["ActorA"] == rPartner

 

structure

 

which you'd get by first constructing a stringmap with the data for the act, then parking that under the actindex in avSexActs

 

let mystringmap["ActorA"] := rPartner

etc

 

let avSexActs[actIndex] := mystringmap

Share this post


Link to post

Lovely!  That's exactly what I was trying to go for.  Thank you kind sir.  : D

Share this post


Link to post
@DoctaSax

Thank you very much for this amazing and very detailed, step-by-step tutorial! I simply can't use formlists any more because of arrays are a lot more useful and easy to work with. And UDF sorting, just wow. Thank you =)

Share this post


Link to post

 

 

 


Ar_Append, for instance, adds an element to an array at the next available key number.
 

Ar_append ArrayVar ValueToAdd.

 
    which is the same as:

let SomeInt := ar_size ArrayVar
let arrayVar[SomeInt] := ValueToAdd

 
If we had to do this with maps and stringmaps, and had no clue which keys they already had, we'd have to pick a random key, check whether the (string)map already had it with ar_HasKey and only then add it or not, picking another. Or use ar_last and add something to that to create a new key.
    
Ar_Insert adds a value to an array at a key number that you specify, shifting up the one that's already there, as well as those with a higher key. You can also ar_insert to the total size of the array, in fact doing the same like ar_append. You can't insert at an index higher than the total size though, because you'd be missing a piece.

    ar_insert ArrayToAddToVar KeyToAddAt ValueToAdd
    let array1 := ar_list "value1", "value2", "value3", "value4"
    ar_insert array1 2 "value2.5"

    will be:
    0  "value1"
    1  "value2"
    2  "value2.5"
    3  "value3"
    4  "value4"
    
Ar_InsertRange takes that one step further, and lets you insert a range of values in the middle or at the end of an array. You specify the range as yet another array.

    ar_InsertRange ArrayToAddToVar KeyToStartAddingAt ArrayToAddVar

  
    So this is where we pick up our earlier example from chapter 6, adding array2 to the end of array1:
    

    let array1 := ar_list value1, value2, value3, value4, ... value20    ; who knows what type or how many
    let array2 := ar_list value1, value2, value3, value4, ... value20    ; who knows what type or how many
    
    let iSize := ar_size array1               ; don't strictly need this, how about:
    ar_InsertRange array1 iSize array2        ; ar_InsertRange array1 (ar_size array1) array2

 

 

Little confused.  Put the things in question under a spoiler.

 

If your examples, you've got things like ar_InsertRange array1 (ar_size array1) array2.

 

Shouldn't it be ((ar_size array1) - 1)?  Otherwise, wouldn't it try to skip a cell and then fail/complain since regular arrays can only have consecutive numbers starting at 0?

Share this post


Link to post

No, Ar_Size is the next available (unused) slot, -1 is the last used slot.

 

let Arry := Ar_List A, B

 

Arry[0] == A

Arry[1] == B

 

Ar_Size == 2

 

let this := Ar_List C, D

 

   Ar_InsertRange Arry, 2, this ; ** for A, B, C, D

or

   Ar_InsertRange Arry, 1, this ; ** for A, C, D, B

Share this post


Link to post

Well derpgasms.  :blush:  Thanks, Odessa.  Now...what was it I was saying not long ago about not looking at code when I'm not fully awake?

Share this post


Link to post

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

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...