dynamic arrays? (prototype)

Users who are viewing this thread

sphere

Grandmaster Knight
This is a prototype for creating dynamic arrays within the scripting system in mnb (warband or native)

Since party is the only dynamically create-able in-game entity at the moment, it is implemented using disabled parties with special signature assigned so that it is identifiable as arrays...

The aim
* attempt to break free from the "make array out of static troop/party/fixed slot region range".  array internally also uses slot range >= 1000 (so that it can use slots lower than 1000 for meta-data), but if accessed through array scripts, it is transparent (user should only see the index number)
* Upgradeable and be independent of exact implementation.  Current implementation is through fake parties and slot ranges.  But if arrays are created and accessed through the abstracted interface scripts, the implementation can be changed without affecting the callers, even though a little less efficient than directly setting/reading slot values.  Also, version is embedded in each array object (party), such that if  there is an upgrade to array implementation in the future, the older array objects can be detected and upgraded to any new format, hopefully resolving any save-game compatibility issues.

Features
* dynamic.  No llimit to how many you can create in-game (except any limits to party numbers)
* garbage collected.  Can be set to be dependent on parent party(array), such that when parent is removed, child arrays will be garbage collected by an optional simple_trigger.  Can also choose to delete array immediately or set an array for garbage collection collection.
* do pushes, pops, inserts, removes, all with range checking.  Some operations also have can-fail variants which can act as automatic conditions, and non-failing versions.
* access meta-data, like size, owner, version, function to test array for array signature to see if it is a party used as an array
* max size of array is up to slot range allowed for parties... iow, I don't know. ask dev?
* can be nested.  i.e. arrays of arrays (of arrays of arrays ....)

Planned but not-implemented:
* Search array for some value, returning index (optionally within a range)
* Count instances of certain value  (optionally within a range)


