OSP Code SP [WB] Order: Skirmish Mode

Users who are viewing this thread

The following code allows the player to give more nuanced instructions to his/her troops about their behavior in battle. Particularly useful for ranged units, archers and horse archers, it adds a "skirmish mode" where by troops will work to maintain a set distance from the enemy, while concurrently attempting to follow Natively-given orders. (The general principle could be applied to AIs as well, but has not been here.)

The order is given to divisions/groups of troops as Native orders and will scroll on screen as native orders do. (This has not, however been worked into the panel with the map.) It also works in conjunction with other standing orders. If a group is ordered to charge but also to skirmish, the troops of that group will charge until they reach a minimum distance--customizable via Constants and pre-set to 15 meters--at which point they will retreat until they reach a safe distance--also customizable and pre-set to 25 meters--where they will resume their standing order, closing on the enemy again. This may provide better results for horse archers and the like. Similarly if the group is ordered to hold position and that position is encroached on by the enemy, they will not stand to be slaughtered, but retreat to a safe distance and then attempt to return to the position; useful for foot archers. Skirmishing ceases to happen near the map boundaries to prevent troops from routing accidentally.

This should be compatible with most any other code bits/mods/what have you, as it does not add any slots or globals nor does it monkey with the original source code. (It uses party slots assigned for centers--see the Constants code--to track orders to battle divisions; as these are unused for mobile parties, there is no conflict.) The only compatibility issue will be to find a suitable, unused key for the order. Currently, it is set to use F7. Give it once and your selected divisions will be ordered to "Avoid Melee". Tap the key again with groups already ordered to skirmish selected and they will be ordered to "Stand and Fight". Giving the skirmish order to a mixed set of groups (some in skirmish mode, others not) will order all selected groups to skirmish.

EXAMPLE VIDEOS
They aren't the highest quality...but quickly done they give a taste of how it works (aka "OK", not perfect).
[+]Foot Archer Skirmish
[+]Horse Archers Skirmishing

Code:
  
  # script_cf_order_skirmish_check
  # Input: Nothing
  # Output: Nothing    
  # Check for an active Skirmish Order, in lieu of global variables
("cf_order_skirmish_check", [ 
   (get_player_agent_no, ":player"),
   (agent_get_party_id, ":player_party", ":player"),
   
   (assign, ":skirmish", 0),
   (try_for_range, ":i", slot_party_skirmish_d0, slot_party_skirmish_d8 + 1),
         (party_slot_eq, ":player_party", ":i", 1),
         (assign, ":skirmish", 1),
   (try_end),
   (eq, ":skirmish", 1),
    ]), 
Code:
  # script_order_skirmish_begin_end
  # Input: Nothing
  # Output: Nothing
  # On key depression, determine if beginning or ending skirmish
  # If ending, stop any retreating. If beginning, call order_skirmish_skirmish
  # Display appropriate order text on screen.
