# 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 and terminationCommands 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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"
  }
}