OSP Code Combat [WB, SP] Order: Volley Fire!

Caba`drin

Count
M&BWBWF&SNWVC
Best answers
0
The following code allows the player to order their archers and crossbow men to synchronize their shots into volleys. (The general principle could be applied to AIs as well, but has not been here. Similarly, the item type checks could be extended to pistols or muskets, but again, are not marked as such below.) Archers fire a shot every 3 seconds and crossbowmen get a shot off every 5 seconds...given this adds some delay to archers, this could be useful for preservation of arrows, but in my tests crossbowmen find it much more effective.

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.) Give the volley order once and your selected divisions will be ordered to "Prepare to Volley". Tap the key again with groups already ordered to volley will be ordered to "End their Volley". Giving the volley order to a mixed set of groups (some ordered to volley, others not) will order all selected groups to volley fire. Only troops currently ordered to hold position or stand ground can respond to a volley command.

This should be compatible with most any other code bits/mods/what have you, as it does not add any globals nor does it monkey with the original source code. (It uses team slots--see the Constants code--to track orders to battle divisions; as these are unused in single player, there is no conflict.) Two possible compatibility issues to pay attention to: one will be to find a suitable, unused key for the order. Currently, it is set to use F8. Second, ensure that the agent slot is unused elsewhere in constants. (For a more elegant integration with my other order codes, see the source for Pre-Battle Orders & Deployment.)

EXAMPLE VIDEOS

Without further ado, the code:
Code:
  
  # script_cf_order_volley_check
  # Input: Nothing
  # Output: Nothing    
  # Check for an active Volley Fire order
 ("cf_order_volley_check", [ 
    (assign, ":active", 0),
    (assign, ":end", 4),
    (assign, ":end2", slot_team_d0_order_volley + 9),
    (try_for_range, ":team", 0, ":end"),
        (try_for_range, ":i", slot_team_d0_order_volley, ":end2"),
            (team_slot_ge, ":team", ":i", 1),
            (assign, ":active", 1),
            (assign, ":end", 0),
            (assign, ":end2", slot_team_d0_order_volley),
        (try_end),
    (try_end),
    (eq, ":active", 1),
    ]), 
Code:
  # script_order_volley_begin_end
  # Input: Nothing
  # Output: Nothing
  # On key depression, determine if beginning or ending volley fire
  # Display appropriate order text on screen.
 ("order_volley_begin_end", [ 
     (get_player_agent_no, ":player"),
     (agent_get_team, ":playerteam", ":player"),
     
     (assign, ":volley", 0),
     
     (try_for_range, ":class", 0, 8),
         (class_is_listening_order, ":playerteam", ":class"), #Listening to Order
         (store_add, ":class_ordered", slot_team_d0_order_volley, ":class"),
         (team_slot_eq, ":playerteam", ":class_ordered", 0), 
         (team_set_slot, ":playerteam", ":class_ordered", 1),
         (assign, ":volley", 1),
         (str_store_string, s1, "@prepare to volley"),
         (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?
         (try_begin),
             (eq, ":volley", 0),
             (store_add, ":class_ordered", slot_team_d0_order_volley, ":class"),
             (team_set_slot, ":team", ":class_ordered", 0),
             (try_begin),
                (agent_slot_ge, ":agent", slot_agent_volley_fire, 1), 
                (agent_set_slot, ":agent", slot_agent_volley_fire, 0),
                (agent_set_attack_action, ":agent", 0, 0), #Release (if holding)
             (try_end),
             (str_store_string, s1, "@end volley"),
             (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),
         (else_try),
            (eq, ":volley", 1),
            (agent_get_horse, ":horse", ":agent"),
            (le, ":horse", 0), #Not Mounted
            
            (assign, ":volley_wpn_type", -1),
            (try_begin),
                (team_get_movement_order, ":mordr", ":team", ":class"),
                (this_or_next|eq, ":mordr", mordr_hold),
                (eq, ":mordr", mordr_stand_ground),
                
                (agent_get_wielded_item, ":wielded", ":agent", 0),
                (ge, ":wielded", 0),
                (item_get_type, ":type", ":wielded"),
                (try_begin),
                    (this_or_next|eq, ":type", itp_type_bow),
                    (eq, ":type", itp_type_crossbow),
                    (assign, ":volley_wpn_type", ":type"),
                (else_try),
                    (assign, ":end", ek_head),
                    (try_for_range, ":i_slot", ek_item_0, ":end"),
                        (agent_get_item_slot, ":item", ":agent", ":i_slot"),
                        (ge, ":item", 0),
                        (item_get_type, ":type", ":item"),
                        (this_or_next|eq, ":type", itp_type_bow),
                        (eq, ":type", itp_type_crossbow),
                        (agent_set_wielded_item, ":agent", ":item"),
                        (assign, ":volley_wpn_type", ":type"),
                        (assign, ":end", ek_item_0),
                    (try_end),
                (try_end),
            (try_end),
            (agent_set_slot, ":agent", slot_agent_volley_fire, ":volley_wpn_type"),         
         (try_end), #Volley End or Begin
     (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),
    ]),
Two scripts: the check for an active order and code for actually giving the order to begin/stop volley fire and display the appropriate text on screen. Place where ever within scripts = [... The beginning works just fine.

Code:
order_volley_triggers = [
    (0, 0, 1, [(key_clicked, key_for_volley)], [(call_script, "script_order_volley_begin_end")]),
    (1, 0, 0, [(call_script, "script_cf_order_volley_check")], [
        (try_for_range, ":team", 0, 4),
            (try_for_range, ":division", 0, 9),
                (store_add, ":slot", slot_team_d0_order_volley, ":division"),
                (team_slot_ge, ":team", ":slot", 1),
                (team_get_slot, ":volley_counter", ":team", ":slot"),
                (val_add, ":volley_counter", 1),
                (team_set_slot, ":team", ":slot", ":volley_counter"),
            (try_end),
        (try_end),
        
        (try_for_agents, ":agent"),
            (agent_is_non_player, ":agent"),
            (agent_slot_ge, ":agent", slot_agent_volley_fire, 1),
            (agent_get_ammo, ":ammo", ":agent", 1),
            (gt, ":ammo", 0),
            
            (agent_get_team, ":team", ":agent"),
            (agent_get_division, ":division", ":agent"),
            (store_add, ":slot", slot_team_d0_order_volley, ":division"),
            (team_get_slot, ":volley_counter", ":team", ":slot"),
            
            (agent_get_slot, ":volley_wpn_type", ":agent", slot_agent_volley_fire),
            (try_begin),
                (eq, ":volley_wpn_type", itp_type_bow),
                (assign, ":delay", 3),
            (else_try),
                (eq, ":volley_wpn_type", itp_type_crossbow),
                (assign, ":delay", 5),
            (try_end),
            (agent_get_combat_state, ":cs", ":agent"),
            (this_or_next|eq, ":cs", 1),
            (eq, ":cs", 3),
                        
            (store_mod, reg0, ":volley_counter", ":delay"),        
            (try_begin),
                (eq, reg0, 0),
                (agent_set_attack_action, ":agent", 0, 0), #Fire
            (else_try),
                (agent_set_attack_action, ":agent", 0, 1), #Ready and Aim
            (try_end),
        (try_end),
     ]),
 
    (1, 0, ti_once, [(neq, "$g_battle_result", 0)], [   #Disable Volley @ end of battle 
        (try_for_range, ":team", 0, 4),
            (try_for_range, ":slot", slot_team_d0_order_volley, slot_team_d0_order_volley + 9),
                (team_set_slot, ":team", ":slot", 0),
            (try_end),
        (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_volley_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_volley_triggers,
  ),

Code:
slot_agent_volley_fire             = 33
slot_team_d0_order_volley     = 10 #plus 8 more for the other divisions

from header_triggers import *
key_for_volley   = key_f8
Add to bottom of module_constants.py, after customizing the key used to your taste and ensuring no conflicts.

This has been tested and is working as described.

Comments and suggestions, etc, are--as always--welcome.
 

Caba`drin

