Search the Community
Showing results for tags 'ar_'.
-
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. 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: 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? 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
- 80 replies
-
1
-
- array
- array variable
-
(and 2 more)
Tagged with: