PYTHON SCRIPT/SCHEME EXCHANGE

Users who are viewing this thread

Armor Damage Script (Singleplayer)





Step 1: Set up per-agent item durability via slots and fill them in ti_on_agent_spawn

In module_constants
Python:
# First among your agent slots
agent_armor_dur_head = 100 # whatever number is open for you
agent_armor_dur_hand = agent_armor_dur_head + 1
agent_armor_dur_feet = agent_armor_dur_head + 2
agent_armor_dur_main = agent_armor_dur_head + 3

# and in your item_slots
slot_damaged_variant = 100 # Or, again, whatever you have available

In mission_templates, attached to a common ti_on_agent_spawn
Python:
agent_spawn_effects = (
    ti_on_agent_spawn, 0, 0, [], [
    (store_trigger_param_1, ":agent"),

    (try_for_range, ":equipment_slot", 4, 8),
        (agent_get_item_slot, ":equipment", ":agent", ":equipment_slot"),
        (gt, ":equipment", 0), # Agent does have an armor equipped here

        (item_get_hit_points, ":hp", ":equipment"),

        (try_begin),
            (eq, ":equipment_slot", 4), # Head
            (agent_set_slot, ":agent", agent_armor_dur_head, ":hp"),
        (else_try),
            (eq, ":equipment_slot", 5),    # Body
            (agent_set_slot, ":agent", agent_armor_dur_main, ":hp"),
        (else_try),
            (eq, ":equipment_slot", 6),    # Leg
            (agent_set_slot, ":agent", agent_armor_dur_feet, ":hp"),
        (else_try),
            # Would be 7, Hand
            (agent_set_slot, ":agent", agent_armor_dur_hand, ":hp"),
        (try_end),
    (try_end),

    ]),

As you can tell, this assumes you are declaring hitpoints for EACH AND EVERY ARMOR, if you do not, every hit will be shattering these poor things.

You could also just make a flat hitpoint level by changing

(item_get_hit_points, ":hp", ":equipment"),
to
(assign, ":hp", 100),

Also you need to set up a script inside game_start and game_quick_start to assign the damaged variants to their original forms

Step 2: Locational Damage, Breaking and Swapping -
First, we need to determine where damage was on the agent.

In mission_templates, attached to a common ti_on_agent_hit
Python:
common_damage_system= (
ti_on_agent_hit, 0, 0, [],
[
    (set_fixed_point_multiplier, 100),
    (store_trigger_param, ":agent", 1),
    (store_trigger_param, ":attacker", 2),
    (store_trigger_param, ":damage", 3),
    (store_trigger_param, ":bodypart", 4),
    # (store_trigger_param, ":missile", 5),
    (agent_get_troop_id, ":troop", ":agent"),
    (agent_get_troop_id, ":attacker_troop", ":attacker"),
    (assign, ":attacker_weapon", reg0),

    (agent_is_alive, ":agent"),
    (agent_is_human, ":agent"),

    (store_agent_hit_points, ":health_remaining_actual", ":agent", 1),

    (store_troop_health, ":current_health", ":troop"),
    (troop_set_health, ":troop", 100),
    (store_troop_health, ":max_health_actual", ":troop", 1),
    (troop_set_health, ":troop", ":current_health"),

    (try_begin),
        (gt, ":attacker_weapon", 0),
        (item_get_swing_damage_type, ":damage_type", ":attacker_weapon"),
        # I dunno how to determine which attack was unleashed, but this will work
        # We can use this to make certain damage types, such as blunt, do more damage
        # to armor durability vs things like pierce which should nearly pass through
    (else_try),
        (assign, ":damage_type", 2), # If you don't have a weapon it's fists.
    (try_end),

    (try_begin),
        (eq, ":damage_type", 0),    # Cut
        (assign, ":dampen", 30),
    (else_try),
        (eq, ":damage_type", 1),    # Pierce
        (assign, ":dampen", 35),
    (else_try),
        (eq, ":damage_type", 2),    # Blunt
        (assign, ":dampen", 15),
    (try_end),

    (store_div, ":durability_hit", ":damage", ":dampen"),

    (agent_get_slot, ":head_durability", ":agent", agent_armor_dur_head),
    (agent_get_slot, ":hand_durability", ":agent", agent_armor_dur_hand),
    (agent_get_slot, ":feet_durability", ":agent", agent_armor_dur_feet),
    (agent_get_slot, ":main_durability", ":agent", agent_armor_dur_main),

    (agent_get_item_slot, ":head_armor", ":agent", 4),
    (agent_get_item_slot, ":hand_armor", ":agent", 7),
    (agent_get_item_slot, ":feet_armor", ":agent", 6),
    (agent_get_item_slot, ":main_armor", ":agent", 5),

    (item_get_slot, ":head_damaged_var", ":head_armor", slot_damaged_variant),
    (item_get_slot, ":hand_damaged_var", ":hand_armor", slot_damaged_variant),
    (item_get_slot, ":feet_damaged_var", ":feet_armor", slot_damaged_variant),
    (item_get_slot, ":main_damaged_var", ":main_armor", slot_damaged_variant),

    (try_begin),
        (this_or_next|eq, ":bodypart", 0), # Guts
        (is_between, ":bodypart", 7, 9), # Spine and Thorax

        (gt, ":main_armor", 0), # Has Armor

        (val_sub, ":main_durability", ":durability_hit"),
        (val_max, ":main_durability", 0), # Durability cannot be negative

        (agent_set_slot, ":agent", agent_armor_dur_main, ":main_durability"),
        (eq, ":main_durability", 0),
        (agent_unequip_item, ":agent", ":main_armor", 5),
        (neq, ":main_damaged_var", 0),
        (item_get_hit_points, ":hp", ":main_damaged_var"),
        (agent_set_slot, ":agent", agent_armor_dur_main, ":hp"),
        (agent_equip_item, ":agent", ":main_damaged_var", 5),
    (else_try),
        (is_between, ":bodypart", 1, 7), # Legs

        (gt, ":feet_armor", 0), # Has Armor

        (val_sub, ":feet_durability", ":durability_hit"),
        (val_max, ":feet_durability", 0), # Durability cannot be negative

        (agent_set_slot, ":agent", agent_armor_dur_feet, ":feet_durability"),
        (eq, ":feet_durability", 0),
        (agent_unequip_item, ":agent", ":feet_armor", 4),
        (neq, ":feet_damaged_var", 0),
        (item_get_hit_points, ":hp", ":feet_damaged_var"),
        (agent_set_slot, ":agent", agent_armor_dur_feet, ":hp"),
        (agent_equip_item, ":agent", ":feet_damaged_var", 4),
    (else_try),
        (eq, ":bodypart", 9), # head

        (gt, ":head_armor", 0), # Has Armor

        (val_sub, ":head_durability", ":durability_hit"),
        (val_max, ":head_durability", 0), # Durability cannot be negative

        (agent_set_slot, ":agent", agent_armor_dur_head, ":head_durability"),
        (eq, ":head_durability", 0),
        (agent_unequip_item, ":agent", ":head_armor", 5),
        (neq, ":head_damaged_var", 0),
        (item_get_hit_points, ":hp", ":head_damaged_var"),
        (agent_set_slot, ":agent", agent_armor_dur_head, ":hp"),
        (agent_equip_item, ":agent", ":head_damaged_var", 5),
    (else_try),
        (this_or_next|is_between, ":bodypart", 12, 14), # Left Forearms and Hand
        (gt, ":bodypart", 17), # Right Forearms and Hand

        (gt, ":hand_armor", 0), # Has Armor

        (val_sub, ":hand_durability", ":durability_hit"),
        (val_max, ":hand_durability", 0), # Durability cannot be negative

        (agent_set_slot, ":agent", agent_armor_dur_hand, ":hand_durability"),
        (eq, ":hand_durability", 0),
        (agent_unequip_item, ":agent", ":hand_armor", 5),
        (neq, ":hand_damaged_var", 0),
        (item_get_hit_points, ":hp", ":hand_damaged_var"),
        (agent_set_slot, ":agent", agent_armor_dur_hand, ":hp"),
        (agent_equip_item, ":agent", ":hand_damaged_var", 5),
    (try_end),

    (set_trigger_result, ":damage"),
])
That bad boy will also handle breaking, and equipping a damaged variant if applicable


Tweaks I would do to make it more satisfying:
Store the location of the bone, make a particle burst at the location.
Create a physics item for the armor in the mod and allow it to pop off into the scene if destroyed.
This could probably be nested in a way to pretty it up and shorten the script, but this works for readability.
You could also simply move the item quality modifier down a stage and include various levels of disrepair that way, while keeping your items nice and tidy
 
Last edited:
This is from out friend Veni Vidi Vici#9348 on the modding Discord.

Convert Angles to Floating Point Numbers

