MP Info Module System Warband Modding: Server and Client Events

Users who are viewing this thread

Yoshiboy

Count
Updated 17/5/10: Fixed a few bugs in the sample code.
Updated 16/1/11: Added some extra notes.


So recently I've had quite a few PMs asking about various troubles people have had with warband modding - mostly to do with the idea of getting something to sync between the server all the clients. So what I thought I'd do is write a tutorial going through it all, so hopefully people get the idea. It's rather simple, providing you know what you're looking for. So here we go.

Part 1 - How to think about multiplayer modding

Ok, so the first problem we ran into with this sort of thing in Hunt was that gun sounds would only play on the computer of the player who shot them. Of course this wasn't really the end of the world - but it was a pain - so I looked into how we could get them to sync. In this tutorial I'm going to be going through how to add new server and client events, in this case to enable our gun sounds (or any other sounds) to sync between players. So the running example will be this gun event - but it should be applicable to basically all other events you'll need and problems of the same sort.

What I did first was take a look at all the new multiplayer commands. We've got some ones which look like this:

Code:
# multiplayer
multiplayer_send_message_to_server   = 388 # (multiplayer_send_int_to_server, <message_type>),
multiplayer_send_int_to_server       = 389 # (multiplayer_send_int_to_server, <message_type>, <value>),
multiplayer_send_2_int_to_server     = 390 # (multiplayer_send_2_int_to_server, <message_type>, <value>, <value>),
multiplayer_send_3_int_to_server     = 391 # (multiplayer_send_3_int_to_server, <message_type>, <value>, <value>, <value>),
multiplayer_send_4_int_to_server     = 392 # (multiplayer_send_4_int_to_server, <message_type>, <value>, <value>, <value>, <value>),
multiplayer_send_string_to_server    = 393 # (multiplayer_send_string_to_server, <message_type>, <string_id>),
multiplayer_send_message_to_player   = 394 # (multiplayer_send_message_to_player, <player_id>, <message_type>),
multiplayer_send_int_to_player       = 395 # (multiplayer_send_int_to_player, <player_id>, <message_type>, <value>),
multiplayer_send_2_int_to_player     = 396 # (multiplayer_send_2_int_to_player, <player_id>, <message_type>, <value>, <value>),
multiplayer_send_3_int_to_player     = 397 # (multiplayer_send_3_int_to_player, <player_id>, <message_type>, <value>, <value>, <value>),
multiplayer_send_4_int_to_player     = 398 # (multiplayer_send_4_int_to_player, <player_id>, <message_type>, <value>, <value>, <value>, <value>),
multiplayer_send_string_to_player    = 399 # (multiplayer_send_string_to_player, <player_id>, <message_type>, <string_id>),

All these seem to be about sending things between the players and the server. More on these later.

Also we have this one:

Code:
multiplayer_is_server                = 417 # (multiplayer_is_server),

Which is a conditional thing in "try" blocks which only allows the server to execute the commands following it (you can also negate it with neg| to make it client only)

So basically the server and the client both run the whole module in it's entirety with the exception of the server running these "multiplayer_is_server" blocks and the client only running the negated ones. Most of the time this actually works really well for us - along with all the hard coded stuff this means almost everything is already synced. We only run into issues with a few things. Like when the player or server generated a random number - these will be different. Or when the player does certain things like executing the code after firing a weapon or the code after interacting with a scene prop - this is something the server doesn't track or take into account.

So when you're thinking about multiplayer modding, just remember the following:

* Each client and the server run the whole module in it's entirety with the exception of the server running "multiplayer_is_server" blocks and the client running the negated ones.
* The Server sees everyone else as just another "agent" or, in more depth, a "player". It doesn't keep track of what is happening at their end.
* Same goes for the client - everyone is just another "player" and all it tries to do is put them in the right position, doing the right thing.

Part 2 - Setting up our gun

So taking a quick look at the gun code:

Code:
["flintlock_pistol", "Flintlock Pistol", [("flintlock_pistol",0)], itp_type_pistol |itp_merchandise|itp_primary ,itcf_shoot_pistol|itcf_reload_pistol, 230 , weight(1.5)|difficulty(0)|spd_rtng(38) | shoot_speed(160) | thrust_damage(45 ,pierce)|max_ammo(1)|accuracy(65),imodbits_none,
 [(ti_on_weapon_attack, [(play_sound,"snd_pistol_shot"),(position_move_x, pos1,27),(position_move_y, pos1,36),(particle_system_burst, "psys_pistol_smoke", pos1, 15)])]],

It's fairly obvious what is wrong. The (play_sound,"snd_pistol_shot") just plays a noise on the client which uses it, and doesn't send data to everyone else.

(Note: the particle stuff seems to already have hardcoded events so we don't have to sync this.)

The solution is to use one of the new multiplayer commands, to send the data to the server, telling everyone that we've shot the gun, so that he can then relay this to all the other players. But all those commands take a <message_type> parameter. You might be wondering what this is. Well it's the event type - of which all the current ones are listed in header_common, and we need to set up our own before we can use the command. We're adding a new "client_event", because it is the client who is sending the message to the server. We'll call ours "multiplayer_event_sound_made_by_player"

Code:
...
multiplayer_event_admin_set_friendly_fire_damage_self_ratio   = 40
multiplayer_event_admin_set_friendly_fire_damage_friend_ratio = 41
multiplayer_event_admin_set_allow_player_banners              = 42
multiplayer_event_admin_set_force_default_armor               = 43
multiplayer_event_admin_set_anti_cheat                        = 44

# NEW EVENTS

multiplayer_event_sound_made_by_player = 45

...

(Note: In newer versions of M&B more events have been added, so the constant numbers above might have to be changed. Just make sure there are no two event numbers that overlap and that you don't go above 128 because this is the hardcoded limit. If you need more events, consider using sub-events by always using one event number but then passing an extra parameter to differentiate between what you want to do.)

Now changing the gun code so that it uses the event is fairly simple. We remove the "play_sound" command and replace it with our new message to the server. Idealy we'll want the server to relay this sound event back to all players - including the one who made the sound.

Code:
["flintlock_pistol", "Flintlock Pistol", [("flintlock_pistol",0)], itp_type_pistol |itp_merchandise|itp_primary ,itcf_shoot_pistol|itcf_reload_pistol, 230 , weight(1.5)|difficulty(0)|spd_rtng(38) | shoot_speed(160) | thrust_damage(45 ,pierce)|max_ammo(1)|accuracy(65),imodbits_none,
 [(ti_on_weapon_attack, [(multiplayer_send_int_to_server,multiplayer_event_sound_made_by_player,"snd_pistol_shot"),(position_move_x, pos1,27),(position_move_y, pos1,36),(particle_system_burst, "psys_pistol_smoke", pos1, 15)])]],

Part 3 - Telling the server what to do

So now we've got the gun sending a message to the server, but the server doesn't know what to do when it receives this message, so we need to add some code for that. Open up module_scripts and search for this script "game_receive_network_message".

This is a very important script. Basically it is the script which is called whenever any of the new "multiplayer_send_X_to_Y" commands are used, and the various script parameters correspond to the extra data which is send by the command. So in here, we want to add some code that is executed when it picks up on our messages. Slightly confusingly we want to add our code into the section labelled with "SERVER EVENTS" as it is the server which executes our stuff. We'll add our code right at the end so scroll down to just above where we have "CLIENT EVENTS" in a comment block and we'll add some code:

Code:
...
          (assign, "$g_multiplayer_force_default_armor", ":value"),
        (try_end),
      (else_try),
	  
	  # NEW EVENTS ADDED
	  
	  (eq,":event_type", multiplayer_event_sound_made_by_player),
	  (neq,":player_no",0),
	  (store_script_param, ":sound", 3),
	  
          (get_max_players, ":num_players"),
          (try_for_range, ":cur_player", 0, ":num_players"),
            (player_is_active,":cur_player"),
            (multiplayer_send_2_int_to_player, ":cur_player", multiplayer_event_sound_at_player, ":sound",":player_no"),
          (try_end),
	  
	  (else_try),
	  
	  # END NEW EVENTS ADDED
	  
        ###############
        #CLIENT EVENTS#
        ###############
        (neq, multiplayer_is_server),
        (try_begin),      
...

Ok so we've got a few things going on here. First of all you'll notice a new event I've sneaked in - "multiplayer_event_sound_at_player". We'll need to add this to header_common, under the server events section (as it is the server who uses it)

Code:
...
multiplayer_event_return_anti_cheat                           = 104

# NEW SERVER EVENTS

multiplayer_event_sound_at_player = 105
...

This is the event which the clients pick up, to tell them that some player (":player_no") has made a sound (":sound"). ":player_no" is a variable grabbed right at the beginning of the script, and corresponds to the player who send the event message to the server in the first place. ":sound" is just the sound variable which was sent by the player - we grab this from the script parameters.

Then finally we have a try for range block - which loops over all the players, and sends them this message. And we're done with the server.

Part 4 - Telling the clients what to do

Getting the clients to do the right thing is actually really quite similar. You might have spotted the massive comments block labelled "CLIENT EVENTS" and this is exactly where we are going to add our code for catching this new message the server just sent out. Scroll down right to the bottom of this "CLIENT EVENTS" block and we'll add our new code.

Code:
...
            (store_add, "$g_my_spawn_count", "$g_my_spawn_count", ":value"),
          (else_try),
            (assign, "$g_my_spawn_count", ":value"),      
          (try_end),
		
		# NEW CLIENT EVENTS
		
		(else_try),
			(eq, ":event_type", multiplayer_event_sound_at_player),
			(store_script_param, ":sound", 3),
			(store_script_param, ":player", 4),
		
			(player_get_agent_id,":agent",":player"),
			
			(try_begin),
			(neq,":agent",-1),
				(display_message,"@BANG!"), # This is just a debug message, it isn't essential.
				(agent_play_sound,":agent",":sound"),
			(end_try),
			
		# END NEW CLIENT EVENTS  
		  
        (try_end),
     ]), 
