OSP Code Combat Overhauled Morale and Routing (Also Reduces Stuttering)

Users who are viewing this thread

Hfmtr.jpg

Here's a set of codes that should make the morale system in Warband work better and run a lot faster. Soldiers take into account the number of enemies of the battlefield and the number of their own dead, so it's possible for a small army to rout a larger one if the casualty ratios are large enough (to an extent).

After a -lot- of tweaking with the morale system in Warband, I found that one of the scripts (script_apply_effect_of_other_people_on_courage_scores) was poorly coded in that it would stack over the course of a battle and cause a ton of unnecessary lag (there were 2 try_for_agents blocks inside each other -- in a full custom battle that's 160,000 clunky and essentially pointless calculations every 3 seconds). So here's a script that should rectify that -- I've got a number of fairly cumbersome try_for_agent debug scripts running, and I can still do 400 v 400 battles with virtually no stuttering.

The script replaces apply_effect_of_other_people_on_courage_scores with a balance of power system. Large numbers of men are less likely to rout in the face of small numbers, but on a battlewide basis rather than a localised one.

Module Scripts

If you're using motomataru's FormAI, use the second script and paste it in formAI_scripts_wb.py after formAI_replacement_scripts = [. If you aren't using my infantry square script, comment out or delete the lines with the "#jacobhinds infantry square script" comments.

Code:
# # Line added to clear scripted mode right before each (agent_start_running_away, ":cur_agent")
  # script_decide_run_away_or_not
  # Input: none
  # Output: none
  ("decide_run_away_or_not",
    [
      (store_script_param, ":cur_agent", 1),
      (store_script_param, ":mission_time", 2),
      


      (assign, ":force_retreat", 0),
      (agent_get_team, ":agent_team", ":cur_agent"),
      (agent_get_division, ":agent_division", ":cur_agent"),
      (try_begin),
        (lt, ":agent_division", 9), #static classes
        (team_get_movement_order, ":agent_movement_order", ":agent_team", ":agent_division"),
        (eq, ":agent_movement_order", mordr_retreat),
        (assign, ":force_retreat", 1),
      (try_end),

      (agent_get_slot, ":is_cur_agent_running_away", ":cur_agent", slot_agent_is_running_away),
      (try_begin),
		#(neg|agent_slot_eq, ":cur_agent", slot_agent_courage_score_bonus, -1),
        (eq, ":is_cur_agent_running_away", 0),
        (try_begin),
          (eq, ":force_retreat", 1),
          (agent_start_running_away, ":cur_agent"),
		  (call_script, "script_cf_agent_remove_from_square", ":cur_agent"), #jacobhinds infantry square script
          (agent_set_slot, ":cur_agent",  slot_agent_is_running_away, 1),
		  #(agent_set_speed_limit, ":cur_agent", 60),
        (else_try),
          (ge, ":mission_time", 4), #first 4 seconds nobody runs away whatever happens.
          (agent_get_slot, ":agent_courage_score", ":cur_agent",  slot_agent_courage_score),
          (store_agent_hit_points, ":agent_hit_points", ":cur_agent"),
          (val_mul, ":agent_hit_points", 10), #was 4

		  (try_begin),
			(call_script, "script_cf_agent_can_rout", ":cur_agent"),
			(agent_is_ally, ":cur_agent"),
			(val_add, ":agent_hit_points", "$battle_ratio"),
		  (else_try),
			(call_script, "script_cf_agent_can_rout", ":cur_agent"),
			(val_sub, ":agent_hit_points", "$battle_ratio"),
		  (try_end),

          #(val_mul, ":agent_hit_points", 10),
          (store_sub, ":start_running_away_courage_score_limit", 300, ":agent_hit_points"), #was 3500
          (lt, ":agent_courage_score", ":start_running_away_courage_score_limit"), #if (courage score < 3500 - (agent hit points * 40)) and (agent is not running away) then start running away, average hit points : 50, average running away limit = 1500

          (agent_get_troop_id, ":troop_id", ":cur_agent"), #for now do not let heroes to run away from battle
          (neg|troop_is_hero, ":troop_id"),
                                
          (agent_start_running_away, ":cur_agent"),
		  (call_script, "script_cf_agent_remove_from_square", ":cur_agent"), #jacobhinds infantry square script
          (agent_set_slot, ":cur_agent",  slot_agent_is_running_away, 1),
        (try_end),


      (else_try),
		#(neg|agent_slot_eq, ":cur_agent", slot_agent_courage_score_bonus, -1),
        (neq, ":force_retreat", 1),
        (agent_get_slot, ":agent_courage_score", ":cur_agent",  slot_agent_courage_score),
        (store_agent_hit_points, ":agent_hit_points", ":cur_agent"),      
        (val_mul, ":agent_hit_points", 10), #was 4

		  (try_begin),
			(call_script, "script_cf_agent_can_rout", ":cur_agent"),
			(agent_is_ally, ":cur_agent"),
			(val_add, ":agent_hit_points", "$battle_ratio"),
		  (else_try),
			(call_script, "script_cf_agent_can_rout", ":cur_agent"),
			(val_sub, ":agent_hit_points", "$battle_ratio"),
		  (try_end),

        #(val_mul, ":agent_hit_points", 10),
        (store_sub, ":stop_running_away_courage_score_limit", 0, ":agent_hit_points"), 
        (ge, ":agent_courage_score", ":stop_running_away_courage_score_limit"), #if (courage score > 3700 - agent hit points) and (agent is running away) then stop running away, average hit points : 50, average running away limit = 1700
        (agent_stop_running_away, ":cur_agent"),
        (agent_set_slot, ":cur_agent",  slot_agent_is_running_away, 0),
      (try_end),      
  ]), #ozan
]

Code:
# # Line added to clear scripted mode right before each (agent_start_running_away, ":cur_agent")
  # script_decide_run_away_or_not
  # Input: none
  # Output: none
  ("decide_run_away_or_not",
    [
      (store_script_param, ":cur_agent", 1),
      (store_script_param, ":mission_time", 2),
      


      (assign, ":force_retreat", 0),
      (agent_get_team, ":agent_team", ":cur_agent"),
      (agent_get_division, ":agent_division", ":cur_agent"),
      (try_begin),
        (lt, ":agent_division", 9), #static classes
        (team_get_movement_order, ":agent_movement_order", ":agent_team", ":agent_division"),
        (eq, ":agent_movement_order", mordr_retreat),
        (assign, ":force_retreat", 1),
      (try_end),

      (agent_get_slot, ":is_cur_agent_running_away", ":cur_agent", slot_agent_is_running_away),
      (try_begin),
      #(neg|agent_slot_eq, ":cur_agent", slot_agent_courage_score_bonus, -1),
        (eq, ":is_cur_agent_running_away", 0),
        (try_begin),
          (eq, ":force_retreat", 1),
          (agent_clear_scripted_mode, ":cur_agent"),   #handle scripted mode troops - motomataru
          (agent_start_running_away, ":cur_agent"),
        (call_script, "script_cf_agent_remove_from_square", ":cur_agent"), #jacobhinds infantry square script
          (agent_set_slot, ":cur_agent",  slot_agent_is_running_away, 1),
        #(agent_set_speed_limit, ":cur_agent", 60),
        (else_try),
          (ge, ":mission_time", 4), #first 4 seconds nobody runs away.
          (agent_get_slot, ":agent_courage_score", ":cur_agent",  slot_agent_courage_score),
          (store_agent_hit_points, ":agent_hit_points", ":cur_agent"),
          (val_mul, ":agent_hit_points", 10), #was 4

        (try_begin),
         (call_script, "script_cf_agent_can_rout", ":cur_agent"),
         (agent_is_ally, ":cur_agent"),
         (val_add, ":agent_hit_points", "$battle_ratio"),
        (else_try),
         (call_script, "script_cf_agent_can_rout", ":cur_agent"),
         (val_sub, ":agent_hit_points", "$battle_ratio"),
        (try_end),

          #(val_mul, ":agent_hit_points", 10),
          (store_sub, ":start_running_away_courage_score_limit", 300, ":agent_hit_points"), #was 3500
          (lt, ":agent_courage_score", ":start_running_away_courage_score_limit"), #if (courage score < 3500 - (agent hit points * 40)) and (agent is not running away) then start running away, average hit points : 50, average running away limit = 1500

          (agent_get_troop_id, ":troop_id", ":cur_agent"), #for now do not let heroes to run away from battle
          (neg|troop_is_hero, ":troop_id"),

        (try_begin),
           (call_script, "script_dplmc_store_troop_is_female", ":troop_id"), #shout "retreat" if male
           (neq, reg0, 1),
           (agent_play_sound, ":cur_agent", "snd_man_retreat"),
        (try_end),
                                
          (agent_clear_scripted_mode, ":cur_agent"),   #handle scripted mode troops - motomataru
          (agent_start_running_away, ":cur_agent"),
        (call_script, "script_cf_agent_remove_from_square", ":cur_agent"), #jacobhinds infantry square script
        #(agent_set_speed_limit, ":cur_agent", 60),
          (agent_set_slot, ":cur_agent",  slot_agent_is_running_away, 1),
        (try_end),


      (else_try),
      #(neg|agent_slot_eq, ":cur_agent", slot_agent_courage_score_bonus, -1),
        (neq, ":force_retreat", 1),
        (agent_get_slot, ":agent_courage_score", ":cur_agent",  slot_agent_courage_score),
        (store_agent_hit_points, ":agent_hit_points", ":cur_agent"),      
        (val_mul, ":agent_hit_points", 10), #was 4

        (try_begin),
         (call_script, "script_cf_agent_can_rout", ":cur_agent"),
         (agent_is_ally, ":cur_agent"),
         (val_add, ":agent_hit_points", "$battle_ratio"),
        (else_try),
         (call_script, "script_cf_agent_can_rout", ":cur_agent"),
         (val_sub, ":agent_hit_points", "$battle_ratio"),
        (try_end),

        #(val_mul, ":agent_hit_points", 10),
        (store_sub, ":stop_running_away_courage_score_limit", 0, ":agent_hit_points"), 
        (ge, ":agent_courage_score", ":stop_running_away_courage_score_limit"), #if (courage score > 3700 - agent hit points) and (agent is running away) then stop running away, average hit points : 50, average running away limit = 1700
        (agent_stop_running_away, ":cur_agent"),
        (agent_set_slot, ":cur_agent",  slot_agent_is_running_away, 0),
      (try_end),      
  ]), #ozan
]

