[OSP] Shield Toss

Users who are viewing this thread

This is a combat ability to toss your shield in battle, which will deal damage to certain amount of targets within line of sight in specified radius and then come back to you. The damage amount depends on your Shield skill, Power Throw skill and the resistance stat of the shield itself. All aspects of this feature are customizable in constants...

...and they might as well be our starting point:
1)
Python:
# Shield Toss
ST_Button = key_x           # the key you'd like to bind this ability on
ST_Targets = 7              # the amount of targets shield should hit before returning
ST_Radius = 1500            # the distance from player targets will be searched within
ST_Time_Factor = 16         # this factor directly affects shield moving speed. The higher it is, the faster shield will move
ST_Shield_Skill_Factor = 10 # your Shield skill level will be multiplied by this amount before being added to min/max tresholds
ST_PT_Skill_Factor = 3      # your Power Throw skill level will be multiplied by this amount before being added to max treshold only
shield_toss_slots = 409     # all the slots start from here so when you need to move them you adjust only 1 number
slot_shield_throw_state = shield_toss_slots + 1
slot_shield_throw_tracker = shield_toss_slots + 2
slot_shield_throw_target_hit = shield_toss_slots + 3
slot_shield_throw_targets_left = shield_toss_slots + 4
slot_shield_throw_cur_target = shield_toss_slots + 5
slot_shield_throw_item_id = shield_toss_slots + 6
2)
Replace any two unused_human_animations with these. They are essentially just duplicates of native anim_defend_shield_right and anim_defend_shield_left with higher priority so they don't get overwridden by equip animations.
Python:
  ["shield_throw", 0, amf_priority_fall_from_horse|amf_rider_rot_shield|amf_use_defend_speed|amf_play|amf_restart|amf_client_owner_prediction|amf_use_inertia,
    [0.75, "defend_shield_right", 5, 26, arf_blend_in_3],
  ],
  ["shield_catch", 0, amf_priority_fall_from_horse|amf_rider_rot_shield|amf_use_defend_speed|amf_play|amf_restart|amf_client_owner_prediction|amf_use_inertia,
    [0.75, "defend_shield_left", 5, 26, arf_blend_in_3],
  ],
3)
Python:
  # script_send_shield_to_next_target
  # Input: 1 - tracker prop id
  # Output: none
  # Sends the thrown shield to the next target in queue, or back to player if the queue is empty.
  ("send_shield_to_next_target",
  [
    (store_script_param_1, ":prop"),
    (assign, ":target", -1),
    (assign, ":min_found", -1),
    (get_player_agent_no, ":player"),
    (agent_get_team, ":p_team", ":player"),
    (agent_get_position, pos2, ":player"),
    (prop_instance_get_position, pos3, ":prop"),
    (position_move_z, pos2, 150),
    (try_for_agents, ":cur_agent"),
      (agent_is_alive, ":cur_agent"),
      (agent_is_human, ":cur_agent"),
      (agent_is_non_player, ":cur_agent"),
      (agent_slot_eq, ":cur_agent", slot_shield_throw_target_hit, 0),
      (agent_get_team, ":a_team", ":cur_agent"),
      (agent_get_position, pos4, ":cur_agent"),
      (position_move_z, pos4, 150),
      (get_distance_between_positions, ":cur_dist", pos4, pos2),
      (le, ":cur_dist", ST_Radius),
      (position_has_line_of_sight_to_position, pos4, pos2),
      (teams_are_enemies, ":a_team", ":p_team"),
      (try_begin),
        (neq, ":min_found", 1),
        (assign, ":min_dist", ":cur_dist"),
        (assign, ":target", ":cur_agent"),
        (assign, ":min_found", 1),
      (else_try),
        (lt, ":cur_dist", ":min_dist"),
        (assign, ":min_dist", ":cur_dist"),
        (assign, ":target", ":cur_agent"),
      (try_end),
    (end_try),
    (try_begin),
      (ge, ":target", 0),
      (scene_prop_slot_ge, ":prop", slot_shield_throw_targets_left, 1),
      (scene_prop_set_slot, ":prop", slot_shield_throw_state, 1), # seeking targets
      (scene_prop_set_slot, ":prop", slot_shield_throw_cur_target, ":target"),
      (agent_get_position, pos4, ":target"),
      (agent_get_bone_position, pos5, ":target", 8, 1),
      (position_copy_rotation, pos5, pos4),
      (store_random_in_range, ":rot", 0, 360),
      (position_rotate_z, pos5, ":rot"),
    (else_try),
      (scene_prop_set_slot, ":prop", slot_shield_throw_state, 2), # returning
    (try_end),
    (try_begin),
      (scene_prop_slot_eq, ":prop", slot_shield_throw_state, 2), # returning
      (scene_prop_slot_eq, ":prop", slot_shield_throw_targets_left, ST_Targets),
      (scene_prop_set_slot, ":prop", slot_shield_throw_targets_left, 0),
      (copy_position, pos5, pos2),
      (position_move_z, pos5, 150),
      (position_move_y, pos5, 1000),
      (store_random_in_range, ":rot", 0, 360),
      (position_rotate_z, pos5, ":rot"),
    (else_try),
      (scene_prop_slot_eq, ":prop", slot_shield_throw_state, 2), # returning
      (scene_prop_set_slot, ":prop", slot_shield_throw_state, 0), # cought
      (agent_get_bone_position, pos5, ":player", 12, 1),
      (scene_prop_set_slot, ":prop", slot_shield_throw_cur_target, ":player"),
    (try_end),   
    (get_distance_between_positions, ":f_dist", pos3, pos5),
    (store_div, ":time", ":f_dist", ST_Time_Factor),
    (prop_instance_animate_to_position, ":prop", pos5, ":time"),
  ]),