Uses?
* Virtual Inventory.  Instead of requiring an actual merchant/whatever troop to store items, virtual inventory can be stored using 3 arrays working together (or a 1 array with 3 sub-arrays), storing [item_id],[mod],[amount] respectively.  then can create utility functions to convert between virtual inventories and real troops.  Can compress certain items (e.g. instead of storing say a plate_mail in 2 slots in real inventory, can store in 1 slot in virtual inventory.  Can also allow entities which normally cannot have inventories to have inventories (just "unzip" the virtual inventory into some temp troop's inventory for use, then "zip" it back)

* Virtual parties list.  Alternative to storing troops using the actual party-troop arrangements, can store them as raw integer values. e.g. 2 arrays storing [troop_id],[troop_number].  The benefit is that it has unlimited troop slots + fully controllable (e.g. sorting etc).  Useful for cases where real parties of troops not needed, but used just for counting, analysis, backup etc.
* extended relations? Currently, each troop vs troop relation stored in troop itself, with only a single value.  With arrays, easy to expand and change it to a multi value system to indicate more complex relations.
* Formations? formation-related stuff? (some may depend on whether or not spawn party can work during missions/battles though.  If it cannot work, it COULD be possible to use agents to implement a mission-only array, but I don't know mission/agents well enough yet)
* temporary store anything in list.  If multiple value tuple array required, use multiple lists.  Can even hold information in a hierarchy using lists of lists (of lists...)

Please remember that this is a PROTOTYPE of an idea that is not proven to work totally yet (though prelim tests seems ok so far).  Not all the functions are tested fully.  bugs are expected.

Comments, advices, improvements, problems welcome... esp on whether there are any hidden problems or limitations wrt game engine.

Sample source
(to use, you'll have to add those in scripts list to module_scripts.py and the function in "simple_triggers" to module_simple_trigger.py.  There are some test_scripts which are not required, but can serve as example on how to use the array scripts)
Code:
from header_common import *
from header_operations import *
from module_constants import *
from header_parties import *
from header_skills import *
from header_mission_templates import *
from header_items import *
from header_triggers import *
from header_terrain_types import *
from header_music import *
from ID_animations import *



array_signature     = 0xFFFE # Just a magic number.  But should not be changed if desire to be compativle with older save-games
array_version       = 1 # marks the current structure version of array, possibly allowing graceful upgrading of older versions of arrays in existing savegame
array_slot_offset   = 1000 # start of array values.  Shifted up by 1000 to avoid clash with other party slots

slot_array_0        = array_slot_offset  # first element

# generate constants from slot_array_1 to slot_array_99, the lazy fashion
try:
    import sys
    module = sys.modules[__name__]    
    for i in range(1,100):
      setattr(module, "slot_array_%d"%(i), eval("slot_array_%d+1" %(i-1)))
except:
    raise


slot_array_size     = 999 # stores current size of array
slot_array_owner    = 998 #(if 0, array will never be cleaned up, otherwise, this is the party id that owns the array, and when party no longer exists, the array can be cleaned up)
slot_array_signature= 997 # a special slot that will contain a magic value (array_signature) that identifies that this is array
slot_array_version  = 996 # see array_version



# array script definitions
scripts=[

# script_array_create
# Function: Create and return a new array (party)
# Basically, create a dummy party and sets a recognition pattern in certain slots
# Arguments:
# Return: reg0 = id of new array
("array_create",[
    # create fake party
    (set_spawn_radius, 0),
    (spawn_around_party, 1, "pt_none"), # use p_temp_party for position
    (assign, ":array", reg0),
    (party_set_flags, ":array", pf_disabled, 1),
    
    # initialize array data
    
    (party_set_slot, ":array", slot_array_size, 0),
    (party_set_slot, ":array", slot_array_owner, 0),
    (party_set_slot, ":array", slot_array_signature, array_signature),  # magic number to use for array identification            
    (party_set_slot, ":array", slot_array_version, array_version),      # array version  
(display_log_message, "@array[{reg0}] created"),
    
]), # array_create


# script_array_destroy
# Function: Destroys an array.  Does nothing if not an array
# Arguments:array id
# Return:	
("array_destroy",[
    (store_script_param_1, ":array"),
     (try_begin),
        (call_script, "script_cf_array_is_array", ":array"),
        (remove_party, ":array"),
(assign, reg0, ":array"),
(display_log_message, "@array[{reg0}] destroyed"),
     (try_end),
]), #


# script_cf_array_is_array
# Function: checks if something is an array
# fails if target is not an array
# Arguments:array id
# Return:
# Fail: if not an array
("cf_array_is_array",[
    (store_script_param_1, ":array"),
     (party_slot_eq, ":array", slot_array_signature, array_signature),
]), #

# script_array_get_owner
# Function: Gets the owner for an array, which affects automatic garbage collection
# Arguments:array id
# Return:reg0= owner
("array_get_owner",[
    # note: no type checking for now
    (store_script_param_1, ":array"),
    (party_get_slot, reg0, ":array", slot_array_owner),
]), #

# script_array_get_version
# Function: Gets the version for an array, which affects automatic garbage collection
# Arguments:array id
# Return:reg0= version
("array_get_version",[
    # note: no type checking for now
    (store_script_param_1, ":array"),
    (party_get_slot, reg0, ":array", slot_array_version),
]), #


# script_array_set_owner
# Function: Sets owner for an array, which affects automatic garbage collection
# Arguments:array id, owner(party) : 0 is a special value indicating that 
#     array should never be cleaned up.  Otherwise, if the owning party is no 
#     longer active, it will be cleaned up during garbage collection.
#     Setting to -1 explicitly will also make it clean up on next garbage collection
# Return:	
("array_set_owner",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":owner"),
    (try_begin),
        (call_script, "script_cf_array_is_array", ":array"),
        (party_set_slot, ":array", slot_array_owner, ":owner"),
    (try_end),
]), #


("array_clear",[
    (store_script_param_1, ":array"),
    (party_set_slot, ":array", slot_array_size, 0), # quick clear.  just set size to 0    
]), #

# script_array_get_size
# Function: gets array size
# Arguments:array id
# Return: reg0 = array size
("array_get_size",[
    (store_script_param_1, ":array"),
    (party_get_slot, reg0, ":array", slot_array_size),
]), #


# script_cf_array_get_element
# Function: can fail version which gets array element at position
# Arguments:array id, index
# Return: reg0 = element at index
# Fail: if index out of range
# Also see: script_array_get_element
("cf_array_get_element",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (party_get_slot, ":size", ":array", slot_array_size),
    (ge, ":index", 0),
    (gt, ":size", ":index"),
    (val_add, ":index", array_slot_offset), # add offset to get actual slot for element
    (party_get_slot, reg0, ":array", ":index"),    
]), #

# script_cf_array_set_element
# Function: can fail version which sets array element at position
# Arguments:array id, index, value
# Return:
# Fail: if index out of range
# Also see: script_array_set_element
("cf_array_set_element",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (store_script_param, ":value", 3),
    (party_get_slot, ":size", ":array", slot_array_size),
    (ge, ":index", 0),
    (gt, ":size", ":index"),
    (val_add, ":index", array_slot_offset), # add offset to get actual slot for element
    (party_set_slot, ":array", ":index", ":value"),
]), #


# script_array_get_element
# Function: non failing version which gets array element at position, no range checking
# Arguments:array id, index, default_value
# Return: reg0 = element at index if found, default value otherwise
# Also see: script_cf_array_get_element
("array_get_element",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (party_get_slot, ":size", ":array", slot_array_size),
    (try_begin),
        (ge, ":index", 0),
        (gt, ":size", ":index"),
        (val_add, ":index", array_slot_offset), # add offset to get actual slot for element
        (party_get_slot, reg0, ":array", ":index"),    
    (else_try),
        (store_script_param, reg0,3), # return default value        
    (try_end),
]), #