This script calculates the battle ratio (i.e. balance of power) which is used in several scripts.

Code:
#script_cf_calculate_battle_ratio
#assigns battle size difference
#Inputs = none
#Outputs = none
	("cf_calculate_battle_ratio",
		[
			(assign, "$battle_ratio", 0),

			(assign, "$j_num_us_ready", 0),
			(assign, "$j_num_us_wounded", 0),
			(assign, "$j_num_us_routed", 0),
			(assign, "$j_num_us_dead", 0),

			(assign, "$j_num_allies_ready", 0),
			(assign, "$j_num_allies_wounded", 0),
			(assign, "$j_num_allies_routed", 0),
			(assign, "$j_num_allies_dead", 0),

			(assign, "$j_num_enemies_ready", 0),
			(assign, "$j_num_enemies_wounded", 0),
			(assign, "$j_num_enemies_routed", 0),
			(assign, "$j_num_enemies_dead", 0),

			#count and categorize agents (me, ally, enemy/wounded, dead, routed, alive)
			(try_for_agents, ":cur_agent"),
			  (agent_is_human, ":cur_agent"),
			  (agent_get_party_id, ":agent_party", ":cur_agent"),
			  (try_begin),
				(eq, ":agent_party", "p_main_party"),
				(try_begin),
				  (agent_is_alive, ":cur_agent"),
				  (val_add, "$j_num_us_ready", 1),
				(else_try),
				  (agent_is_wounded, ":cur_agent"),
				  (val_add, "$j_num_us_wounded", 1),
				(else_try),
				  (agent_is_routed, ":cur_agent"),
				  (val_add, "$j_num_us_routed", 1),
				(else_try),
				  (val_add, "$j_num_us_dead", 1),
				(try_end),
			  (else_try),
				(agent_is_ally, ":cur_agent"),
				(try_begin),
				  (agent_is_alive, ":cur_agent"),
				  (val_add, "$j_num_allies_ready", 1),
				(else_try),
				  (agent_is_wounded, ":cur_agent"),
				  (val_add, "$j_num_allies_wounded", 1),
				(else_try),
				  (agent_is_routed, ":cur_agent"),
				  (val_add, "$j_num_allies_routed", 1),
				(else_try),
				  (val_add, "$j_num_allies_dead", 1),
				(try_end),
			  (else_try),
				(try_begin),
				  (agent_is_alive, ":cur_agent"),
				  (val_add, "$j_num_enemies_ready", 1),
				(else_try),
				  (agent_is_wounded, ":cur_agent"),
				  (val_add, "$j_num_enemies_wounded", 1),
				(else_try),
				  (agent_is_routed, ":cur_agent"),
				  (val_add, "$j_num_enemies_routed", 1),
				(else_try),
				  (val_add, "$j_num_enemies_dead", 1),
				(try_end),
			  (try_end),
			(try_end),

			# ALLY STRENGTH
			(assign, ":ally_strength", 1),
			(val_add, ":ally_strength", "$j_num_enemies_routed"),
			(val_add, ":ally_strength", "$j_num_enemies_dead"),
			(val_add, ":ally_strength", "$j_num_enemies_wounded"),
			#ready is counted three times
			(val_add, ":ally_strength", "$j_num_us_ready"),
			(val_add, ":ally_strength", "$j_num_us_ready"),
			(val_add, ":ally_strength", "$j_num_us_ready"),
			(val_add, ":ally_strength", "$j_num_allies_ready"),	
			(val_add, ":ally_strength", "$j_num_allies_ready"),	
			(val_add, ":ally_strength", "$j_num_allies_ready"),	
	
			# ENEMY STRENGTH
			(assign, ":enemy_strength", 1),
			(val_add, ":enemy_strength", "$j_num_us_dead"),
			(val_add, ":enemy_strength", "$j_num_us_wounded"),
			(val_add, ":enemy_strength", "$j_num_us_routed"),
			(val_add, ":enemy_strength", "$j_num_allies_dead"),
			(val_add, ":enemy_strength", "$j_num_allies_wounded"),
			(val_add, ":enemy_strength", "$j_num_allies_routed"),
			#ready is counted three times
			(val_add, ":enemy_strength", "$j_num_enemies_ready"),
			(val_add, ":enemy_strength", "$j_num_enemies_ready"),
			(val_add, ":enemy_strength", "$j_num_enemies_ready"),

			#(A*10/E)
			#10/1 ratio = 10,000 morale penalty
			(store_mul, ":enemy_value", ":enemy_strength", battle_ratio_multiple),
			(val_div, ":enemy_value", ":ally_strength"), 

			#(E*10/A)
			(store_mul, ":ally_value", ":ally_strength", battle_ratio_multiple),
			(val_div, ":ally_value", ":enemy_strength"),

			#if enemy value is greater, use negative of that. 
			(try_begin),
				(gt, ":enemy_value", ":ally_value"),
				(val_sub, ":enemy_value", battle_ratio_multiple),
				(store_sub, ":enemy_value", 0, ":enemy_value"),
				(assign, "$battle_ratio", ":enemy_value"),
			(else_try),
				(val_sub, ":ally_value", battle_ratio_multiple),
				(assign, "$battle_ratio", ":ally_value"),
			(try_end),

			#100-10	= ~400
			#100-30	= ~150
			#100-50	= ~75

			#50-10	= ~100
			#50-30	= ~25
			#50-40	= ~10
		]
	),

