A quick idea I came up with while reminiscing about algebra and geometry last night. Pretty self-explanatory -- upon pressing a key (default T), soldiers will form a hollow square formation, where the first script parameter determines the rank depth, up to 3 (although a modder with some knowledge of basic algebra could quite easily add as many ranks as they want).
The Basics
The script separates the infantry square into "ranks", which start from the outside and work their way in. Each rank is then created by cycling through agents in a given division, moving one space at a time in a square. Successive ranks are made smaller and placed "inside" the largest square. There's very basic maths and geometry involved, but the script is a little spaghetti-like as it's all within a single try_for_agents.
The algebra you see below is used to determine how wide a formation needs to be. A stands for the width of the first rank, and B refers to the number of men in the entire formation. The relationship between these helps make the process a lot easier, and thus dynamically sized infantry squares can be produced nice and easily.
Storing the position, size and "allegiance" of infantry squares
There are also some dummy troops for storing the size and positioning of an infantry square, meaning that by accessing these values, you can give bonuses to troops in squares or whatever. Here's how it works:
A unique "Square ID" is chosen for each infantry square. It is is created by (team x 10) + division + 1. for example, square 23 is an an enemy ranged square.
The square ID is then used as the slot number for the dummy troop, and the slot value is either pos_x, pos_y, or the number of men in the infantry square, depending on which troop you're accessing the slot of.
These values can be used to do stuff like the above, by trying for the range 0-51 (highest and lowest possible values for infantry squares, although in hindsight it's probably 41), and checking the slots of the troops to retrieve the values (size value 0 also means there is no current infantry square).
Slowing down horses
Every second, a trigger checks for all the horses in a scene, and slows them down by 80% if they get close to an infantry square. This effect can be cancelled or diminished by another trigger; if someone dies or routs and the infantry square gets smaller, this will affect its "radius", and eventually disband the square if there are too few guys.
This trigger is based off the Diplomacy Dynamic Horse Speed feature. Simply overwrite the trigger since I've added/changed almost all of it.
The script separates the infantry square into "ranks", which start from the outside and work their way in. Each rank is then created by cycling through agents in a given division, moving one space at a time in a square. Successive ranks are made smaller and placed "inside" the largest square. There's very basic maths and geometry involved, but the script is a little spaghetti-like as it's all within a single try_for_agents.
The algebra you see below is used to determine how wide a formation needs to be. A stands for the width of the first rank, and B refers to the number of men in the entire formation. The relationship between these helps make the process a lot easier, and thus dynamically sized infantry squares can be produced nice and easily.
Storing the position, size and "allegiance" of infantry squares
There are also some dummy troops for storing the size and positioning of an infantry square, meaning that by accessing these values, you can give bonuses to troops in squares or whatever. Here's how it works:
A unique "Square ID" is chosen for each infantry square. It is is created by (team x 10) + division + 1. for example, square 23 is an an enemy ranged square.
The square ID is then used as the slot number for the dummy troop, and the slot value is either pos_x, pos_y, or the number of men in the infantry square, depending on which troop you're accessing the slot of.
These values can be used to do stuff like the above, by trying for the range 0-51 (highest and lowest possible values for infantry squares, although in hindsight it's probably 41), and checking the slots of the troops to retrieve the values (size value 0 also means there is no current infantry square).
Slowing down horses
Every second, a trigger checks for all the horses in a scene, and slows them down by 80% if they get close to an infantry square. This effect can be cancelled or diminished by another trigger; if someone dies or routs and the infantry square gets smaller, this will affect its "radius", and eventually disband the square if there are too few guys.
This trigger is based off the Diplomacy Dynamic Horse Speed feature. Simply overwrite the trigger since I've added/changed almost all of it.
Code:
#script_cf_create_infantry_square
#Width of 1st rank = a+1
#Width of 2nd rank = a-1
#Width of 3rd rank = a-3
#Input: 1: ranks, 2: team, 3: unit
#Output: none
("cf_create_infantry_square",
[
#creates an infantry square using quarter sections. creates first rank first, second rank, and then third, if specified. "leftover" troops are included as rounding calculation creates larger square than is present in selection.
#One rank = (4a)
#Two ranks = (8a-8) 4a+4(a-2)
#Three ranks = (12a-32) 4a+4(a-2)+4(a-4)
#One rank = b/4
#Two ranks = b/8 + 1
#Three ranks = b+32 / 12
(set_fixed_point_multiplier, 1),
(store_script_param, ":ranks", 1),
(store_script_param, ":team", 2),
(store_script_param, ":division", 3),
(store_script_param, ":is_ally", 4),
(call_script, "script_cf_team_get_average_position_of_agents_with_type_to_pos1", ":team", ":division"),
(assign, ":unit_count", reg0),
(set_fixed_point_multiplier, 1),
(ge, ":unit_count", 32),
#initialise value of "a" (formation width)
(try_begin),
(eq, ":ranks", 1),
(store_div, ":value_a", ":unit_count", 4),
(else_try),
(eq, ":ranks", 2),
(store_div, ":value_a", ":unit_count", 8),
(val_add, ":value_a", 1),
(else_try),
(eq, ":ranks", 3),
(store_add, ":value_a", ":unit_count", 32),
(val_div, ":value_a", 12),
(try_end),
# (init_position, pos1),
# (call_script, "script_battlegroup_get_position", pos1, ":team", ":class"),
(init_position, pos2),
(copy_position, pos2, pos1), #pos2 remains as original
#(set_fixed_point_multiplier, 1),
#move by a/2 in x and y so that square is centred on formation properly
(store_div, ":value_a_half", ":value_a", 2),
(store_sub, ":value_a_half", 0, ":value_a_half"), #make negative
(val_mul, ":value_a_half", 120), #make negative
(position_move_x, pos2, ":value_a_half", 1),
(position_move_y, pos2, ":value_a_half", 1),
#square ID is created by (team x 10) + division + 1. e.g. square 22 is an an enemy ranged square.
(store_mul, ":square_id", ":team", 10),
(val_add, ":square_id", ":division"),
(val_add, ":square_id", 1),
(position_get_x, ":pos_x", pos2),
(position_get_y, ":pos_y", pos2),
#housekeeping
# (position_set_z_to_ground_level, pos2),
# (particle_system_burst, "psys_flare", pos2, 900000),
#(display_message, "@SQUARE"),
#housekeeping
(copy_position, pos1, pos2), #pos2 remains as original
(troop_set_slot, "trp_jacobhinds_form_musket_square_pos_x", ":square_id", ":pos_x"),
(troop_set_slot, "trp_jacobhinds_form_musket_square_pos_y", ":square_id", ":pos_y"),
(troop_set_slot, "trp_jacobhinds_form_musket_square_size", ":square_id", ":unit_count"),
(troop_set_slot, "trp_jacobhinds_form_musket_square_is_ally", ":square_id", ":is_ally"),
#initialises shift variables from ranks
# (assign, ":shifted_2", 0),
# (assign, ":shifted_3", 0),
(assign, "$square_formation_progress", 0),
(assign, "$square_formation_section", r1s1),
(try_for_agents, ":agent"),
(agent_is_alive, ":agent"),
(agent_is_human, ":agent"),
(agent_slot_eq, ":agent", slot_agent_is_running_away, 0), #not routing, prevents huge formation holes
#check if troop is in correct group/team
(agent_get_division, ":agent_division", ":agent"),
(agent_get_team, ":agent_team", ":agent"),
(eq, ":agent_team", ":team"),
(eq, ":agent_division", ":division"),
#If $square_formation_progress has reached value_a + 1, val_add to $square_formation_section.
#also rotate by 90 degrees to allow troops to face outwards.
(try_begin),
(gt, "$square_formation_progress", ":value_a"),
(val_add, "$square_formation_section", 1),
(assign, "$square_formation_progress", 1),
(position_rotate_z, pos1, 90),
(try_end),
#First Rank:
(try_begin),
(eq, "$square_formation_section", r1s1),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_x, pos1, 120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r1s2),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_y, pos1, 120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r1s3),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_x, pos1, -120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r1s4),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_y, pos1, -120, 1),
(val_add, "$square_formation_progress", 1),
(try_end),
#first rank crouch if formation is two or more deep
#possibly add to end when all troops are in position...?
#perhaps save time by doing so after a time proportional to formation size
(try_begin),
(ge, ":ranks", 2),
(is_between, "$square_formation_section", r1s1, r2s1),
#(agent_set_crouch_mode, ":agent", 1),
(try_end),
#when first rank finished:
#move position down and across by 1 "unit" (0.5 metres currently)
#also sub 2 from value_a to accommodate narrower formation
(try_begin),
#(eq, ":shifted_2", 0),
(eq, "$square_formation_section", r2s1),
(eq, "$square_formation_progress", 1),
#(copy_position, pos1, pos2),
(position_move_y, pos1, 120, 1),
(position_move_x, pos1, 120, 1),
#(val_sub, ":value_a", 2),
#(display_message, "@shifting..."),
(try_end),
#Second Rank:
(try_begin),
(ge, ":ranks", 2),
(try_begin),
(eq, "$square_formation_section", r2s1),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_x, pos1, 120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r2s2),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_y, pos1, 120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r2s3),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_x, pos1, -120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r2s4),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_y, pos1, -120, 1),
(val_add, "$square_formation_progress", 1),
(try_end),
(try_end),
#when second rank finished:
#move position down and across by 1 "unit" (0.5 metres currently)
#also sub 2 from value_a to accommodate narrower formation
(try_begin),
#(eq, ":shifted_3", 0),
(eq, "$square_formation_section", r3s1),
(eq, "$square_formation_progress", 1),
#(copy_position, pos1, pos2),
(position_move_y, pos1, 120, 1),
(position_move_x, pos1, 120, 1),
(val_sub, ":value_a", 2),
#(assign, ":shifted_2", 1),
#(display_message, "@shifting..."),
(try_end),
#Third Rank:
(try_begin),
(ge, ":ranks", 3),
(try_begin),
(eq, "$square_formation_section", r3s1),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_x, pos1, 120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r3s2),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_y, pos1, 120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r3s3),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_x, pos1, -120, 1),
(val_add, "$square_formation_progress", 1),
(else_try),
(eq, "$square_formation_section", r3s4),
(agent_set_scripted_destination, ":agent", pos1, 1),
(position_move_y, pos1, -120, 1),
(val_add, "$square_formation_progress", 1),
(try_end),
(try_end),
(try_end),
]),
#script_end_infantry_square
#Input: 1: team, 2: unit
#Output: none
("cf_end_infantry_square",
[
(set_fixed_point_multiplier, 1),
(store_script_param, ":team", 1),
(store_script_param, ":division", 2),
(try_for_agents, ":agent"),
(agent_is_alive, ":agent"),
(agent_is_human, ":agent"),
(agent_slot_eq, ":agent", slot_agent_is_running_away, 0), #not routing
(agent_get_division, ":agent_division", ":agent"),
(agent_get_team, ":agent_team", ":agent"),
(eq, ":agent_team", ":team"),
(eq, ":agent_division", ":division"),
(agent_clear_scripted_mode, ":agent"),
(try_end),
#square ID is created by (team x 10) + division + 1. e.g. square 23 is an an enemy ranged square.
(store_mul, ":square_id", ":team", 10),
(val_add, ":square_id", ":division"),
(val_add, ":square_id", 1),
(troop_set_slot, "trp_jacobhinds_form_musket_square_pos_x", ":square_id", 0),
(troop_set_slot, "trp_jacobhinds_form_musket_square_pos_y", ":square_id", 0),
(troop_set_slot, "trp_jacobhinds_form_musket_square_size", ":square_id", 0),
(troop_set_slot, "trp_jacobhinds_form_musket_square_is_ally", ":square_id", -1),
]),
#script_agent_remove_from_square
#every time someone dies, flees or gets wounded,
#reduce the infantry square size until it's below
#30, then remove the square.
#Input: 1: troop
#Output: none
("agent_remove_from_square",[
(store_script_param, ":agent_no", 1),
#(agent_is_alive, ":agent_no"),
(agent_is_human, ":agent_no"),
(agent_get_team, ":team", ":agent_no"),
(agent_get_division, ":division", ":agent_no"),
#calculate square id
(store_mul, ":square_id", ":team", 10),
(val_add, ":square_id", ":division"),
(val_add, ":square_id", 1),
#get square size. if less than 32, remove square altogether. else, reduce size by 1.
(troop_get_slot, ":square_size", "trp_jacobhinds_form_musket_square_size", ":square_id"),
#make sure there's a square in the first place
(neq, ":square_size", 0),
#housekeeping
#(assign, reg2, ":square_size"),
#(assign, reg3, ":square_id"),
(try_begin),
(lt, ":square_size", 32),
(call_script, "script_cf_end_infantry_square", ":team", ":division", 1),
#(display_message, "@not enough dudes in square {reg3} ({reg2})"),
(else_try),
(val_sub, ":square_size", 1),
(troop_set_slot, "trp_jacobhinds_form_musket_square_size", ":square_id", ":square_size"),
#(display_message, "@someone left square number {reg3} ({reg2})"),
(try_end),
]),
#
Code:
#jacobhinds form musket square BEGIN
jacobhinds_form_square_init = (
ti_before_mission_start, 0, 0, [],
[
(try_for_range, ":square_id", 0, 150), #clears dummy troop array for infantry squares
(troop_set_slot, "trp_jacobhinds_form_musket_square_size", ":square_id", 0),
(try_end),
])
jacobhinds_form_square = (
0, 0, 0, [(key_clicked, key_t)],
[
#find player team
(get_player_agent_no, ":player"),
(agent_get_team, ":playerteam", ":player"),
(try_for_range, ":class", 0, 9),
(class_is_listening_order, ":playerteam", ":class"),
(store_mul, ":square_id", ":playerteam", 10),
(val_add, ":square_id", ":class"),
(val_add, ":square_id", 1),
#make sure they aren't already in square, otherwise end
(try_begin),
(troop_slot_eq, "trp_jacobhinds_form_musket_square_size", ":square_id", 0),
(call_script, "script_cf_create_infantry_square", 3, ":playerteam", ":class", 1),
(str_store_class_name, s1, ":class"),
(display_message, "@Forming square with {s1}"),
(else_try),
(call_script, "script_cf_end_infantry_square", ":playerteam", ":class"),
(str_store_class_name, s1, ":class"),
(display_message, "@Disbanding square with {s1}"),
(try_end),
(try_end),
],
)
jacobhinds_infantry_square_check = (
ti_on_agent_killed_or_wounded, 0, 0, [],
#every time someone dies, flees or gets wounded,
#reduce the infantry square size until it's below
#30, then remove the square.
[
(store_trigger_param_1, ":dead_agent_no"),
(call_script, "script_agent_remove_from_square", ":dead_agent_no"),
])
#jacobhinds form musket square END
Code:
#jacobhinds form musket square BEGIN
dplmc_horse_speed = (
1.1, 0, 0, [], #was 1
[
(set_fixed_point_multiplier, 1),
(try_for_agents, ":agent_no"),
(agent_is_alive, ":agent_no"),
(agent_is_human, ":agent_no"),
(agent_get_horse, ":horse_agent", ":agent_no"),
(try_begin),
(ge, ":horse_agent", 0),
(store_agent_hit_points, ":horse_hp",":horse_agent"),
(store_sub, ":lost_hp", 100, ":horse_hp"),
(try_begin),
(le, ":lost_hp", 15),
(val_div, ":lost_hp", 2),
(store_add, ":speed_factor", 100, ":lost_hp"),
(else_try),
(val_mul, ":lost_hp", 2),
(val_div, ":lost_hp", 3),
(store_sub, ":speed_factor", 115, ":lost_hp"),
(try_end),
#makes horses slow near infantry squares
(agent_get_position, pos2, ":agent_no"),
#find horse speed
# (agent_get_speed, pos6, ":horse_agent"),
# (init_position, pos7),
# (get_distance_between_positions_in_meters, ":velocity", pos6, pos7),
#housekeeping
# (assign, reg1, ":velocity"),
# (display_message, "@{reg1}"),
#housekeeping
(try_for_range, ":square_id", 1, 51),
(troop_get_slot, ":pos_x", "trp_jacobhinds_form_musket_square_pos_x", ":square_id"),
(troop_get_slot, ":pos_y", "trp_jacobhinds_form_musket_square_pos_y", ":square_id"),
(troop_get_slot, ":square_size", "trp_jacobhinds_form_musket_square_size", ":square_id"),
(troop_get_slot, ":is_ally", "trp_jacobhinds_form_musket_square_is_ally", ":square_id"),
(gt, ":square_size", 0),
# (gt, ":pos_x", 0),
# (gt, ":pos_y", 0),
(assign, ":continue", 0),
(try_begin),
(agent_is_ally, ":agent_no", 1), #if agent and square are both allies
(eq, ":is_ally", 1),
(assign, ":continue", 1),
(else_try),
(agent_is_ally, ":agent_no", 0), #if neither agent nor square are allies
(eq, ":is_ally", 0),
(assign, ":continue", 1),
(try_end),
(eq, ":continue", 0),
(position_set_x, pos1, ":pos_x"),
(position_set_y, pos1, ":pos_y"),
(get_distance_between_positions_in_meters, ":distance", pos1, pos2),
#find infantry square radius
#if closer than (size+32/24)+5, slow down
#(inverse of original algebra, halved to make sure it doesn't
#extend too far on both sides, with a little room (5m) for horses)
(store_add, ":square_radius", ":square_size", 32),
(val_div, ":square_radius", 24),
(val_add, ":square_radius", 5),
#(val_add, ":square_radius", 50),
(lt, ":distance", ":square_radius"),
#housekeeping
# (assign, reg1, ":distance"),
# (assign, reg2, ":square_size"),
# (display_message, "@slowing down"),
#housekeeping
(val_sub, ":speed_factor", 80),
(val_max, ":speed_factor", 0),
# (store_random_in_range, ":rand", 0, 6), #1/6 chance to rear horse
# (try_begin),
# (eq, ":rand", 0),
# (agent_set_animation, ":horse_agent", "anim_horse_rear"),
# (agent_set_animation, ":agent_no", "anim_ride_rear"),
# (try_end),
(try_end),
(agent_get_troop_id, ":agent_troop", ":agent_no"),
(store_skill_level, ":skl_level", skl_riding, ":agent_troop"),
(store_mul, ":speed_multi", ":skl_level", 2),
(val_add, ":speed_multi", 100),
(val_mul, ":speed_factor", ":speed_multi"),
(val_div, ":speed_factor", 100),
(agent_set_horse_speed_factor, ":agent_no", ":speed_factor"),
(try_end),
(try_end),
])
#jacobhinds form musket square END
Code:
#jacobhinds form musket square BEGIN
#assigns counted agents to reg0 (saves time)
(assign, reg0, ":num_agents"),
#jacobhinds form musket square END
Code:
#jacobhinds form musket square BEGIN
["jacobhinds_form_musket_square_pos_x", "{!} Current musket squares", "{!} Current musket squares", 0, no_scene, reserved, fac_commoners, [], def_attrib, 0, 0, merchant_face_1, merchant_face_2 ],
["jacobhinds_form_musket_square_pos_y", "{!} Current musket squares", "{!} Current musket squares", 0, no_scene, reserved, fac_commoners, [], def_attrib, 0, 0, merchant_face_1, merchant_face_2 ],
["jacobhinds_form_musket_square_size", "{!} Current musket squares", "{!} Current musket squares", 0, no_scene, reserved, fac_commoners, [], def_attrib, 0, 0, merchant_face_1, merchant_face_2 ],
["jacobhinds_form_musket_square_is_ally", "{!} Current musket squares", "{!} Current musket squares", 0, no_scene, reserved, fac_commoners, [], def_attrib, 0, 0, merchant_face_1, merchant_face_2 ],
#jacobhinds form musket square END
Code:
#jacobhinds form musket square BEGIN
r1s1 = 1
r1s2 = 2
r1s3 = 3
r1s4 = 4
r2s1 = 5
r2s2 = 6
r2s3 = 7
r2s4 = 8
r3s1 = 9
r3s2 = 10
r3s3 = 11
r3s4 = 12
#jacobhinds form musket square END
Remove Routing Troops From Squares
Find every instance of agent_start_running_away in your module system, and add:
(call_script, "script_agent_remove_from_square", ":cur_agent"), #jacobhinds infantry square script
In the line after it.
If you DON'T have diplomacy:
Find these mission templates:
lead_charge
village_attack_bandits
village_raid
quick_battle_battle
And add the names of these triggers somewhere in the consequence block:
jacobhinds_infantry_square_check,
jacobhinds_form_square_init,
jacobhinds_form_square,
dplmc_horse_speed,
If you DO have diplomacy:
Add these triggers to dplmc_battle_mode_triggers:
jacobhinds_infantry_square_check,
jacobhinds_form_square,
jacobhinds_form_square_init,
Find every instance of agent_start_running_away in your module system, and add:
(call_script, "script_agent_remove_from_square", ":cur_agent"), #jacobhinds infantry square script
In the line after it.
If you DON'T have diplomacy:
Find these mission templates:
lead_charge
village_attack_bandits
village_raid
quick_battle_battle
And add the names of these triggers somewhere in the consequence block:
jacobhinds_infantry_square_check,
jacobhinds_form_square_init,
jacobhinds_form_square,
dplmc_horse_speed,
If you DO have diplomacy:
Add these triggers to dplmc_battle_mode_triggers:
jacobhinds_infantry_square_check,
jacobhinds_form_square,
jacobhinds_form_square_init,
Currently functional. The "listening" group of troops will form a square upon pressing T, and disband it upon clicking again. "Enemy" horses will slow down if they get too close to the square. Works for the AI too, although it's up to you when to get them to use it:
Code:
(call_script, "script_cf_create_infantry_square", ":number_of_ranks", ":team_no", ":division", 1),
If the square is "allied" to the player, set the last parameter to 1, but if not, set it to 0. The script does the rest.