# script_array_set_element
# Function: non fail version which sets array element at position, and does nothing if index out of range
# Arguments:array id, index, value
# Return:
# Also see: script_cf_array_set_element
("array_set_element",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (store_script_param, ":value", 3),
    (try_begin),
        (party_get_slot, ":size", ":array", slot_array_size),
        (ge, ":index", 0),
        (gt, ":size", ":index"),
        (val_add, ":index", array_slot_offset), # add offset to get actual slot for element
        (party_set_slot, ":array", ":index", ":value"),
    (try_end),
]), #


# array_pushback
# Function: Inserts a value at the end of array
# Arguments:array id, value
# Return:
("array_pushback",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":value"),
    (party_get_slot, ":size", ":array", slot_array_size),    
    (store_add, ":slot_end", ":size", array_slot_offset),
    (party_set_slot, ":array", ":slot_end", ":value"),
    
    # update size  
    (val_add, ":size", 1),  
    (party_set_slot, ":array", slot_array_size, ":size"),
]), #

# array_pushfront
# Function: Inserts a value at the front of array
# Arguments:array id, value
# Return:
("array_pushfront",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":value"),
    (party_get_slot, ":size", ":array", slot_array_size),    
    (val_sub, ":size",1),
    (call_script, "script_array_p_shift_range", ":array", 0, ":size", 1, ":value"), # shift all values right by 1 place, fill empty with value
    # update size  
    (val_add, ":size", 1),  
    (party_set_slot, ":array", slot_array_size, ":size"),
]), #


# array_popback
# Function: removes and returns the last element in array if it exists
# Arguments:array id
# Fail: if array is empty
# Return: reg0=value which was at the back of array
("cf_array_popback",[
    (store_script_param_1, ":array"),
    (party_get_slot, ":size", ":array", slot_array_size),    
    (ge, ":size", 0),
    (val_sub, ":size", 1),    
    (store_add, ":slot_back", ":size", array_slot_offset),
    (party_get_slot, reg0, ":array", ":slot_back"),    
    (party_set_slot, ":array", slot_array_size, ":size"),
]), #

# array_popfront
# Function: removes and returns the first element in array if it exists
# Arguments:array id
# Fail: if array is empty
# Return:
# Return: reg0=value which was at the front of array
("cf_array_popfront",[
    (store_script_param_1, ":array"),
    (party_get_slot, ":size", ":array", slot_array_size),    
    (ge, ":size", 0),

    (party_get_slot, ":value", ":array", slot_array_0),

    (call_script, "script_array_remove", ":array", 0), # remove element 0
    # update size  
    (val_sub, ":size",1),
    (party_set_slot, ":array", slot_array_size, ":size"),
    (assign, reg0, ":value"),
]), #


# script_cf_array_insert
# Function: Inserts a value at position (other values will be shifted backwards)
# Arguments:array id, index, value
# Return:
# Fail: if index out of range
("cf_array_insert",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (store_script_param, ":value", 3),
    (party_get_slot, ":size", ":array", slot_array_size),
    (ge, ":index", 0),
    (ge, ":size", ":index"),
    
    (call_script, "script_array_p_shift_range", ":array",":index", ":size", 1, ":value"),
    
    # update size  
    (val_add, ":size", 1),  
    (party_set_slot, ":array", slot_array_size, ":size"),
]), #