This script alters the way agents react to fellow and enemy deaths.
Code:
  # script_apply_death_effect_on_courage_scores
  # Input: dead agent id, killer agent id
  # Output: none
  ("apply_death_effect_on_courage_scores",
    [
      (store_script_param, ":dead_agent_no", 1),
      (store_script_param, ":killer_agent_no", 2),



      (try_begin),
        (agent_is_human, ":dead_agent_no"),

        (try_begin),
          (agent_is_ally, ":dead_agent_no"),
          (assign, ":is_dead_agent_ally", 1),
        (else_try),
          (assign, ":is_dead_agent_ally", 0),
        (try_end),

        (try_for_agents, ":agent_no"),
          (agent_is_human, ":agent_no"),
          (agent_is_alive, ":agent_no"),

          (try_begin),
            (agent_is_ally, ":agent_no"),
            (assign, ":is_agent_ally", 1),
          (else_try),
            (assign, ":is_agent_ally", 0),
          (try_end),

          (try_begin), # each agent is effected by a killed agent positively if he is rival or negatively if he is ally.
            (neq, ":is_dead_agent_ally", ":is_agent_ally"),
            (assign, ":agent_delta_courage_score", 0),  # if killed agent is agent of rival side, add points to fear score #was 10 #after that, was 1
          (else_try),
            (assign, ":agent_delta_courage_score", -4), # if killed agent is agent of our side, decrease points from fear score #jacobhinds was -15, was -4 before battle_ratio overhaul, FEAR SCORE

            (agent_get_slot, ":dead_agent_was_running_away_or_not", ":dead_agent_no",  slot_agent_is_running_away), #look dead agent was running away or not.
            (try_begin),
              (eq, ":dead_agent_was_running_away_or_not", 1),
              (val_div, ":agent_delta_courage_score", 3), #was 3 # if killed agent was running away his negative effect on ally courage scores become much less. This added because
            (try_end),                                     # running away agents are easily killed and courage scores become very in a running away group after a time, and
          (try_end),                                       # they do not stop running away although they pass near a new powerful ally party.
          (agent_get_position, pos1, ":agent_no"),
          (get_distance_between_positions, ":dist", pos0, pos1),

          (try_begin), #if agent is the killer, give him x20 more courage than usual
            (eq, ":killer_agent_no", ":agent_no"),
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 20),
            (val_add, ":agent_courage_score", ":agent_delta_courage_score"),
            (agent_set_slot, ":agent_no", slot_agent_courage_score, ":agent_courage_score"),
          (try_end),

			#jacobhinds edit: prevent morale from getting too high
          (try_begin),
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
			(ge, ":agent_courage_score", max_morale),
            (agent_set_slot, ":agent_no", slot_agent_courage_score, max_morale),
          (try_end),

			
          (try_begin),
            (lt, ":dist", 100), #0-1 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 100), #was 150
          (else_try),
            (lt, ":dist", 200), #2 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 100), #was 120
          (else_try),
            (lt, ":dist", 300), #3 meter
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 100),
          (else_try),
            (lt, ":dist", 400), #4 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 90),
          (else_try),
            (lt, ":dist", 600), #5-6 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 80),
          (else_try),
            (lt, ":dist", 800), #7-8 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 70),
          (else_try),
            (lt, ":dist", 1000), #9-10 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 60),
          (else_try),
            (lt, ":dist", 1500), #11-15 meter
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 50),
          (else_try),
            (lt, ":dist", 2500), #16-25 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 40),
          (else_try),
            (lt, ":dist", 4000), #26-40 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 30),
          (else_try),
            (lt, ":dist", 6500), #41-65 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 20),
          (else_try),
            (lt, ":dist", 10000), #61-100 meters
            (agent_get_slot, ":agent_courage_score", ":agent_no", slot_agent_courage_score),
            (val_mul, ":agent_delta_courage_score", 10),
          (try_end),


          #this script exists to make denser formations more resistant to morale shocks. requires the advanced
          #formations setting in module.ini, otherwise just remove it.
          (try_begin),
			  (eq, ":is_agent_ally", ":is_dead_agent_ally"),
			  #reduce penalty for close ranked infantry: 10 / 10+[value]
			  (agent_get_team, ":team", ":agent_no"),
			  (agent_get_division, ":division", ":agent_no"),
			  (team_get_movement_order, ":order", ":team", ":division"),

			  (agent_get_slot, ":closeness", ":agent_no", slot_agent_rank_closeness),
			  (agent_get_slot, ":rank_depth", ":agent_no", slot_agent_rank_depth),

			  (store_add, ":value", ":closeness", ":rank_depth"),
			  (val_add, ":value", 7),

			  (try_begin),
				  (eq, ":order", mordr_charge),
				  (assign, ":value", 11),
			  (try_end),


			  (try_begin),
				(val_mul, ":agent_delta_courage_score", 7),
				(val_div, ":agent_delta_courage_score", ":value"),
			  (try_end),

			  #housekeeping
			  # (assign, reg5, ":value"),
			  # (assign, reg6, ":order"),
			  # (team_get_gap_distance, reg7, ":team", ":division"),
			  # (display_message, "@{reg5}, {reg7}"),

			  (val_add, ":agent_courage_score", ":agent_delta_courage_score"),
			  (agent_set_slot, ":agent_no", slot_agent_courage_score, ":agent_courage_score"),
		  (try_end),
        (try_end),
      (try_end),

			#housekeeping
			# (store_random_in_range, ":rand", 0, 10),
			# (try_begin),
				# (eq, ":rand", 1),
				# (assign, reg4, ":agent_delta_courage_score"),
				# (display_message, "@{reg4}"),
			# (try_end),
			#housekeeping

      ]), #ozan

