OSP Code Combat Shield Bash (OSP)

Users who are viewing this thread

xenoargh

Grandmaster Knight
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:

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:
Code:
multiplayer_event_admin_set_disallow_ranged_weapons           = 47
This:
Code:
mp_agent_play_sound_client                                     = 48
mp_agent_shield_bash                                           = 49

And

Under:
Code:
multiplayer_event_return_disallow_ranged_weapons              = 112
This:
Code:
mp_shield_bash_server                                      = 113


Extra:

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),
    ])
as the (game_is_in_multiplayer_mode), is supposed to be (game_in_multiplayer_mode),


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],   
   ],
 
Checking that the animation isn't already a bash isn't exactly the best condition. Maybe have agent_get_defend_action = 2 (blocking) as a condition instead?

Also, for the position check, even if you move it 75cm ahead, it probably checks enemies 25cm behind the player. Maybe have stuff like position_has_line_of_sight_to_position and get_angle_between_positions (with angle dependent on shield width)?
 
Checking that the animation isn't already a bash isn't exactly the best condition. Maybe have agent_get_defend_action = 2 (blocking) as a condition instead?
If the concern is that they can break out and spam it, then it's easy enough to fix that:

Code:
["shield_bash", acf_enforce_all, amf_play|amf_priority_striked|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],   
],

But technically, it's not a block.  It's an alternate attack, like dropping a block to throw a slash.  So watching for a blocking state wouldn't make such sense.

for the position check, even if you move it 75cm ahead, it probably checks enemies 25cm behind the player.
Sure, but they'd have to be practically on top of them

I'm not sure if LOS takes Agents into account, or just stuff with bo_ meshes.  Angles might be worth doing- i.e., within 45 degrees of the agent's aim, so that it doesn't knock down guys to either side.  IDK what the "fixed point" in the documentation means, though- is it returning angles as pos, where we can dig out the radians, or what?
 
Shiel Bash!? Is back!? Great! :shock:
Thanks for sharing! Very useful to me! Besides, I always thought it's fun to hit with the shield. :grin:
 
I would also stop a script after first hit. Kinda not real to have several people bashed to the ground with one shield

Somebody said:
Also, for the position check, even if you move it 75cm ahead, it probably checks enemies 25cm behind the player. Maybe have stuff like position_has_line_of_sight_to_position and get_angle_between_positions (with angle dependent on shield width)?
IMO, having a target circle in front of you is a bit more efficient than angles + distances check (i.e target sector) because you calculate distance anyway in both cases (can use some quite some cpu with mass battle btw), but don't calculate/check angles in the first one. Effect is mostly identical
 
Stopping people after first hit is trivial:

Code:
("shield_bash",[  
(this_or_next|multiplayer_is_server),
(neg|game_in_multiplayer_mode),	
(assign, ":already_hit", 0),
(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),
                        (lt, ":already_hit", 1),
			(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"),		
                        (assign, ":already_hit", 1),	
		(try_end),
	(try_end),
(try_end),
(try_end),
]), 

Two-buttons... uh... if you're finding that hard, I'm tempted to say something incredibly sarcastic :wink:  Re-read what's already posted, and figure it out.  The answer's right in front of you  :mrgreen:
 
The point of RMB+LMB is so that you are already blocking (using the shield) before bashing with it - hence my suggestion to check for defend action. Also, you might want to relocate (lt, ":already_hit", 1) near (gt, ":agent", 0),
 
Somebody said:
The point of RMB+LMB is so that you are already blocking (using the shield) before bashing with it...
Exactly. And it's more convenient IMO.

xenoargh said:
Two-buttons... uh... if you're finding that hard, I'm tempted to say something incredibly sarcastic :wink:  Re-read what's already posted, and figure it out.  The answer's right in front of you  :mrgreen:
[very cold voice] I know how to change it myself, thank you very much... :razz:
 
Will it also work in 1.011 if I delete some operations from Warband?
 
Has anyone managed to get this working for MP? I can compile everything fine until I reach the module_scripts, can't quite figure it out.
 
hi guys,

first thx xenoargh to share this amazing code.

it since 2 days i tried to implement old shield bash (1.011) without success. so it's works quite well. but, i ve got red script error when bashing. maybe i miss something.

EDIT : Warning on code 1768: invalid agent_id:0; line no: 6:
          At script cf_shield_bash

(it's error when bashing)

sry if i annoy you but i need a little help to put me on the good way.

BTW, i ve found a cool animation bashing in the ShinobiKinzokuHasu mod. it's like 300 movie's when spartiats lines push back the Persians with their shield. i think it's a cool anim for bashing.
but the problem is i haven't got source code for this animation, only BRF. so i need some advise on how implement this anim with shield bash. i know how major system files works but i m a noob with flags, variables, constants, numbers, and other stuff

i'm not complain or troll about your work.

oh and of course, u will be credited in my mod
:grin: :grin: :grin:

EDIT 2 : i ve successfully changed key control for bashing :smile: :smile:  : i ve put right mouse button hold then push left control. in my opinion, it's better because if i bash with left mouse button, it was messed up with the regular strike. :grin: tell me if u want the code but it's very easy. but i ve still red script error when bashing :cry: :cry: :cry: HELP ME PLEASE :cry: :cry: :cry:
 
Turanien said:
Has anyone managed to get this working for MP? I can compile everything fine until I reach the module_scripts, can't quite figure it out.
Won't work in MP atm. You'd need to rescript quite a bit to make it work in MP. I can do it if people want.
 
Back
Top Bottom