# script_cf_array_insert_n
# Function: Inserts n instances of a value at position (other values will be shifted backwards)
# Arguments:array id, index, value, n
# Return:
# Fail: if index out of range
("cf_array_insert_n",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (store_script_param, ":value", 3),
    (store_script_param, ":n", 4),
    (party_get_slot, ":size", ":array", slot_array_size),
    (ge, ":index", 0),
    (ge, ":n", 0),
    (ge, ":size", ":index"),    
    (call_script, "script_array_p_shift_range", ":array", ":index", ":size", ":n", ":value"),    
    # update size  
    (val_add, ":size", ":n"),  
    (party_set_slot, ":array", slot_array_size, ":size"),
]), #


# script_cf_array_remove
# Function: Removes a value at position (other values will be shifted forward)
# Arguments:array id, index
# Return:
# Fail: if index out of range
("cf_array_remove",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (party_get_slot, ":size", ":array", slot_array_size),
    (ge, ":index", 0),
    (gt, ":size", ":index"),
    (val_add, ":index", 1),    
    (call_script, "script_array_p_shift_range", ":array", ":index", ":size", -1, 0),
    
    # update size  
    (val_sub, ":size", 1),  
    (party_set_slot, ":array", slot_array_size, ":size"),
]), #

# script_cf_array_remove_n
# Function: Removes n values at position (other values will be shifted forward).  If end of array is reached before n elements are removed, it is stop there.
# Arguments:array id, index, n
# Return:
# Fail: if index out of range
("cf_array_remove_n",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (store_script_param, ":n", 3),
    (party_get_slot, ":size", ":array", slot_array_size),
    (ge, ":index", 0),
    (ge, ":n", 0),
    (gt, ":size", ":index"),    
    (val_add, ":index", ":n"),
    (store_sub, ":neg_n", 0, ":n"),
    (call_script, "script_array_p_shift_range",":array", ":index", ":size", ":neg_n", 0),    
    # update size  
    (val_sub, ":size", ":n"),  
    (party_set_slot, ":array", slot_array_size, ":size"),
]), #




# non-failing versions of insert and remove, which is similar to the can fail version, just don't do anything if index out of range.

# script_array_insert
# Function: Inserts a value at position (other values will be shifted backwards)
# Arguments:array id, index, value
# Return:
("array_insert",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (store_script_param, ":value", 3),
    (party_get_slot, ":size", ":array", slot_array_size),
    (try_begin),
        (ge, ":index", 0),
        (ge, ":size", ":index"),
        
        (call_script, "script_array_p_shift_range",":array", ":index", ":size", 1, ":value"),
        
        # update size  
        (val_add, ":size", 1),  
        (party_set_slot, ":array", slot_array_size, ":size"),
    (try_end),
]), #

# script_array_insert_n
# Function: Inserts n instances of a value at position (other values will be shifted backwards)
# Arguments:array id, index, value, n
# Return:
("array_insert_n",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (store_script_param, ":value", 3),
    (store_script_param, ":n", 4),
    (party_get_slot, ":size", ":array", slot_array_size),
    (try_begin),
        (ge, ":index", 0),
        (ge, ":n", 0),
        (ge, ":size", ":index"),    
        (call_script, "script_array_p_shift_range", ":array",":index", ":size", ":n", ":value"),    
        # update size  
        (val_add, ":size", ":n"),  
        (party_set_slot, ":array", slot_array_size, ":size"),
    (try_end),
]), #



# script_array_remove
# Function: Removes a value at position (other values will be shifted forward)
# Arguments:array id, index
# Return:
# Fail: if index out of range
("array_remove",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (party_get_slot, ":size", ":array", slot_array_size),
    (try_begin),
        (ge, ":index", 0),
        (gt, ":size", ":index"),
        (val_add, ":index", 1),    
        (call_script, "script_array_p_shift_range", ":array",":index", ":size", -1, 0),
        
        # update size  
        (val_sub, ":size", 1),  
        (party_set_slot, ":array", slot_array_size, ":size"),
    (try_end),
]), #