("order_skirmish_begin_end", [ 
     (get_player_agent_no, ":player"),
     (agent_get_team, ":playerteam", ":player"),
     (agent_get_party_id, ":player_party", ":player"),
     
     (assign, ":skirmish", 0),
     
     (try_for_range, ":class", 0, 8),
          (class_is_listening_order, ":playerteam", ":class"), #Listening to Order
         (store_add, ":class_ordered", ":class", slot_party_skirmish_d0),
         (party_slot_eq, ":player_party", ":class_ordered", 0), 
         (party_set_slot, ":player_party", ":class_ordered", 1),
         (assign, ":skirmish", 1),
         (str_store_string, s1, "@avoid melee"),
             (try_begin), #Mark class as selected--used for display text
                 (eq, ":class", 0),
                 (assign, ":group0_is_selected", 1),
             (else_try),
                 (eq, ":class", 1),
                 (assign, ":group1_is_selected", 1),   
             (else_try),
                 (eq, ":class", 2),
                 (assign, ":group2_is_selected", 1),         
             (else_try),
                 (eq, ":class", 3),
                 (assign, ":group3_is_selected", 1),         
             (else_try),
                 (eq, ":class", 4),
                 (assign, ":group4_is_selected", 1),         
             (else_try),
                 (eq, ":class", 5),
                 (assign, ":group5_is_selected", 1),   
             (else_try),
                 (eq, ":class", 6),
                 (assign, ":group6_is_selected", 1),         
             (else_try),
                 (eq, ":class", 7),
                 (assign, ":group7_is_selected", 1),         
             (else_try),
                 (eq, ":class", 8),
                 (assign, ":group8_is_selected", 1),                       
             (try_end),
     (try_end), #Class Loop
       
     (try_for_agents, ":agent"),
         (agent_is_alive, ":agent"),
         (agent_is_human, ":agent"),
         (agent_is_non_player, ":agent"),
         (agent_get_team, ":team", ":agent"),
         (eq, ":team", ":playerteam"), #On Player's side?
         (agent_get_division, ":class", ":agent"),
         (try_begin), #Mark class as in battle--used for display text
             (eq, ":class", 0),
             (assign, ":group0_in_battle", 1),
         (else_try),
             (eq, ":class", 1),
             (assign, ":group1_in_battle", 1),   
         (else_try),
             (eq, ":class", 2),
             (assign, ":group2_in_battle", 1),         
         (else_try),
             (eq, ":class", 3),
             (assign, ":group3_in_battle", 1),         
         (else_try),
             (eq, ":class", 4),
             (assign, ":group4_in_battle", 1),         
         (else_try),
             (eq, ":class", 5),
             (assign, ":group5_in_battle", 1),   
         (else_try),
             (eq, ":class", 6),
             (assign, ":group6_in_battle", 1),         
         (else_try),
             (eq, ":class", 7),
             (assign, ":group7_in_battle", 1),         
         (else_try),
             (eq, ":class", 8),
             (assign, ":group8_in_battle", 1),                       
         (try_end),
         (class_is_listening_order, ":team", ":class"), #Is the agent's division selected?
         (eq, ":skirmish", 0),
         (store_add, ":class_ordered", ":class", slot_party_skirmish_d0),
         (party_set_slot, ":player_party", ":class_ordered", 0),
         (try_begin),
             (agent_slot_eq, ":agent", slot_agent_is_running_away, 0), #Is not routing or ordered to retreat         
             (agent_stop_running_away, ":agent"),
         (try_end),
         (str_store_string, s1, "@stand and fight"),
             (try_begin), #Mark class as selected--used for display text
                 (eq, ":class", 0),
                 (assign, ":group0_is_selected", 1),
             (else_try),
                 (eq, ":class", 1),
                 (assign, ":group1_is_selected", 1),   
             (else_try),
                 (eq, ":class", 2),
                 (assign, ":group2_is_selected", 1),         
             (else_try),
                 (eq, ":class", 3),
                 (assign, ":group3_is_selected", 1),         
             (else_try),
                 (eq, ":class", 4),
                 (assign, ":group4_is_selected", 1),         
             (else_try),
                 (eq, ":class", 5),
                 (assign, ":group5_is_selected", 1),   
             (else_try),
                 (eq, ":class", 6),
                 (assign, ":group6_is_selected", 1),         
             (else_try),
                 (eq, ":class", 7),
                 (assign, ":group7_is_selected", 1),         
             (else_try),
                 (eq, ":class", 8),
                 (assign, ":group8_is_selected", 1),                       
             (try_end),
     (try_end), #Agent loop
          
     (str_clear, s2),
     (str_clear, s3),
     (assign, ":count_possible", 0),
     (assign, ":count_selected", 0),
     (try_begin),
         (eq, ":group0_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group0_is_selected", 1),
         (val_add, ":count_selected", 1),
         (str_store_class_name, s2, 0),
     (try_end),
     (try_begin),
          (eq, ":group1_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group1_is_selected", 1),
         (val_add, ":count_selected", 1),
         (try_begin),
             (neg|str_is_empty, s2),
             (str_store_class_name, s3, 1),
             (str_store_string, s2, "@{!}{s2}, {s3}"),
         (else_try),
             (str_store_class_name, s2, 1),
         (try_end),
     (try_end),
     (try_begin),
          (eq, ":group2_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group2_is_selected", 1),
         (val_add, ":count_selected", 1),
         (try_begin),
             (neg|str_is_empty, s2),
             (str_store_class_name, s3, 2),
             (str_store_string, s2, "@{!}{s2}, {s3}"),
         (else_try),
             (str_store_class_name, s2, 2),
         (try_end),
     (try_end),
     (try_begin),
          (eq, ":group3_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group3_is_selected", 1),
         (val_add, ":count_selected", 1),
         (try_begin),
             (neg|str_is_empty, s2),
             (str_store_class_name, s3, 3),
             (str_store_string, s2, "@{!}{s2}, {s3}"),
         (else_try),
             (str_store_class_name, s2, 3),
         (try_end),
     (try_end),
     (try_begin),
          (eq, ":group4_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group4_is_selected", 1),
         (val_add, ":count_selected", 1),
         (try_begin),
             (neg|str_is_empty, s2),
             (str_store_class_name, s3, 4),
             (str_store_string, s2, "@{!}{s2}, {s3}"),
         (else_try),
             (str_store_class_name, s2, 4),
         (try_end),
     (try_end),
     (try_begin),
          (eq, ":group5_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group5_is_selected", 1),
         (val_add, ":count_selected", 1),
         (try_begin),
             (neg|str_is_empty, s2),
             (str_store_class_name, s3, 5),
             (str_store_string, s2, "@{!}{s2}, {s3}"),
         (else_try),
             (str_store_class_name, s2, 5),
         (try_end),
     (try_end),
     (try_begin),
          (eq, ":group6_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group6_is_selected", 1),
         (val_add, ":count_selected", 1),
         (try_begin),
             (neg|str_is_empty, s2),
             (str_store_class_name, s3, 6),
             (str_store_string, s2, "@{!}{s2}, {s3}"),
         (else_try),
             (str_store_class_name, s2, 6),
         (try_end),
     (try_end),
     (try_begin),
          (eq, ":group7_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group7_is_selected", 1),
         (val_add, ":count_selected", 1),
         (try_begin),   
             (neg|str_is_empty, s2),
             (str_store_class_name, s3, 7),
             (str_store_string, s2, "@{!}{s2}, {s3}"),
         (else_try),
             (str_store_class_name, s2, 7),
         (try_end),
     (try_end),
     (try_begin),
          (eq, ":group8_in_battle", 1),
         (val_add, ":count_possible", 1),
         (eq, ":group8_is_selected", 1),
         (val_add, ":count_selected", 1),
         (try_begin),
             (neg|str_is_empty, s2),
             (str_store_class_name, s3, 8),
             (str_store_string, s2, "@{!}{s2}, {s3}"),
         (else_try),
             (str_store_class_name, s2, 8),
         (try_end),
     (try_end),    
     (try_begin),
         (eq, ":count_selected", ":count_possible"),
         (str_store_string, s2, "@Everyone"),
     (try_end),
     (try_begin),
         (gt, ":count_selected", 0),
         (display_message, "@{!}{s2}, {s1}!", 0xFFDDDD66),
     (try_end),
     (str_clear, s2),
     (str_clear, s3),
     (try_begin),
         (eq, ":skirmish", 1),
         (call_script, "script_order_skirmish_skirmish"),
     (try_end),     
    ]),
