# Multiplayer missions
Multiplayer missions are missions where every player is connected to a common server. That server accepts commands from players and also send notifications to the players. This enables player-to-player communication, as well as adding web operators to interact with the players.
Missions are built on shared data which is mutated and events are generated as a result of those mutations. All clients may subscribe to areas of the data and then be advised of all changes. Consistency is assured by the client and server buffering which enables in-order guarantee on message delivery. Clients disconnecting and reconnecting will have their messages automatically delivered.
# Server Termination Commands (terminationCommands
)
The server will interpret the terminationCommands
shared data. This section is specifically for clients to register commands which will be run when they are disconnected and then purged from the server after not reconnecting for some time. terminationCommands
are very important in that they assure server data is not stale even in the face of player disconnects.
Generally, you'll want to add yourself (with an ID) to a connectedAircraft
. Given that, you'll want to also set up a terminationCommands.{id}
which goes and removes connectedAircraft.{id}
. This way, when you disconnect the server is able to automatically clean up for you and clients will be notified of the delete operation, in case they need to respond accordingly.
# Shared Data
Shared data is the foundation of the multiplayer platform. The server will store arbitrary values and the clients may subscribe to updates on those values. Each client carefully issues commands to update
and delete
data, using a policy
to avoid conflicts and enable merging of commands.
Both on the aircraft and on the web you may use set_shared_data
to issue these commands. In the aircraft, MultiplayerClient
is your gateway to multiplayer data.
# MultiplayerClient
MultiplayerClient
is the type of object returned by fn.create_multiplayer_connection
. This object represents the interface to the multiplayer server and it has various functions to call and state to access.
Function | Parameters | Remarks |
---|---|---|
Connect | url , userId , roomId , roomPassword | Establish a connection to the server. |
Subscribe | path , callback<object> | Subscribe to a path and retrieve the current data via callback |
Get | path , callback<object> | Get a pat and retrieve the current data via callback |
Send | message | Send a message object |
Close | None. | Disconnect and destroy the connection |
Message Types:
Message Type | Parameters | Remarks |
---|---|---|
read | path , value | The server has returned an error regarding a recent command you sent. |
update | value , value , policy | The server has returned an error regarding a recent command you sent. |
delete | path | The server is sending you data about shared data changes. |
Policy | Remarks |
---|---|
delta | Value is relative. |
no_overwrite | Ignore update if path exists. |
Property | Remarks |
---|---|
Status | Connection status. |
Event Handler | Parameters | Remarks |
---|---|---|
OnError | error (string) | The server has returned an error regarding a recent command you sent. |
OnMessage | data (object) | The server is sending you data about shared data changes. |
Connection status values:
Status | Remarks |
---|---|
Unknown | Default state, this is the status before calling connect. |
Disconnected | The connection is disconnected, retry will be automatically attempted. |
Connecting | Connecting to the server. |
LoggingIn | Server is connected, handshake in progress. |
LoginFailed | Fatal. Login was not successful. |
Connected | Currently connected. |
# Multiplayer simple scoring example
This mission creates a score table and each aircraft has a button to set the score for that player.
Additional features:
- Show flight plans on the web client map
- Clients will clean up their
connectedAircraft
andterminationCommands
entries, but not their score.
{
"title": "Multiplayer Score Mission Test Program",
"author":"davux3",
"api_version": 0.1,
"aircraft": ["H145"],
"data":{
"server_url": "wss://5ed547d.online-server.cloud/mpserver/ws",
"create_room_url": "https://davux.com/dispatcher/",
"webConfig": {
"fligihtPlans": {
"type":"map_line",
"source":{"static":"flightPlans"},
"name":"Flight Plan",
"stroke":{"no_resolve":{"color": "#d303fc", "width":2}},
"icon":{"static":"icons.wp_blue"}
},
"connectedAircraftIcons": {
"type":"map_point",
"source":{"static":"connectedAircraft"},
"name":"Connected Aircraft",
"text":"{UserName}",
"icon":{"static":"icons.h160_icon"}
},
"scoreList": {
"type":"list",
"source":{"static":"gameScores"},
"title":"Game Scores",
"emptyText":"No players have connected yet.",
"rows":{
"row0":{
"1": {"text":"{UserName}"},
"2": {"text":"Total Score: {0}", "params": [ {"round":{"param":"Score"}} ]},
"3": {"text":""}
}
}
},
"connectedAircraftList": {
"type":"list",
"source":{"static":"connectedAircraft"},
"title":"Connected Aircraft",
"emptyText":"No aircraft are connected right now",
"rows":{
"row0":{
"1": {"icon":{"static":"icons.h160_icon"}},
"2": {"text":"{UserName}"},
"4": {"button":"View","commands": [ {"set_map_center": {"param": "location"}, "zoom": 16} ]}
}
}
}
}
},
"briefing":[
{"#comment":[
"MP_MODE ... 0: not set, 1: offline, 2: online"
]},
{"title":"Mission Initial Setup", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"buttonbar":[
{"title":"Offline (Single player)", "commands": [ {"set":{"local":"MP_MODE"}, "value":1} ]},
{"title":"Online (Multiplayer)", "commands": [ {"call_macro":"mp_open_login_dialog"} ]}
], "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"title":"Multiplayer (Online)", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 2}},
{"buttonbar":[
{"title":"View Multiplayer Status", "commands": [ {"call_macro":"mp_open_login_dialog"} ]}
], "show_condition": {"require":{"local":"MP_MODE"}, "eq": 2}},
{"title":"Game Score", "show_condition": {"require":{"local":"MP_MODE"},"ne":0}},
{"text":"My score: {local:MY_SCORE}", "show_condition": {"require":{"local":"MP_MODE"},"ne":0}},
{"buttonbar":[
{
"title":"Increment My Score",
"commands":[
{"set":{"local":"MY_SCORE"},"value":{"add":[ {"local":"MY_SCORE"}, 1 ]}},
{"set_shared_data":"update", "path":"gameScores.{service_auth}.Score", "value": {"local":"MY_SCORE"} }
]
}
],
"show_condition": {"require":{"local":"MP_MODE"},"ne":0}}
],
"events": {
"ON_MISSION_ABORTING": {
"commands": [ {"call_macro":"mp_aborting_mission"} ]
}
},
"macros":{
"mp_open_login_dialog":[
{"#comment": "Show the login dialog dispatch (or multiplayer status"},
{"set_dispatch":[
{"buttonbar":[ {"title":"<- Back to briefing", "commands": [{"set_briefing_dialog":1} ]} ]},
{"title":"Log in", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"text":"You are playing offline.", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 1}},
{"text":{"text":"User Id: {0}", "params":[{"local":"service_auth"}]}, "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"text":"User Name:", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"textbox":"mp_userName", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"text":"Room:", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"textbox":"mp_room", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"text":"Password:", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"textbox":"mp_password", "show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"buttonbar":[
{"title":"Create Room (Opens on PC)", "commands": [ {"open_url":"{static:create_room_url}?room={local:mp_room}"} ]},
{"title":"Log In", "commands": [ {"call_macro":"mp_login"} ]}
],
"disabled_condition":{"require":{"struct":{"local":"MP_CONN"}, "path":"Status"},"eq":"Connected"},
"show_condition": {"require":{"local":"MP_MODE"}, "eq": 0}},
{"text":{"text":"MP Connection Status: {0}", "params":[
{"struct":{"local":"MP_CONN"}, "path":"Status"}
]}, "show_condition": {"require":{"local":"MP_MODE"}, "ne": 1}},
{"text":{"text":"MP Server Last Error: {local:MP_LAST_ERROR}"}, "show_condition": {"require":{"local":"MP_MODE"}, "ne": 1}},
{"title":"Debug Info"},
{"text":{"text":"Multiplayer Mode: {0}", "params":[
{"switch":{"local":"MP_MODE"}, "case":{
"0": "Undecided",
"1": "Offline, Singleplayer",
"2": "Multiplayer"
}}
]}},
{"#comment":{"text":"Debug MP Message: {local:MP_MSG}"}, "show_condition": {"require":{"local":"MP_MODE"}, "ne": 1}}
]},
{"set_dispatch_dialog":1}
],
"mp_login":[
{"#comment":"try to make the actual connection to the server"},
{"set":{"param":"service_auth"},"value":{"local":"service_auth"}},
{"set":{"local":"MP_LAST_ERROR"},"value":""},
{"set":{"local":"MP_CONN"}, "value": {"fn": "create_multiplayer_connection"}},
{"set":{"local":"MP_CONN", "path":"OnError"}, "value":{"js:create_async_function":[
{"set":{"local":"MP_LAST_ERROR"},"value":{"struct": {"param":"$args"}, "index": 0}}
]}},
{"set":{"local":"MP_CONN", "path":"OnMessage"}, "value":{"js:create_async_function":[
{"set":{"param":"arg0"},"value":{"struct": {"param":"$args"}, "index": 0}},
{"call_macro":"mp_on_message","params":{"msg": {"param":"arg0"}}}
]}},
{"set":{"param":"unused"},"value":{"struct":{"local":"MP_CONN"}, "function":"Connect", "params":[
{"static":"server_url"}, {"param":"service_auth"}, {"local":"mp_room"}, {"local":"mp_password"}
]}},
{"create_thread":{"commands":[
{"wait_for":{"struct":{"local":"MP_CONN"}, "path":"Status"},"eq":"Connected"},
{"#comment":"once we log in once, we're committed to muoltiplayer"},
{"set":{"local":"MP_MODE"}, "value": 2},
{"set_briefing_dialog":1},
{"#comment":"First create terminationCommands with no_overwrite, then add an entry for us, and then populate with commands to clear us from connectedAircraft and terminationCommands when we become stale on the server"},
{"set_shared_data":"update",
"path":"terminationCommands",
"policy":"no_overwrite",
"value": {"create_struct":{}}
},
{"set_shared_data":"update",
"path":"terminationCommands.{service_auth}",
"value": {"create_struct":{
"removeFromConnectedAircraft":{"create_struct":{
"type":"delete",
"path":"connectedAircraft.{service_auth}"
}},
"removeFromFlightPlans":{"create_struct":{
"type":"delete",
"path":"flightPlans.{service_auth}"
}},
"removeFromTerminationCommands":{"create_struct":{
"type":"delete",
"path":"terminationCommands.{service_auth}"
}}
}}},
{"#comment":"make sure we have connectedAircraft table. all players must use no_overwrite when ensuring the table exists to prevent anybody from destroying the table."},
{"set_shared_data":"update", "path":"connectedAircraft", "policy":"no_overwrite", "value": {"create_struct":{}} },
{"set_shared_data":"update", "path":"icons", "policy":"no_overwrite", "value": {"fn":"get_mission_icons"} },
{"set_shared_data":"update", "path":"flightPlans", "policy":"no_overwrite", "value": {"create_struct":{}} },
{"set_shared_data":"update", "path":"webConfig", "policy":"no_overwrite", "value": {"static":"webConfig"} },
{"set_shared_data":"update", "path":"gameScores", "policy":"no_overwrite", "value": {"create_struct":{}} },
{"set_shared_data":"update",
"path":"connectedAircraft.{service_auth}",
"value": {"create_struct":{
"location":{"resolve_location":"$USER"},
"UserName": {"local":"mp_userName"}
}}},
{"set_shared_data":"update",
"path":"gameScores.{service_auth}",
"value": {"create_struct":{
"UserName": {"local":"mp_userName"},
"Score": 0
}}
},
{"#comment":"update our location, score and flightplan (if changed) forever"},
{"while":1,"eq":1,"do":[
{"sleep":5},
{"set_shared_data":"update", "path":"connectedAircraft.{service_auth}.location", "value": {"resolve_location":"$USER"} },
{"set_shared_data":"update", "path":"gameScores.{service_auth}.Score", "value": {"local":"MY_SCORE"} },
{"if":{"json:stringify": {"local":"$FLIGHTPLAN"}}, "ne": {"param":"FPL"},"then":[
{"set":{"param":"FPL"},"value":{"json:stringify": {"local":"$FLIGHTPLAN"}}},
{"set_shared_data":"update", "path":"flightPlans.{service_auth}", "value": {"create_struct":{
"points":{"local":"$FLIGHTPLAN"}
}}}
]}
]}
]}}
],
"mp_initialize":[
{"#comment":"setup for multiplayer operations later"},
{"set":{"local":"MP_LAST_ERROR"},"value":""},
{"set":{"local":"MP_MODE"},"value":0},
{"#comment":"MP_MODE 0: undecided, 1: offline, 2:online"},
{"#comment":"these are for debugging only"},
{"set":{"local":"MP_MSG"},"value":""},
{"set":{"local":"mp_room"},"value":""},
{"set":{"local":"mp_password"},"value":""},
{"set":{"local":"mp_userName"},"value":{"var":["ATC AIRLINE","string"]}},
{"#comment": "Create or access a unique ID to identify you on the server irrespective of callsign"},
{"set":{"local":"service_auth"}, "value":{"fn":"create_guid"}},
{"create_thread":{"commands":[
{"wait_for":{"local":"MP_MODE"},"ne":0},
{"call_macro":"mp_begin"}
]}}
],
"mp_on_message":[
{"#comment":"param - msg"},
{"#comment":"handle READ, UPDATE and DELETE operations below"},
{"set":{"param":"json"},"value":{"json:stringify":{"param":"msg"}}},
{"switch":{"struct": {"param":"msg"}, "path": "type"}, "case": {
"read":[
{"set":{"local":"MP_MSG"},"value": "we got an read: {json}"}
],
"update": [
{"set":{"local":"MP_MSG"},"value": "we got an update: {json}"}
],
"delete": [
{"set":{"local":"MP_MSG"},"value": "we got an delete: {json}"}
]
}}
],
"mp_begin":[
{"#comment":"called once we decided if we are single or muliplayer. MP_MODE 1:offline, 2:online"},
{"#comment":"offline case, manually run the logic and complete logic"},
{"set_objective_title":"Ready to play the game!"}
],
"mp_aborting_mission":[
{"#comment":"we want to clean up our multiplayer connection if it was created"},
{"if":{"local":"MP_CONN"},"ne":null, "then":[
{"set":{"param":"unused"},"value":{"struct":{"local":"MP_CONN"}, "function":"Close", "params":[]}}
]}
]
},
"objectives": [
{
"title": "Setup required",
"commands": [
{"set":{"local":"MY_SCORE"},"value":0},
{"call_macro":"mp_initialize"},
{"sleep": "forever"}
]
}
],
"icons":{
"wp_blue":"data:image/png;base64,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",
"h160_icon": "data:image/png;base64,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"
}
}