# script_array_remove_n
# Function: Removes n values at position (other values will be shifted forward)
# Arguments:array id, index, n
# Return:
# Fail: if index out of range
("array_remove_n",[
    (store_script_param_1, ":array"),
    (store_script_param_2, ":index"),
    (store_script_param, ":n", 3),
    (party_get_slot, ":size", ":array", slot_array_size),
    (try_begin),
        (ge, ":index", 0),
        (ge, ":n", 0),
        (gt, ":size", ":index"),    
        (val_add, ":index", ":n"),
        (store_sub, ":neg_n", 0, ":n"),
        (call_script, "script_array_p_shift_range",":array", ":index", ":size", ":neg_n", 0),    
        # update size  
        (val_sub, ":size", ":n"),  
        (party_set_slot, ":array", slot_array_size, ":size"),
    (try_end),
]), #


##### private scripts #####
# Private function not to be called by non-array scripts


# script_array_p_shift_range
# Function: private function to shift range of values in position (start_idx, end_idx-1) right by n places. (n>0 means shift right, n<0 means shift lef, n=0 means no shift)
#   Note that this shift is done REGARDLESS of actual array size, as long as the indices do not go below 0.  If so, operations regarding those values will be ignored.
#   Spaces created by shifts will be filled with default_value
# Arguments: array id, start_idx, end_idx, shift_offset, default_value
# Fail: never
("array_p_shift_range",[
    (store_script_param, ":array", 1),
    (store_script_param, ":start_index", 2),
    (store_script_param, ":end_index", 3),
    (store_script_param, ":shift_offset", 4),
    (store_script_param, ":default_value", 5),
    
    (val_add, ":end_index",1),
    
#debug start
(assign, reg21, ":array"),
(assign, reg22, ":start_index"),
(assign, reg23, ":end_index"),
(assign, reg24, ":shift_offset"),
(assign, reg25, ":default_value"),
(display_debug_message, "@array_p_shift_range array={reg21}, start_index={reg22}, end_index={reg23}, shift_offset={reg24}, default_value={reg25}"),
#debug end
    (store_add, ":dst_start_index", ":start_index", ":shift_offset"),
    (store_add, ":dst_end_index", ":end_index", ":shift_offset"),
    
    (try_begin),
        (gt, ":shift_offset", 0), # shifting right
#(display_log_message, "@shift_offset>0"),
        (try_for_range_backwards, ":i", ":start_index", ":end_index"),
#(assign, reg31, ":i"),                                  
#(display_log_message, "@i={reg31}"),
            (store_add, ":slot_i", ":i", array_slot_offset),
            (party_get_slot, ":value", ":array", ":slot_i"), # value = A[i]
            (store_add, ":slot_dst_i", ":slot_i", ":shift_offset"),
            (party_set_slot, ":array", ":slot_dst_i", ":value"), # A[i + offset] = value
            (try_begin), # if position is blank left by shifting, fill in with default value
                (gt, ":dst_start_index", ":i"),
                (party_set_slot, ":array", ":slot_i", ":default_value"), # A[i] = default_value                    
            (try_end),                      
#(assign, reg32, ":slot_i"),
#(assign, reg34, ":slot_dst_i"),
#(display_log_message, "@slot_i={reg32}"),
#(display_log_message, "@slot_dst_i={reg34}"),
        (try_end),
    (else_try),
        (gt, 0, ":shift_offset"), # shifting left
#(display_log_message, "@shift_offset<0"),
        (try_for_range, ":i", ":start_index", ":end_index"),
            (try_begin),
                (store_add, ":dst_i", ":i", array_slot_offset),
                (ge, ":dst_i", 0),
                
                (store_add, ":slot_i", ":i", array_slot_offset),
                (party_get_slot, ":value", ":array", ":slot_i"), # value = A[i]
                (store_add, ":slot_dst_i", ":slot_i", ":shift_offset"),
                (party_set_slot, ":array", ":slot_dst_i", ":value"), # A[i - offset] = value
                (try_begin), # if position is blank left by shifting, fill in with default value
                    (ge, ":i", ":dst_end_index"),
                    (party_set_slot, ":array", ":slot_i", ":default_value"), # A[i] = default_value                    
                (try_end),                                                            
            (try_end),        
        (try_end),
    (try_end),
]), #



