So anyone who knows a bit about computing would guess that the game server needs a way to somehow talk to an external server storing the player information. What we are going to do is implement exactly this roundtrip. When a player joins our game server, the server is going to send a message to the character server, and will log the response in its log. What you need to see it in action on your own machine:
- A Warband installation
- The Warband module system (matching your Warband build number)
- The Warband dedicated server files (matching your Warband build number)
- A webserver with a server side scripting language (xampp will do)
I won't explain how the module system works or how you set up a webserver, you can learn that elsewhere.
First, make a copy of the Native module and call it "dRPG" or whatever name you like. Install the module system therein. Open module_scripts.py and find the string "multiplayer_server_player_joined_common" insert the following snippet right at the end of this script block, after the last "(try_end)," line and before the square bracket:
(player_get_unique_id, ":player_unique_id", ":player_no"),
(assign, reg0, ":player_unique_id"),
(str_store_player_username, s1, ":player_no"),
(server_add_message_to_log, "@http://localhost/dRPG/character.php?id={reg0}&name={s1}"),
(send_message_to_url, "@http://localhost/dRPG/character.php?id={reg0}&name={s1}"),
This code runs whenever a player joins your server, and it gets his unique id (probably a hash of the cd-key) and the player name, adds a log entry and sends this information to our webserver that we are going to set up in a moment.
Next, in the same file, find the string "game_receive_url_response". In this mostly commented-out block, insert the following, again between the last "(try_end)," and the square bracket:
(server_add_message_to_log, "@Received response: {s0}{reg0}"),
This code is run when the game server receives a message back from our character server that we are soon going to set up. Here, we are going to log the server's response. In cRPG, the player troop is probably set up here with stats and items retrieved from the character server's response. Maybe we will get to that later, but at the moment, all we are going to do is log the response, so we can see if the roundtrip worked. Save the script and compile your module.
Now it's time to set up our game server. Extract the dedicated server files anywhere on your computer. The server also needs your new module, so copy the dRPG module folder from your Warband installation into the dedicated server's Modules folder. Then go back to the server's main folder, and note the different .txt and matching .bat files. We are going to use the sample battle server, so open Sample_Battle.txt and paste this into it:
#WARNING: Make sure that you change the capital values with proper ones.
#uncomment the line below when you set a valid administrator password
#set_pass_admin ADMINPASS
#if you have premium members, set a password for them, otherwise delete/comment out the line below
#uncomment the line below when you set a valid private password
#set_pass_private PRIVATEPASS
#uncomment the line below when you set a valid server name
set_server_name dRPG_Test_Server
#uncomment the line below when you set a valid welcome message
set_welcome_message You don't belong here. Go away.
#Steam must be running in order to use valve anti cheat
#Also you must use the Steam version of the dedicated server in order to use this option
set_enable_valve_anti_cheat 0
#setting battle (multiplayer_bt) mode
set_mission multiplayer_bt
#setting max players, first one is non-premium member limit, second one is premium member limit
set_max_players 32 32
set_num_bots_voteable 20
set_map multi_scene_1
add_map multi_scene_2
add_map multi_scene_4
add_map multi_scene_7
add_map multi_scene_9
add_map multi_scene_11
add_map multi_scene_12
add_map random_multi_plain_medium
add_map random_multi_plain_large
add_map random_multi_steppe_medium
add_map random_multi_steppe_large
#adding all kingdoms to both sides just to randomize all of them
#adding less kingdoms will reduce the randomization set (used in set_randomize_factions command)
add_factions fac_kingdom_1 fac_kingdom_1
add_factions fac_kingdom_2 fac_kingdom_2
add_factions fac_kingdom_3 fac_kingdom_3
add_factions fac_kingdom_4 fac_kingdom_4
add_factions fac_kingdom_5 fac_kingdom_5
add_factions fac_kingdom_6 fac_kingdom_6
set_randomize_factions 1
#since default team point limit is 300, the line below is necessary for this mode
set_team_point_limit 10
#if the bottleneck is your server's bandwidth, then make sure that you set a correct value for upload limit
set_upload_limit 100000000
#if you are running more than one dedicated server on the same computer, you must give different ports to each of them
set_port 7240
#if you are running the Steam version of the dedicated server, this port must also be set, and same limitations of set_port apply for Steam port
#set_steam_port 7241
set_server_log_folder Logs
set_server_ban_list_file Logs\ban_list.txt
start
You can also use your own server config if you know how to set one up, as there is nothing dRPG related in this config. Next, rightclick the Sample_Battle.bat file and edit it. Here, we have to point the server executable to our new module instead of the native module, so replace "-m Native" with "-m dRPG" or whatever name you chose for your module. Then save the file and double click it to run your server. Maybe you'll have to confirm a firewall popup to allow the server to be reachable through Windows' firewall. Don't worry about your external firewall, we are going to set up a local character server with xampp.
Now with our Module prepared both on the client and the game server it's time to set up the character server. Install the xampp installer in the default location "C:\xampp". Launch the control panel when done, but don't start anything yet. Navigate to C:\xampp\htdocs and create a new folder called "dRPG". Note that if you choose another name for this folder, you'll have to change the URLs in the script blocks above, or the game server won't find your character server. In this folder, create a new file called character.php and paste the following code into it:
<?php
$id = $_GET['id'];
$name = $_GET['name'];
echo "Hello $name, your id is |$id";
?>
This code retrieves the message values from the game server and sends back a response (with the same values, which is boring, but we only want to see the roundtrip in action at the moment). What cRPG probably does here is retrieve the character data from a database and send back all stats and items to the game server. Save the file, and in the xampp control panel, launch Apache. You'll probably have to confirm a few firewall popups again so that Apache is allowed to accept traffic on the local interface.
And that's it. Now start Warband, and in the launcher, select your module, then make a new character and join your server (switch from internet to local servers to find it). Select a class and spawn, then alt-x and go to your dedicated server folder. In there, you find a folder "Logs". Open the contained log file and you should see those two lines:
13:06:29 - http://localhost/dRPG/character.php?id=SOME_NUMBER&name=PLAYER_NAME
13:06:32 - Received response: Hello PLAYER_NAME, your id is SOME_NUMBER
That's it. With this knowledge, you can now start your own cRPG (or dRPG) project. All you have to do is dig a little deeper in the module system and learn how all the other fancy stuff we got in cRPG is done.
Now I know the basic roundtrip wasn't that exciting yet. It shows how the principle works, but there is of course a lot of work that still has to be done to get anything remotely similar to cRPG going. What we are going to do now is expand on the first part, so that we can make use of the information that is coming from the character server and make available a few items to the player according to the information we get back.
The first thing we have to do is create new troop types that don't have any preset items and attributes/skills, because we are going to assign those with information taken from the character server. I also recommend to create new factions for this, which is why you should open module_factions.py and search for the text "kingdom_6". As you can see, these 6 lines define the native Warband factions. Paste the following code below them:
("kingdom_7", "Kingdom of Kingliness", 0, 0.9, [], [], 0x1133BB),
("kingdom_8", "Free state of Freedomhood", 0, 0.9, [], [], 0xBB1111),
This defines two new kingdoms. If you want to learn about the individual values, read the topmost part of the file, each field is explained there.
The next part is a bit tedious, because you have to register those new factions in several locations. I recommend to follow this tutorial: http://forums.taleworlds.com/index.php/topic,116286.msg2801692.html#msg2801692 Come back, once you are done with module_presentations.py and module_scripts.py.
Now that our factions are added, we have to create a troop and assign it to them. Open module_troops.py and search for the text "sarranid_mamluke_multiplayer". This is the entry for the cavalry class of the Sarranids in native (note that it spans multiple lines). We are going to paste the following code directly below this block:
["fighter1","Fighter","Fighters",tf_guarantee_all,0,0,fac_kingdom_7,
[],
def_attrib_multiplayer|level(1),wpe(0,0,0,0),0,swadian_face_young_1,swadian_face_old_2],
["fighter2","Fighter","Fighters",tf_guarantee_all,0,0,fac_kingdom_8,
[],
def_attrib_multiplayer|level(1),wpe(0,0,0,0),0,swadian_face_young_1,swadian_face_old_2],
We are going to add two empty troops, one for each kingdom, because I couldn't figure out how to add the same troop to multiple kingdoms. chadz mentioned that they completely removed native restrictions such as this one, but unfortunately, I don't know how to do that.
Now that our troops are ready, we can get to the more interesting stuff. Open module_scripts.py and search for the text "multiplayer_server_player_joined_common". At the bottom of this block are the lines of code we inserted in the first part. Change them to this:
# dRPG hook
(player_get_unique_id, ":player_unique_id", ":player_no"),
(assign, reg0, ":player_unique_id"),
(assign, reg1, ":player_no"),
(str_store_player_username, s1, ":player_no"),
(server_add_message_to_log, "@http://localhost/dRPG/character.php?id={reg0}&name={s1}&no={reg1}"),
(send_message_to_url, "@http://localhost/dRPG/character.php?id={reg0}&name={s1}&no={reg1}"),
In addition to the player's name and unique id, we are now also sending his ingame player number (starts from 1, increases when someone joins) to the server. You will see why in a moment.
Now to find the location where the server's response is processed, search for the text "game_receive_url_response". Again, the code block contained therein should be familiar, because it's what we added in part 1. Replace it with this:
# dRPG hook
# Response: s0 = player name, reg0 = player number, reg1 = item1, reg2 = item2
(server_add_message_to_log, "@Received response: {s0} {reg0} {reg1} {reg2}"),
(multiplayer_send_4_int_to_player, reg0, 113, reg1, reg2, 0, 0),
(call_script, "script_multiplayer_set_item_available_for_troop", reg1, "trp_fighter1"),
(call_script, "script_multiplayer_set_item_available_for_troop", reg2, "trp_fighter1"),
(call_script, "script_multiplayer_set_item_available_for_troop", reg1, "trp_fighter2"),
(call_script, "script_multiplayer_set_item_available_for_troop", reg2, "trp_fighter2"),
Now, it's important to understand that this code block is only run on the game server, since he is the only machine that gets the response from the character server. This means we'll have to send the values from the character server's response to the correct client machine, which is done with the multiplayer_send_bla command. This command needs a player number to know which machine to send the information to, which is why we added the player number to the character server roundtrip. It will sit in reg0, and can be used as destination in the multiplayer_send_bla command. We also hardcode a new event type here (113), for which we are going to filter later on. And the rest of the command are the retrieved values (only two for the moment, for the remaining two, we send 0).
After that, we finally set the items received from the server as available for our new troop types (but only on the server so far).
Now search for the text "cf_multiplayer_evaluate_poll". We are not going to edit this block, but the one above it, which is a long ass block, so you are faster if you go to the block below, because we are again going to add to the end of it, right after "(display_message, "str_server_s0", 0xFFFF6666)," and before "(try_end),":
# dRPG hook
(else_try),
(eq, ":event_type", 113),
(store_script_param, ":item1", 3),
(store_script_param, ":item2", 4),
(store_script_param, ":item3", 5),
(store_script_param, ":item4", 6),
(display_message, "@Client received server call"),
(call_script, "script_multiplayer_set_item_available_for_troop", ":item1", "trp_fighter1"),
(call_script, "script_multiplayer_set_item_available_for_troop", ":item2", "trp_fighter1"),
(call_script, "script_multiplayer_set_item_available_for_troop", ":item1", "trp_fighter2"),
(call_script, "script_multiplayer_set_item_available_for_troop", ":item2", "trp_fighter2"),
In this block, we are first checking whether it's our event type 113 that has been sent. If yes, we store the sent values in easy to access variables, display a message, and then assign the items to our troops like we did on the server.
That's it for the module, compile it and when it's done, copy it to the dedicated server.
The last thing we now have to do is change the code on the character server. Open your character.php file and change the code to this:
<?php
$id = $_GET['id'];
$name = $_GET['name'];
$no = $_GET['no'];
echo "$name|$no|434|437";
?>
We retrieve the unique id (not used atm), player name and player number from the request and send back a hardcoded message with player name, player number and two items. These items are the "Sword" (itm_sword_medieval_a) and "Arming Sword" (itm_sword_medieval_c). I tried different items, but it seems that there are further restrictions in place that will have to be overcome at a later date, because not all worked. You can also see that they are hardcoded as number. The numbers are their id in the item_kinds1.txt. It's unfortunately not possible to work with item identifiers at runtime, I guess because the module systems compiles them into the integer numbers as we use them above.
To make sure the dedicated server uses our new factions only, edit the "Sample_Battle.txt" file and find the text "add_factions" replace the whole block up until and including the line "set_randomize_factions 1" with the following:
add_factions fac_kingdom_7 fac_kingdom_8
#add_factions fac_kingdom_2 fac_kingdom_2
#add_factions fac_kingdom_3 fac_kingdom_3
#add_factions fac_kingdom_4 fac_kingdom_4
#add_factions fac_kingdom_5 fac_kingdom_5
#add_factions fac_kingdom_6 fac_kingdom_6
#set_randomize_factions 1
This adds our first new kingdom as faction one and the second new kingdom as faction two and disables randomization.
That's it for part 2. Make sure Apache is started in your xampp control panel, launch the dedicated server, launch warband with your module and connect to the server. You should see the message "Client received server call" after about a second and be able to choose one of our new factions and the troop "Fighter". This troop should have the "Sword" and "Arming Sword" as weapons, once you click any of the righthand empty hand symbols (weapon slots).
The next step will be to assign stats to the player. You will be able to read about it here in part 3, once I get my lazy ass up and dig into the matter.