This script allows you to retreat from battle if all enemies are routing/routed/killed. This allows players to leave the battle without suffering casualties from fleeing agents.
This is called by a mission template (mentioned below).
Code:
    #script_all_enemies_routed
    # This script checks that all enemies are routed or killed.
    # INPUT: none
    # OUTPUT: reg0 = enemies routed 0 or 1
    ("all_enemies_routed", [
      (assign, reg0, 0),

      (try_for_agents, ":agent"),
		(neg|agent_is_ally, ":agent"),
		(agent_is_alive, ":agent"),
		(agent_is_human, ":agent"),
		(agent_get_slot, ":routing", ":agent", slot_agent_is_running_away),
		(neq, ":routing", 1),
		(val_add, reg0, 1),
      (try_end),
	]),

#scipt_cf_agent_can_rout
#determines whether a troop can rout or not (more than 10% casualties)
#input: cur_agent
("cf_agent_can_rout", [
	(store_script_param, ":agent", 1),

	(try_begin),
		(agent_is_ally, ":agent"),
		#count ready allies
		(assign, ":ready", "$j_num_us_ready"),
		(val_add, ":ready", "$j_num_allies_ready"),

		#count deady allies
		(store_add, ":deady", "$j_num_us_wounded", "$j_num_us_routed"),
		(val_add, ":deady", "$j_num_us_dead"),
		(val_add, ":deady", "$j_num_allies_wounded"),
		(val_add, ":deady", "$j_num_allies_routed"),
		(val_add, ":deady", "$j_num_allies_dead"),
		(val_mul, ":deady", 10),
	(else_try),
		#count ready enemies
		(assign, ":ready", "$j_num_enemies_ready"),

		#count deady enemies
		(store_add, ":deady", "$j_num_enemies_wounded", "$j_num_enemies_routed"),
		(val_add, ":deady", "$j_num_enemies_dead"),
		(val_mul, ":deady", 10),
	(try_end),

	# (display_message, "@agents cannot rout"),

	(gt, ":deady", ":ready"),

	# (display_message, "@agents can rout"),

]),

