Common WARP - Functional programming in Warband

Users who are viewing this thread

Autolykos

Regular
Common Warband ARray Processing
Greenspun's tenth rule said:
Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
- This seems to be true for Mount&Blade as well (except that I haven't found any bugs with it yet).

Disclaimer: This may or may not be news, but I couldn't find anything similar here or in any of the guides. OTOH, "functional", "script" and "parameter" are poor keywords for a search, so I might have just missed it.

After a bit of toying around with Warband scripts on the one hand and functional programming on the other, I discovered that scripts are perfectly valid parameters for other scripts, which enables a whole lot of really neat programming tricks. This seems to be unintentional - I have never seen it used that way in the vanilla scripts.
As a proof of concept, I wrote a bunch of scripts that make use of this feature to implement a few simple algorithms (for now, a natural mergesort and array shuffling, but more will follow):

WARNING: Wall of code
For this to work, create a new party called "warp_temp" and copy this code into module_scripts:
Code:
###################################
# Common Warband ARray Processing #
###################################
# Functional programming using Party Slots as arrays (or stacks)
# Parties are the only way to dynamically allocate memory in WB scripts
# Slots are already arrays of integers (or IDs), making them ideal for storage
# Slot zero is reserved for the number of elements

# Part I - Building Blocks
##########################

  # script_warp_array_clear
  # Clears an array by setting its length to zero
  # Input: arg1 = array_id
  # Output: nothing
  ("warp_array_clear",
  [ (store_script_param, ":array_id", 1),
    (party_set_slot, ":array_id", 0, 0),
  ]),

  # script_warp_array_length
  # Returns the length of an array
  # Input: arg1 = array_id
  # Output: reg0 = length
  ("warp_array_length",
  [ (store_script_param, ":array_id", 1),
    (party_get_slot, reg0, ":array_id", 0),
  ]),  

  # script_warp_array_init_value
  # Fills the array with (arg2) repetitions of (arg3)
  # Input: arg1 = array_id
  #        arg2 = length
  #        arg3 = value
  # Output: nothing
  ("warp_array_init_value",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":length", 2),
    (store_script_param, ":value", 3),
	
    (party_set_slot, ":array_id", 0, ":length"),
    (val_add, ":length", 1),
    (try_for_range, ":i", 1, ":length"), 
        (party_set_slot, ":array_id", ":i", ":value"),
    (try_end),
  ]),  
  
  # script_warp_array_init_range
  # Fills the array with the numbers/IDs from (arg2) to (arg3)
  # Input: arg1 = array_id
  #        arg2 = first
  #        arg2 = end
  # Output: nothing
  ("warp_array_init_range",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":first", 2),
    (store_script_param, ":end", 3),
	
    (assign, ":slot", 0),
    (try_for_range, ":i", ":first", ":end"), 
        (val_add, ":slot", 1),
        (party_set_slot, ":array_id", ":slot", ":i"),
    (try_end),
    (party_set_slot, ":array_id", 0, ":slot"),
  ]),  
  
  # script_warp_array_init_random
  # Fills the array with (arg2) random numbers in the range between (arg3) and (arg4)
  # Input: arg1 = array_id
  #        arg2 = length
  #        arg3 = min_value
  #        arg4 = max_value
  # Output: nothing
  ("warp_array_init_random",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":length", 2),
    (store_script_param, ":min", 3),
    (store_script_param, ":max", 4),
	
    (party_set_slot, ":array_id", 0, ":length"),
        (val_add, ":length", 1),
    (try_for_range, ":i", 1, ":length"), 
        (store_random_in_range, ":value", ":min", ":max"),
        (party_set_slot, ":array_id", ":i", ":value"),
    (try_end),
  ]),  
  
  # script_warp_array_push
  # Appends an element to an array
  # Input: arg1 = array_id
  #        arg2 = element
  # Output: nothing
  ("warp_array_push",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":element", 2),
	
    (party_get_slot, ":length", ":array_id", 0),
    (val_add, ":length", 1),
    (party_set_slot, ":array_id", ":length", ":element"),
    (party_set_slot, ":array_id", 0, ":length"),
  ]),
  
  # script_cf_warp_array_pop
  # Removes the last element and writes it to reg0
  # Fails if the array is empty
  # Input: arg1 = array_id
  # Output: reg0 = popped element
  ("cf_warp_array_pop",
  [ (store_script_param, ":array_id", 1),
	
    (party_get_slot, ":length", ":array_id", 0),
    (gt, ":length", 0),
    (party_get_slot, reg0, ":array_id", ":length"),
    (val_sub, ":length", 1),
    (party_set_slot, ":array_id", 0, ":length"),
  ]),
  
  # script_warp_array_remove_last
  # Removes the last element if it exists
  # Input: arg1 = array_id
  # Output: nothing
  ("warp_array_remove_last",
  [ (store_script_param, ":array_id", 1),
	
    (party_get_slot, ":length", ":array_id", 0),
    (val_sub, ":length", 1),
    (val_max, ":length", 0),
    (party_set_slot, ":array_id", 0, ":length"),
  ]),
  
  # script_cf_warp_array_last
  # Writes the last element to reg0
  # Fails if the array is empty
  # Input: arg1 = array_id
  # Output: reg0 = last element
  ("cf_warp_array_last",
  [ (store_script_param, ":array_id", 1),
	
    (party_get_slot, ":length", ":array_id", 0),
    (gt, ":length", 0),
    (party_get_slot, reg0, ":array_id", ":length"),
  ]),
  
  # script_cf_warp_array_set
  # Sets the (arg2)th element to (arg3)
  # Fails if the array is too small
  # Input: arg1 = array_id
  #        arg2 = index
  #        arg3 = value
  # Output: nothing
  ("cf_warp_array_set",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":index", 2),
    (store_script_param, ":value", 3),
	
    (gt, ":index", 0),
    (store_sub, ":index-1", ":index", 1),
    (party_slot_ge, ":array_id", 0, ":index-1"),
    (try_begin),
        (party_slot_eq, ":array_id", 0, ":index-1"),
        (party_set_slot, ":array_id", 0, ":index"),
    (try_end),
    (party_set_slot, ":array_id", ":index", ":value"),
  ]),
  
  # script_cf_warp_array_get
  # Writes the (arg2)ths element to reg0
  # Fails if the index is out of bounds
  # Input: arg1 = array_id
  #        arg2 = index
  # Output: reg0 = value
  ("cf_warp_array_get",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":index", 2),
	
    (gt, ":index", 0),
    (party_slot_ge, ":array_id", 0, ":index"),
    (party_get_slot, reg0, ":array_id", ":index"),
  ]),
  
  # script_warp_array_copy
  # Copies array 1 from array 2
  # Input: arg1 = dest_array
  #        arg2 = src_array
  # Output: nothing
  ("warp_array_copy",
  [ (store_script_param, ":dest_array", 1),
    (store_script_param, ":src_array", 2),
    
    (party_get_slot, ":length", ":src_array", 0),
    (party_set_slot, ":dest_array", 0, ":length"),
    (val_add, ":length", 1),
    (try_for_range, ":i", 1, ":length"), 
        (party_get_slot, ":v", ":src_array", ":i"),
        (party_set_slot, ":dest_array", ":i", ":v"),
    (try_end),
  ]),
    
  # script_cf_warp_array_copy_range
  # Copies the subrange (arg2)..(arg3) from array 1 to array 2
  # Input: arg1 = dest_array
  #        arg2 = src_array
  #        arg3 = first
  #        arg4 = end
  #        
  # Output: nothing
  ("cf_warp_array_copy_range",
  [ (store_script_param, ":dest_array", 1),
    (store_script_param, ":src_array", 4),
    (store_script_param, ":first", 2),
    (store_script_param, ":end", 3),
	
    (store_sub,":last",":end",1),
    (party_slot_ge, ":src_array",0,":last"), # Is the source array long enough?
    (assign, ":j", 0),
    (try_for_range, ":i", ":first", ":end"), 
        (val_add,":j",1), # count the elements written
        (party_get_slot, ":v", ":src_array", ":i"),
        (party_set_slot, ":dest_array", ":j", ":v"),
    (try_end),
    (party_set_slot, ":dest_array", 0, ":j"),
  ]),
  
  # script_warp_array_reverse
  # Reverses the array in place
  # Input: arg1 = array_id
  # Output: nothing
  ("warp_array_reverse",
  [ (store_script_param, ":array_id", 1),
	
    (party_get_slot, ":last", ":array_id", 0),
    (try_for_range, ":i", 1, ":last"), 
        (gt,":last",":i"), # otherwise, we're done
        (party_get_slot, ":v1", ":array_id", ":i"),
        (party_get_slot, ":v2", ":array_id", ":last"),
        (party_set_slot, ":array_id", ":i", ":v2"),
        (party_set_slot, ":array_id", ":last", ":v1"),
        (val_sub,":last",1),
    (try_end),
  ]),
  
  # script_warp_array_filter
  # Removes all elements that fail the check of cf_filter
  # Input: arg1 = array_id
  #        arg2 = cf_filter
  # Output: nothing
  ("warp_array_filter",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":cf_filter", 2),

    (party_get_slot, ":end", ":array_id", 0),
    (val_add, ":end", 1),
    (assign, ":matches", 0),
    (try_for_range, ":i", 1, ":end"), 
        (party_get_slot, ":ce", ":array_id", ":i"),
        (call_script,":cf_filter",":ce"),
        (val_add,":matches",1),
        (gt,":i",":matches"),
        (party_set_slot, ":array_id", ":matches", ":ce"),
    (try_end),
    (party_set_slot, ":array_id", 0, ":matches"),
  ]),
  
  # script_warp_array_map
  # Applies a mapping function (p1->reg0) to every element and writes the result in its place
  # Elements are removed if the mapping function fails
  # Input: arg1 = array_id
  #        arg2 = cf_map
  # Output: nothing
  ("warp_array_map",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":cf_map", 2),
	
    (party_get_slot, ":end", ":array_id", 0),
    (val_add, ":end", 1),
    (assign, ":matches", 0),
    (try_for_range, ":i", 1, ":end"), 
        (party_get_slot, ":ce", ":array_id", ":i"),
        (call_script,":cf_map",":ce"),
        (val_add,":matches",1),
        (party_set_slot, ":array_id", ":matches", reg0),
    (try_end),
    (party_set_slot, ":array_id", 0, ":matches"),
  ]),
  
  # script_warp_array_unique
  # Makes sure the array contains each element only once
  # Input: arg1 = array_id
  # Output: nothing
  ("warp_array_unique",
  [ (store_script_param, ":array_id", 1),
	
    (party_get_slot, ":end", ":array_id", 0),
    (val_add, ":end", 1),
    (assign, ":unique", 0),
    (try_for_range, ":i", 1, ":end"), 
        (party_get_slot, ":ce", ":array_id", ":i"),
        (assign,":found",0),
        (try_for_range, ":j", 1, ":i"),
            (party_slot_eq, ":array_id", ":j", ":ce"),
            (assign,":found",1),
            (assign,":j",":i"),
        (try_end),
        (eq,":found",0),
        (val_add,":unique",1),
        (gt,":i",":unique"),
        (party_set_slot, ":array_id", ":unique", ":ce"),
    (try_end),
    (party_set_slot, ":array_id", 0, ":unique"),
  ]),

  # script_warp_array_sort
  # Sorts the array using natural merge sort and a function cf_order(a,b) that fails iff a,b is the wrong order
  # Input: arg1 = array_id
  #        arg2 = cf_order
  # Output: nothing
  ("warp_array_sort",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":cf_order", 2),
	
    (party_get_slot, ":end", ":array_id", 0),
    (val_add, ":end", 1),
    (call_script,"script_warp_array_sort_range_aux",":array_id","p_warp_temp",1,":end",":cf_order"),
    (try_begin),
        (eq,reg0,"p_warp_temp"),
        (try_for_range, ":i", 1, ":end"),
            (party_get_slot, ":v", "p_warp_temp", ":i"),
            (party_set_slot, ":array_id", ":i", ":v"),
        (try_end),
    (try_end),
  ]),
  
  # script_warp_array_sort_range
  # Sorts only a subrange of the array; otherwise same as "script_warp_array_sort"
  # Input: arg1 = array_id
  #        arg2 = first
  #        arg3 = end
  #        arg4 = cf_order
  # Output: nothing
  ("warp_array_sort_range",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":first", 2),
    (store_script_param, ":end", 3),
    (store_script_param, ":cf_order", 4),
	
    (call_script,"script_warp_array_sort_range_aux",":array_id","p_warp_temp",":first",":end",":cf_order"),
    (try_begin),
        (eq,reg0,"p_warp_temp"),
        (try_for_range, ":i", ":first", ":end"),
            (party_get_slot, ":v", "p_warp_temp", ":i"),
            (party_set_slot, ":array_id", ":i", ":v"),
        (try_end),
    (try_end),
  ]),
  
  # Auxiliary function; recursively performs one round of mergesort from array_1 to array_2 and writes the sorted array_id to reg0 when done
  ("warp_array_sort_range_aux",
  [ (store_script_param, ":array_1", 1),
    (store_script_param, ":array_2", 2),
    (store_script_param, ":first", 3),
    (store_script_param, ":end", 4),
    (store_script_param, ":cf_order", 5),
	
    (assign,":p1",1),
    (assign,":p2",-1),
    (assign,":sorted",1),

    (store_add, ":second", ":first", 1),
    (store_sub, ":last", ":end", 1),
    (try_for_range, ":i", ":second", ":last"),
        (store_sub, ":i-1", ":i", 1),
        (party_get_slot, ":val_a", ":array_1", ":i-1"),
        (party_get_slot, ":val_b", ":array_1", ":i"),
        (try_begin),
            (call_script,":cf_order", ":val_a", ":val_b"),
        (else_try), # Check failed -> wrong order!
            (assign,":sorted",0),
            (try_begin),
                (le,":p2",0),
                (assign,":p2",":i"),
            (else_try),
                (call_script,"script_warp_array_merge_range_aux",":array_1",":array_2",":p1",":p2",":i",":cf_order"),
                (assign,":p1",":i"),
                (assign,":p2",-1),
            (try_end),
        (try_end),
    (try_end),
        (try_begin), # Merge last two runs (if there are)
            (gt,":p2",0),
            (lt,":p2",":end"),
            (call_script,"script_warp_array_merge_range_aux",":array_1",":array_2",":p1",":p2",":end",":cf_order"),
        (else_try), # Or copy the last run (if necessary)
            (lt,":p1",":end"),
            (eq,":sorted",0), # otherwise, just return array_1
            (try_for_range, ":i", ":p1", ":end"),
                (party_get_slot, ":v", ":array_1", ":i"),
                (party_set_slot, ":array_2", ":i", ":v"),
            (try_end),
        (try_end),
        (try_begin),
            (eq,":sorted",1), # Am I done yet?
            (assign,reg0,":array_1"),
        (else_try),
            (call_script,"script_warp_array_sort_range_aux",":array_2",":array_1",":first",":end",":cf_order"),
        (try_end),
    (try_end),
  ]),  
  ("warp_array_merge_range_aux",
  [ (store_script_param, ":array_1", 1),
    (store_script_param, ":array_2", 2),
    (store_script_param, ":pos1", 3),
    (store_script_param, ":pos2", 4),
    (store_script_param, ":pos3", 5),
    (store_script_param, ":cf_order", 6),
	
    (assign,":c1",":pos1"),
    (assign,":c2",":pos2"),
	
    (try_for_range, ":i", ":pos1", ":pos3"), 
        (try_begin),
            (eq,":c1",":pos2"),
            (party_get_slot, ":v", ":array_1", ":c2"),
            (party_set_slot, ":array_2", ":i", ":v"),
            (val_add,":c2",1),
        (else_try),
            (eq,":c2",":pos3"),
            (party_get_slot, ":v", ":array_1", ":c1"),
            (party_set_slot, ":array_2", ":i", ":v"),
            (val_add,":c1",1),
        (else_try),
            (party_get_slot, ":val_a", ":array_1", ":c1"),
            (party_get_slot, ":val_b", ":array_1", ":c2"),
            (try_begin),
                (call_script,":cf_order", ":val_a", ":val_b"),
                (party_get_slot, ":v", ":array_1", ":c1"),
                (party_set_slot, ":array_2", ":i", ":v"),
                (val_add,":c1",1),
            (else_try),
                (party_get_slot, ":v", ":array_1", ":c2"),
                (party_set_slot, ":array_2", ":i", ":v"),
                (val_add,":c2",1),
            (try_end),
        (try_end),
    (try_end),
  ]),  
  
  # script_warp_array_shuffle
  # Shuffles the array (puts all elements in random order)
  # Input: arg1 = array_id
  # Output: nothing
  ("warp_array_shuffle",
  [ (store_script_param, ":array_id", 1),
	
    (party_get_slot, ":end", ":array_id", 0),
    (val_add, ":end", 1),
    (call_script,"script_warp_array_shuffle_range",":array_id",1,":end"),
  ]),
  
  # script_warp_array_shuffle_range
  # Shuffles a subrange of the array
  # Input: arg1 = array_id
  #        arg2 = first
  #        arg3 = end
  # Output: nothing
  ("warp_array_shuffle_range",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":first", 2),
    (store_script_param, ":end", 3),

    (try_for_range, ":i", ":first", ":end"),
        (store_random_in_range,":j",":i",":end"),
        (neq,":i",":j"),
        (party_get_slot, ":vi", ":array_id", ":i"),
        (party_get_slot, ":vj", ":array_id", ":j"),
        (party_set_slot, ":array_id", ":i", ":vj"),
        (party_set_slot, ":array_id", ":j", ":vi"),
    (try_end),
  ]),
  
  # script_warp_print_array
  # Writes an array's content to s3, using a map function and custom separators
  # e.g. (1,4,6,9),"script_warp_roman","@ or ","@ or maybe" -> "I or IV or VI or maybe IX"
  # Input: arg1 = array_id
  # 	   arg2 = script that writes the ID's name to s0 - MUST NOT TOUCH s1, s2 or s3!
  # Set before calling:
  # 	   s1 = middle separator (usually comma or space)
  # 	   s2 = last separator (usually and)
  # Output: s3 = List of strings returned by arg4
  ("warp_print_array",
  [
    (store_script_param, ":array_id", 1),
    (store_script_param, ":script_name", 2),
	
    (party_get_slot, ":array_last", ":array_id", 0),
    (str_clear,s3),
	
    (try_begin),
        (eq,":array_last",1),
        (party_get_slot, ":v", ":array_id", ":array_last"),
        (call_script,":script_name",":v"),
        (str_store_string,s3,"@{s0}"),
    (else_try),
        (ge,":array_last",2),
        (party_get_slot, ":v", ":array_id", 1),
        (call_script,":script_name",":v"),
        (str_store_string,s3,"@{s0}"),
        (try_for_range, ":i", 2, ":array_last"),
            (party_get_slot, ":v", ":array_id", ":i"),
            (call_script,":script_name",":v"),
            (str_store_string,s3,"@{s3}{s1}{s0}"),
        (try_end),
        (party_get_slot, ":v", ":array_id", ":array_last"),
        (call_script,":script_name",":v"),
        (str_store_string,s3,"@{s3}{s2}{s0}"),
    (try_end),
  ]),
  
  # script_warp_print_array_comma
  # Writes an array's content to s1, separated by commas and using a map function
  # e.g. (1,4,6,9),"script_warp_roman" -> "I, IV, VI, IX"
  # Input: arg1 = array_id
  # 	   arg2 = script that writes the ID's name to s0 - MUST NOT TOUCH s1!
  # Output: s1 = List of strings returned by arg4
  ("warp_print_array_comma",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":script_name", 2),
	
    (party_get_slot, ":array_last", ":array_id", 0),
    (store_add,":array_end",":array_last",1),
    (str_clear,s1),
	
    (try_begin),
        (eq,":array_last",1),
        (party_get_slot, ":v", ":array_id", ":array_last"),
        (call_script,":script_name",":v"),
        (str_store_string,s1,"@{s0}"),
    (else_try),
        (ge,":array_last",2),
        (party_get_slot, ":v", ":array_id", 1),
        (call_script,":script_name",":v"),
        (str_store_string,s1,"@{s0}"),
        (try_for_range, ":i", 2, ":array_end"),
            (party_get_slot, ":v", ":array_id", ":i"),
            (call_script,":script_name",":v"),
            (str_store_string,s1,"@{s1}, {s0}"),
        (try_end),
    (try_end),
  ]),
  
  # script_warp_print_array_and
  # Writes an array's content to s1, separated by commas, a final "and" and using a map function
  # e.g. (1,4,6,9),"script_warp_roman" -> "I, IV, VI and IX"
  # Input: arg1 = array_id
  # 	   arg2 = script that writes the ID's name to s0 - MUST NOT TOUCH s1!
  # Output: s1 = List of strings returned by arg4
  ("warp_print_array_and",
  [ (store_script_param, ":array_id", 1),
    (store_script_param, ":script_name", 2),
	
    (party_get_slot, ":array_last", ":array_id", 0),
    (str_clear,s1),
	
    (try_begin),
        (eq,":array_last",1),
        (party_get_slot, ":v", ":array_id", ":array_last"),
        (call_script,":script_name",":v"),
        (str_store_string,s1,"@{s0}"),
    (else_try),
        (ge,":array_last",2),
        (party_get_slot, ":v", ":array_id", 1),
        (call_script,":script_name",":v"),
        (str_store_string,s1,"@{s0}"),
        (try_for_range, ":i", 2, ":array_last"),
            (party_get_slot, ":v", ":array_id", ":i"),
            (call_script,":script_name",":v"),
            (str_store_string,s1,"@{s1}, {s0}"),
        (try_end),
        (party_get_slot, ":v", ":array_id", ":array_last"),
        (call_script,":script_name",":v"),
        (str_store_string,s1,"@{s1} and {s0}"),
    (try_end),
  ]),
  