Essentially, we can rotate positions by floating point values, but, we have no operation that can get a positions rotation in fixed point, so this will convert rotational positional data into fixed point to allow for more exact mathematics for rotation.

Python:
     #("script_rvs_get_floating_angles", pos##),
     # ATTENTION!!!
     # This script is only working correctly when either X or Y rotation is approx.= 0. In other cases it produces different output which deviates from normal angles..
     # by +-, 90, 180, whatever degrees. I'm not very comfortable with rotation matrixes or whatever that is so if you could help me improve the script i would appreciate that :3
     # Input: any pos## for calculation
     # Output: reg41: Pitch (Y) reg42: Yaw (Z) reg43: Roll (X)
     # Alternative calc output: reg44: Pitch (Y) reg45: Yaw (Z) reg46: Roll (X)
  ("rvs_get_floating_angles",
    [
      (store_script_param, ":input_pos", 1),

      (set_fixed_point_multiplier,1000000),

      (copy_position,pos52,":input_pos"),
      (position_move_x,pos52,10000),
      (position_get_y,":root_y",":input_pos"),
      (position_get_y,":vector_y",pos52),
      (position_get_x,":root_x",":input_pos"),
      (position_get_x,":vector_x",pos52),
      (position_get_z,":root_z",":input_pos"),
      (position_get_z,":vector_z",pos52),
      (store_sub,":x_offset",":vector_x",":root_x"),
      (store_sub,":y_offset",":vector_y",":root_y"),
      (store_sub,":z_offset",":vector_z",":root_z"),
      (store_atan2,":yaw",":y_offset",":x_offset"),

      (store_div,":reduced_x",":x_offset",1000000),
      (store_div,":reduced_y",":y_offset",1000000),
      (store_mul,":sq_x",":reduced_x",":reduced_x"),
      (store_mul,":sq_y",":reduced_y",":reduced_y"),
      (store_add,":sq_sum",":sq_x",":sq_y"),
      (convert_to_fixed_point,":sq_sum"),
      (store_sqrt,":hyp_xy",":sq_sum"), # Pythagor
      (store_atan2,":pitch",":hyp_xy",":z_offset"),
      (store_sub,":pitch",90000000,":pitch"),

      (val_mul,":y_offset",1000),
      (val_div,":hyp_xy",1000),
      (store_div,":y_hypxy",":y_offset",":hyp_xy"),
      (store_asin,":yaw2",":y_hypxy"),
   #   (val_div,":y_offset",1000),
   #   (val_mul,":hyp_xy",1000),

      (convert_to_fixed_point,":z_offset"),
      (store_div,":z_magnitude",":z_offset",100000000),
      (store_asin,":pitch2",":z_magnitude"),
      (convert_from_fixed_point,":z_offset"),

      (try_begin),# Remove this bul****tery if you prefer <-180|180> angles over <0|360>
       (lt,":yaw",0),
       (store_add,":yaw",360000000,":yaw"),
      (try_end),
      (try_begin),
       (lt,":yaw2",0),
       (store_add,":yaw2",360000000,":yaw2"),
      (try_end),
      (val_mul,":pitch",-1),
      (val_mul,":pitch2",-1),

      (assign,reg41,":pitch"),
      (assign,reg42,":yaw"),
      (assign,reg44,":pitch2"),
      (assign,reg45,":yaw2"),

      # Debuggery
    #  (assign,reg45,":x_offset"),
    #  (assign,reg46,":y_offset"),
    #  (assign,reg47,":z_offset"),
    #  (assign,reg48,":hyp_xy"),
    #  (assign,reg49,":y_hypxy"),
    #  (assign,reg50,":z_magnitude"),
    #
    #  (assign,reg51,":root_x"),
    #  (assign,reg52,":root_y"),
    #  (assign,reg53,":root_z"),
    #  (assign,reg54,":vector_x"),
    #  (assign,reg55,":vector_y"),
    #  (assign,reg56,":vector_z"),
    #
    #  (assign,reg39,":sq_x"),
    #  (assign,reg40,":sq_y"),

      # Now for X!

      (copy_position,pos52,":input_pos"),
      (position_move_y,pos52,10000),
      (position_get_y,":root_y",":input_pos"),
      (position_get_y,":vector_y",pos52),
      (position_get_x,":root_x",":input_pos"),
      (position_get_x,":vector_x",pos52),
      (position_get_z,":root_z",":input_pos"),
      (position_get_z,":vector_z",pos52),
      (store_sub,":x_offset",":vector_x",":root_x"),
      (store_sub,":y_offset",":vector_y",":root_y"),
      (store_sub,":z_offset",":vector_z",":root_z"),

      (store_div,":reduced_x",":x_offset",1000000),
      (store_div,":reduced_y",":y_offset",1000000),
      (store_mul,":sq_x",":reduced_x",":reduced_x"),
      (store_mul,":sq_y",":reduced_y",":reduced_y"),
      (store_add,":sq_sum",":sq_x",":sq_y"),
      (convert_to_fixed_point,":sq_sum"),
      (store_sqrt,":hyp_xy",":sq_sum"), # Pythagor
      (store_atan2,":roll",":hyp_xy",":z_offset"),
      (store_sub,":roll",90000000,":roll"),

      (convert_to_fixed_point,":z_offset"),
      (store_div,":z_magnitude",":z_offset",100000000),
      (store_asin,":roll2",":z_magnitude"),
      (convert_from_fixed_point,":z_offset"),

      (try_begin),# Remove this bul****tery if you prefer <-180|180> angles over <0|360>
       (lt,":roll",0),
       (store_add,":roll",360000000,":roll"),
      (try_end),
      (try_begin),
       (lt,":roll2",0),
       (store_add,":roll2",360000000,":roll2"),
      (try_end),

      (assign,reg43,":roll"),
      (assign,reg46,":roll2"),
    ]),

My only tweak was divorcing it from a specific pos## register and allowing it to work with whatever you choose to input into it. If you can improve this, pop into the #wb-scripts channel of the Discord and lets hammer out an updated version.