4)
Python:
("shield_tracker", sokf_moveable, "barrier_sphere", "0",
  [
  (ti_on_scene_prop_is_animating,
    [
    (store_trigger_param_1, ":prop"),
    (scene_prop_get_slot, ":tracker", ":prop", slot_shield_throw_tracker),
    (prop_instance_is_valid, ":tracker"),
    (prop_instance_get_position, pos20, ":prop"),
    (prop_instance_set_position, ":tracker", pos20),
    ]),
  (ti_on_scene_prop_animation_finished,
    [
    (store_trigger_param_1, ":prop"),
    (get_player_agent_no, ":player"),
    (scene_prop_get_slot, ":shield", ":prop", slot_shield_throw_item_id),
    (try_begin),
      (scene_prop_slot_eq, ":prop", slot_shield_throw_state, 0),
      (scene_prop_get_slot, ":tracker", ":prop", slot_shield_throw_tracker),    
      (agent_equip_item, ":player", ":shield"),
      (agent_set_wielded_item, ":player", ":shield"),
      (agent_set_animation, ":player", "anim_shield_catch", 1),    
      (scene_prop_set_slot, ":prop", slot_shield_throw_cur_target, -1),
      (try_for_agents, ":agent"),
        (agent_set_slot, ":agent", slot_shield_throw_target_hit, 0),
      (try_end),
      (prop_instance_is_valid, ":tracker"),
      (scene_prop_set_prune_time, ":tracker", 0),
    (else_try),
      (call_script, "script_send_shield_to_next_target", ":prop"),
      (scene_prop_get_slot, ":target", ":prop", slot_shield_throw_cur_target),
      (agent_is_active, ":target"),
      (agent_is_non_player, ":target"),      
      (item_get_body_armor, ":res", ":shield"),
      (store_skill_level, ":skl_shield", skl_shield),
      (store_skill_level, ":skl_pt", skl_power_throw),
      (store_mul, ":s_boost", ":skl_shield", ST_Shield_Skill_Factor),
      (store_mul, ":pt_boost", ":skl_pt", ST_PT_Skill_Factor),
      (store_add, ":min_dmg", 20, ":s_boost"),
      (store_add, ":max_dmg", 60, ":s_boost"),
      (val_add, ":min_dmg", ":res"),
      (val_add, ":max_dmg", ":pt_boost"),
      (store_random_in_range, ":real_dmg", ":min_dmg", ":max_dmg"),
      (agent_deliver_damage_to_agent, ":player", ":target", ":real_dmg", "itm_maul"),
      (try_begin),
        (item_has_property, ":shield", itp_wooden_parry),
        (assign, ":sound", "snd_shield_hit_wood_wood"),
      (else_try),
        (assign, ":sound", "snd_shield_hit_metal_metal"),
      (try_end),
      #(try_begin),
      #  (agent_get_troop_id, ":trp", ":target"),
      #  (troop_is_hero, ":trp"),
      #  (assign, ":sound", "snd_gong"),
      #(try_end),
      (agent_play_sound, ":target", ":sound"),
      (agent_set_slot, ":target", slot_shield_throw_target_hit, 1),
      (scene_prop_get_slot, ":tgts_left", ":prop", slot_shield_throw_targets_left),
      (val_sub, ":tgts_left", 1),
      (scene_prop_set_slot, ":prop", slot_shield_throw_targets_left, ":tgts_left"),
      (agent_get_horse, ":horse", ":target"),
      (agent_is_active, ":horse"),
      (agent_start_running_away, ":horse"),
      (agent_set_slot, ":horse", slot_agent_is_running_away, 1),
      (agent_set_animation, ":target", "anim_rider_fall_right", 1),    
    (try_end),
    ])]),