# Part II - Helper Scripts
##########################
  # script_warp_number
  # Takes a number and writes it to s0
  # Input: arg1 = number
  # Output: s0 = number string
  ("warp_number",
  [ (store_script_param, ":number", 1),
    (assign,reg0,":number"),
    (str_store_string,s0,"@{reg0}"),
  ]),
  # script_warp_roman
  # Takes a number and writes its roman numeral to s0
  # Input: arg1 = number
  # Output: s0 = roman numeral
  ("warp_roman",
  [ (store_script_param, ":number", 1),

    (try_begin),
        (gt,":number",0),
        (str_clear,s0),
        (call_script,"script_warp_roman_aux",":number"),
    (else_try),
        (eq,":number",0),
        (str_store_string,s0,"@0"),
    (else_try),
        (store_sub,":neg_number",0,":number"),
        (str_clear,s0),
        (call_script,"script_warp_roman_aux",":neg_number"),
        (str_store_string,s0,"@-{s0}"),
    (try_end),
  ]),
  # Recursive auxiliary function; requires empty s0 and n>0 at the beginning
  ("warp_roman_aux",
  [ (store_script_param, ":number", 1),

    (try_begin),
        (ge,":number",1000),
        (store_sub,":rest",":number",1000),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@M{s0}"),
    (else_try),
        (ge,":number",900),
        (store_sub,":rest",":number",900),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@CM{s0}"),
    (else_try),
        (ge,":number",500),
        (store_sub,":rest",":number",500),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@D{s0}"),
    (else_try),
        (ge,":number",400),
        (store_sub,":rest",":number",400),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@CD{s0}"),
    (else_try),
        (ge,":number",100),
        (store_sub,":rest",":number",100),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@C{s0}"),
    (else_try),
        (ge,":number",90),
        (store_sub,":rest",":number",90),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@XC{s0}"),
    (else_try),
        (ge,":number",50),
        (store_sub,":rest",":number",50),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@L{s0}"),
    (else_try),
        (ge,":number",40),
        (store_sub,":rest",":number",40),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@XL{s0}"),
    (else_try),
        (ge,":number",10),
        (store_sub,":rest",":number",10),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@X{s0}"),
    (else_try),
        (ge,":number",9),
        (store_sub,":rest",":number",9),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@IX{s0}"),
    (else_try),
        (ge,":number",5),
        (store_sub,":rest",":number",5),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@V{s0}"),
    (else_try),
        (ge,":number",4),
        (store_sub,":rest",":number",4),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@IV{s0}"),
    (else_try),
        (ge,":number",1),
        (store_sub,":rest",":number",1),
        (call_script,"script_warp_roman_aux",":rest"),
        (str_store_string,s0,"@I{s0}"),
    (try_end),
  ]),