An update from Vetrogor!
Vetrogor said:
Tweaked version of calculating float_point_rotation of position. I added second param output_fpm which convert rotation to desired fixed_point_multiplier.
Python:
  #("script_rvs_get_floating_angles", pos##),
  # ATTENTION!!!
  # This script is only working correctly when either X or Y rotation is approx.= 0. In other cases it produces different output which deviates from normal angles..
  # by +-, 90, 180, whatever degrees. I'm not very comfortable with rotation matrixes or whatever that is so if you could help me improve the script i would appreciate that :3
  # Input: <arg1> - any pos## for calculation
  #        <arg2> - output fpm
  # Output: reg41: Pitch (Y) reg42: Yaw (Z) reg43: Roll (X)
  # Alternative calc output: reg44: Pitch (Y) reg45: Yaw (Z) reg46: Roll (X)
  ("rvs_get_floating_angles", [
    (store_script_param, ":input_pos", 1),
    (store_script_param, ":output_fpm", 2),

    (assign, ":saved_fpm", 1),
    (convert_to_fixed_point, ":saved_fpm"),
    (set_fixed_point_multiplier,1000000),

    (copy_position,pos52,":input_pos"),
    (position_move_x,pos52,10000),
    (position_get_y,":root_y",":input_pos"),
    (position_get_y,":vector_y",pos52),
    (position_get_x,":root_x",":input_pos"),
    (position_get_x,":vector_x",pos52),
    (position_get_z,":root_z",":input_pos"),
    (position_get_z,":vector_z",pos52),
    (store_sub,":x_offset",":vector_x",":root_x"),
    (store_sub,":y_offset",":vector_y",":root_y"),
    (store_sub,":z_offset",":vector_z",":root_z"),
    (store_atan2,":yaw",":y_offset",":x_offset"),

    (store_div,":reduced_x",":x_offset",1000000),
    (store_div,":reduced_y",":y_offset",1000000),
    (store_mul,":sq_x",":reduced_x",":reduced_x"),
    (store_mul,":sq_y",":reduced_y",":reduced_y"),
    (store_add,":sq_sum",":sq_x",":sq_y"),
    (convert_to_fixed_point,":sq_sum"),
    (store_sqrt,":hyp_xy",":sq_sum"), # Pythagor
    (store_atan2,":pitch",":hyp_xy",":z_offset"),
    (store_sub,":pitch",90000000,":pitch"),

    (val_mul,":y_offset",1000),
    (val_div,":hyp_xy",1000),
    (store_div,":y_hypxy",":y_offset",":hyp_xy"),
    (store_asin,":yaw2",":y_hypxy"),
    #(val_div,":y_offset",1000),
    #(val_mul,":hyp_xy",1000),

    (convert_to_fixed_point,":z_offset"),
    (store_div,":z_magnitude",":z_offset",100000000),
    (store_asin,":pitch2",":z_magnitude"),
    (convert_from_fixed_point,":z_offset"),

    (try_begin),# Remove this bul****tery if you prefer <-180|180> angles over <0|360>
      (lt,":yaw",0),
      (store_add,":yaw",360000000,":yaw"),
    (try_end),
    (try_begin),
      (lt,":yaw2",0),
      (store_add,":yaw2",360000000,":yaw2"),
    (try_end),
    (val_mul,":pitch",-1),
    (val_mul,":pitch2",-1),

    (assign,reg41,":pitch"),
    (assign,reg42,":yaw"),
    (assign,reg44,":pitch2"),
    (assign,reg45,":yaw2"),

    # Debuggery
    #  (assign,reg45,":x_offset"),
    #  (assign,reg46,":y_offset"),
    #  (assign,reg47,":z_offset"),
    #  (assign,reg48,":hyp_xy"),
    #  (assign,reg49,":y_hypxy"),
    #  (assign,reg50,":z_magnitude"),
    #
    #  (assign,reg51,":root_x"),
    #  (assign,reg52,":root_y"),
    #  (assign,reg53,":root_z"),
    #  (assign,reg54,":vector_x"),
    #  (assign,reg55,":vector_y"),
    #  (assign,reg56,":vector_z"),
    #
    #  (assign,reg39,":sq_x"),
    #  (assign,reg40,":sq_y"),

    # Now for X!

    (copy_position,pos52,":input_pos"),
    (position_move_y,pos52,10000),
    (position_get_y,":root_y",":input_pos"),
    (position_get_y,":vector_y",pos52),
    (position_get_x,":root_x",":input_pos"),
    (position_get_x,":vector_x",pos52),
    (position_get_z,":root_z",":input_pos"),
    (position_get_z,":vector_z",pos52),
    (store_sub,":x_offset",":vector_x",":root_x"),
    (store_sub,":y_offset",":vector_y",":root_y"),
    (store_sub,":z_offset",":vector_z",":root_z"),

    (store_div,":reduced_x",":x_offset",1000000),
    (store_div,":reduced_y",":y_offset",1000000),
    (store_mul,":sq_x",":reduced_x",":reduced_x"),
    (store_mul,":sq_y",":reduced_y",":reduced_y"),
    (store_add,":sq_sum",":sq_x",":sq_y"),
    (convert_to_fixed_point,":sq_sum"),
    (store_sqrt,":hyp_xy",":sq_sum"), # Pythagor
    (store_atan2,":roll",":hyp_xy",":z_offset"),
    (store_sub,":roll",90000000,":roll"),

    (convert_to_fixed_point,":z_offset"),
    (store_div,":z_magnitude",":z_offset",100000000),
    (store_asin,":roll2",":z_magnitude"),
    (convert_from_fixed_point,":z_offset"),

    (try_begin),# Remove this bul****tery if you prefer <-180|180> angles over <0|360>
     (lt,":roll",0),
     (store_add,":roll",360000000,":roll"),
    (try_end),
    (try_begin),
     (lt,":roll2",0),
     (store_add,":roll2",360000000,":roll2"),
    (try_end),

    (assign,reg43,":roll"),
    (assign,reg46,":roll2"),

    (val_mul, reg41, ":output_fpm"), (convert_from_fixed_point, reg41),
    (val_mul, reg42, ":output_fpm"), (convert_from_fixed_point, reg42),
    (val_mul, reg43, ":output_fpm"), (convert_from_fixed_point, reg43),
    (val_mul, reg44, ":output_fpm"), (convert_from_fixed_point, reg44),
    (val_mul, reg45, ":output_fpm"), (convert_from_fixed_point, reg45),
    (val_mul, reg46, ":output_fpm"), (convert_from_fixed_point, reg46),
    (set_fixed_point_multiplier, ":saved_fpm"),
  ]),

I figured, since I was sharing someone else's work, I might as well tag in with something of my own that is somewhat related.

I originally made this about a year or two ago for myself, but then found out this function is already included in the VC module_system, so I wanted to share my version just for fun.

Lookat!

Rotate one position to look at a second position. (With +Y being the facing)
I actually use this all the time, so It should be useful for someone else


Python:
        #("script_lookat", pos##, pos##),
        #This script will take a position and rotate it such that +Y will face the target exactly.
        #It does this with some basic trig, I'll explain it in the comments inside the script.
        #INPUT:
        #    Param 1: Positional register of the pos## that will be rotated
        #    Param 2: Positional register of the pos## that will be targeted
        #OUTPUT:
        #    N/A

    ("lookat",
    [
    (store_script_param, ":looker", 1),        # This is the positional register that will turn to face the :target (+Y being forward)
    (store_script_param, ":target", 2),        # This is the positional register that will be targeted
    (assign, ":local", pos13),                # This just makes the local_var :local equivilent to pos13

    (init_position, pos1),                        # Get a clean positional register so. . .
    (position_copy_rotation, ":looker", pos1),    # We can scrub the rotational data from the :looker to simplify the math

    # The maths are pretty easy once we figure it out. Essentially, make two triangles with the two positions
    # Use those triangles to determine the angles that the :looker will need to rotate to face the :target

    (position_transform_position_to_local, ":local", ":looker", ":target"),    # We will use the local data to determine side lengths

    (position_get_z, ":l_z", ":looker"),        # :looker z value

    (position_get_z, ":t_z", ":target"),        # :target z value

    (position_get_x, ":local_x", ":local"),        # The opposite side of the first triangle
    (position_get_y, ":local_y", ":local"),        # The adjacent side of the first triangle

    (store_sub, ":z_dis", ":l_z", ":t_z"),        # The adjacent side of the second triangle

    (get_distance_between_positions, ":hypo", ":looker", ":target"),    # Distance between the positions will be the hypotenuse of both triangles

    (convert_to_fixed_point, ":z_dis"),        # When doing fixed point maths only one needs to be fixed point or else it won't convert from the fp correctly
    (convert_to_fixed_point, ":local_x"),    # When doing fixed point maths only one needs to be fixed point or else it won't convert from the fp correctly

        # SOH CAH TOA, sin(opp/hyp), cos(adj/hyp), tan(opp/adj)

    (store_div, ":adj_hyp", ":z_dis", ":hypo"),    # Use the second triangle's adjacent / hypotenuse

    (store_acos, ":x_angle", ":adj_hyp"),        # To get the angle we need to adjust the pitch
    (convert_from_fixed_point, ":x_angle"),        # Convert from fixed point to make it useable by the rotation
    (val_add, ":x_angle", 270),                    # Move it by 270 degrees to convert the angle into the correct quadrant

    (try_begin),
        (eq, ":local_y", 0),                    # If the adjacent side's length would be 0
        (assign, ":z_angle", 0),                # Set the yaw to 0 to prevent division by zero errors
    (else_try),
        (store_div, ":opp_adj", ":local_x", ":local_y"),    # Use the second triangle's opposite side / adjacent side
        (store_atan, ":z_angle", ":opp_adj"),                # To get the angle of the yaw
        (convert_from_fixed_point, ":z_angle"),                # Convert from fixed point to make it useable by the rotation
        (val_mul, ":z_angle", -1),                            # Mirror the yaw to change it from CCW to CW
    (try_end),

    (try_begin),
        (lt, ":local_y", 0),            # If the :target is behind the :looker
        (val_add, ":z_angle", 180),        # Move the yaw 180 degrees
    (try_end),


    (position_rotate_z, ":looker", ":z_angle"),    # Rotate left/right (yaw) first
    (position_rotate_x, ":looker", ":x_angle"),    # Then rotate up/down (pitch) last
    ]),

Addendum, we were discussing prop spawning on inclined surfaces, and an interesting use case presented itself.

This is a short script that uses lookat in conjunction with cast_ray to determine the normal of the surface.

Python:
[(ti_on_missile_hit, [
    (set_fixed_point_multiplier, 100),

 
(copy_position, pos2, pos1), # Make a duplicate of landing location
            (position_move_z, pos2, 50, 1), # Move it up
            (call_script, "script_lookat", pos2, pos1), # Look at that position
          
            (position_rotate_x, pos2, -90), # There seems to be an issue inside the ti_on_missile_hit where the axis are mislabeled somehow, requiring us to do this.
          
            (cast_ray, reg1, pos3, pos2), # Cast a ray, this should make a duplicate of pos1, but with the normals of the surface stored in the rotational values
          
            (position_rotate_x, pos3, 90), # This is interesting, it appears the axis' inside of ti_on_missile_hit are swapped.
      # THE REST HERE #
                ]),
            ],
 
