I know it's been done before, but I figured that it'd be best to just write one from scratch, since it was fairly easy and I wanted a very specific effect.
The results are really cool and feel pretty good, in SP. Players get the ability to semi-heroically shield-bash enemies to their front, using the left-hand CTRL key, which results in a small move backwards and a brief stun state, but without damage. It's like a kick you can sweep. It's quite OP vs. single enemies, as written, but it could be balanced in various ways for SP, such as a STR test, etc. or just minor tweaking on durations.
I don't know if it's totally MP-friendly or not. Testing and code tweaks are welcome. And it is not currently AI-friendly, as I am not sure that there is a reasonably-efficient way to trigger it for AIs yet (ideas on that would be welcome).
I honestly think that a weaker version of this (smaller radius, shorter stun duration or longer duration of attack) would go a very long way to making sword-and-board much more interesting and realistic in MP, as it would give sword-and-board people another of the big advantages they had IRL.
Anyhow, enough buildup. Here's the code:
module_scripts:
module_mission_templates (you obviously need to put it into specific mt's like other common_ scripts):
module_animations (needs to replace a free unused_human_anim ofc):
MP version of the script, modifying sinisterius's code(see below) by Arch3r. I haven't tested this.
MP version of the script, by Sinisterius. I haven't tested this.
EDIT.
Updated to latest version.
The results are really cool and feel pretty good, in SP. Players get the ability to semi-heroically shield-bash enemies to their front, using the left-hand CTRL key, which results in a small move backwards and a brief stun state, but without damage. It's like a kick you can sweep. It's quite OP vs. single enemies, as written, but it could be balanced in various ways for SP, such as a STR test, etc. or just minor tweaking on durations.
I don't know if it's totally MP-friendly or not. Testing and code tweaks are welcome. And it is not currently AI-friendly, as I am not sure that there is a reasonably-efficient way to trigger it for AIs yet (ideas on that would be welcome).
I honestly think that a weaker version of this (smaller radius, shorter stun duration or longer duration of attack) would go a very long way to making sword-and-board much more interesting and realistic in MP, as it would give sword-and-board people another of the big advantages they had IRL.
Anyhow, enough buildup. Here's the code:
module_scripts:
Code:
#Shield Bash Script
("shield_bash",[
(this_or_next|multiplayer_is_server),
(neg|game_in_multiplayer_mode),
(get_player_agent_no,":player_agent"),
(try_begin),
(gt, ":player_agent", 0),
(agent_get_animation, ":anim", ":player_agent",0),
(agent_get_horse, ":my_horse", ":player_agent"),
(agent_get_wielded_item, ":shield_item", ":player_agent", 1),
(try_begin),
(neq, ":anim", "anim_shield_bash"),
(eq, ":my_horse", -1),
(is_between, ":shield_item", "itm_wooden_shield", "itm_steel_shield_herald03"),
(agent_set_animation, ":player_agent","anim_shield_bash"),
(agent_get_position, pos63,":player_agent"),
(position_move_y,pos63,75),#75 cm directly ahead, so it's not a cuboid space around player center
(agent_get_troop_id, ":id", ":player_agent"),
(troop_get_type, ":type", ":id"),
(try_begin),
(eq, ":type", tf_male),
(agent_play_sound, ":player_agent", "snd_man_yell"),
(else_try),
(agent_play_sound, ":player_agent", "snd_woman_yell"),
(try_end),
(try_for_agents,":agent"),
(gt, ":agent", 0),
(neg|agent_is_ally,":agent"),#don't bash allies
(agent_is_human, ":agent"),#stop if not human
(agent_is_active,":agent"),
(agent_is_alive,":agent"),
(try_begin),
(agent_get_position,pos62,":agent"),
(get_distance_between_positions,":dist",pos63,pos62),
(lt,":dist",100),#Set this to whatever you like- 1 meter radius clears a big section of crowd
(agent_get_horse, ":horse", ":agent"),
(eq, ":horse", -1),
(neq,":agent",":player_agent"),
(agent_play_sound, ":player_agent", "snd_wooden_hit_low_armor_high_damage"),
(position_move_y,pos62,-25),
(agent_set_position, ":agent", pos62),
(agent_set_animation, ":agent","anim_shield_strike"),
(try_end),
(try_end),
(try_end),
(try_end),
]),
#End Shield Bash Script
module_mission_templates (you obviously need to put it into specific mt's like other common_ scripts):
Code:
common_shield_bash = (0,0,0,[
(key_clicked, key_left_control),
(neg|main_hero_fallen),
],
[
(call_script, "script_shield_bash"),
])
module_animations (needs to replace a free unused_human_anim ofc):
Code:
["shield_bash", 0, amf_play|amf_priority_defend|amf_use_defend_speed|amf_client_owner_prediction,
[0.75, "defend_shield_parry_all", 1, 50, blend_in_defense], #Adjust duration for balance. Currently at 0.75 seconds, fixed.
[0.75, "defend_shield_right", 1, 50, blend_in_defense],
[0.75, "defend_shield_left", 1, 50, blend_in_defense],
[0.75, "defend_shield_right", 1, 50, blend_in_defense],
],
["shield_strike", acf_enforce_all|acf_align_with_ground, amf_priority_striked|amf_play|amf_accurate_body|amf_restart,
[1.0, "anim_human", blow+5000, blow+5010, arf_blend_in_3|arf_make_custom_sound],
[1.7, "anim_human", blow+5400, blow+5453, arf_blend_in_2|arf_make_custom_sound],
[1.44, "anim_human", blow+5400, blow+5445, arf_blend_in_2|arf_make_custom_sound],
],
MP version of the script, modifying sinisterius's code(see below) by Arch3r. I haven't tested this.
Arch3r said:An update for module_scripts (sinisterius MP codes):
Search in game_receive_network_message:
Code:(else_try), (eq, ":event_type", multiplayer_event_admin_set_disallow_ranged_weapons), (try_begin), (store_script_param, ":value", 3), #validity check (player_is_admin, ":player_no"), (is_between, ":value", 0, 2), #condition checks are done (assign, "$g_multiplayer_disallow_ranged_weapons", ":value"), (try_end),
And paste directly under that, this:
Code:(else_try), (eq, ":event_type", mp_shield_bash_server), #Get sender data & max players. # (player_is_active, ":player_no"), (player_get_agent_id, ":agent_no", ":player_no"), # (agent_is_active, ":agent_no"), (agent_is_alive, ":agent_no"), # (agent_is_human, ":agent_no"), #Check if the sender is in the correct condition. (agent_get_wielded_item, ":shield", ":agent_no", 1), #Offhand item. (is_between, ":shield", "itm_wooden_shield", "itm_darts"), (agent_get_horse, ":horse", ":agent_no"), (eq, ":horse", -1), #No horse. #If everything is correct, then set the sender agent up for bash. (get_max_players, ":max_players"), (try_for_range, ":player", 0, ":max_players"), (player_is_active, ":player"), (agent_set_animation, ":agent_no", "anim_shield_bash"), (multiplayer_send_2_int_to_player, ":player", mp_agent_shield_bash, ":agent_no", "anim_shield_bash"), (try_end), #Bash. Get the closest agent within 175cm~1.75m. (agent_get_position, pos1, ":agent_no"), (assign, ":minimum_distance", 100), #1 meter (assign, ":victim", -1), (try_for_agents, ":suspect"), (agent_is_active, ":suspect"), (agent_is_alive, ":suspect"), (neq, ":suspect", ":agent_no"), #Suspect can't be our local agent. (agent_is_human, ":suspect"), #Compare distances. (agent_get_position, pos2, ":suspect"), (neg|position_is_behind_position, pos2, pos1), (get_distance_between_positions, ":distance", pos1, pos2), (le, ":distance", ":minimum_distance"), #If distance is sufficient.. (assign, ":minimum_distance", ":distance"), (assign, ":victim", ":suspect"), (try_end), #If we have the victim, aka the closest agent, then deal with him. Else, do nothing. (ge, ":victim", 0), (agent_get_horse, ":horse", ":victim"), (eq, ":horse", -1), #No horse. (try_for_range, ":player", 0, ":max_players"), (player_is_active, ":player"), (multiplayer_send_2_int_to_player, ":player", mp_agent_play_sound_client, ":victim", "snd_wooden_hit_low_armor_high_damage"), (try_end), (agent_get_position, pos2, ":victim"), (position_move_y, pos2, -25), (position_get_distance_to_ground_level, ":distance", pos2), (try_begin), (le, ":distance", 25), (agent_set_position, ":victim", pos2), (agent_set_animation, ":victim", "anim_shield_strike"), (try_for_range, ":player", 0, ":max_players"), (player_is_active, ":player"), (multiplayer_send_2_int_to_player, ":player", mp_agent_shield_bash, ":victim", "anim_shield_strike"), (try_end), (else_try), (gt, ":distance", 25), (agent_set_animation, ":victim", "anim_shield_strike"), (try_for_range, ":player", 0, ":max_players"), (player_is_active, ":player"), (multiplayer_send_2_int_to_player, ":player", mp_agent_shield_bash, ":victim", "anim_shield_strike"), (try_end), (try_end), (player_get_troop_id, ":troop_no", ":player_no"), (troop_get_type, ":type_no", ":troop_no"), (try_begin), (eq, ":type_no", tf_male), (try_for_range, ":player", 0, ":max_players"), (player_is_active, ":player"), (multiplayer_send_2_int_to_player, ":player", mp_agent_play_sound_client, ":agent_no", "snd_man_yell"), (try_end), (else_try), (eq, ":type_no", tf_female), (try_for_range, ":player", 0, ":max_players"), (player_is_active, ":player"), (multiplayer_send_2_int_to_player, ":player", mp_agent_play_sound_client, ":agent_no", "snd_woman_yell"), (try_end), (try_end),
And search for:
Code:(else_try), ############### #CLIENT EVENTS# ############### (neq, multiplayer_is_server), (try_begin), (eq, ":event_type", multiplayer_event_return_renaming_server_allowed), (store_script_param, ":value", 3), (assign, "$g_multiplayer_renaming_server_allowed", ":value"),
And paste under that:
Code:(else_try), (eq, ":event_type", mp_agent_play_sound_client), (store_script_param, ":value", 3), (store_script_param, ":value_2", 4), (agent_play_sound, ":value", ":value_2"), (else_try), (eq, ":event_type", mp_agent_shield_bash), (store_script_param, ":value", 3), (store_script_param, ":value_2", 4), (agent_set_animation, ":value", ":value_2"),
Add to header_common.py:
Under:
This:Code:multiplayer_event_admin_set_disallow_ranged_weapons = 47
Code:mp_agent_play_sound_client = 48 mp_agent_shield_bash = 49
And
Under:
This:Code:multiplayer_event_return_disallow_ranged_weapons = 112
Code:mp_shield_bash_server = 113
Extra:
as the (game_is_in_multiplayer_mode), is supposed to be (game_in_multiplayer_mode),Code:mp_shield_bash = ( 0, 0, 0, [ (game_key_is_down, gk_defend), (game_key_clicked, gk_attack), # (game_in_multiplayer_mode), ], [ (multiplayer_get_my_player, ":my_player"), (player_get_agent_id, ":agent_no", ":my_player"), (agent_get_wielded_item, ":shield", ":agent_no", 1), #Offhand item. (is_between, ":shield", "itm_wooden_shield", "itm_darts"), # (display_message, "@shield bash initiated."), # (agent_get_animation, ":agent_anim", ":agent_no", 1), (multiplayer_send_message_to_server, mp_shield_bash_server), ])
MP version of the script, by Sinisterius. I haven't tested this.
EDIT.
Updated to latest version.
Code:
mp_shield_bash = (
0, 0, 0,
[
(game_key_is_down, gk_defend),
(game_key_clicked, gk_attack),
(game_is_in_multiplayer_mode),
],
[
(multiplayer_send_message_to_server, mp_shield_bash_server),
])
Code:
#Server/client events.
#Copy-paste this stuff right after the store_script_params in game_receive_network_message.
(try_begin),
(eq, ":event_type", mp_shield_bash_server),
#Get sender data & max players.
(player_is_active, ":player_no"),
(player_get_agent_id, ":agent_no", ":player_no"),
(agent_is_active, ":agent_no"),
(get_max_players, ":max_players"),
#Check if the sender is in the correct condition.
(agent_get_wielded_item, ":shield", ":agent_no", 1), #Offhand item.
(is_between, ":shield", "itm_wooden_shield", "itm_steel_shield_herald03"),
(agent_get_defend_action, ":action", ":agent_no"),
(eq, ":action", 2), #Blocking.
(agent_get_animation, ":anim", ":agent_no", 0),
(neq, ":anim", "anim_shield_bash"),
(agent_get_horse, ":horse", ":agent_no"),
(eq, ":horse", -1), #No horse.
#If everything is correct, then set the sender agent up for bash.
(agent_set_animation, ":agent_no", "anim_shield_bash"),
(player_get_troop_id, ":troop_no", ":player_no"),
(troop_get_type, ":type_no", ":troop_no"),
(try_begin),
(eq, ":type", tf_male),
(try_for_range, ":player", 0, ":max_players"),
(player_is_active, ":player"),
(multiplayer_send_2_int_to_player, ":player", mp_agent_play_sound_client, ":agent_no", "snd_man_yell"),
(try_end),
(else_try),
(eq, ":type", tf_female),
(try_for_range, ":player", 0, ":max_players"),
(player_is_active, ":player"),
(multiplayer_send_2_int_to_player, ":player", mp_agent_play_sound_client, ":agent_no", "snd_woman_yell"),
(try_end),
(try_end),
#Bash. Get the closest agent within 175cm~1.75m.
(agent_get_position, pos1, ":agent"),
(assign, ":minimum_distance", 400),
(assign, ":victim", -1),
(try_for_agents, ":suspect"),
(neq, ":suspect", ":agent"), #Suspect can't be our local agent.
(agent_is_active, ":suspect"),
(agent_is_alive, ":suspect"),
(agent_is_human, ":suspect"),
#Compare distances.
(agent_get_position, pos2, ":suspect"),
(neg|position_is_behind_position, pos2, pos1),
(get_distance_between_positions, ":distance", pos1, pos2),
(le, ":distance", ":minimum_distance"),
#If distance is sufficient..
(assign, ":minimum_distance", ":distance"),
(assign, ":victim", ":suspect"),
(try_end),
#If we have the victim, aka the closest agent, then deal with him. Else, do nothing.
(ge, ":victim", 0),
(agent_get_horse, ":horse", ":victim"),
(eq, ":horse", -1), #No horse.
(try_for_range, ":player", 0, ":max_players"),
(player_is_active, ":player"),
(multiplayer_send_2_int_to_player, ":player", mp_agent_play_sound_client, ":victim", "snd_wooden_hit_low_armor_high_damage"),
(try_end),
(agent_get_position, pos2, ":victim"),
(position_move_y, pos2, -25),
(position_get_distance_to_ground_level, ":distance", pos2),
(try_begin),
(le, ":distance", 25),
(agent_set_position, ":victim", pos2),
(agent_set_animation, ":victim", "anim_shield_strike"),
(else_try),
(gt, ":distance", 25),
(agent_set_animation, ":victim", "anim_shield_strike"),
(try_end),
(else_try),
(neq, multiplayer_is_server),
(try_begin),
(eq, ":event_type", mp_agent_play_sound_client),
(store_script_param, ":agent", 3),
(store_script_param, ":sound", 4),
(agent_play_sound, ":agent", ":sound"),
(try_end),
(try_end),
Code:
#These animations go into module_animations.py
#They MUST replace existing unused animations.
##################
##Copyright: xenoargh. ##
##################
["shield_bash", 0, amf_play|amf_priority_defend|amf_use_defend_speed|amf_client_owner_prediction,
[0.75, "defend_shield_parry_all", 1, 50, blend_in_defense], #Adjust duration for balance. Currently at 0.75 seconds, fixed.
[0.75, "defend_shield_right", 1, 50, blend_in_defense],
[0.75, "defend_shield_left", 1, 50, blend_in_defense],
[0.75, "defend_shield_right", 1, 50, blend_in_defense],
],
["shield_strike", acf_enforce_all|acf_align_with_ground, amf_priority_striked|amf_play|amf_accurate_body|amf_restart,
[1.0, "anim_human", blow+5000, blow+5010, arf_blend_in_3|arf_make_custom_sound],
[1.7, "anim_human", blow+5400, blow+5453, arf_blend_in_2|arf_make_custom_sound],
[1.44, "anim_human", blow+5400, blow+5445, arf_blend_in_2|arf_make_custom_sound],
],