Code:
  # script_order_skirmish_skirmish
  # Input: Nothing 
  # Output: Nothing
  # Cycle through agents, checking and maintaining distance
("order_skirmish_skirmish", [ 
     (get_player_agent_no, ":player"),
     (agent_get_team, ":playerteam", ":player"),
     (agent_get_party_id, ":player_party", ":player"),
     
     (set_fixed_point_multiplier, 1),
     (get_scene_boundaries, pos2, pos3),
     (position_get_x, ":bound_right", pos2),
     (position_get_y, ":bound_top", pos2),
     (position_get_x, ":bound_left", pos3),
     (position_get_y, ":bound_bottom", pos3),
     
     (try_for_agents, ":agent"),
         (agent_is_alive, ":agent"),
         (agent_is_human, ":agent"),
         (agent_is_non_player, ":agent"),
         (agent_get_team, ":team", ":agent"),
         (eq, ":team", ":playerteam"), #On Player's side?
         (agent_slot_eq, ":agent", slot_agent_is_running_away, 0), #Is not routing or ordered to retreat
         (agent_get_division, ":class", ":agent"),
         (store_add, ":ordered_class", ":class", slot_party_skirmish_d0),
         (party_slot_eq, ":player_party", ":ordered_class", 1), #Division is skirmishing
         
         (agent_get_position, pos1, ":agent"),   
         (position_get_x, ":agent_x", pos1),
         (position_get_y, ":agent_y", pos1),
         (store_sub, ":dist_right", ":agent_x", ":bound_right"),
         (store_sub, ":dist_top", ":agent_y", ":bound_top"),
         (store_sub, ":dist_left", ":bound_left", ":agent_x"),
         (store_sub, ":dist_bottom", ":bound_bottom", ":agent_y"),
         (try_begin), #If agent is too close to edge of map, stop skirmishing. Will resume when back into map
             (this_or_next|le, ":dist_right", 20),  #Limits accidental routing, of cav in particular
             (this_or_next|le, ":dist_top", 20),
             (this_or_next|le, ":dist_left", 20),
             (le, ":dist_bottom", 20),
             (agent_stop_running_away, ":agent"),
         (else_try),
             (call_script, "script_get_closest3_distance_of_enemies_at_pos1", ":team", pos1), # Find distance of nearest 3 enemies
             (assign, ":avg_dist", reg0),
             (assign, ":closest_dist", reg1),
             (try_begin),
                 (this_or_next|lt, ":avg_dist", skirmish_min_distance),
                 (lt, ":closest_dist", 700), #If enemy group is getting near or an enemy is on top of agent
                 (agent_start_running_away, ":agent"),         
             (else_try),    
                 (ge, ":avg_dist", skirmish_max_distance), #If distance from enemy is (too) large, resume previous order
                 (agent_stop_running_away, ":agent"),         
             (try_end), #Distance to enemy
         (try_end), #Distance from edge
     (try_end), #Agent loop
    ]),