Last edited:
This is a basic script for debugging positions. It saves registers wich later are restored. It also prints fixed_point_multiplier as <fp>.
Python:
  # script_debug_pos
  # Input:  arg1=<pos>
  # Output: none
  # Note:  Print all values from position
  ("debug_pos", [
    (store_script_param_1, ":pos"),
    (assign, ":reg0_bac", reg0),
    (assign, ":reg1_bac", reg1),
    (assign, ":reg2_bac", reg2),
    (assign, ":reg3_bac", reg3),
    (assign, ":reg4_bac", reg4),
    (assign, ":reg5_bac", reg5),
    (assign, ":reg6_bac", reg6),
    (assign, ":reg7_bac", reg7),
    (position_get_x, reg0, ":pos"),
    (position_get_y, reg1, ":pos"),
    (position_get_z, reg2, ":pos"),
    (position_get_rotation_around_x, reg3, ":pos"),
    (position_get_rotation_around_y, reg4, ":pos"),
    (position_get_rotation_around_z, reg5, ":pos"),
    (assign, reg6, ":pos"),
    (assign, reg7, 1),
    (convert_to_fixed_point, reg7),
    (display_message, "@{!}pos{reg6} ({reg0},{reg1},{reg2}, {reg3},{reg4},{reg5}) fp={reg7}"),
    (assign, reg0, ":reg0_bac"),
    (assign, reg1, ":reg1_bac"),
    (assign, reg2, ":reg2_bac"),
    (assign, reg3, ":reg3_bac"),
    (assign, reg4, ":reg4_bac"),
    (assign, reg5, ":reg5_bac"),
    (assign, reg6, ":reg6_bac"),
    (assign, reg7, ":reg7_bac"),
  ]),
 
Last edited:
Started Battles

Do you know that feel when you join a started battle between two parties and turns out they still have yet to clash? Almost like they have been waiting for your join before actully start a fight. Weird feel, I know. Well guess what, it's fixed now! When you join a battle, all troops except for your party members will spawn in random spot within 10m radius from the middle of the map. So you will see kill feed from the very beginning, which should create a feeling that the battle was indeed going on when you joined. Sure it means more casualties for allies, but it's far more realistic. Reinforcements however will come from usual entrypoints for both sides.

Just put this trigger in lead_charge mission:
Code:
(ti_on_agent_spawn, 0, 0,
  [
   (ge, "$g_ally_party", 0), # if player has joined already started battle
   (eq, "$attacker_reinforcement_stage", 0), # and there hasn't been any reinforcements yet
   (eq, "$defender_reinforcement_stage", 0),
  ],
  [
    (store_trigger_param_1, ":agent"),
    (assign, ":agent_to_move", -1),
    (try_begin),
      (agent_is_human, ":agent"),
      (agent_get_party_id, ":a_party", ":agent"),
      (neq, ":a_party", "p_main_party"),
      (agent_is_non_player, ":agent"),
      (assign, ":agent_to_move", ":agent"),
    (else_try), 
      (neg|agent_is_human, ":agent"),
      (agent_get_rider, ":rider", ":agent"),
      (agent_get_party_id, ":r_party", ":rider"),
      (neq, ":r_party", "p_main_party"),
      (agent_is_non_player, ":rider"),
      (assign, ":agent_to_move", ":agent"),
    (try_end),   
    (neq, ":agent_to_move", -1),
    (get_scene_boundaries, pos10, pos11),
    (set_fixed_point_multiplier, 100),
    (position_get_x, "$g_scene_max_x", pos11),
    (position_get_y, "$g_scene_max_y", pos11),
    (val_add, "$g_scene_max_x", 2400), # 2400 has been subtracted automatically because of barriers from outer terrain
    (val_add, "$g_scene_max_y", 2400),
    (store_div, ":pos_x", "$g_scene_max_x", 2),
    (store_div, ":pos_y", "$g_scene_max_x", 2),
    (init_position, pos22), # map center
    (store_random_in_range, ":x_shift", -1000, 1000),
    (store_random_in_range, ":y_shift", -1000, 1000),
    (val_add, ":pos_x", ":x_shift"),
    (val_add, ":pos_y", ":y_shift"),
    (position_set_x, pos22, ":pos_x"),
    (position_set_y, pos22, ":pos_y"),
    (agent_set_position, ":agent_to_move", pos22),
  ]),

Shoutouts to Tocan for pointing me in the right direction here.
 
Last edited:
I formerly wrote a script that allows AI to kick which is worth sharing:

Python:
AI_kick_enhancement =  (
    2, 0, 0,
    [], [
    (get_player_agent_no,":player"),
    (try_for_agents, ":agent"),
        (neq, ":agent", ":player"),
        (agent_is_alive, ":agent"),
        (agent_is_human, ":agent"),
        (agent_is_active, ":agent"),
        (agent_slot_eq, ":agent", slot_agent_is_running_away, 0),
        ##He's an eligible human.  Now see if he's in a position to kick.
        (agent_get_attack_action, ":attack_action", ":agent"), # return value: spare - 0, prepare - 1, attack - 2, hit - 3, was defended - 4,reload - 5, release - 6, cancel - 7
        (agent_get_defend_action, ":defend_action", ":agent"),
        (this_or_next|eq,":attack_action",4),
        (this_or_next|eq,":defend_action",1), # defend enemy
        ##So he'll only try to kick if he just parried an enemy attack, or his own attack just got parried.
        (agent_get_team, ":team", ":agent"),
        (assign, ":maximum_distance", 100),
        # get target
        (agent_ai_get_look_target,":suspect",":agent"),
        (gt,":suspect",0),
        (agent_is_alive, ":suspect"),
        (agent_is_human, ":suspect"),
        (agent_is_active, ":suspect"),
        (agent_get_team, ":suspect_team", ":suspect"),
        (neq, ":suspect_team", ":team"),
        (agent_get_position, pos1, ":agent"), # distance check
        (agent_get_position, pos2, ":suspect"),
        (neg|position_is_behind_position, pos2, pos1), #enemy cannot be behind player
        (get_distance_between_positions, ":distance", pos1, pos2),
        (le, ":distance", ":maximum_distance"),
        (store_random_in_range,":kickchance", 1, 10),
        (try_begin),
            (eq,":kickchance",1),
                (display_message, "@Agent kicks."),
                (agent_set_animation, ":agent", "anim_prepare_kick_0"),
                (agent_deliver_damage_to_agent, ":agent", ":suspect", 3),
                (agent_set_animation, ":suspect", "anim_strike3_abdomen_front"),
            (try_end),
       (try_end),
       ])

I never used this because I totally forgot about this until I took a look into my google drive..
 
Last edited:
This scripts will help to point missile from one position to another adjusted with missile speed and gravitation without air resistance. I don't know exact warband formulas. So this can be different a bit.

Example: (call_script, "script_point_missile_position", pos3, pos4, ":speed"),

