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
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.
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:
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.
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
[+]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
]),
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),]),
]
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
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.