OSP Code Optimisation [WB] Lav's AgentFinder OSP script library v.2.00

Users who are viewing this thread

Lav

Sergeant Knight at Arms
AgentFinder version 2.00 released!

AgentFinder is a code library for tracking agents on the field, allowing developers to iterate through those agents using a number of filters (conditions match, distance from point, contains in rectangle, custom callback match).

This is a Module System alternative to corresponding WSE operation, made as efficient as possible.

The library splits the mission map into a grid of cells. Level of detail can be controlled by developer from mission to mission (so on battle mission you may want a 24x24 grid while for general town visit you are satisfied with 8x8 grid). Each grid cell tracks what agents are located inside, and the library tracks agents movement between cells, spawning and death, updating the grid accordingly.

For version 2.0 data storage model has been converted from arrays to lists. This means there are no longer any limits to number of agents tracked by a single cell, and almost no limit to the number of cells in the grid (up to 500x500 as far as I'm aware).

Download URL #1: http://www.nexusmods.com/mountandblade/mods/3103
Download URL #2: http://www.mbrepository.com/file.php?id=3103

This is a script library which tracks agents on the mission map, allowing you to do the following:

1. Search effectively for all agents within specified radius of any map position, matching custom filter criteria. This is obviously less effective than WSE, but considerably more effective than direct iteration through all agents on the scene.
2. Library splits the map into a grid of smaller cells, each containing a number of agents. Library allows you to access both individual cells and rectangular sets of cells, extracting lists of agents present in those cells, filter those lists with custom filter criteria, counting number of agents matching criteria (when you don't need the list), or extracting an "agent presence" matrix for a cell range, containing the number of agents matching your filter in each cell.

Download URL: http://www.mbrepository.com/file.php?id=3103

Code usage examples:

Code:
(call_script, "script_agent_get_agents_near_self", <agent_no>, <slot_id_to_store_resulting_list>, <radius>, <filter_criteria>),
(call_script, "script_agent_get_agents_near_position", <agent_no>, <slot_id_to_store_resulting_list>, <position>, <radius>, <filter_criteria>),
These two are top-level functions, fully abstracting the internal workings of the library (grid and cell structure of the map etc). You just call them and voila - in the slots of the <agent_no>, starting from the <slot_id>, there's a list of agents who are located in the requested region and match the filter criteria (with list size stored in reg0).

Code:
(call_script, "script_agent_filter_agents_list", <agent_no>, <slot_id_where_list_starts>, <list_length>, <filter_criteria>),
(call_script, "script_agent_filter_agents_list_by_circle", <agent_no>, <slot_id_where_list_starts>, <list_length>, <position>, <radius>),
Once you have a list of agents, you can further filter it either by filter criteria, or by physical location.

Code:
(call_script, "script_agent_get_agents_in_cell", <agent_no>, <slot_id_to_store_resulting_list>, <cell_x>, <cell_y>),
(call_script, "script_agent_get_agents_in_cellrange", <agent_no>, <slot_id_to_store_resulting_list>, <topleft_cell_x>, <topleft_cell_y>, <bottomright_cell_x>, <bottomright_cell_y>),
These functions allow you to get a list of agents in a rectangular area of the map (either a single cell, or a cell range). This list can then be filtered as necessary.

Code:
(call_script, "script_agent_count_agents_in_cell", <agent_no>, <cell_x>, <cell_y>, <filter_criteria>),
(call_script, "script_agent_count_agents_in_cellrange", <agent_no>, <topleft_cell_x>, <topleft_cell_y>, <bottomright_cell_x>, <bottomright_cell_y>, <filter_criteria>),
These functions allow you to calculate the number of agents matching the filter in the specified rectangular area of the map.

Code:
(call_script, "script_agent_get_agents_presence_matrix", <agent_no>, <slot_id_to_store_matrix>, <topleft_cell_x>, <topleft_cell_y>, <bottomright_cell_x>, <bottomright_cell_y>, <filter_criteria>),
Same as above, but this function not only returns the total number of matching agents in the area, but also records a per-cell matrix of how many agents matching the filter are in each respective cell. This function was written with AI in mind. For example, your agent can easily scan a 5x5 area centered on himself, determining number of enemies and friends in each cell, then proceed to estimating his situation.

And the filters. They are quite expandable (this needs additional coding obviously, but nothing extreme).
Code:
afinder_no_filter    = 0
afinder_humans       = 1    # human agents only
afinder_horses       = 2    # horse agents only (includes horses with riders)
afinder_same_team    = 4    # agents from same team only
afinder_friendly     = 8    # agents from same or allied teams
afinder_hostile      = 16   # agents from hostile teams
afinder_footed       = 32   # implies afinder_humans
afinder_mounted      = 64   # implies afinder_humans
afinder_ranged       = 128  # implies afinder_humans
afinder_riderless    = 256  # implies afinder_horses
afinder_exclude_self = 512  # will exclude the agent who is making the search

afinder_horsearchers = afinder_mounted | afinder_ranged
afinder_traitors     = afinder_humans | afinder_same_team | afinder_hostile # No, this doesn't really work. :-)
 
Heh, this is exactly the same way the game uses to keep track of agents (and the one that WSE exploits for its agent finding operation). It's a pity that vanilla scripting compatible solutions have to bring additional overhead to do something that could have been added by devs with very little effort.
Nonetheless, awesome job. Yours is by far one of the most elegant Warband scripts I've ever seen.
 
cmpxchg8b said:
Heh, this is exactly the same way the game uses to keep track of agents (and the one that WSE exploits for its agent finding operation).
Oh. Hopefully Armagan won't sue me. :smile:

cmpxchg8b said:
It's a pity that vanilla scripting compatible solutions have to bring additional overhead to do something that could have been added by devs with very little effort.
This. Just... this.

cmpxchg8b said:
Nonetheless, awesome job. Yours is by far one of the most elegant Warband scripts I've ever seen.
It has to be, if it's ever going to compete with WSE. :smile:

Though honestly, I don't really think WSE makes this script obsolete. At least for single-player mods, where WSE dependency inevitably reduces user base. Ah, wishful thinking... :wink:
 
Nono, you're right... not many mods use WSE and most of them use it only server side. Your script is a great alternative for those who don't want to use it.

Back on topic, something that I noticed in script agentfinder_update_all_agent_positions:
Code:
      (try_for_agents, ":agent_no"),
        (try_begin),
          #(agent_is_alive, ":agent_no"),
          (agent_is_active, ":agent_no"),
          (call_script, "script_agentfinder_update_agent_position", ":agent_no"),
        (else_try),
          (agent_slot_eq, ":agent_no", slot_agent_agentfinder, 1),
          (call_script, "script_agentfinder_remove_agent_from_his_cell", ":agent_no"), # If agent is dead but still tracked, we remove him from processing
        (try_end),
      (try_end),
agent_is_active will only fail if its parameter is not a valid agent id, but try_for_agents already has that check (it will only return valid ids), so in this case agent_is_active will never fail. If your intention is to remove dead agents, I think agent_is_alive is the operation you want (alternatively you could remove agents by adding a ti_on_agent_killed_or_wounded trigger).
 
Using a linked list (each agent in a particular list has slots with ID of previous agent in a list and slot with ID of next agent in a list) might have been slightly more efficient instead of ordinary array since it seems you never need order of agents in the array for any significant role, and all the adding/deleting/reassigning is somewhat easier with a linked list. Not essential though

I did not go deep into the battlemap cell division algo, sorry. Does the cell structure allow for multy-storey maps (3d cell structure)? and if yes how you filter out unnecessary cells
 
GetAssista said:
I did not go deep into the battlemap cell division algo, sorry. Does the cell structure allow for multy-storey maps (3d cell structure)? and if yes how you filter out unnecessary cells
The code stores the array in linearized form (same as all compilers store their multidimensional arrays). Thus, the task of converting the code to 3D is quite simple. However I have difficulties envisioning why on Earth this would be necessary, it's not like we see a lot of 3D action in Warband.

And sorry, but I can't understand what you mean by unnecessary cells. Are you speaking about distance search optimization or about storage effectiveness? There's none of the latter, all cells currently use the same amount of slot memory, it's just that if a cell is empty, it's never accessed.

GetAssista said:
Using a linked list (each agent in a particular list has slots with ID of previous agent in a list and slot with ID of next agent in a list) might have been slightly more efficient instead of ordinary array since it seems you never need order of agents in the array for any significant role, and all the adding/deleting/reassigning is somewhat easier with a linked list. Not essential though
Hmm, this is perfectly possible, and may even provide certain advantages (like infinite scalability) without losing any functionality, so is definitely worth a shot. On the other hand, I spent quite a lot of time debugging my precious arrays, so I'm a bit reluctant to change anything right now. Probably a bit later. :smile:

cmpxchg8b said:
agent_is_active will only fail if its parameter is not a valid agent id, but try_for_agents already has that check (it will only return valid ids), so in this case agent_is_active will never fail. If your intention is to remove dead agents, I think agent_is_alive is the operation you want (alternatively you could remove agents by adding a ti_on_agent_killed_or_wounded trigger).
Oh, so my first version (commented out in the code) was actually correct. Thanks for comment! Fixed and re-uploaded.
 
As I'm currently developing a list-based version, here's my current state of work. It is not yet complete, in fact I haven't tried to compile it yet, even once. And this is definitely NOT the code you should inject into your project. When final version comes, I'll update the first post.

Until that time, comments and suggestions from other fellow coders are welcome.

Note that after converting to lists, the approach to coding changed noticeably. No longer the code generated lists stored in slots (though this is perfectly possible), instead, the code using the library should looks like:

(call_script, "script_af_reset_filters"),
(call_script, "script_af_set_filter", afinder_humans|afinder_hostile),
(call_script, "script_af_set_filter_distance", ":agent_x", ":agent_y", 50000), # 50 meters radius
(call_script, "script_af_iterate_agents", ":current_agent_id", "script_my_callback"),

So you can set several filters (only one filter of each type is available) and then make a call to iterate_agents() which in turn will call your callback script for each agent matching the defined filters.

Cells no longer contain direct references to agents, but instead two lists: one list for active agents and another for inactive (dead or wounded). So the cell now occupies only 4 slots: first slot for cell agent count, second links to first agent in the list, third links to last agent in the list, fourth links to first agent in the dead agents list. Lists of dead agents are not used anywhere in the code, but shouldn't present a problem as their structure is absolutely the same.

There are some other improvements (like dynamic grid size, so you can fine-tune it to your mission, or defineable precision so you don't have to stick to centimeters), but nothing groundbreaking. For easier reading, I removed some background operations which are the same as in array-based version.

Code follows.
Code:
af_cells = "trp_cells"
af_settings = "trp_agentfinder"

# Slot IDs in af_settings follow:
af_setting_grid_size   = 0
af_setting_cell_width  = 1
af_setting_cell_height = 2
af_setting_stack_ptr   = 3

af_filter_condition =  4
af_filter_cell_x1   =  5
af_filter_cell_y1   =  6
af_filter_cell_x2   =  7
af_filter_cell_y2   =  8
af_filter_rect      =  9
af_filter_rect_x1   = 10
af_filter_rect_y1   = 11
af_filter_rect_x2   = 12
af_filter_rect_y2   = 13
af_filter_distance  = 14
af_filter_center_x  = 15
af_filter_center_y  = 16
af_filter_radius    = 17
af_filter_custom    = 18
af_filter_callback  = 19

af_cells_offset = 20 # Keeping in mind the possibility to merge af_cells and af_settings

af_precision = 1000 # Precision up to millimeters, that is 1/af_precision meters

# Cell pseudo-tuple structure follows (4 slots per cell):
so_cell_agents_count = 0
so_cell_first_agent = 1
so_cell_last_agent = 2
so_cell_dead_agents = 3

# Agent slots:
slot_agent_af_processed = 32
slot_agent_af_cell      = 33
slot_agent_af_previous  = 34
slot_agent_af_next      = 35

Code:
#+---------------------------------------------------------------------------------------------------------------------------------+
#| System functions - should not be called directly, some must be called from triggers in mission template                         |
#+---------------------------------------------------------------------------------------------------------------------------------+

  # Must be called in before_mission_start trigger
  ("agentfinder_initialize",
    [
      # Initialize af_settings
      (store_script_param_1, ":grid_size"),
      (get_scene_boundaries, pos2, pos3),
      (position_transform_position_to_local, pos4, pos2, pos3),
      (set_fixed_point_multiplier, af_precision),
      (position_get_x, ":map_width", pos4),
      (position_get_y, ":map_height", pos4),
      (set_fixed_point_multiplier, 1),
      (store_div, ":cell_width", ":map_width", ":grid_size"),
      (store_div, ":cell_height", ":map_height", ":grid_size"),
      (party_set_slot, af_settings, af_setting_grid_size, ":grid_size"),
      (party_set_slot, af_settings, af_setting_cell_width, ":cell_width"),
      (party_set_slot, af_settings, af_setting_cell_height, ":cell_height"),

      # Initialize af_cells
      (store_mul, ":total_cells", ":grid_size", ":grid_size"),
      (assign, reg1, af_cells_offset),
      (try_for_range, reg0, 0, ":total_cells"),
        (troop_set_slot, af_cells, reg1, 0), # No elements in the cell
        (val_add, reg1, 1),
        (troop_set_slot, af_cells, reg1, -1), # No link to first element
        (val_add, reg1, 1),
        (troop_set_slot, af_cells, reg1, -1), # No link to last element
        (val_add, reg1, 1),
        (troop_set_slot, af_cells, reg1, -1), # No link to first dead element
        (val_add, reg1, 1),
      (try_end),

      (troop_set_slot, af_settings, af_setting_stack_ptr, 20),
    ]
  ),

  # Must be called from ti_on_agent_spawn trigger
  ("agentfinder_update_agent",
    [
      (store_script_param, ":agent_no", 1),
      (try_begin),
        (agent_is_active, ":agent_no"),
        (call_script, "script_cell_find_by_agent", ":agent_no"),
        (call_script, "script_agentfinder_add_agent_to_cell", ":agent_no", reg2),
      (else_try),
        (agent_slot_eq, ":agent_no", slot_agent_af_processed, 1),
        (call_script, "script_cell_find_by_agent", ":agent_no"),
        (call_script, "script_agentfinder_add_dead_agent_to_cell", ":agent_no", reg2),
      (try_end),
    ]
  ),

  # Must be called from some regular trigger
  ("agentfinder_update_all_agents",
    [
      (call_script, "script_af_reset_filters"),
      (call_script, "script_af_iterate_agents", 0, "script_agentfinder_update_agent"),
    ]
  ),

  # Should not be called directly
  ("agentfinder_add_agent_to_cell",
    [
      (store_script_param_1, ":agent_no"),
      (store_script_param_2, ":cell_offset"),

      (try_begin),
        # If agent is already in this cell, we don't care
        (neg|agent_slot_eq, ":agent_no", slot_agent_af_cell, ":cell_offset"),

        # If he's still in another, we remove him first
        (try_begin),
          (agent_slot_eq, ":agent_no", slot_agent_af_processed, 1),
          (call_script, "script_agentfinder_remove_agent_from_cell", ":agent_no"),
        (try_end),

        (store_add, ":cell_first", ":cell_offset", so_cell_first_agent),
        (store_add, ":cell_last", ":cell_offset", so_cell_last_agent),
        (troop_get_slot, ":agents_in_cell", af_cells, ":cell_offset"),

        (try_begin),
          (eq, ":agents_in_cell", 0), # cell empty
          (agent_set_slot, ":agent_no", slot_agent_af_previous, -1),
          (troop_set_slot, af_cells, ":cell_first", ":agent_no"),
        (else_try),
          (troop_get_slot, ":previous_agent_no", af_cells, ":cell_last"),
          (agent_set_slot, ":agent_no", slot_agent_af_previous, ":previous_agent_no"),
          (agent_set_slot, ":previous_agent_no", slot_agent_af_next, ":agent_no"),
        (try_end),

        (agent_set_slot, ":agent_no", slot_agent_af_processed, 1),
        (agent_set_slot, ":agent_no", slot_agent_af_cell, ":cell_offset"),
        (agent_set_slot, ":agent_no", slot_agent_af_next, -1),
        (troop_set_slot, af_cells, ":cell_last", ":agent_no"),
        (val_add, ":agents_in_cell", 1),
        (troop_set_slot, af_cells, ":cell_offset", ":agents_in_cell"),

      (try_end),
    ]
  ),

  # Should not be called directly
  ("agentfinder_remove_agent_from_cell",
    [
      (store_script_param_1, ":agent_no"),
      (try_begin),
        (agent_slot_eq, ":agent_no", slot_agent_af_processed, 1),

        (agent_get_slot, ":cell_offset", ":agent_no", slot_agent_af_cell),
        (agent_get_slot, ":previous_agent_no", ":agent_no", slot_agent_af_previous),
        (agent_get_slot, ":next_agent_no", ":agent_no", slot_agent_af_next),
        (store_add, ":cell_first", ":cell_offset", so_cell_first_agent),
        (store_add, ":cell_last", ":cell_offset", so_cell_last_agent),
        #(store_add, ":cell_dead", ":cell_offset", so_cell_dead_agent),
        (troop_get_slot, ":agents_in_cell", af_cells, ":cell_offset"),

        (try_begin),
          (eq, ":previous_agent_no", -1),
          (troop_set_slot, af_cells, ":cell_first", ":next_agent_no"),
        (else_try),
          (agent_set_slot, ":previous_agent_no", slot_agent_af_next, ":next_agent_no"),
        (try_end),

        (try_begin),
          (eq, ":next_agent_no", -1),
          (troop_set_slot, af_cells, ":cell_last", ":previous_agent_no"),
        (else_try),
          (agent_set_slot, ":next_agent_no", slot_agent_af_previous, ":previous_agent_no"),
        (try_end),

        (val_sub, ":agents_in_cell", 1),
        (troop_set_slot, af_cells, ":cell_offset", ":agents_in_cell"),
        (agent_set_slot, ":agent_no", slot_agent_af_processed, 0),
      (try_end),
    ]
  ),

  # Should not be called directly
  ("agentfinder_add_dead_agent_to_cell",
    [
      (store_script_param_1, ":agent_no"),
      (store_script_param_2, ":cell_offset"),

      (try_begin),
        # If he's still processed, we remove him first
        (try_begin),
          (agent_slot_eq, ":agent_no", slot_agent_af_processed, 1),
          (call_script, "script_agentfinder_remove_agent_from_cell", ":agent_no"),
        (try_end),

        (store_add, ":cell_dead", ":cell_offset", so_cell_dead_agent),

        (try_begin),
          (troop_slot_eq, af_cells, ":cell_dead", -1), # no dead agents yet
          (agent_set_slot, ":agent_no", slot_agent_af_next, -1),
        (else_try),
          (troop_get_slot, ":next_agent_no", af_cells, ":cell_dead"),
          (agent_set_slot, ":agent_no", slot_agent_af_next, ":next_agent_no"),
          (agent_set_slot, ":next_agent_no", slot_agent_af_previous, ":agent_no"),
        (try_end),

        (agent_set_slot, ":agent_no", slot_agent_af_cell, ":cell_offset"),
        (agent_set_slot, ":agent_no", slot_agent_af_previous, -1),
        (troop_set_slot, af_cells, ":cell_dead", ":agent_no"),

      (try_end),
    ]
  ),

#+---------------------------------------------------------------------------------------------------------------------------------+
#| Filtering Operations - applying filters to lists                                                                                |
#+---------------------------------------------------------------------------------------------------------------------------------+

  # script_cf_agentfinder_filter_condition
  # INPUT : arg1 = <searching_agent_no>, arg2 = <found_agent_no>, arg3 = condition code (see module_constants.py for "afinder_*" constants)
  # OUTPUT: none (fails or succeeds)
  # NOTES : This script checks if found agent matches our search criteria. If you want other search criterias, add their code to constants and expand this script appropriately.

  ("cf_agentfinder_filter_condition",
    [
      (store_script_param, ":agent_no", 1),
      (store_script_param, ":found_agent_no", 2),
      (store_script_param, ":filter_condition", 3),
      (agent_get_troop_id, ":troop_id", ":found_agent_no"),
      (assign, ":filter_failed", 0),
      (try_begin),
        (ge, ":filter_condition", afinder_exclude_self),
        (val_sub, ":filter_condition", afinder_exclude_self),
        (eq, ":agent_no", ":found_agent_no"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_riderless),
        (val_sub, ":filter_condition", afinder_riderless),
        (eq, ":filter_failed", 0),
        (agent_get_rider, ":rider_id", ":found_agent_no"),
        (this_or_next|ge, ":rider_id", 0),
        (agent_is_human, ":found_agent_no"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_ranged),
        (val_sub, ":filter_condition", afinder_ranged),
        (eq, ":filter_failed", 0),
        (neg|troop_is_guarantee_ranged, ":troop_id"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_mounted),
        (val_sub, ":filter_condition", afinder_mounted),
        (agent_get_horse, ":horse_id", ":found_agent_no"),
        (this_or_next|lt, ":horse_id", 0),
        (neg|agent_is_human, ":found_agent_no"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_footed),
        (val_sub, ":filter_condition", afinder_footed),
        (eq, ":filter_failed", 0),
        (agent_get_horse, ":horse_id", ":found_agent_no"),
        (this_or_next|ge, ":horse_id", 0),
        (neg|agent_is_human, ":found_agent_no"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_hostile),
        (val_sub, ":filter_condition", afinder_hostile),
        (eq, ":filter_failed", 0),
        (agent_get_team, ":team1", ":agent_no"),
        (agent_get_team, ":team2", ":found_agent_no"),
        (this_or_next|eq, ":team1", ":team2"),
        (neg|teams_are_enemies, ":team1", ":team2"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_friendly),
        (val_sub, ":filter_condition", afinder_friendly),
        (eq, ":filter_failed", 0),
        (agent_get_team, ":team1", ":agent_no"),
        (agent_get_team, ":team2", ":found_agent_no"),
        (teams_are_enemies, ":team1", ":team2"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_same_team),
        (val_sub, ":filter_condition", afinder_same_team),
        (eq, ":filter_failed", 0),
        (agent_get_team, ":team1", ":agent_no"),
        (agent_get_team, ":team2", ":found_agent_no"),
        (neq, ":team1", ":team2"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_horses),
        (val_sub, ":filter_condition", afinder_horses),
        (eq, ":filter_failed", 0),
        (agent_is_human, ":found_agent_no"),
        (assign, ":filter_failed", 1),
      (try_end),
      (try_begin),
        (ge, ":filter_condition", afinder_humans),
        (val_sub, ":filter_condition", afinder_humans),
        (eq, ":filter_failed", 0),
        (neg|agent_is_human, ":found_agent_no"),
        (assign, ":filter_failed", 1),
      (try_end),
      (eq, ":filter_condition", 0), # filter_condition must be zero after processing, if it's not then an illegal value was passed to the script
      (eq, ":filter_failed", 0),
    ]
  ),


  # script_cf_agentfinder_filter_circle
  # INPUT : arg1 = <searching_agent_no>, arg2 = <found_agent_no>, arg3 = condition code (see module_constants.py for "afinder_*" constants)
  # OUTPUT: none (fails or succeeds)
  # NOTES :

  ("cf_agentfinder_filter_circle",
    [
      (store_script_param, ":agent_no", 1),
      (store_script_param, ":position", 2),
      (store_script_param, ":radius", 3),
      (copy_position, pos60, ":position"),
      (store_mul, ":sq_radius", ":radius", ":radius"),
      (agent_get_position, pos61, ":agent_no"),
      (get_sq_distance_between_positions, ":actual_distance", pos60, pos61),
      (val_mul, ":actual_distance", af_precision*af_precision//100), # Because contrary to documentation claims, this returns squared distance in DECImeters, not CENTImeters.
      (le, ":actual_distance", ":sq_radius"),
    ]
  ),

  ("cf_agentfinder_filter_rect",
    [
      (store_script_param, ":x", 1),
      (store_script_param, ":y", 2),
      (store_script_param, ":x1", 3),
      (store_script_param, ":y1", 4),
      (store_script_param, ":x2", 5),
      (store_script_param, ":y2", 6),
      (ge, ":x", ":x1"),
      (le, ":x", ":x2"),
      (ge, ":y", ":y1"),
      (le, ":y", ":y2"),
    ]
  ),

#+---------------------------------------------------------------------------------------------------------------------------------+
#| Set Filter Operations - setting filters for use in subsequent calls to iterate() and get_list()                                 |
#+---------------------------------------------------------------------------------------------------------------------------------+

  ("af_reset_filters",
    [
      (troop_get_slot, ":max_cell", af_settings, af_setting_grid_size),
      (val_sub, ":max_cell", 1),
      (troop_set_slot, af_settings, af_filter_condition, afinder_no_filter),
      (troop_set_slot, af_settings, af_filter_cell_x1, 0),
      (troop_set_slot, af_settings, af_filter_cell_y1, 0),
      (troop_set_slot, af_settings, af_filter_cell_x2, ":max_cell"),
      (troop_set_slot, af_settings, af_filter_cell_y2, ":max_cell"),
      (troop_set_slot, af_settings, af_filter_rect, 0),
      (troop_set_slot, af_settings, af_filter_distance, 0),
      (troop_set_slot, af_settings, af_filter_custom, 0),
    ]
  ),

  ("af_set_filter_cellrange",
    [
      (store_script_param, ":x1", 1),
      (store_script_param, ":y1", 2),
      (store_script_param, ":x2", 3),
      (store_script_param, ":y2", 4),
      (troop_get_slot, ":cur_x1", af_settings, af_filter_cell_x1),
      (troop_get_slot, ":cur_y1", af_settings, af_filter_cell_y1),
      (troop_get_slot, ":cur_x2", af_settings, af_filter_cell_x2),
      (troop_get_slot, ":cur_y2", af_settings, af_filter_cell_y2),
      (val_min, ":x1", ":cur_x1"),
      (val_min, ":y1", ":cur_y1"),
      (val_min, ":x2", ":cur_x2"),
      (val_min, ":y2", ":cur_y2"),
      (troop_set_slot, af_settings, af_filter_cell_x1, ":x1"),
      (troop_set_slot, af_settings, af_filter_cell_y1, ":y1"),
      (troop_set_slot, af_settings, af_filter_cell_x2, ":x2"),
      (troop_set_slot, af_settings, af_filter_cell_y2, ":y2"),
    ]
  ),

  ("af_set_filter",
    [
      (store_script_param, ":filter", 1),
      (troop_set_slot, af_settings, af_filter_condition, ":filter"),
    ]
  ),

  ("af_set_filter_rect",
    [
      (store_script_param, ":x1", 1),
      (store_script_param, ":y1", 2),
      (store_script_param, ":x2", 3),
      (store_script_param, ":y2", 4),
      (troop_set_slot, af_settings, af_filter_rect, 1),
      (troop_set_slot, af_settings, af_filter_rect_x1, ":x1"),
      (troop_set_slot, af_settings, af_filter_rect_y1, ":y1"),
      (troop_set_slot, af_settings, af_filter_rect_x2, ":x2"),
      (troop_set_slot, af_settings, af_filter_rect_y2, ":y2"),
      (call_script, "script_cell_find_by_coordinates", ":x2", ":y2"),
      (assign, reg3, reg0),
      (assign, reg4, reg1),
      (assign, reg5, reg2),
      (call_script, "script_cell_find_by_coordinates", ":x1", ":y1"),
      (call_script, "script_af_set_filter_cellrange", reg0, reg1, reg3, reg4),
    ]
  ),

  ("af_set_filter_distance",
    [
      (store_script_param, ":x", 1),
      (store_script_param, ":y", 2),
      (store_script_param, ":radius", 3),
      (troop_set_slot, af_settings, af_filter_distance, 1),
      (troop_set_slot, af_settings, af_filter_center_x, ":x"),
      (troop_set_slot, af_settings, af_filter_center_y, ":y"),
      (troop_set_slot, af_settings, af_filter_radius, ":radius"),
      (store_sub, ":x1", ":x", ":radius"),
      (store_sub, ":y1", ":y", ":radius"),
      (store_add, ":x2", ":x", ":radius"),
      (store_add, ":y2", ":y", ":radius"),
      (call_script, "script_cell_find_by_coordinates", ":x2", ":y2"),
      (assign, reg3, reg0),
      (assign, reg4, reg1),
      (assign, reg5, reg2),
      (call_script, "script_cell_find_by_coordinates", ":x1", ":y1"),
      (call_script, "script_af_set_filter_cellrange", reg0, reg1, reg3, reg4),
    ]
  ),

  ("af_set_filter_custom",
    [
      (store_script_param, ":callback", 1),
      (troop_set_slot, af_settings, af_filter_custom, 1),
      (troop_set_slot, af_settings, af_filter_callback, ":callback"),
    ]
  ),

#+---------------------------------------------------------------------------------------------------------------------------------+
#| Actual operations to iterate or get agents list (latter not implemented yet)                                                    |
#+---------------------------------------------------------------------------------------------------------------------------------+

  #
  ("af_iterate_agents",
    [
      # Loading starting data
      (store_script_param, ":searching_agent_no", 1),
      (store_script_param, ":callback", 2),

      # Loading current filters:

      # Cellrange filter
      (troop_get_slot, ":cell_x1", af_settings, af_filter_cell_x1),
      (troop_get_slot, ":cell_y1", af_settings, af_filter_cell_y1),
      (troop_get_slot, ":cell_x2", af_settings, af_filter_cell_x2),
      (troop_get_slot, ":cell_y2", af_settings, af_filter_cell_y2),
      (val_add, ":cell_x2", 1),
      (val_add, ":cell_y2", 1),

      # Rectangle filter
      (troop_get_slot, ":check_rect", af_settings, af_filter_rect),
      (try_begin),
        (eq, ":check_rect", 1),
        (troop_get_slot, ":rect_x1", af_settings, af_filter_rect_x1),
        (troop_get_slot, ":rect_y1", af_settings, af_filter_rect_y1),
        (troop_get_slot, ":rect_x2", af_settings, af_filter_rect_x2),
        (troop_get_slot, ":rect_y2", af_settings, af_filter_rect_y2),
      (try_end),

      # Distance filter
      (troop_get_slot, ":check_distance", af_settings, af_filter_distance),
      (try_begin),
        (eq, ":check_distance", 1),
        (troop_get_slot, ":center_x", af_settings, af_filter_center_x),
        (troop_get_slot, ":center_y", af_settings, af_filter_center_y),
        (troop_get_slot, ":radius", af_settings, af_filter_radius),
        (store_mul, ":radius_sq", ":radius", ":radius"),
      (try_end),

      # Standard and custom conditional filters
      (troop_get_slot, ":filter", af_settings, af_filter_condition),
      (troop_get_slot, ":check_custom", af_settings, af_filter_custom),
      (troop_get_slot, ":custom_callback", af_settings, af_filter_callback),

      (troop_get_slot, ":grid_size", af_settings, af_setting_grid_size),
      (set_fixed_point_multiplier, af_precision),

      (try_for_range, ":cell_y", ":cell_y1", ":cell_y2"),
        (ge, ":cell_y", 0),
        (lt, ":cell_y", ":grid_size"),
        (try_for_range, ":cell_x", ":cell_x1", ":cell_x2"),
          (ge, ":cell_x", 0),
          (lt, ":cell_x", ":grid_size"),
          (call_script, "script_get_cell_offset", ":cell_x", ":cell_y"),
          (assign, ":cell_offset", reg0),
          (call_script, "script_agentfinder_iterate_in_cell_with_filter", reg0, ":filter", ":callback"),
          (troop_slot_ge, af_cells, ":cell_offset", 1),
          (store_add, ":cell_first", ":cell_offset", 1),
          (troop_get_slot, ":agents_in_cell", af_cells, ":cell_offset"),
          (troop_get_slot, ":agent_no", af_cells, ":cell_first"),
          (try_for_range, ":cell_offset", 0, ":agents_in_cell"),
            (ge, ":agent_no", 0),

            # Now we check found agent_no for all currently defined filters.
            # For optimization, checks are performed in the following order:
            #   cellrange (already done by bounding code), rect, filter, custom_filter, distance
            (assign, ":filter_failed", 0),
            (agent_get_position, pos60, ":agent_no"),
            (position_get_x, ":x", pos60),
            (position_get_y, ":y", pos60),

            # Rectangle filter
            (try_begin),
              (eq, ":check_rect", 1),
              (lt, ":x", ":rect_x1"),
              (lt, ":y", ":rect_y1"),
              (gt, ":x", ":rect_x2"),
              (gt, ":y", ":rect_y2"),
              (assign, ":filter_failed", 1),
            (try_end),

            # Conditional filter
            (try_begin),
              (call_script, "script_cf_agentfinder_filter", ":searching_agent_no", ":agent_no", ":filter"),
            (else_try),
              (assign, ":filter_failed", 1),
            (try_end),

            # Custom filter
            (try_begin),
              (eq, ":check_custom", 1),
              (try_begin),
                (call_script, ":custom_callback", ":agent_no"),
              (else_try),
                (assign, ":filter_failed", 1),
              (try_end),
            (try_end),

            # Distance filter
            (try_begin),
              (eq, ":check_distance", 1),
              (val_sub, ":x", ":center_x"),
              (val_mul, ":x", ":x"),
              (val_sub, ":y", ":center_y"),
              (val_mul, ":y", ":y"),
              (store_add, ":distance_sq", ":x", ":y"),
              (gt, ":distance_sq", ":radius_sq"),
              (assign, ":filter_failed", 1),
            (try_end),

            # Finally if all filters passed, we perform the requested callback
            (try_begin),
              (eq, ":filter_failed", 0),
              (call_script, ":callback", ":agent_no"),
            (try_end),

            (troop_get_slot, ":agent_no", ":agent_no", slot_agent_af_next),
          (try_end),
        (try_end),
      (try_end),
      (set_fixed_point_multiplier, 1),
    ]
  ),
 
Version 2.0 released, see first post.

I have removed lists generation and filtering for now, currently only script_iterate_agents is available. List-generating code may be recreated using script_iterate_agents with appropriate callback.

Data storage model has been converted to lists. Now the grid may be as large as 500x500 and the number of agents per cell is unlimited. Also the library now tracks dead agents, maintaining a separate list of dead agents per cell, though no support is currently provided to iterate through this list.

I'm currently planning to release a PartyFinder version of this library by a personal request. This library will work on the global map and process parties instead of agents, but otherwise will be exactly the same. Further improvement for AgentFinder library is planned, but is not currently a high-priority task, so if anyone wishes to contribute, this is perfectly welcome.
 
:S...

the compiler says it Error no object: agent_get_agents_near_self or smthing. And i searched module_scripts.py and there really was no agent_get_agents_near_self.

Though I'm quite sure this is a stupid mistake i made some how...
 
AgentFinder had been seriously refactored. Function you are trying to use belongs to the first verison of the library.
 
Interesting code but now it's not needed because try_for_agents has radius. And lists of agents like horsearchers, footers, riders and etc. not implemented. It is not finished. But the method to create lists of agents is greate. I will use it. Thanks for that.
 
Interesting code but now it's not needed because try_for_agents has radius. And lists of agents like horsearchers, footers, riders and etc. not implemented. It is not finished. But the method to create lists of agents is greate. I will use it. Thanks for that.
Keep in mind that this is older than eight years by now. Some operations have been expanded since that time and new operations have been added.
 
Back
Top Bottom