#
#("array_",[
#]), #
#
#

] # scripts


# These are supposed to be added to module_simple_triggers.py
# If not added, it just means there are no garbage collection done for arrays
simple_triggers=[

  # array Garbage Collection.  Will delete all arrays marked for deletion (-ve value for owner), or if their owner (party) is no longer active
    (1, [
(display_log_message, "@array GC"),
    
        (try_for_parties, ":array"),
            (call_script, "script_cf_array_is_array", ":array"),
            (call_script, "script_array_get_owner", ":array"),
            (assign, ":owner", reg0),
            (try_begin),
                (this_or_next|eq, ":owner", 0), # no GC
                (party_is_active, ":owner"),
(display_log_message, "@array GC: owner active or is 0"),
            (else_try),
(assign, reg21, ":array"),            
(display_log_message, "@array GC: removing {reg21}"),
                # do garbage collection
                (call_script, "script_array_destroy", ":array"),
            (try_end),
            
        (try_end),        
    ]),



] # simple_triggers




#samples
# To create an array and add values into it
test_scripts = [
("array_test",
[
    # create an array
    (call_script,"script_array_create"),
    (assign, ":array", reg0),
    
    # push in values
    (assign,reg21,reg0),
(display_log_message, "@array {reg21} pushback 10"),
    (call_script,"script_array_pushback", ":array", 10), # element 0
    (call_script,"script_array_print", ":array"),
(display_log_message, "@array {reg21} pushback 20"),
    (call_script,"script_array_pushback", ":array", 20), # element 1
    (call_script,"script_array_print", ":array"),
(display_log_message, "@array {reg21} pushback 30"),
    (call_script,"script_array_pushback", ":array", 30), # element 2
    (call_script,"script_array_print", ":array"),
    
(display_log_message, "@array {reg21} pushfront 5"),
    (call_script,"script_array_pushfront", ":array", 5), # element 2
    (call_script,"script_array_print", ":array"),

(display_log_message, "@array {reg21} popback"),
    (call_script,"script_cf_array_popback", ":array"),
(display_log_message, "@popoed value = {reg0}"),    
    (call_script,"script_array_print", ":array"),

(display_log_message, "@array {reg21} popfront"),
    (call_script,"script_cf_array_popfront", ":array"),
(display_log_message, "@popoed value = {reg0}"),    
    (call_script,"script_array_print", ":array"),

    # insert new element at position
(display_log_message, "@array {reg21} insert 1 15"),
    (call_script,"script_array_insert_n", ":array", 1, 15), # insert before element 1
    (call_script,"script_array_print", ":array"),
    
    # remove element at position
(display_log_message, "@array[{reg21}] remove 1"),
    (call_script,"script_array_remove_n", ":array", 1), # remove element from position 1
    (call_script,"script_array_print", ":array"),
    
    #print array
    #(call_script,"script_array_print", ":array"),

    # mark array so that it will exist indefinitely
    (call_script,"script_array_set_owner", ":array", 0),
       
    ## mark array so that it will exist until it's owner (party) is no longer active
    #(call_script,"script_array_set_owner", ":array", ":someparty"),
    
    # mark array so that it will be auto-cleaned up on next garbage collection cycle (if the simple_trigger is active)
    (call_script,"script_array_set_owner", ":array", -1),
    
    # destroy array immediately
    #(call_script,"script_array_destroy", ":array"),    
    #(call_script,"script_array_print", ":array"),
]),

# print contents of an array in log_messages
("array_print",
[
    (store_script_param_1, ":array"),
    
   
    #iterate and print out values
    (call_script,"script_array_get_size", ":array"),
    (assign, ":size", reg0),
    (try_for_range, ":i", 0, ":size"),
        (call_script,"script_cf_array_get_element", ":array", ":i"),
        (assign, reg2, reg0),
        (assign, reg1, ":i"),
        (display_log_message, "@array[{reg1}] = {reg2}"),
    (try_end),
]),
]

scripts.extend(test_scripts)

#def modmerge_scripts(orig_scripts):
	#from util_scripts import add_scripts
	#add_scripts(orig_scripts, scripts, True)
#
# fc_end
 