module_mission_templates.py

Go through the file and comment out this trigger:

Code:
 (3, 0, 0, [
           (call_script, "script_apply_effect_of_other_people_on_courage_scores"),
              ], []), #calculating and applying effect of people on others courage scores

If you want morale to work in any other mission template (custom battles are a blast with this), place this somewhere within the brackets of, for example quick_battle_battle:

Code:
		(3, 0, 0, [
          (try_for_agents, ":agent_no"),
            (agent_is_human, ":agent_no"),
            (agent_is_alive, ":agent_no"),          
            (gt, ":agent_no", 0),       #jacobhinds edit; prevents MT errors
            (store_mission_timer_a,":mission_time"),
            (ge,":mission_time",3),          
            (call_script, "script_decide_run_away_or_not", ":agent_no", ":mission_time"),
          (try_end),          
              ], []), #controlling courage score and if needed deciding to run away for each agent


This code recovers the morale of routing soldiers by 30 every 3 seconds. This allows routing soldiers to return to the battlefield given enough time. Place this in lead_charge and any other mission templates you might want this in.
Code:
#routing soldiers recover morale by 30 every 3 seconds.
jacobhinds_morale_recover = (
	3, 0, 0, [
    (eq, "$g_battle_won", 1)], #no recovery once battle is over
	[
		(try_for_agents, ":agent"),
			(agent_is_alive, ":agent"),
			(agent_is_human, ":agent"),
			(gt, ":agent", 1),

			(agent_get_slot, ":courage", ":agent", slot_agent_courage_score),

			#Morale recovery based on number of ready troops on the battlefield, routing or not (prevents rush morale shock exploit)
			(assign, ":strength", 0),
			(try_begin),
				(agent_is_ally, ":agent"),
				(store_add, ":strength", "$j_num_us_ready", "$j_num_allies_ready"),
			(else_try),
				(assign, ":strength", "$j_num_enemies_ready"),
			(try_end),

			#If battle ratio/2 is higher, use that
			#allows winning army to rally troops
			#remember to check for possible reinforcement exploits.
			(try_begin),
				(agent_is_ally, ":agent"),
				(store_div, ":half_ratio", "$battle_ratio", 2),
				(gt, ":half_ratio", ":strength"),
				(assign, ":strength", "$battle_ratio"),
			(else_try),
				(store_div, ":half_ratio", "$battle_ratio", -2),
				(gt, ":strength", ":half_ratio"),
				(assign, ":strength", "$battle_ratio"),
			(try_end),

			#tweaking
			(val_mul, ":strength", 4),
			(val_div, ":strength", 1),

			(val_add, ":courage", ":strength"),
			(agent_set_slot, ":agent", slot_agent_courage_score, ":courage"),
		(try_end),
	],
						)