PHP:
  # script_cf_quadratic_roots
  # Input:  <a_fixed_point>, <b_fixed_point>, <c_fixed_point> : ax^2+bx+c=0
  # Output: <reg0> - number of roots, <reg1_fixed_point> - first root, <reg2_fixed_point> - second root
  # Note:   Fixed_point_multiplier can be any. It will be converted for precise calculation.
  ("cf_quadratic_roots", [
    (store_script_param, ":a", 1),
    (store_script_param, ":b", 2),
    (store_script_param, ":c", 3),

    (assign, ":save_fpm", 1),
    (convert_to_fixed_point, ":save_fpm"),
    (set_fixed_point_multiplier, 100000), # precise calculation
    (convert_to_fixed_point, ":a"),
    (convert_to_fixed_point, ":b"),
    (convert_to_fixed_point, ":c"),
    (val_div, ":a", ":save_fpm"),
    (val_div, ":b", ":save_fpm"),
    (val_div, ":c", ":save_fpm"),

    (store_mul, ":b2", ":b", ":b"),
    (convert_from_fixed_point, ":b2"),
    (val_mul, ":c", ":a"),
    (convert_from_fixed_point, ":c"),
    (val_mul, ":c", 4),
    (val_sub, ":b2", ":c"),
   
    (try_begin),
      (lt, ":b2", 0),
      (assign, reg0, 0),
      (assign, reg1, -1),
      (assign, reg2, -1),
    (else_try),
      (eq, ":b2", 0),
      (assign, reg0, 1),
      (assign, reg2, -1),
      (store_mul, reg1, ":b", -1),
      (val_mul, ":a", 2),
      (convert_to_fixed_point, reg1),
      (val_div, reg1, ":a"),
      (val_mul, reg1, ":save_fpm"),
      (convert_from_fixed_point, reg1),
    (else_try),
      (assign, reg0, 2),
      (store_sqrt, reg1, ":b2"),
      (store_mul, reg2, reg1, -1),
      (val_sub, reg1, ":b"),
      (val_sub, reg2, ":b"),
      (val_mul, ":a", 2),
      (convert_to_fixed_point, reg1),
      (convert_to_fixed_point, reg2),
      (val_div, reg1, ":a"),
      (val_div, reg2, ":a"),
      (val_mul, reg1, ":save_fpm"),
      (val_mul, reg2, ":save_fpm"),
      (convert_from_fixed_point, reg1),
      (convert_from_fixed_point, reg2),
    (try_end),
    (set_fixed_point_multiplier, ":save_fpm"),
    (gt, reg0, 0), # There are no roots
  ]),

  # script_point_missile_position
  # Input:  <missile_pos>, <target_pos>, <speed_fixed_point>
  # Output: <missile_pos>
  ("point_missile_position", [
    (store_script_param, ":missile_pos", 1),
    (store_script_param, ":target_pos", 2),
    (store_script_param, ":speed", 3),

    (assign, ":save_fpm", 1),
    (convert_to_fixed_point, ":save_fpm"),
    (set_fixed_point_multiplier, 1000), # precise calculation
    (assign, ":g", 9807),
    (convert_to_fixed_point, ":speed"),
    (val_div, ":speed", ":save_fpm"),

    #remove current rotation
    (position_get_x, ":from_x", ":missile_pos"),
    (position_get_y, ":from_y", ":missile_pos"),
    (position_get_z, ":from_z", ":missile_pos"),
    (init_position, ":missile_pos"),
    (position_set_x, ":missile_pos", ":from_x"),
    (position_set_y, ":missile_pos", ":from_y"),
    (position_set_z, ":missile_pos", ":from_z"),
   
    #horizontal rotation - Yaw
    (position_get_x, ":change_in_x", ":target_pos"),
    (val_sub, ":change_in_x", ":from_x"),
    (position_get_y, ":change_in_y", ":target_pos"),
    (val_sub, ":change_in_y", ":from_y"),
   
    (try_begin),
      (this_or_next|neq, ":change_in_y", 0),
      (neq, ":change_in_x", 0),
      (store_atan2, ":theta", ":change_in_y", ":change_in_x"),
      (assign, ":ninety", 90),
      (convert_to_fixed_point, ":ninety"),
      (val_sub, ":theta", ":ninety"), #point Y axis at to position
      (position_rotate_z_floating, ":missile_pos", ":theta"),
    (try_end),

    #vertical rotation - Roll
    (get_distance_between_positions, ":distance_between", ":missile_pos", ":target_pos"),
    (try_begin),
      (gt, ":distance_between", 0),
      (position_get_z, ":z_distance", ":target_pos"),
      (position_get_z, ":z_missile", ":missile_pos"),
      (val_sub, ":z_distance", ":z_missile"),

      # Get plane distance
      (val_mul, ":change_in_x", ":change_in_x"),
      (convert_from_fixed_point, ":change_in_x"),
      (val_mul, ":change_in_y", ":change_in_y"),
      (convert_from_fixed_point, ":change_in_y"),
      (store_add, ":plane_distance", ":change_in_x", ":change_in_y"),
      (store_sqrt, ":plane_distance", ":plane_distance"),

      # A
      (store_mul, ":a", ":g", -1),
      (val_mul, ":a", ":plane_distance"),
      (convert_from_fixed_point, ":a"),
      (store_mul, ":divisor", ":speed", ":speed"),
      (convert_from_fixed_point, ":divisor"),
      (val_mul, ":divisor", 2),
      (convert_to_fixed_point, ":a"),
      (val_div, ":a", ":divisor"),

      # B
      (assign, ":b", 1),
      (convert_to_fixed_point, ":b"),

      # C
      (store_mul, ":c", ":z_distance", -1),
      (convert_to_fixed_point, ":c"),
      (val_div, ":c", ":plane_distance"),
      (val_add, ":c", ":a"),

      (try_begin),
        (call_script, "script_cf_quadratic_roots", ":a", ":b", ":c"),
        (try_begin),
          (eq, reg0, 1),
          (store_atan, ":theta", reg1),
        (else_try),
          (eq, reg0, 2),
          (store_atan, ":theta", reg1),
          (store_atan, ":theta2", reg2),
          (try_begin),
            (lt, ":theta2", ":theta"),
            (assign, ":theta", ":theta2"),
          (try_end),
        (try_end),
        (position_rotate_x_floating, ":missile_pos", ":theta"),
      (else_try),
        (gt, "$cheat_mode", 0),
        (display_message, "@Error: script_point_missile_position invalid roots"),
      (try_end),
    (try_end),

    (set_fixed_point_multiplier, ":save_fpm"),
  ]),
 
Hi all, this is nothing earthshaking compared to the awesome code others have posted here, it's just a hack to let your character and NPCs look cooler in court scenes by giving them dress weapons. It can easily be adapted to have a character wear any weapon or clothing/headgear/footwear for court scenes as well.


be42493ecacab8c1a4f62ac87f61f33e.jpg



Here's a sample from my mod, every lord gets to wear a random dress dagger on appearing in a court scene. I made a few katars and other Indo-Persian daggers, but since they don't work well in combat they might as well just add a bit of atmosphere :smile:

To set up dress weapons or other court apparel:
  1. Enter your dress daggers or court apparel into your module_items file in sequence. For example, court_dagger_a to court_dagger_e. Take note of the item entry following the last dress dagger, say that's last_item.
  2. In module_scripts, find "enter_court"
  3. Between each instance of set_visitor and try_end, insert the following:
Python:
                (store_random_in_range,":court_dagger","itm_your_first_court_dagger","itm_entry_after_last_court_dagger"),
                    (mission_tpl_entry_add_override_item,"mt_visit_town_castle",":cur_pos",":court_dagger"),         
               (val_add,":cur_pos", 1),