On my resurrection of sphere's gems tour, I've also found this to be an interesting notion and quite useful in at least one of my coding endeavors. To that end I slapped the scripts and simple triggers in their own ModMerger-ready files (and added a script for recursive deletion of sub-arrays) and figured I might as well post that if it saves anyone a few minutes of work: http://www.mbrepository.com/file.php?id=3416 ; http://www.nexusmods.com/mountandblade/mods/3416
 
I originally made a comment along the lines (before I deleted it) that I couldn't see the point in this.  I've now changed my tune, even though this is making my head spin a little.  Realised that it would be better to have a list of skirmisher agent IDs in my battle scene rather than going through every agent in the battle and working out if they are a skirmisher.  While it is obvious from Sphere's examples how to set up an array, I'm a little confused on how I can make an array that is going to last beyond the script it is created in.  Well, assuming there is a difference.  Since the created arrays are using the party side of things, does this mean any array created is automatically global (at least for the current scene)?  Also since Lavs Agent Finder scripts seem to use a similar idea with regards to parties to create dynamic arrays/lists, can these two lots of scripts co-exist?  All seems insanely complicated just to save a few CPU cycles (well ok probably more than a few) -- especially agent finder...

Basically I'm going to add a skirmisher's ID to the list when spawned, and remove it when they die.  Then run through the list to determine whether or not a skirmisher is in 'skirmisher mode'.  I'm hoping this will have less overhead than just brute force running through all agents every now and then.
 
Hatonastick said:
I'm a little confused on how I can make an array that is going to last beyond the script it is created in.
Store the array ID (party ID/no) returned in reg0 by the script "array_create" in a slot or a global variable and then use that slot value/global for future calls. The dummy party will remain on the map (invisible and disabled) until the "garbage collection" deletes it (if it were marked for deletion) or you call the script "array_destroy".

Hatonastick said:
All seems insanely complicated just to save a few CPU cycles (well ok probably more than a few) -- especially agent finder...
I must agree that I had the same thought for the agent finder, but then again, I'm honestly not sure if I fully understand the power of what can be accomplished with it.

Hatonastick said:
I originally made a comment along the lines (before I deleted it) that I couldn't see the point in this.
Once Floris 2.5 gets through another round of beta and the code checks out, I'll be posting my first use of this--as a way to create a dynamic merchant's log that tracks the "Assess profits" option, by recording the item, origin town, origin game hours, destination town, and estimated profit each in its own array (the array ID of each stored as elements in a 'master' array, whose array ID is stored in a troop slot for potential NPC-specific arrays). But since one "entry" in the log will have the same element ID across all 5 arrays, I can then sort the display by any of the arrays, or remove an entry based on its value in any one of the arrays, etc, and new logs can be created/destroyed as needed throughout the game.
 
Caba`drin said:
Hatonastick said:
All seems insanely complicated just to save a few CPU cycles (well ok probably more than a few) -- especially agent finder...
I must agree that I had the same thought for the agent finder, but then again, I'm honestly not sure if I fully understand the power of what can be accomplished with it.

For single try_for_agent runs I think it isn't really necessary; after all we might only have at most ~200 bots/players which accounts to at most 400 runs (if all mounted).

But, for example, I have a manual hit detection script which runs a try_for_agent for each "missile" each frame with about up to 100 or more "missiles" that can appear at the same time. When I do that w/o the agent finder the game freezes up until all the missiles are cleared.

("missile" == scene-prop)

Or for AI, you might need to know for each agent how many agents are close by etc... Then it would best to not have scripts with O(n^2) time complexity.

So actually for the skirmisher thing, I don't think it's necessary to use a dynamic array.
 
Caba`drin said:
Hatonastick said:
I'm a little confused on how I can make an array that is going to last beyond the script it is created in.
Store the array ID (party ID/no) returned in reg0 by the script "array_create" in a slot or a global variable and then use that slot value/global for future calls. The dummy party will remain on the map (invisible and disabled) until the "garbage collection" deletes it (if it were marked for deletion) or you call the script "array_destroy".

Hatonastick said:
All seems insanely complicated just to save a few CPU cycles (well ok probably more than a few) -- especially agent finder...
I must agree that I had the same thought for the agent finder, but then again, I'm honestly not sure if I fully understand the power of what can be accomplished with it.