5)
Python:
(0, 0, 0,
    [(neg|main_hero_fallen),  
    (key_clicked, ST_Button),],
    [
    (get_player_agent_no, ":player"),
    (agent_get_wielded_item, ":shield", ":player", 1),
    (try_begin),
      (lt, ":shield", 1),
      (display_message, "@You need a shield for that.", 0xFF5050),
      (play_sound, "snd_quest_cancelled"),
    (else_try),
      (agent_unequip_item, ":player", ":shield"),
      (agent_set_animation, ":player", "anim_shield_throw", 1),
      (agent_play_sound, ":player", "snd_sword_swing"),
      (agent_get_position, pos2, ":player"),
      (position_move_z, pos2, 150),
      (agent_get_bone_position, pos3, ":player", 12, 1),
      (set_spawn_position, pos3),
      (spawn_item_without_refill, ":shield", 0, 180),
      (assign, ":s_prop", reg0),
      (try_begin),
        (agent_slot_eq, ":player", slot_shield_throw_tracker, 0),
        (spawn_scene_prop, "spr_shield_tracker"),
        (scene_prop_set_visibility, reg0, 0),
        (agent_set_slot, ":player", slot_shield_throw_tracker, reg0),
        (assign, ":t_prop", reg0),
      (else_try),
        (agent_get_slot, ":t_prop", ":player", slot_shield_throw_tracker),
      (try_end),
      (prop_instance_set_position, ":t_prop", pos3),
      (scene_prop_set_slot, ":t_prop", slot_shield_throw_item_id, ":shield"),
      (scene_prop_set_slot, ":t_prop", slot_shield_throw_tracker, ":s_prop"),
      (scene_prop_set_slot, ":t_prop", slot_shield_throw_targets_left, ST_Targets),
      (scene_prop_set_slot, ":t_prop", slot_shield_throw_cur_target, -1),
      (scene_prop_set_slot, ":t_prop", slot_shield_throw_state, 2),
      (call_script, "script_send_shield_to_next_target", ":t_prop"),
    (try_end),
  ]),
End result:
 
Last edited:
For some strange reason one could combine it with this OSP :iamamoron:

Nice work!
 
Couple of suggestions

you can check whether it has itp_wooden_parry to play the wood instead of metal sound, or even play the gong sound when you hit a hero.

Aim at random, different bones to achieve different effects, for example disarming if it hits arm bones & lowering movement/dehorsing if it hits leg bones.

You can also store the item's other parameters to tweak how well it performs, such as
  • Use weight to determine cooldown period between uses similar to shield bash, perhaps checking player agility
  • Calculate the ratio of height/width of the shield & speed rating to determine how aerodynamic it is (as a modifier alongside ST_Time_Factor)
  • Take shield resistance as the min damage, modify max with power throw so it requires investment into multiple skills?
  • Add in the unfun chance to break base on shield hp + imod, or even not returning so you have to do a walk of shame to pick it back up
 
you can check whether it has itp_wooden_parry to play the wood instead of metal sound, or even play the gong sound when you hit a hero.
Indeed, added. The gong part is commented out tho, it's a bit too much.

Aim at random, different bones to achieve different effects, for example disarming if it hits arm bones & lowering movement/dehorsing if it hits leg bones.
Loweing movement with additional trigger checks to restore it back goes beyond this OSP's scope. Sure one can do it, but each mod may have its own approach to agent speed management, so it's better the user will add it to affected targets on his end. Disarming could be a thing, but on my experience bots draw their weapons back in 90% cases so not that much of a use. Dehorsing is already guaranteed to happen, and tbh it looks better when shield hits chest than legs.

Use weight to determine cooldown period between uses similar to shield bash, perhaps checking player agility
Cooldown to this could be implemented in so many ways that I leave it for user to add as well. My older OSPs had them integrated, which resulted in some users having to cut them off to add their own ones. So it will be easier to add when there isn't any included. I like the agility idea a lot though.

Calculate the ratio of height/width of the shield & speed rating to determine how aerodynamic it is (as a modifier alongside ST_Time_Factor)
As much as I sincerely agree that rhodok pavise and khergit cav shield shouldn't behave exactly the same while flying, I'm also afraid I wouldn't be able to pull this one off by myself. Any kinds of aerodynamic calculations always give me a hard time, but if you provide a code piece for that I'll gladly include it.

Take shield resistance as the min damage, modify max with power throw so it requires investment into multiple skills?
That's fair, these should have their impact on damage as well. Added that, lowering the default tresholds with them in mind.

Add in the unfun chance to break base on shield hp + imod, or even not returning so you have to do a walk of shame to pick it back up
Those are too optional to include in base pack :p I'd love to add some downsides like damaging the shield hp a bit after each throw, but MS is very limited in terms of agent item manipulations and so far I haven't found any tools for that. Best I could do was I went with spawn_item_without_refill which sometimes returns shield in a bit damaged state even when it was thrown at full hp. Sometimes it doesn't. I'm not sure why it has any effect at all, since it's not the operation to equip the shield, only to spawn it. And I still haven't figured out what exactly differs it from spawn_item in that regard.
 
Back
Top Bottom