...

So this is all fairly straight foward. We test to see if it is our new event, if so we grab the parameter for which sound was made and which player made the sound. Then we find the agent that player uses and get it to make the sound. I've also added a debug message for testing. To stop some errors we also have to test for a null agent.

Part 5 - You're done

And this is it. Believe it or not you're done. Compile your code, load up the server running your new mod, get a couple of players on there and shoot away.

bang.png


Finally it is probably worth noting the difference between dedicated servers and hosted games and how this effects your code. The main difference is that in hosted games, player number 0 is also a player playing the game (and as such will have to be sent sound events and the like) where as with a dedicated server, player number 0 is always the server. This can get you into different bits of trouble if you're not careful and I believe there have been introduced some other peculiarities in recent versions too which I'm not familiar with. The way to see if the game is running as dedicated or hosted is to use the command "multiplayer_is_dedicated_server".

Lots of people have been saying they did this tutorial exactly and that it didn't work (i.e didn't play any sound). I'd love to help you, but realize that I cannot read minds. Unless you give me some more info then I don't have any more idea than you on how to get it working. What you should do at this point is try adding in some debug messages using "display_message" to see what parts of the code are getting executed and which are not. Really any info will help me tell you where things might be going wrong.

If anyone finds any errors in the tutorial or has any issues then please say. I did test it in-game and it was being a bit funny (although the code is exactly the same as for hunt so I'm not sure why).

Anyway, hope that helps. If anyone has any other requests for Warband modding tutorials please say.

- Dan
 
I see this hasn't gathered much attention.

Well, I'll thank Yoshi for this tutorial once again. This really helped me to understand the Multiplayer aspect of Warband modding.
 
Yeawell done, we came up with the same problem with our musket sounds,you should have just asked Beaver how to fix it lol!
nice tutorial though, very clear
 
Bite Me said:
Yeawell done, we came up with the same problem with our musket sounds,you should have just asked Beaver how to fix it lol!
nice tutorial though, very clear
Hehe, that wouldn't have worked for them since they got crossbows in their mod as well. And Yoshi's solution is much better than mine... :razz:
Besides, this has helped me a lot too. I believe I already said thanks in a pm, but it doesn't hurt to post it too! Thanks a lot!  :grin:
 
Cheers. If anyone else has any other requests for warband modding tutorials or something similar don't hesitate to ask.
 
I have a question, Yoshi.

Is it possible to play a sound at a specific position or at a scene prop? I know it's possible to play a sound at a agent (as seen in your tutorial, agent_play_sound).
 
Not sure but I wouldn't bet on it. There isn't any "play_sound_at_position" command or anything like that, although when the "play_sound" command is used in the context a scene prop trigger it does appear to be attached to that scene prop - perhaps that could be used somehow. Make a network trigger set a global variable, which then is tested for on the prop and if true it plays the sound. Something like that might work.
 
This is a great tutorial, as well as all your other ones, but I wish we had a 'correct' place for these type of posts.  :sad:  I've suggested this multiple times before but we really need a module system documentation board that we can post in!    I'd like to just un-lock the the Documentation board in this thread and move threads like this, shield bash, spear brace, jik's guide, and all the other tutorials, scripts, and other things to that sub board.  Once the warband module system comes out and people try to do single-player and multi-player coding we're going to have even more questions in this thread and the tutorials and other good information get completely lost...

edit: I started a new thread about this
http://forums.taleworlds.com/index.php/topic,106266.0.html
 
I have a big headache after trying to add a new race (or even modifying existing one) for multiplayer warband.


The game crashes when ever I put a new body part in one of the skins in module_skins. Is there somewhere else I have to define the bodyparts (eg body mesh, head mesh)? Adding new races could be very important for some mods, so any help anyone can give would be appreciated...
 
Wookie Warlord, could you explain to me how to add weapons to multiplayer please  :mrgreen: We all hate not knowing how... :sad:
 
Oh yea well I figured that one out:


go to module_scripts. find the area with:



 
Code:
    #arrows
      (item_set_slot, "itm_arrows", slot_item_multiplayer_item_class, multi_item_class_type_arrow),      
      (item_set_slot, "itm_barbed_arrows", slot_item_multiplayer_item_class, multi_item_class_type_arrow),      
      (item_set_slot, "itm_bodkin_arrows", slot_item_multiplayer_item_class, multi_item_class_type_arrow),


It has one of those for each item in multiplayer, create your own ones at the bottom of the list, eg I have:



Code:
      (item_set_slot, "itm_ig_armour3", slot_item_multiplayer_item_class, multi_item_class_type_light_armor),
      (item_set_slot, "itm_tau_helmet", slot_item_multiplayer_item_class, multi_item_class_type_light_helm),
      (item_set_slot, "itm_tau_pulse_rifle", slot_item_multiplayer_item_class, multi_item_class_type_bow),
make sure it is the right class type.


then just below this list is a list which assigns possible items to the troops:



Code:
     (call_script, "script_multiplayer_set_item_available_for_troop", "itm_concentrated_pulse_charge", "trp_swadian_crossbowman_multiplayer"),
      (call_script, "script_multiplayer_set_item_available_for_troop", "itm_tau_pulse_rifle", "trp_swadian_crossbowman_multiplayer"),
so you see this adds the items 'tau_pulse_rifle' and 'concentrated_pulse_charge' which i also added the said list above to the available list of the troop 'swadian_crossbowman_multiplayer.


and... thats about it
 
Erm, this isn't really about server and client events. Anyway, adding new races for multiplayer games using module_skins is basically impossible at the moment because the player picks the race (male or female) from their profile, and this overrides anything the mod tries to do.
 
Oh right. Well AI troops should be fine - they work the same as singleplayer. What you're getting wrong is something in module_skins.
 
Back
Top Bottom