These are some more codes which sort out the battle ratio and apply it to agents when they spawn.

Code:
jacobhinds_battle_ratio_init = ( 
#proportionalises battle ratio by adding ABS of ratio to both sides when they spawn
	0, 0, ti_once, [],
	[
		(call_script, "script_cf_calculate_battle_ratio"),
	],
						)

jacobhinds_battle_ratio_spawn_bonus = ( 
#proportionalises battle ratio by adding absolute value of ratio to both sides when they spawn
	ti_on_agent_spawn, 0.1, 0, [],
	[
		(store_trigger_param_1, ":agent"),

		(agent_get_slot, ":courage", ":agent", slot_agent_courage_score),
		(try_begin),
			(ge, "$battle_ratio", 0),
			(val_add, ":courage", "$battle_ratio"),
		(else_try),
			(val_sub, ":courage", "$battle_ratio"),
		(try_end),
		(agent_set_slot, ":agent", slot_agent_courage_score, ":courage"),
	],
						)

jacobhinds_battle_ratio_calculate = (
	10, 0, 0, [],
	[
		(call_script, "script_cf_calculate_battle_ratio"),
	],
						)

These triggers work out the rank depth and formation closeness of a given division (no easy workaround for this because there is currently no operation to retrieve the actual values).
This is used in script_apply_death_effect_on_courage_scores. Tighter formations get reduced morale penalties for nearby casualties.

Code:
agent_assign_rank_depth = (ti_on_order_issued, 0, 0, [], [

    (store_trigger_param_1,":order"),
    (store_trigger_param_2,":agent"),

	(is_between, ":order", mordr_form_1_ranks, mordr_form_5_ranks_plus_one),

	(val_sub, ":order", mordr_form_0_rank),
	(agent_set_slot, ":agent", slot_agent_rank_depth, ":order"),

	])