Count
M&BWBWF&SNWVC
Best answers
0
theAthenian said:
Sounds awesome. Can someone plz make a vid out of it?
Ah heck, had some time, didn't feel like coding or actually playing, so two videos added to the OP.
 

Cromcrom

This and skirmishing, among others, are gorgeous. I will definitely add it to rigale, sooner than late. Thank you so much for sharing :smile:
 

theAthenian

Ah heck, had some time, didn't feel like coding or actually playing, so two videos added to the OP.

Thnx Caba'Drin. You did a very nice job. I was looking for something like this for a very long time.
 

Cromcrom

Caba'drin, it seems the volley firing is automatic. Do you think it could be tied to a key, so that the player orders the fire ? (SteaaaaaaaadyyyyyyyyyyyyyyyyyySHOOT)  :twisted:
 

Llew2

Cheap ass bum
Count
M&BWB
Best answers
0
Very nice indeed! Would go well in HYW. Also, it looks easy to change the volley timing; I think 2 or 2.5 seconds would be better for the archers.
 

Caba`drin

Count
M&BWBWF&SNWVC
Best answers
0
Llew2 said:
Very nice indeed! Would go well in HYW. Also, it looks easy to change the volley timing; I think 2 or 2.5 seconds would be better for the archers.
Yeah, you're probably right that the archer time is too long. To do half seconds, you'd need to adjust the trigger timing, too, which would mean the team-slot counters would be in half seconds rather than full seconds so they would need a tweak, but otherwise just deal with the delay variable and you're good to go. (Which you probably already figured out :wink: )

Cromcrom said:
Caba'drin, it seems the volley firing is automatic. Do you think it could be tied to a key, so that the player orders the fire ? (SteaaaaaaaadyyyyyyyyyyyyyyyyyySHOOT)  :twisted:
So that each order (Ready, Aim, Fire) is tied to a key press? That could be done, too, yes.
My thought here was that the player would want to tell the troops to volley fire and then leave them to it.

It could be much simplified if it were to be micromanaged in this way.
Roughly, something like this, I think would work:
Would just take 1 trigger, and an agent slot defined in module_constants.
Code:
order_volley_trigger = 
    (0, 0, 1, [(key_clicked, key_for_volley)], [
        (get_player_agent_no, ":player"),
        (agent_get_team, ":playerteam", ":player"),
        (agent_get_slot, ":volley_stage", ":player", slot_agent_volley),
        (try_begin),
            (eq, ":volley_stage", 0),
            (display_message, "@Ready!"),
            (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"),
                (class_is_listening_order, ":team", ":class"),
    
                (team_get_movement_order, ":mordr", ":team", ":class"),
                (this_or_next|eq, ":mordr", mordr_hold),
                (eq, ":mordr", mordr_stand_ground),
                
                (agent_get_wielded_item, ":wielded", ":agent", 0),
                (ge, ":wielded", 0),
                (item_get_type, ":type", ":wielded"),
                (assign, ":volley", 0),
                (try_begin),
                    (this_or_next|eq, ":type", itp_type_bow),
                    (eq, ":type", itp_type_crossbow),
                    (assign, ":volley", 1),
                (else_try),
                    (assign, ":end", ek_head),
                    (try_for_range, ":i_slot", ek_item_0, ":end"),
                        (agent_get_item_slot, ":item", ":agent", ":i_slot"),
                        (ge, ":item", 0),
                        (item_get_type, ":type", ":item"),
                        (this_or_next|eq, ":type", itp_type_bow),
                        (eq, ":type", itp_type_crossbow),
                        (agent_set_wielded_item, ":agent", ":item"),
                        (assign, ":volley", 1),
                        (assign, ":end", ek_item_0),
                    (try_end),
                (try_end),
                (agent_set_slot, ":agent", slot_agent_volley, ":volley"),
            (try_end),
            (agent_set_slot, ":player", slot_agent_volley, 1),
        (else_try),
            (eq, ":volley_stage", 1),
            (display_message, "@AIM!"),
            (try_for_agents, ":agent"),
                (agent_is_non_player, ":agent"),
                (agent_slot_eq, ":agent", slot_agent_volley, 1),
                (agent_get_ammo, ":ammo", ":agent", 1),
                (gt, ":ammo", 0),
                
                (agent_get_combat_state, ":cs", ":agent"),
                (this_or_next|eq, ":cs", 1),
                (eq, ":cs", 3),
                            
                (agent_set_attack_action, ":agent", 0, 1), #Ready and Aim
            (try_end),
            (agent_set_slot, ":player", slot_agent_volley, 2),
        (else_try),
            (eq, ":volley_state", 2),
            (display_message, "@FIRE!"),
            (try_for_agents, ":agent"),
                (agent_is_non_player, ":agent"),
                (agent_slot_ge, ":agent", slot_agent_volley, 1),
                (agent_set_slot, ":agent", slot_agent_volley, 0),
                (agent_get_ammo, ":ammo", ":agent", 1),
                (gt, ":ammo", 0),

                (agent_get_combat_state, ":cs", ":agent"),
                (this_or_next|eq, ":cs", 1),
                (eq, ":cs", 3),
                            
                (agent_set_attack_action, ":agent", 0, 0), #Fire
            (try_end),
            (agent_set_slot, ":player", slot_agent_volley, 0),
        (try_end),
     ])
 

reaver456

Veteran
WB
Best answers
0
wow thats neat man. Making all your archers fire a volley by the press of a key definitely help with a preservation of ammo.
 

Mandible

Grandmaster Knight
WB
Best answers
0
Could you limit this ability to certain troops? So instead of ordering your archers to fire and having all of them (marksmen as well as raw recruits) firing in perfect coordination, could you have only those troops above a certain level or armed with a certain bow fire together?
 

Caba`drin

Count
M&BWBWF&SNWVC
Best answers
0
Mandible said:
Could you limit this ability to certain troops? So instead of ordering your archers to fire and having all of them (marksmen as well as raw recruits) firing in perfect coordination, could you have only those troops above a certain level or armed with a certain bow fire together?
Certainly. I'd add a check to the script that deals with the order beginning/ending, so that troops you don't want to be allowed to volley are excluded from having anything but -1 set in their agent volley slot.