# More printing
  ("warp_troop_name",[(store_script_param, ":troop", 1), (str_store_troop_name, s0, ":troop")]),
  ("warp_party_name",[(store_script_param, ":party", 1), (str_store_party_name, s0, ":party")]),
  ("warp_item_name",[(store_script_param, ":item", 1), (str_store_item_name, s0, ":item")]),
# Some basic sorting functions
  ("cf_ascending",
  [ (store_script_param, ":a", 1),
    (store_script_param, ":b", 2),
    (ge,":b",":a"),
  ]),
  ("cf_descending",
  [ (store_script_param, ":a", 1),
    (store_script_param, ":b", 2),
    (ge,":a",":b"),
  ]),

As an example, say you want to print a list of all Swadian lords, sorted by their current renown. You would do it this way:
Code:
(call_script,"script_warp_array_init_range","p_temp_party","trp_knight_1_1","trp_knight_2_1"),
(call_script,"script_warp_array_sort","p_temp_party","script_cf_renown_ge"),
(call_script,"script_warp_print_array_and","p_temp_party","script_warp_troop_name"),
(display_message, "@These lords are the most important: {s1}"),
with a simple helper script:
Code:
  ("cf_renown_ge",
  [ (store_script_param, ":lord_a", 1),
    (store_script_param, ":lord_b", 2),
    (troop_get_slot, ":b_renown", ":lord_b", slot_troop_renown),
    (troop_slot_ge, ":lord_a", slot_troop_renown, ":b_renown"),
  ]),