agent_assign_rank_closeness = (ti_on_order_issued, 0, 0, [], [

    (store_trigger_param_1,":order"),
    (store_trigger_param_2,":agent"),

	(agent_get_slot, ":closeness", ":agent", slot_agent_is_running_away),
	(try_begin),
		(eq, ":order", mordr_stand_closer),
		(val_add, ":closeness", 1),
	(else_try),
		(eq, ":order", mordr_spread_out),
		(val_sub, ":closeness", 1),
	(try_end),

	(val_clamp, ":closeness", -1, 3), #just an assumption based on observation; the loosest formation is only 1 below the defualt
	(agent_set_slot, ":agent", slot_agent_rank_closeness, ":closeness"),

	])

This code makes ranged units rout much more readily when they're in melee combat. This is probably only applicable to me since I overhauled the AI and made cavalry a lot less overpowered, and felt they needed a niche role in mopping up skirmishers. In native I can imagine this would be nothing short of annoying because nobody uses formations and the AI has a tendency to just throw cavalry at you, which would rout most of your archers before they could retaliate.

If you don't want this, simply remove this trigger.

Code:
jacobhinds_ranged_melee_morale_penalty = (
#ranged units get morale penalties for being in melee, eventually routing
#for now defined by guarantee_ranged
	1, 0, 0, [],
	[
		(try_for_agents, ":agent_no"),
			(agent_is_human, ":agent_no"),
			(agent_is_alive, ":agent_no"),
			(agent_get_troop_id, ":troop_id", ":agent_no"),
			(troop_is_guarantee_ranged, ":troop_id"),

			#put your exemptions here (e.g. javelinmen, heavy archers, peltast-types, grenadiers, heavy infantry --  i.e. any unit which is guarantee_ranged which you don't want to rout in melee)
			(neq, ":troop_id", "trp_swadian_recruit"),

			(agent_get_simple_behavior, ":behaviour", ":agent_no"),
			(eq, ":behaviour", aisb_melee),

			#is troop in a square?
			#calculate square id
			(agent_get_team, ":team", ":agent_no"),
			(agent_get_division, ":division", ":agent_no"),

			(store_mul, ":square_id", ":team", 10),
			(val_add, ":square_id", ":division"),
			(val_add, ":square_id", 1),

			(troop_slot_eq, "trp_jacobhinds_form_musket_square_size", ":square_id", 0),

			#closer ranks means less morale shock
			(agent_get_slot, ":rank_depth", ":agent_no", slot_agent_rank_depth),
			(val_mul, ":rank_depth", 200),

			(agent_ai_get_cached_enemy, ":enemy_agent", ":agent_no", 0),
			(agent_slot_eq, ":enemy_agent", slot_agent_is_running_away, 0),

			(agent_get_slot, ":courage", ":agent_no", slot_agent_courage_score),
			(val_sub, ":courage", 3000),
			(val_add, ":courage", ":rank_depth"),
			(agent_set_slot, ":agent_no", slot_agent_courage_score, ":courage"),
		(try_end),

	],
						)

This code allows you to retreat/win a battle against a routing army as if all the enemies had been killed.


In common_battle_check_victory_condition, Comment out:
Code:
(all_enemies_defeated, 5),

And replace it with:
Code:
(call_script, "script_all_enemies_routed"),
(eq, reg0, 0),

This can also be done in custom_battle_check_victory_condition, where you'll have to replace (all_enemies_defeated, 2).
Obviously you'll need to have script_all_enemies_routed (mentioned above) in module_scripts.



This is a debug trigger; view the log to see the courage scores of agents. Doesn't cause much lag, but unless you're looking to tweak further this isn't necessary. Place this at the beginning of the file with all the other preset triggers, then add jacobhinds_rout_check, (with the comma) to whichever mission templates you want to include morale in.

Code:
jacobhinds_rout_check = (
   5, 0, 0, [],
   [
      (try_for_agents, ":agent"),
         (agent_is_alive, ":agent"),
         (agent_is_human, ":agent"),
         (agent_get_slot, ":courage", ":agent", slot_agent_courage_score),
         (agent_get_slot, ":routing", ":agent", slot_agent_is_running_away),
         (assign, reg0, ":courage"),
         (assign, reg1, "$battle_ratio"),
         (assign, reg2, ":routing"),
         (str_store_string, s1, "@{reg2?routing:steady}"),
         (agent_get_troop_id, ":troop", ":agent"),
         (str_store_troop_name, s0, ":troop"),
         (display_message, "@Fear = {reg0} {s0} ({s1})"),
      (try_end),
   ],
                  )


And finally, with all these triggers placed in the mission_templates file, you can now implement these triggers in mission templates of your choosing. Here is a list for your convenience:

Code:
jacobhinds_morale_triggers = [
	agent_assign_rank_depth,
	agent_assign_rank_closeness,
	jacobhinds_battle_ratio_init,
	jacobhinds_battle_ratio_spawn_bonus,
	jacobhinds_battle_ratio_calculate,
	jacobhinds_morale_recover,
	jacobhinds_ranged_melee_morale_penalty,

jacobhinds_morale_triggers can then be appended to any mission templates you want battle morale to be active in.

module_constants.py

The first 4 values are merely for convenience of tweaking the effects of the battle ratio. The rest are slots used in some of the scripts.
Code:
battle_ratio_multiple = 7000
max_morale = 35000
max_ratio = max_morale/2
initial_morale = 10000

slot_agent_courage_score_bonus	  = 27
slot_agent_rank_depth			  = 28
slot_agent_rank_closeness		  = 29
 
Cool! Thanks for this, I will see if it can be put into the next version of Perisno.
 
I've updated the code. There's a mission template, a mission template edit, and a script.

One allows routing troops to recover morale (sudden morale shocks aren't as likely to cause instant victory), while the other two allow the player to claim victory if all all the remaining enemy agents are routing.
 
Fixed it. The new code is bugged. Should look like this:

  (
  3, 0, 0, [],
    [
        (try_for_agents, ":agent"),
            (agent_is_alive, ":agent"),
            (agent_is_human, ":agent"),
            (agent_get_slot, ":routing", ":agent", slot_agent_is_running_away),
            (eq, ":routing", 1),
            (agent_get_slot, ":courage", ":agent", slot_agent_courage_score),
            (val_add, ":courage", 100),
            (agent_set_slot, ":agent", slot_agent_courage_score, ":courage"),
        (try_end),
  ]),
 
jacobhinds said:
Probably, but I'm not an expert on how saves handle new global variables being thrust on them.

as long as the variables are assigned before being used (i.e: they are not assigned at the game_start script, which would not be run a 2nd time on a script), the save is compatible. however, any saves you made with these new variables will not be compatible with normal native.
 
I've re-overhauled the morale system once again (this is about the fifth time for me). There are a couple of small bugs in the current code, but the main issue is that the tide of battle doesn't play a big enough part in getting soldiers to rout. As a result, I've recoded the script that deals with the battle ratio, giving a soft limit of around 5:1 odds before everyone on that team begins to flee. Allied deaths and routs are taken into account as well, which should allow an outnumbered army to turn the tide and rout a larger force if they do well enough.
I'm currently experimenting with making the equation work realistically -- at the moment, if A = ally strength and E = enemy strength, it's (√A-√E)^3 + (A-E), which is then added to the minimum amount of morale required to get an agent to rout. Adjusted w.r.t. teams, of course.

This of course makes massive armies more volatile and smaller ones more stable, so I've revamped the "morale recovery" script -- every 3 seconds, every agent gets a bonus to morale that is equivalent either to the number of friendly allies on the field, or the battle ratio -- whichever is larger. That way a 300 v 300 battle is more prone to morale shocks, but is more likely to recover from a chain rout. Similarly, if you rout a small group of bandits, they're probably not coming back. This will require quite a lot of testing to get right.

Not sure as to how long this will take to balance, but when it's done I might make it compatible with modmerger as there are quite a few fiddly script replacements.
 
jacobhinds said:
I've re-overhauled the morale system once again (this is about the fifth time for me). There are a couple of small bugs in the current code, but the main issue is that the tide of battle doesn't play a big enough part in getting soldiers to rout. As a result, I've recoded the script that deals with the battle ratio, giving a soft limit of around 5:1 odds before everyone on that team begins to flee. Allied deaths and routs are taken into account as well, which should allow an outnumbered army to turn the tide and rout a larger force if they do well enough.
I'm currently experimenting with making the equation work realistically -- at the moment, if A = ally strength and E = enemy strength, it's (√A-√E)^3 + (A-E), which is then added to the minimum amount of morale required to get an agent to rout. Adjusted w.r.t. teams, of course.

This of course makes massive armies more volatile and smaller ones more stable, so I've revamped the "morale recovery" script -- every 3 seconds, every agent gets a bonus to morale that is equivalent either to the number of friendly allies on the field, or the battle ratio -- whichever is larger. That way a 300 v 300 battle is more prone to morale shocks, but is more likely to recover from a chain rout. Similarly, if you rout a small group of bandits, they're probably not coming back. This will require quite a lot of testing to get right.

Not sure as to how long this will take to balance, but when it's done I might make it compatible with modmerger as there are quite a few fiddly script replacements.

I'm not sure if you fixed it, but I had to edit your script to ensure it wasn't checking horses for morale, as battles wouldn't end.
 
Back
Top Bottom