If you want different factions to have different regalia, overwrite the part of enter_court from (call_script, "script_get_heroes_attached_to_center", to (assign, ":lady_meets_visitors", 0), with this:

Just change the items and faction checks as needed for your mod.

Python:
      (call_script, "script_get_heroes_attached_to_center", ":center_no", "p_temp_party"),
      (party_get_num_companion_stacks, ":num_stacks","p_temp_party"),
      (try_for_range, ":i_stack", 0, ":num_stacks"),
        (party_stack_get_troop_id, ":stack_troop","p_temp_party",":i_stack"),
        (lt, ":cur_pos", 32), # spawn up to entry point 32 - is it possible to add another 10 spots?
        (set_visitor, ":cur_pos", ":stack_troop"),
            #DRQ improved court dagger
            (store_faction_of_troop,":fac",":stack_troop"),
                (try_begin),
                    (neq,":fac","fac_kingdom_21"),
                            (store_random_in_range,":court_dagger","itm_jamdhar_katari","itm_katar_a_shield"),
                                (mission_tpl_entry_add_override_item,"mt_visit_town_castle",":cur_pos",":court_dagger"),
                            (try_begin),#failsafe - I'm seeing lords without daggers
                                (eq,":court_dagger",0),                                                            (mission_tpl_entry_add_override_item,"mt_visit_town_castle",":cur_pos","itm_jamdhar_katari"),
                            (try_end),
                    (else_try),
                        (eq,":fac","fac_kingdom_21"),
                                    (mission_tpl_entry_add_override_item,"mt_visit_town_castle",":cur_pos","itm_dagger"),#Portuguese lords get Native European style dagger
                                    (mission_tpl_entry_add_override_item,"mt_visit_town_castle",":cur_pos","itm_rapier"),#Portuguese lords get rapier too!
                    (try_end),
            #DRQ end improved court dagger
        (val_add,":cur_pos", 1),
      (try_end),
      (try_for_range, ":cur_troop", kingdom_ladies_begin, kingdom_ladies_end),
        (neq, ":cur_troop", "trp_knight_1_1_wife"), #The one who should not appear in game
        #(troop_slot_eq, ":cur_troop", slot_troop_occupation, slto_kingdom_lady),
        (troop_slot_eq, ":cur_troop", slot_troop_cur_center, ":center_no"),

        (assign, ":lady_meets_visitors", 0),

If you want to use my Indo-Persian daggers, they're here https://www.mbrepository.com/file.php?id=2383
 
Last edited by a moderator:
I needed to sort agents by distance from the player. And having never worked with arrays, I have to say, it took me a lot longer than I would like to admit to draft this.

Sort Agents (by distance from arbitrary agent)
Thanks to @Arris Rumi as inspiration by making their Sorting Troops OSP and getting me thinking about sorting algorithms and nonsense that made my head hurt and @Tocan for giving me a reason to work on this.

Python:
    #("script_sort_agents_by_distance", agent_id, pos##),
    # This script produce and sort an array of all agents in a scene, based on distance from
    # the position provided by the scripter. This will skip over the anchor agent if provided
    # by leaving the agent_id parameter as 0, it will include all human agents in the scene
    #INPUT:
    #    Param 1: Anchor Agent (Leave as 0 to skip)
    #    Param 2: Anchor Position
    #OUTPUT:
    #    Reg45    : Total Agents (not counting Anchor Agent)
    #    Agent Array stored in "trp_temp_array_a" troop slots
    #    Distance Array stored in "trp_temp_array_b" troop slots
    #
    # The script sorts with the easiest sorting algorithm I could find.
    # First, first store all agents in a scene and thier distance from anchor, unsorted.
    # Iterate through all agent distances and swap the nearest with the lowest unsorted slot.
    # Then repeat until done. Store total agent count in reg45 to allow scripter to make
    # try_for_range loops using the total count as the upper limit.
 
 
    ("sort_agents_by_distance",
        [
        # Input Variables
        (store_script_param, ":anchor_agent", 1),
        (store_script_param, ":anchor_pos", 2),
  
        # Variables
        (assign, ":array_slot", 0),                        # Assign Minimum Slot Range
        (assign, ":agent_array", "trp_temp_array_a"),    # For Readability, create sorting array troop in a localvar
        (assign, ":adist_array", "trp_temp_array_b"),    # For Readability, create distance array troop in a localvar

        (try_for_agents, ":agent"),                                                    # Iterate through all agents
            (neq, ":agent", ":anchor_agent"),                                        # That is not the anchor
            (agent_is_human, ":agent"),                                                # And that are human
      
            (agent_get_position, pos45, ":agent"),                                    # Where are they?
            (get_distance_between_positions, ":distance", ":anchor_pos", pos45),    # How far is that from the anchor?
      
            (troop_set_slot, ":agent_array", ":array_slot", ":agent"),                # Store this agent in the first available blank slot
            (troop_set_slot, ":adist_array", ":array_slot", ":distance"),            # Store this distnace in the first available blank slot
      
            (val_add, ":array_slot", 1),                                            # Iterate that slot after this, moving to the next empty slot
        (try_end),
  
        (assign, reg45, ":array_slot"),
  
        (try_for_range, ":i", 0, ":array_slot"),                                    # Use the count from above to iterate through the array slots
      
            (assign, ":comp_dist", 999999),                                            # Large number to check against
      
            (try_for_range, ":j", ":i", ":array_slot"),                                # Loops in Loops
                (troop_get_slot, ":adist", ":adist_array", ":j"),                    # Check the distance stored in slot numbered :j
                (lt, ":adist", ":comp_dist"),                                        # If less than the previous comparison number
                (neq, ":adist", 0),                                                    # And not a blank slot
                (assign, ":comp_dist", ":adist"),                                    # Have it replace the comparison number, and,
                (assign, ":nearest", ":j"),                                            # Remember what slot I used to be in
            (try_end),
      
            (troop_get_slot, ":nearest_agent", ":agent_array", ":nearest"),            # Take the remembered slot and pull the assoc. agent
            (troop_get_slot, ":nearest_distance", ":adist_array", ":nearest"),        # Take the remembered slot and pull the assoc. distance
      
            (troop_get_slot, ":swap_agent", ":agent_array", ":i"),                    # Take the previously stored agent
            (troop_get_slot, ":swap_distance", ":adist_array", ":i"),                # Take the previously stored distance
          
            (troop_set_slot, ":agent_array", ":nearest", ":swap_agent"),            # Store this swapped agent to whereever the new nearest used to live
            (troop_set_slot, ":adist_array", ":nearest", ":swap_distance"),         # Store this swapped distance to whereever the new nearest used to live
      
            (troop_set_slot, ":agent_array", ":i", ":nearest_agent"),                # Store this nearest agent as the newest member of the sorted part of the array
            (troop_set_slot, ":adist_array", ":i", ":nearest_distance"),             # Store this nearest distance as the newest member of the sorted part of the array
        (try_end),
 
        ]
    ),
 
Last edited:
Code:
        (try_for_range, ":i", 0, 1000),
            (troop_set_slot, ":agent_array", ":i", 0), # Clear the first 1000 agent slots
        (try_end),
     
        (try_for_range, ":i", 0, 1000),
            (troop_set_slot, ":adist_array", ":i", 0), # Clear the first 1000 distance slots
        (try_end),

I don't think this bit is necessary. The troop array will have some old garbage values in it, but in the loop right below this you reassign the relevant slots in :adist_array anyway, which makes the 0-1000 slot wipe pointless.

Don't worry, I've written about 50 sorting loops in warband alone, not to mention C++, and they still make my head hurt.
 
I don't think this bit is necessary. The troop array will have some old garbage values in it, but in the loop right below this you reassign the relevant slots in :adist_array anyway, which makes the 0-1000 slot wipe pointless.

Don't worry, I've written about 50 sorting loops in warband alone, not to mention C++, and they still make my head hurt.
That's fair, it was left over from earlier attempts when I was doing a much dumber, much uglier way to accomplish this, and I didn't think to remove it. I'll pare it down a bit.
 
I needed to sort agents by distance from the player. And having never worked with arrays, I have to say, it took me a lot longer than I would like to admit to draft this.

Actually game engine does this. Every 2 seconds it creates array of 16 cashed enemies sorted by distance from closest to farthest.
Python:
(agent_ai_get_num_cached_enemies, <destination>, <agent_no>),
# Version 1.165+. Returns total number of nearby enemies as has been cached by agent AI. Enemies are numbered from nearest to farthest.
(agent_ai_get_cached_enemy, <destination>, <agent_no>, <cache_index>),
#Version 1.165+. Return agent reference from AI's list of cached enemies, from nearest to farthest. Returns -1 if the cached enemy is not active anymore.
#Max slots are 16. Every 2 seconds renew cashed enemies in order from nearest to farthest.
 
Actually game engine does this. Every 2 seconds it creates array of 16 cashed enemies sorted by distance from closest to farthest.
Python:
(agent_ai_get_num_cached_enemies, <destination>, <agent_no>),
# Version 1.165+. Returns total number of nearby enemies as has been cached by agent AI. Enemies are numbered from nearest to farthest.
(agent_ai_get_cached_enemy, <destination>, <agent_no>, <cache_index>),
#Version 1.165+. Return agent reference from AI's list of cached enemies, from nearest to farthest. Returns -1 if the cached enemy is not active anymore.
#Max slots are 16. Every 2 seconds renew cashed enemies in order from nearest to farthest.
I didn't try that since it seems to be explicitly for AI agents, and sometimes you may need nearest allies by distance. Does the player agent pop anything out?

Testing it with this script:
Python:
(try_begin),
    (key_clicked, key_k),
    (try_for_agents, reg11),
        (str_store_agent_name, s11, reg11),
        (agent_ai_get_num_cached_enemies, reg12, reg11),
        (display_message, "@{reg12} enemies are cached by {s11}, they are:"),
        (gt, reg12, 0),
        (try_for_range, ":index", 0, reg12),
            (agent_ai_get_cached_enemy, reg13, ":player", ":index"),
            (str_store_agent_name, s13, reg13),
            (display_message, "@{reg13}: {s13}"),
        (try_end),
    (try_end),
(try_end),

inside the arena melee produces

2 enemies are cached by Veteran Fighter, they are:
25: Regular Fighter
25: Regular Fighter
2 enemies are cached by Regular Fighter, they are:
25: Regular Fighter
25: Regular Fighter
2 enemies are cached by Regular Fighter, they are:
25: Regular Fighter
25: Regular Fighter
2 enemies are cached by Regular Fighter, they are:
25: Regular Fighter
25: Regular Fighter
2 enemies are cached by Novice Fighter, they are:
25: Regular Fighter
25: Regular Fighter
1 enemies are cached by Novice Fighter, they are:
25: Regular Fighter
0 enemies are cached by Tournament Master, they are:
0 enemies are cached by Player, they are:
 
Last edited:
@SupaNinjaMan , I don't know what is exactly your code do. Maybe you need to sort all agents on the scene maybe not. But I add some information to clarify. As for arena correct your code to this and watch rgl_log:
Python:
(try_begin),
    (key_clicked, key_k),
    (try_for_agents, reg11),
        (str_store_agent_name, s11, reg11),
        (agent_ai_get_num_cached_enemies, reg12, reg11),
        (display_message, "@{reg12} enemies are cached by {reg11}:{s11}, they are:"),
        (try_for_range, ":index", 0, reg12),
            (agent_ai_get_cached_enemy, reg13, reg11, ":index"),
            (str_store_agent_name, s13, reg13),
            (display_message, "@{reg13}: {s13}"),
        (try_end),
    (try_end),
(try_end),
 
The code isn't meant to accomplish anything. I was demonstrating that it the operation is only a viable solution for sorting agents in certain use cases.

Good eye catching on my typo. My first iteration was only the player, to make sure the didn't have a cache, before I expanded it to a try_for_agents loop so I didn't see that :player lvar still floating around.

But, for cases where you need to have a list include friendly or neutral agents, to sort when the AI isn't alerted yet, or when sorting from any player agent, the Sort Agents script is a better solution.
 
Multiplayer, Server-Side Oddity.

I found out that you can use bug in native prsnt_multiplayer_message_1 and
multiplayer_message_type_defenders_saved_n_targets event in order to display string at the center of the player screen.
This is most likely not reliable way to do that, however I don't know of existence of any other way to achieve it without client side mods.


Why its working?


Python:
### prsnt_multiplayer_message_1
(else_try),
        (eq, "$g_multiplayer_message_type", multiplayer_message_type_defenders_saved_n_targets),

        (try_begin), #for spectators initializing, we assume spectators are fan of team0 so coloring is applied as they are at team0.
          (eq, "$g_defender_team", 0),
          (assign, ":text_font_color", 0xFF33DD11),
        (else_try),
          (assign, ":text_font_color", 0xFFFF4422),
        (try_end), #initializing ends
        (multiplayer_get_my_player, ":my_player_no"),
        (try_begin),
          (ge, ":my_player_no", 0),
          (player_get_agent_id, ":my_player_agent", ":my_player_no"),
          (try_begin),
            (ge, ":my_player_agent", 0),
            (agent_get_team, ":my_player_team", ":my_player_agent"),
            (try_begin),
              (eq, ":my_player_team", "$g_defender_team"),
              (assign, ":text_font_color", 0xFF33DD11),
            (else_try),
              (assign, ":text_font_color", 0xFFFF4422),
            (try_end),
          (try_end),
        (try_end),

        (assign, ":num_targets_saved", "$g_multiplayer_message_value_1"),

        (team_get_faction, ":faction_of_winner_team", "$g_defender_team"),
        (str_store_faction_name, s1, ":faction_of_winner_team"),

        (try_begin),
          (eq, ":num_targets_saved", 1),
          (str_store_string, s0, "str_s1_saved_1_target"),
        (else_try),
          (eq, ":num_targets_saved", 2),
          (str_store_string, s0, "str_s1_saved_2_targets"),
        (try_end),

        (create_text_overlay, "$g_multiplayer_message_1", s0, tf_center_justify|tf_with_outline),
        (overlay_set_color, "$g_multiplayer_message_1", ":text_font_color"),
        (position_set_x, pos1, 350),
        (position_set_x, pos1, 500), #new
        (position_set_y, pos1, 400),
        (overlay_set_position, "$g_multiplayer_message_1", pos1),
        (position_set_x, pos1, 2000),
        (position_set_y, pos1, 2000),
        (overlay_set_size, "$g_multiplayer_message_1", pos1),
        (presentation_set_duration, 400),
      (else_try),

Mostly this part:

Code:
        (try_begin),

          (eq, ":num_targets_saved", 1),

          (str_store_string, s0, "str_s1_saved_1_target"),

        (else_try),

          (eq, ":num_targets_saved", 2),

          (str_store_string, s0, "str_s1_saved_2_targets"),

        (try_end),


Here game expects from us to provide variable num_targets_saved ($g_multiplayer_message_value_1) equal to 1 or 2.
If we don't do that, s0 will be unassigned and for some reason ( i don't know details of game engine), last chat message(?) will be used as s0.