Three scripts: the check for an active order, code for actually giving the order to skirmish/stop skirmishing, and the main workhorse. Place where ever within scripts = [... The beginning works just fine.

Code:
order_skirmish_triggers = [
     (0, 0, 1, [(key_clicked, key_for_skirmish)], [(call_script, "script_order_skirmish_begin_end")]),
     (0.5, 0, 0, [(call_script, "script_cf_order_skirmish_check")], [(call_script, "script_order_skirmish_skirmish")]), 
     (ti_after_mission_start, 0, 0, [], [
          (get_player_agent_no, ":player"),
          (agent_get_party_id, ":player_party", ":player"),
          (try_for_range, ":i", slot_party_skirmish_d0, slot_party_skirmish_d8 + 1),
               (party_set_slot, ":player_party", ":i", 0),
          (try_end),]),
]
Stick them in each of the actual mission templates you want to be able to give the order without the "order_skirmish_triggers" title and brackets, or stick them toward the top of the file (I put them after the multiplayer_* and before the common_* around line 630) and then just include the "order_skirmish_triggers" title in the actual mission templates. I tend to install extra triggers as follows:

Code:
(
    "lead_charge",mtf_battle_mode,charge,
    "You lead your men to battle.",
    [ 
     // bunch of stuff...all the way to the very end
          ] + order_skirmish_triggers,
  ),

Code:
skirmish_min_distance = 1500 #Min distance you wish maintained, in cm. Where agent will retreat
skirmish_max_distance = 2500 #Max distance to maintain, in cm. Where agent will stop retreating

slot_party_skirmish_d0 = slot_town_arena_melee_mission_tpl
slot_party_skirmish_d1 = slot_town_arena_torny_mission_tpl
slot_party_skirmish_d2 = slot_town_arena_melee_1_num_teams
slot_party_skirmish_d3 = slot_town_arena_melee_1_team_size
slot_party_skirmish_d4 = slot_town_arena_melee_2_num_teams
slot_party_skirmish_d5 = slot_town_arena_melee_2_team_size
slot_party_skirmish_d6 = slot_town_arena_melee_3_num_teams
slot_party_skirmish_d7 = slot_town_arena_melee_3_team_size
slot_party_skirmish_d8 = slot_town_arena_melee_cur_tier

from header_triggers import *
key_for_skirmish   = key_f7
Add to bottom of module_constants.py, after customizing the key used to your taste and ensuring no conflicts. Also, the distances where skirmishing begins and ends can be adjusted here (measurements in cm, as noted).

This has been tested and is working as described.

The behavior is a bit rough and may require some additional refinement as it relies on (agent_start_running_away) and (agent_stop_running_away)...for mounted units, this infrequently results in accidental routing/retreat.

Comments and suggestions, etc, are--as always--welcome.
 
You, my friend are a genius... and a psychic.... you see, JethroKirby and I were just discussing the problem with horse archers and their "not using" of bows until after the first charge. And then Jethro all of a sudden says: "Caba made a new script!".  :razz:

Thanks man.  :smile:
 
I have a question regarding this Caba'drin. I'm assuming there is a way to get whether the ai has a ranged weapon and set their default behaviour to skirmish mode. I'll definitely test this out to see how it works. You are a scripting machine man :razz:
 
JethroKirby said:
I have a question regarding this Caba'drin. I'm assuming there is a way to get whether the ai has a ranged weapon and set their default behaviour to skirmish mode. I'll definitely test this out to see how it works. You are a scripting machine man :razz:

There is, definitely. Currently the script is not set up in this fashion, however. The fact that it is solely triggered by a player's order aside, it is configured to work by battlegroup/division (with individual agent decision-making) rather than an agent-by-agent fashion. So the most direct extension to the AI would currently restrict it to a given group (with the AI, this is just infantry/archer/cavalry, but for player the full range of 9 groups). Switching this to an agent-specific coding would require the use of an agent slot...which could be possible, I suppose.

As you test it, I'd appreciate feedback as to whether this would be a helpful change or alternative.

Njunja said:
You, my friend are a genius... and a psychic.... you see, JethroKirby and I were just discussing the problem with horse archers and their "not using" of bows until after the first charge. And then Jethro all of a sudden says: "Caba made a new script!".  :razz:

Thanks man.  :smile:

I'm not so sure about the genius or mind reading...but I'll keep working on it :smile:
 
I've coupled this with your Order: Weapon Switch codes and so far it works pretty well. Horse archers seem to act funny in skirmish mode, they just run without shooting when they get close and then charge toward the group firing their bows and then running again. However, ordering them to use ranged weapons seems to work just fine - they will occasionally run through the group, shooting as they go along, but it seems to work very well. I haven't had much experience using foot troops but I will run some tests and let you know what I find out.

As for horse archers not using their bows until after the initial charge, I may tweak your lancer script to also see if a troop has a ranged weapon and force them to use it.
 
I'm really starting to like this code :grin:. Ranged foot troops do really well in skirmish mode. Its particularly interesting watching javelinmen use this mode. The only issue that I've seen so far seems to be that the behavior keeps going even when they are out of ammo. You don't notice the issue with archers because of their greater ammo reserves, but with "throwing" troops, you notice it fairly often. Basically they charge the group of enemies with their swords up and then run when they get close. The behavior gets stuck in that wierd loop. Obviously, you can just turn off skirmish mode and they will charge like they are supposed to, but it's hard to keep tabs on a battle - especially when you are fighting.
 
I wouldn't see the point in using this for throwing weapons - they're mostly just for front-line infantry to break up a charge before it gets to them. All but the throwing daggers, at least.
 
JethroKirby said:
The only issue that I've seen so far seems to be that the behavior keeps going even when they are out of ammo. You don't notice the issue with archers because of their greater ammo reserves, but with "throwing" troops, you notice it fairly often.

What should be a quick fix for that, if you want them to stop skirmishing if without ammo (though I haven't had a chance to test it)...
Add two lines (agent_get_ammo) and the first (this_or_next|le, ":ammo", 0),...as shown here:
Code:
//...beginning of code...//
        (store_sub, ":dist_left", ":bound_left", ":agent_x"),              
        (store_sub, ":dist_bottom", ":bound_bottom", ":agent_y"),
        (agent_get_ammo, ":ammo", ":agent"),             
        (try_begin), #If agent is too close to edge of map, stop skirmishing. Will resume when back into map             
              (this_or_next|le, ":ammo", 0), #Stop skirmishing and resume orders if out of ammo
              (this_or_next|le, ":dist_right", 20),  #Limits accidental routing, of cav in particular    
//...rest of code...//

JethroKirby said:
Horse archers seem to act funny in skirmish mode, they just run without shooting when they get close and then charge toward the group firing their bows and then running again.

That seems to be a result of me using the retreat/rout command to get them to back off. I wish it worked differently...I'm not 100% satisfied with it either. It seems better suited for foot troops. Trying to manually code moving off was problematic too...I've not found a solution yet.
 
So, so good. Bravo and hooray and huzzah. Caba, you have moved this game forward one more step. (And back several more steps to get away from the screaming bloodthirsty Vikings.)


For he's a jolly good fellow,
for he's a jolly good fellow,
for he's a jolly good fe-elll-ooooow...
which nobody can deny!
 
is there a possibility of getting a video of this in action, im curious as to how well it works...

but its a brilliant idea :grin:
 
Where is constants.py?  I've looked all over the warband folder and have not been able to find anything called that.

Also, what is the extra triggers stuff?  I just left it out...
 
StinkyMcGirk said:
Where is constants.py?  I've looked all over the warband folder and have not been able to find anything called that.

Also, what is the extra triggers stuff?  I just left it out...

Two possible answers to your first question: 1) you won't find any *.py files in your Warband or \Modules\Native\ folder because they are part of the Module System, which generates the .txt files you see there. If this is where you are at, check out the Module System documentation in this forum to get yourself started on using the Module System; 2) I've been more specific in my OP...I'm referring to module_constants.py, in fact all of the files that need to be edited begin with module_

If you do not add the triggers, then the rest of the code will never be called. And this question leads me to believe the first answer above will be most useful to you.

Oddball_E8 said:
is there a possibility of getting a video of this in action, im curious as to how well it works...

I'll see if I can find any time to do so, but this isn't a high priority for me. I'd be more than happy if another had any interest in doing that and wanted to fool around with it.
 
Thanks for the quick reply.  Yeah, I thought I was supposed to modify the notepads there... didn't even know about the existence of a 'module system.'

I have a lot of reading to do to try this stuff out I guess.
 
Okay, so I thought I had it right... but I must have messed something up.  When I try to load the new module with the changes, it crashes and I get this message:

Unexpected End of File while reading file:  Modules\NativeTest\mission_templates.txt

any ideas as to what I did wrong and perhaps how to fix it?
 
StinkyMcGirk said:
Okay, so I thought I had it right... but I must have messed something up.  When I try to load the new module with the changes, it crashes and I get this message:

Unexpected End of File while reading file:  Modules\NativeTest\mission_templates.txt

any ideas as to what I did wrong and perhaps how to fix it?

There is something wrong with either a ] or a ) or a , in how you added the triggers to module_mission_templates.py Double check that part against the directions and compare it how it is supposed to look 'natively' to try and track down where one of those characters went astray.
 
This sounds like a really good idea that has been needed in game for a long time.

How well does it work with the game's routing code? Will the skirmishers be prevented from routing while under the skirmish command?
 
This sounds nice. Brilliant for Horse Archers, even. Unfortunately I'll probably have to port everything I have over to the newest ModSys before I start implementing this, but it sounds very good. :smile:
 
Back
Top Bottom