Hatonastick said:
I originally made a comment along the lines (before I deleted it) that I couldn't see the point in this.
Once Floris 2.5 gets through another round of beta and the code checks out, I'll be posting my first use of this--as a way to create a dynamic merchant's log that tracks the "Assess profits" option, by recording the item, origin town, origin game hours, destination town, and estimated profit each in its own array (the array ID of each stored as elements in a 'master' array, whose array ID is stored in a troop slot for potential NPC-specific arrays). But since one "entry" in the log will have the same element ID across all 5 arrays, I can then sort the display by any of the arrays, or remove an entry based on its value in any one of the arrays, etc, and new logs can be created/destroyed as needed throughout the game.
Ahhh ok now I get it, thanks!

Same here.  It looks and sounds good (as well as insanely complicated), but I'm hoping I can use it for better AI decisions, especially with regards to my skirmishers (and probably tactical AI decisions as well).  Will be interested to see how well it performs in that regard.  Might have a go at getting that working today.

I thought you were involved in Floris!  Was looking at the forum yesterday and saw you were possibly involved.  Will have to check it out when the next version gets released.  Definitely intrigued by the sound of your merchants log idea.  Since I started working on my own mod (again) since coming back to Warband, the number of scripts I'm using with the name "Caba'drin" attached has dwarfed the scripts written by anyone else (myself included) -- I'm curious, are you going to leave anything for the rest of us to do?  :grin:

 
SonKidd said:
Caba`drin said:
Hatonastick said:
All seems insanely complicated just to save a few CPU cycles (well ok probably more than a few) -- especially agent finder...
I must agree that I had the same thought for the agent finder, but then again, I'm honestly not sure if I fully understand the power of what can be accomplished with it.

For single try_for_agent runs I think it isn't really necessary; after all we might only have at most ~200 bots/players which accounts to at most 400 runs (if all mounted).

But, for example, I have a manual hit detection script which runs a try_for_agent for each "missile" each frame with about up to 100 or more "missiles" that can appear at the same time. When I do that w/o the agent finder the game freezes up until all the missiles are cleared.

("missile" == scene-prop)

Or for AI, you might need to know for each agent how many agents are close by etc... Then it would best to not have scripts with O(n^2) time complexity.

So actually for the skirmisher thing, I don't think it's necessary to use a dynamic array.
Yeah I think I'll definitely use the AI scripts.  You may be right about skirmishers.  The only issue is by the time I'm finished I could end up with quite a few scripts running through all agents on a battlefield every "normal" battle.  That's my main concern.  I guess I can always worry about that later and switch to dynamic arrays containing lists of just the agents I need to check if things start to get a bit laggy.
 
Hatonastick said:
I'm hoping this will have less overhead than just brute force running through all agents every now and then.
It's a time/memory trade-off. In theory, you will save time (unless the memory access and engine-side processes makes it moot, but I doubt it).
In essence, you're still in O(N) time, but you've reduced N (to a whole different variable even!). So for algorithms using this list, you have the same worst case (assuming you can have 2 whole companies of skirmishers duking it out), but your average case has become much, much better, and your best case is now constant time.
 
sphere's idea gets my Official Seal of Approval. :smile:

sphere said:
* max size of array is up to slot range allowed for parties... iow, I don't know. ask dev?
IIRC the number of available slots is 1,048,576.

One thing that's bothering me, however, is the number of parties which will be spawned if this code is used extensively. This can considerably slow down try_for_parties operations.

Since the available memory space is quite large, maybe it would be better to implement actual dynamic memory management, something along the lines of FAT. This is what I considered when I was making array-based version of AgentFinder. Then again, with all the invested effort, it's probably better to polish the already existing code instead of re-writing everything from scratch.

I also want to suggest a group of operations for the array which will probably be very useful. Namely, the scripts to copy a range of values from selected array to another entity slots and vice versa. Something along the lines of

(call_script, "script_array_copy_to_agent", ":target_agent_id", ":starting_agent_slot", ":array_id", ":starting_array_slot", ":no_of_elements_to_copy")
(call_script, "script_array_copy_from_item", ":array_id", ":starting_array_slot", ":source_item_id", ":starting_item_slot", ":no_of_elements_to_copy")

Done with consideration for the internal structure of arrays, this will be much more efficient than iterating them one by one, and more secure than letting the library user access them directly.
 
Back
Top Bottom