EDIT: Minor bugfixes, and new functions for reversing arrays and copying subranges.
EDIT2: Some cleanup, and fixed a major bug in warp_array_copy; also changed syntax to be more in line with instructions like str_copy_string_reg (first destination, then source)
 

gdwitt

Knight at Arms
Autolykos,
You could do alot of good work for the mods out there if you were interested.
Many of us are only novice programmers.
Why don't you look around at some of the problems posted on the Forge?
I have trying to create an embedded script that should have been done a long time ago:
Create a list of all the weapons with their item scores.
This is very important because it will accurately predict what the troops will use in battle and is a good monitor of the validity of internal scripts.
Unlike in other games, you don't decide what your troops use in battle.
As it stands now, a troop will use an equipped dagger over a polearm even if you really wanted that troops to use their polearm.
See my thread here:
http://forums.taleworlds.com/index.php?topic=335435.new#new
If you want to see, the code and all its dependencies, click the globe icon beneath my profile picture to get all the BW source code (only 81mb).
 

Autolykos

Regular
Thanks. At the moment I'm kinda busy IRL and use the little spare time I have for toying around with my own ideas, but when I have a bit more leisure I might join a project in need of coders. A first quick answer to your problem is in the other thread...
 

Arch3r

Count
M&BWBNW
This is pretty sweet. Things to make the mess that's called a module system neater, are always welcome.
 

Autolykos

Regular
Update: You can now filter arrays (only keep items matching a specific condition), apply a mapping function (replace each element with the return value of a script), and remove duplicate elements.
 

Swyter

Grandmaster Knight
M&BWBWF&S
Well, this is amazing. Good to see the module system in a comp-sciency light.

The use of callbacks is something I hadn't thought of, good thing they use the same kind of script indexing as variables in their interpreter.
I came here while reviewing a TLD pull request, and what can I say, this is pretty nifty.

M&B modding has always been about overcoming limitations and creatively taking advantage of every bug and feature, and this is a testament to it.
 
Top Bottom