We can then make something like this:

Code:
  (multiplayer_send_string_to_player,":player_no",multiplayer_event_show_server_message, "@display my string at the center of the screen"),
  (multiplayer_send_2_int_to_player, ":player_no", multiplayer_event_show_multiplayer_message, multiplayer_message_type_defenders_saved_n_targets, 20),

It should display text at the center of the ":player_no" screen.
Be aware, this is potentially very unreliable way to do that.
I share this more like oddity than a something useful to use. :grin:

If anybody has more informations on this topic, I would love to see it :grin:

xwXZq9S.jpg
 
a very cool script that diversifies the troop types in a tournament, it can choose participants from among the garrison as well as the player's party, but it leaves at least 8 slots for commoners, if there are more than 8 left it fills the rest of the slots with more random commoners, manhunters, and mercenaries. there is a maximum of 8 per troop type drawn from the garrison or the player's party, and the more of a troop type there are, the more likely it will appear in the tournament, capping at 100 at which point 8 of the troop's type will always join. you could remove the limit but i added it to increase diversity and prevent the player from completely rigging the tournament by bringing huge hordes of peasants :grin: the garrison troops also get priority over the player's troops because i thought that made the most sense (dibs for living and garrisoning the town)

Code:
  # script_fill_tournament_participants_troop
  # Input: arg1 = center_no, arg2 = player_at_center
  # Output: none (fills trp_tournament_participants)
  ("fill_tournament_participants_troop",
    [(store_script_param, ":center_no", 1),
     (store_script_param, ":player_at_center", 2),
     (assign, ":cur_slot", 0),

     (try_begin), # player companions
       (eq, ":player_at_center", 1),
       (party_get_num_companion_stacks, ":num_stacks", "p_main_party"),
       (try_for_range, ":stack_no", 0, ":num_stacks"),
         (party_stack_get_troop_id, ":cur_troop", "p_main_party", ":stack_no"),
         (troop_is_hero, ":cur_troop"),
         (neq, ":cur_troop", "trp_kidnapped_girl"),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", ":cur_troop"),
         (val_add, ":cur_slot", 1),
       (try_end),
     (try_end),

     (party_collect_attachments_to_party, ":center_no", "p_temp_party"),
     (party_get_num_companion_stacks, ":num_stacks", "p_temp_party"),
     (try_for_range, ":stack_no", 0, ":num_stacks"), # center lords
       (party_stack_get_troop_id, ":cur_troop", "p_temp_party", ":stack_no"),
       (troop_is_hero, ":cur_troop"),
       (troop_set_slot, "trp_tournament_participants", ":cur_slot", ":cur_troop"),
       (val_add, ":cur_slot", 1),
     (try_end),
    
     # arena champions
     (try_begin), # xerina
       (store_random_in_range, ":random_no", 0, 100),
       (lt, ":random_no", 50),
       (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_xerina"),
       (val_add, ":cur_slot", 1),
     (try_end),
     (try_begin), # dranton
       (store_random_in_range, ":random_no", 0, 100),
       (lt, ":random_no", 50),
       (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_dranton"),
       (val_add, ":cur_slot", 1),
     (try_end),
     (try_begin), # kradus
       (store_random_in_range, ":random_no", 0, 100),
       (lt, ":random_no", 50),
       (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_kradus"),
       (val_add, ":cur_slot", 1),
     (try_end),
    
     (try_for_range, ":stack_no", 0, ":num_stacks"), # center soldiers
       (party_stack_get_troop_id, ":cur_troop", "p_temp_party", ":stack_no"),
       (neg|troop_is_hero, ":cur_troop"),
       (party_stack_get_size, ":stack_size", "p_temp_party", ":stack_no"),
         (try_for_range, ":unused", 0, 8), # max 8 of each troop type
           (lt, ":cur_slot", 56), # at least 8 slots reserved for commoners (but the garrison gets dibs over player soldiers)
           (store_random_in_range, ":random_no", 0, 100),
           (lt, ":random_no", ":stack_size"),
           (troop_set_slot, "trp_tournament_participants", ":cur_slot", ":cur_troop"),
           (val_add, ":cur_slot", 1),
         (try_end),
     (try_end),
    
     (try_begin), # player soldiers
       (eq, ":player_at_center", 1),
       (party_get_num_companion_stacks, ":num_stacks", "p_main_party"),
       (try_for_range, ":stack_no", 0, ":num_stacks"),
         (party_stack_get_troop_id, ":cur_troop", "p_main_party", ":stack_no"),
         (neg|troop_is_hero, ":cur_troop"),
         (party_stack_get_size, ":stack_size", "p_main_party", ":stack_no"),
         (try_for_range, ":unused", 0, 8), # max 8 of each troop type
           (lt, ":cur_slot", 56), # at least 8 slots reserved for commoners
           (store_random_in_range, ":random_no", 0, 100),
           (lt, ":random_no", ":stack_size"),
           (troop_set_slot, "trp_tournament_participants", ":cur_slot", ":cur_troop"),
           (val_add, ":cur_slot", 1),
         (try_end),
       (try_end),
     (try_end),

     (assign, ":begin_slot", ":cur_slot"), # random peasants, manhunters, and mercenaries
     (try_for_range, ":cur_slot", ":begin_slot", 64),
       (store_random_in_range, ":random_no", 0, 100),
       (try_begin), # expanded list of tournament troops
         (le, ":random_no", 3),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_peasant_woman"),
       (else_try),
         (le, ":random_no", 8),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_farmer"),
       (else_try),
         (le, ":random_no", 12),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_caravan_master"),
       (else_try),
         (le, ":random_no", 20),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_manhunter"),
       (else_try),
         (le, ":random_no", 28),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_slave_driver"),
       (else_try),
         (le, ":random_no", 34),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_slave_hunter"),
       (else_try),
         (le, ":random_no", 38),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_slave_crusher"),
       (else_try),
         (le, ":random_no", 40),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_slaver_chief"),
       (else_try),
         (le, ":random_no", 44),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_townsman"),
       (else_try),
         (le, ":random_no", 50),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_watchman"),
       (else_try),
         (le, ":random_no", 58),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_caravan_guard"),
       (else_try),
         (le, ":random_no", 66),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_mercenary_swordsman"),
       (else_try),
         (le, ":random_no", 72),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_hired_blade"),
       (else_try),
         (le, ":random_no", 80),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_mercenary_crossbowman"),
       (else_try),
         (le, ":random_no", 88),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_mercenary_horseman"),
       (else_try),
         (le, ":random_no", 92),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_mercenary_cavalry"),
       (else_try),
         (le, ":random_no", 94),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_follower_woman"),
       (else_try),
         (le, ":random_no", 96),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_hunter_woman"),
       (else_try),
         (le, ":random_no", 98),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_fighter_woman"),
       (else_try),
         (le, ":random_no", 99),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_refugee"),
       (else_try),
         (troop_set_slot, "trp_tournament_participants", ":cur_slot", "trp_sword_sister"),
       (try_end),
     (try_end),
     ]),
 
I tweaked the Freelancer code to allow the player to sit out fights when they're not wounded, so we don't have to fight pointless tiny battles against bandits.
I added
Code:
               ("dont_want_to_fight",[ #so we don't have to join little battles
            (party_get_battle_opponent, ":commander_opponent", "$enlisted_party"),
            (gt, ":commander_opponent", 0),
            (neq, troop_is_wounded, "trp_player"),

        ],"Guard the camp.",
        [(leave_encounter),(change_screen_map),
        (assign, "$g_infinite_camping", 1),
        (rest_for_hours_interactive, 24 * 365, 5, 1),
        (assign, "$freelancer_no_fight", 1),
        ]),

to the menu selection
Code:
        #checks that the player's health is high enough to join battle
        (store_troop_health, ":player_health", "trp_player"),
        (ge, ":player_health", 50),
        (eq, "$freelancer_no_fight", 0), #this part here

to triggers

and then added a simple trigger which fires every 3 hours to reset $freelancer_no_fight to 0 so the player doesn't get stuck in this non fighting mode. It seems to work fine, though I should probably make the simple trigger fire much less often.
 
Are we still posting python code here?
I have just finished thoroughly testing this:

For now the code is designed to swap my riders between a sword and a lance when they are mounted and on foot.
But it can easily be adapted to change any gear to any gear (including armor, actually) for any troop.
It can also be adapted to work on any troop, player included.
The whole idea is that the troop has 1 set of gear for horseback combat (riding gear) and 1 set of gear for fighting on foot (on-foot gear).
In module_troops.py you set up your troop in the on-foot gear, and then using my code you switch to riding gear and back.
The triggers I've set up are pretty lightweight, as they only fire when:
- troop spawns
- troop mounts a horse
- troop dismounts a horse
- troop has his horse killed under him
 
Calculating routing direction by comparing distance to border from entry.
(l27.04.2023: added code to retreat agent to calculating direction)


Script stores attackers and defenders flee direction. Keep in mind that (0, 0) is the left-bottom corner of map.
Output:
"$defenders_flee_direction"
"$attackers_flee_direction"
0-left, 1-right, 2-top, 3-bottom.


Python:
   (ti_after_mission_start,0,0,[],[ #Set the routing direction
      # Map coordinates
      (set_fixed_point_multiplier, 100),
      (get_scene_boundaries, pos0, pos1),
      (position_get_y, "$g_bound_bottom", pos0),
      (position_get_x, "$g_bound_left", pos0),
      (position_get_y, "$g_bound_top", pos1),
      (position_get_x, "$g_bound_right", pos1),
      (entry_point_get_position, pos1, 1), #defenders
      (position_get_x, ":x", pos1),
      (position_get_y, ":y", pos1),
      (store_sub, ":delta_left", ":x", "$g_bound_left"),
      (store_sub, ":delta_bottom", ":y", "$g_bound_bottom"),
      (store_sub, ":delta_top", "$g_bound_top", ":y"),
      (store_sub, ":delta_right", "$g_bound_right", ":x"),
      (try_begin),
         (lt, ":delta_left", ":delta_right"),
         (lt, ":delta_left", ":delta_bottom"),
         (lt, ":delta_left", ":delta_top"),
         (assign, "$defenders_flee_direction", 0), # 0-left, 1-right, 2-top, 3-bottom
      (else_try),
         (lt, ":delta_right", ":delta_bottom"),
         (lt, ":delta_right", ":delta_top"),
         (assign, "$defenders_flee_direction", 1),
      (else_try),
         (lt, ":delta_top", ":delta_bottom"),
         (assign, "$defenders_flee_direction", 2),
      (else_try),
         (assign, "$defenders_flee_direction", 3),
      (try_end),
      (entry_point_get_position, pos1, 4), #attackers
      (set_fixed_point_multiplier, 100),
      (position_get_x, ":x", pos1),
      (position_get_y, ":y", pos1),
      (store_sub, ":delta_left", ":x", "$g_bound_left"),
      (store_sub, ":delta_bottom", ":y", "$g_bound_bottom"),
      (store_sub, ":delta_top", "$g_bound_top", ":y"),
      (store_sub, ":delta_right", "$g_bound_right", ":x"),
      (try_begin),
         (lt, ":delta_left", ":delta_right"),
         (lt, ":delta_left", ":delta_bottom"),
         (lt, ":delta_left", ":delta_top"),
         (assign, "$attackers_flee_direction", 0), # 0-left, 1-right, 2-top, 3-bottom
      (else_try),
         (lt, ":delta_right", ":delta_bottom"),
         (lt, ":delta_right", ":delta_top"),
         (assign, "$attackers_flee_direction", 1),
      (else_try),
         (lt, ":delta_top", ":delta_bottom"),
         (assign, "$attackers_flee_direction", 2),
      (else_try),
         (assign, "$attackers_flee_direction", 3),
      (try_end),
   ]),

Retreat agent to position from calculating direction
Python:
         #Find retreat position
         (set_fixed_point_multiplier, 100),
         (agent_get_position, pos1, ":cur_agent"),
         (position_get_x, ":x", pos1),
         (position_get_y, ":y", pos1),
         (store_sub, ":delta_left", ":x", "$g_bound_left"),
         (store_sub, ":delta_bottom", ":y", "$g_bound_bottom"),
         (store_sub, ":delta_right", "$g_bound_right", ":x"),
         (store_sub, ":delta_top", "$g_bound_top", ":y"),
         (try_begin),
            (this_or_next|lt, ":delta_left", 3000),
            (this_or_next|lt, ":delta_bottom", 3000),
            (this_or_next|lt, ":delta_right", 3000),
            (lt, ":delta_top", 3000),
            (assign, ":position", pos0), # flee to nearest border if less than 30 meters
         (else_try),
            (assign, ":position", pos1),
            (store_mod, ":side", ":agent_team", 2),
            (try_begin),
               (eq, ":side", 0), #defenders
               (assign, ":flee_direction", "$defenders_flee_direction"),
            (else_try),
               (assign, ":flee_direction", "$attackers_flee_direction"),
            (try_end),
            (try_begin),
               (eq, ":flee_direction", 0),
               (position_set_x, pos1, "$g_bound_left"),
            (else_try),
               (eq, ":flee_direction", 1),
               (position_set_x, pos1, "$g_bound_right"),
            (else_try),
               (eq, ":flee_direction", 2),
               (position_set_y, pos1, "$g_bound_top"),
            (else_try),
               (position_set_y, pos1, "$g_bound_bottom"),
            (try_end),
         (try_end),
 
Last edited:
Nothing too crazy, but a big quality-of-life improvement for me who hates blunt weapons.

One of the quests (namely qst_follow_spy) requires you to capture 2 spies alive. The thing is, they are easily killed.
My troops and me rarely have anything blunt, and those poor spies have almost no hitpoints, so we have to fight a mounted unit with bare hands...
So here's the code to protect them from dying. Even if they get killed, the game will just say they fell unconscious.

The code below goes into module_mission_templates.py, into the ti_on_agent_killed_or_wounded trigger in the "lead_charge" mission template.
Basically, open the file, Ctrl+F for "lead_charge", then Ctrl+F for the first occurence of ti_on_agent_killed_or_wounded below that.
In that trigger find this line:
Python:
(party_add_members,"p_total_enemy_casualties",":dead_agent_troop_id",1),
and right below that insert this:
Python:
                        #MOD BEGIN - quest spies fall unconscious instead of dying
                        (try_begin),
                            (this_or_next|eq,":dead_agent_troop_id","trp_spy"), #the spy that we were following
                            (             eq,":dead_agent_troop_id","trp_spy_partner"), #his boss that was waiting for him, usually accompanied by a few mercenaries
                            (assign,":is_wounded",1), #mark him as just knocked unconscious, not dead (for the rest of the code below)
                            (set_trigger_result,2), #same, but for the engine, and it will take effect only after the code below finishes doing its stuff
                        (try_end),
                        #MOD END - quest spies fall unconscious instead of dying

Have fun.
 
Last edited:
Back
Top Bottom