mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
1 Commits
40c576bc2d
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
![]() |
625fef0d39 |
@@ -46,8 +46,8 @@ module.exports = {
|
|||||||
.addCodeOnlyParameter('currentScene', '')
|
.addCodeOnlyParameter('currentScene', '')
|
||||||
.setHelpPath('/all-features/multiplayer')
|
.setHelpPath('/all-features/multiplayer')
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -74,8 +74,8 @@ module.exports = {
|
|||||||
.addParameter('yesorno', _('Show close button'), '', false)
|
.addParameter('yesorno', _('Show close button'), '', false)
|
||||||
.setHelpPath('/all-features/multiplayer')
|
.setHelpPath('/all-features/multiplayer')
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -102,8 +102,8 @@ module.exports = {
|
|||||||
)
|
)
|
||||||
.setHelpPath('/all-features/multiplayer')
|
.setHelpPath('/all-features/multiplayer')
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -132,8 +132,8 @@ module.exports = {
|
|||||||
.addParameter('string', _('Message name'), '', false)
|
.addParameter('string', _('Message name'), '', false)
|
||||||
.addParameter('string', _('Message content'), '', false)
|
.addParameter('string', _('Message content'), '', false)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -162,8 +162,8 @@ module.exports = {
|
|||||||
.addParameter('string', _('Message name'), '', false)
|
.addParameter('string', _('Message name'), '', false)
|
||||||
.addParameter('variable', _('Variable'), '', false)
|
.addParameter('variable', _('Variable'), '', false)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -194,8 +194,8 @@ module.exports = {
|
|||||||
.addParameter('string', _('Message name'), '', false)
|
.addParameter('string', _('Message name'), '', false)
|
||||||
.addParameter('variable', _('Variable'), '', false)
|
.addParameter('variable', _('Variable'), '', false)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -222,8 +222,8 @@ module.exports = {
|
|||||||
)
|
)
|
||||||
.addCodeOnlyParameter('currentScene', '')
|
.addCodeOnlyParameter('currentScene', '')
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -247,8 +247,8 @@ module.exports = {
|
|||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -272,8 +272,8 @@ module.exports = {
|
|||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -297,8 +297,8 @@ module.exports = {
|
|||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -325,8 +325,8 @@ module.exports = {
|
|||||||
)
|
)
|
||||||
.addParameter('string', _('Message name'), '', false)
|
.addParameter('string', _('Message name'), '', false)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -352,8 +352,8 @@ module.exports = {
|
|||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -377,8 +377,8 @@ module.exports = {
|
|||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -403,8 +403,8 @@ module.exports = {
|
|||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.addParameter('number', _('Player number'), '', false)
|
.addParameter('number', _('Player number'), '', false)
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -429,8 +429,8 @@ module.exports = {
|
|||||||
)
|
)
|
||||||
.addParameter('string', _('Message name'), '', false)
|
.addParameter('string', _('Message name'), '', false)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -453,8 +453,8 @@ module.exports = {
|
|||||||
)
|
)
|
||||||
.addParameter('string', _('Message name'), '', false)
|
.addParameter('string', _('Message name'), '', false)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -477,8 +477,8 @@ module.exports = {
|
|||||||
_('Lobbies'),
|
_('Lobbies'),
|
||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -502,8 +502,8 @@ module.exports = {
|
|||||||
_('Lobbies'),
|
_('Lobbies'),
|
||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -532,8 +532,8 @@ module.exports = {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -555,8 +555,8 @@ module.exports = {
|
|||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -584,8 +584,8 @@ module.exports = {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -607,8 +607,8 @@ module.exports = {
|
|||||||
'JsPlatform/Extensions/multiplayer.svg'
|
'JsPlatform/Extensions/multiplayer.svg'
|
||||||
)
|
)
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -637,8 +637,8 @@ module.exports = {
|
|||||||
'number',
|
'number',
|
||||||
gd.ParameterOptions.makeNewOptions().setDescription(_('Player number'))
|
gd.ParameterOptions.makeNewOptions().setDescription(_('Player number'))
|
||||||
)
|
)
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -669,8 +669,8 @@ module.exports = {
|
|||||||
.addParameter('variable', _('Variable'), '', false)
|
.addParameter('variable', _('Variable'), '', false)
|
||||||
.setHelpPath('/all-features/multiplayer')
|
.setHelpPath('/all-features/multiplayer')
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -700,8 +700,8 @@ module.exports = {
|
|||||||
.addParameter('variable', _('Variable'), '', false)
|
.addParameter('variable', _('Variable'), '', false)
|
||||||
.setHelpPath('/all-features/multiplayer')
|
.setHelpPath('/all-features/multiplayer')
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -731,8 +731,8 @@ module.exports = {
|
|||||||
.addParameter('variable', _('Variable'), '', false)
|
.addParameter('variable', _('Variable'), '', false)
|
||||||
.setHelpPath('/all-features/multiplayer')
|
.setHelpPath('/all-features/multiplayer')
|
||||||
.getCodeExtraInformation()
|
.getCodeExtraInformation()
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
@@ -837,8 +837,8 @@ module.exports = {
|
|||||||
multiplayerObjectBehavior,
|
multiplayerObjectBehavior,
|
||||||
sharedData
|
sharedData
|
||||||
)
|
)
|
||||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||||
.addIncludeFile(
|
.addIncludeFile(
|
||||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||||
)
|
)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -228,7 +228,7 @@ namespace gdjs {
|
|||||||
// Before sending the change owner message, if we are becoming the new owner,
|
// Before sending the change owner message, if we are becoming the new owner,
|
||||||
// we want to ensure this message is acknowledged, by everyone we're connected to.
|
// we want to ensure this message is acknowledged, by everyone we're connected to.
|
||||||
if (variableData.newVariableOwner === currentPlayerNumber) {
|
if (variableData.newVariableOwner === currentPlayerNumber) {
|
||||||
const otherPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||||
const variableOwnerChangedMessageName = gdjs.multiplayerMessageManager.createVariableOwnerChangedMessageNameFromChangeVariableOwnerMessage(
|
const variableOwnerChangedMessageName = gdjs.multiplayerMessageManager.createVariableOwnerChangedMessageNameFromChangeVariableOwnerMessage(
|
||||||
messageName
|
messageName
|
||||||
);
|
);
|
||||||
@@ -243,14 +243,12 @@ namespace gdjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debugLogger.info('Sending change owner message', messageName);
|
debugLogger.info('Sending change owner message', messageName);
|
||||||
const connectedPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||||
for (const peerId of connectedPeerIds) {
|
gdjs.multiplayerMessageManager.sendDataTo(
|
||||||
gdjs.multiplayerMessageManager.sendDataTo(
|
connectedPeerIds,
|
||||||
peerId,
|
messageName,
|
||||||
messageName,
|
messageData
|
||||||
messageData
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the variable from the list of variables ownership changes to sync.
|
// Remove the variable from the list of variables ownership changes to sync.
|
||||||
delete variableOwnershipChangesToSyncAtEndOfFrame[variableNetworkId];
|
delete variableOwnershipChangesToSyncAtEndOfFrame[variableNetworkId];
|
||||||
|
@@ -101,17 +101,19 @@ namespace gdjs {
|
|||||||
}, this._timeBeforeDestroyingObjectWithoutNetworkIdInMs);
|
}, this._timeBeforeDestroyingObjectWithoutNetworkIdInMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _sendDataToPeersWithIncreasedClock(
|
private _sendDataToPeersWithIncreasedClock = async (
|
||||||
messageName: string,
|
messageName: string,
|
||||||
data: Object
|
data: Object
|
||||||
) {
|
) => {
|
||||||
this._clock++;
|
this._clock++;
|
||||||
data['_clock'] = this._clock;
|
data['_clock'] = this._clock;
|
||||||
const connectedPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||||
for (const peerId of connectedPeerIds) {
|
await gdjs.multiplayerMessageManager.sendDataTo(
|
||||||
gdjs.multiplayerMessageManager.sendDataTo(peerId, messageName, data);
|
connectedPeerIds,
|
||||||
}
|
messageName,
|
||||||
}
|
data
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private _isOwnerAsPlayerOrHost() {
|
private _isOwnerAsPlayerOrHost() {
|
||||||
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
|
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
|
||||||
@@ -453,7 +455,7 @@ namespace gdjs {
|
|||||||
// If we are player 1, we are connected to everyone, so we expect an acknowledgment from everyone.
|
// If we are player 1, we are connected to everyone, so we expect an acknowledgment from everyone.
|
||||||
// If we are another player, we are only connected to player 1, so we expect an acknowledgment from player 1.
|
// If we are another player, we are only connected to player 1, so we expect an acknowledgment from player 1.
|
||||||
// In both cases, this represents the list of peers the current user is connected to.
|
// In both cases, this represents the list of peers the current user is connected to.
|
||||||
const otherPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||||
const {
|
const {
|
||||||
messageName: destroyMessageName,
|
messageName: destroyMessageName,
|
||||||
messageData: destroyMessageData,
|
messageData: destroyMessageData,
|
||||||
@@ -563,7 +565,7 @@ namespace gdjs {
|
|||||||
// If we are another player, we are only connected to player 1, so we expect an acknowledgment from player 1.
|
// If we are another player, we are only connected to player 1, so we expect an acknowledgment from player 1.
|
||||||
// In both cases, this represents the list of peers the current user is connected to.
|
// In both cases, this represents the list of peers the current user is connected to.
|
||||||
if (newObjectPlayerNumber === currentPlayerNumber) {
|
if (newObjectPlayerNumber === currentPlayerNumber) {
|
||||||
const otherPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||||
const changeOwnerAcknowledgedMessageName = gdjs.multiplayerMessageManager.createInstanceOwnerChangedMessageNameFromChangeInstanceOwnerMessage(
|
const changeOwnerAcknowledgedMessageName = gdjs.multiplayerMessageManager.createInstanceOwnerChangedMessageNameFromChangeInstanceOwnerMessage(
|
||||||
messageName
|
messageName
|
||||||
);
|
);
|
||||||
@@ -586,6 +588,9 @@ namespace gdjs {
|
|||||||
// If we are the new owner, also send directly an update of the position,
|
// If we are the new owner, also send directly an update of the position,
|
||||||
// so that the object is immediately moved on the screen and we don't wait for the next tick.
|
// so that the object is immediately moved on the screen and we don't wait for the next tick.
|
||||||
if (newObjectPlayerNumber === currentPlayerNumber) {
|
if (newObjectPlayerNumber === currentPlayerNumber) {
|
||||||
|
debugLogger.info(
|
||||||
|
'Sending update message to move the object immediately.'
|
||||||
|
);
|
||||||
const objectNetworkSyncData = this.owner.getNetworkSyncData();
|
const objectNetworkSyncData = this.owner.getNetworkSyncData();
|
||||||
const {
|
const {
|
||||||
messageName: updateMessageName,
|
messageName: updateMessageName,
|
||||||
|
@@ -562,7 +562,7 @@ namespace gdjs {
|
|||||||
// When the connectionId is received, initialise PeerJS so players can connect to each others afterwards.
|
// When the connectionId is received, initialise PeerJS so players can connect to each others afterwards.
|
||||||
if (validIceServers.length) {
|
if (validIceServers.length) {
|
||||||
for (const server of validIceServers) {
|
for (const server of validIceServers) {
|
||||||
gdjs.evtTools.p2p.useCustomICECandidate(
|
gdjs.multiplayerPeerJsHelper.useCustomICECandidate(
|
||||||
server.urls,
|
server.urls,
|
||||||
server.username,
|
server.username,
|
||||||
server.credential
|
server.credential
|
||||||
@@ -570,7 +570,7 @@ namespace gdjs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (brokerServerConfig) {
|
if (brokerServerConfig) {
|
||||||
gdjs.evtTools.p2p.useCustomBrokerServer(
|
gdjs.multiplayerPeerJsHelper.useCustomBrokerServer(
|
||||||
brokerServerConfig.hostname,
|
brokerServerConfig.hostname,
|
||||||
brokerServerConfig.port,
|
brokerServerConfig.port,
|
||||||
brokerServerConfig.path,
|
brokerServerConfig.path,
|
||||||
@@ -578,7 +578,7 @@ namespace gdjs {
|
|||||||
brokerServerConfig.secure
|
brokerServerConfig.secure
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
gdjs.evtTools.p2p.useDefaultBrokerServer();
|
gdjs.multiplayerPeerJsHelper.useDefaultBrokerServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectionId = connectionId;
|
_connectionId = connectionId;
|
||||||
@@ -705,7 +705,7 @@ namespace gdjs {
|
|||||||
// It is possible the connection to other players didn't work.
|
// It is possible the connection to other players didn't work.
|
||||||
// If that's the case, show an error message and leave the lobby.
|
// If that's the case, show an error message and leave the lobby.
|
||||||
// If we are the host, still start the game, as this allows a player to test the game alone.
|
// If we are the host, still start the game, as this allows a player to test the game alone.
|
||||||
const allConnectedPeers = gdjs.evtTools.p2p.getAllPeers();
|
const allConnectedPeers = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||||
if (!isPlayerHost() && allConnectedPeers.length === 0) {
|
if (!isPlayerHost() && allConnectedPeers.length === 0) {
|
||||||
gdjs.multiplayerComponents.displayConnectionErrorNotification(
|
gdjs.multiplayerComponents.displayConnectionErrorNotification(
|
||||||
runtimeScene
|
runtimeScene
|
||||||
@@ -775,7 +775,7 @@ namespace gdjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect from any P2P connections.
|
// Disconnect from any P2P connections.
|
||||||
gdjs.evtTools.p2p.disconnectFromAllPeers();
|
gdjs.multiplayerPeerJsHelper.disconnectFromAllPeers();
|
||||||
|
|
||||||
// Clear the expected acknowledgments, as the game is ending.
|
// Clear the expected acknowledgments, as the game is ending.
|
||||||
gdjs.multiplayerMessageManager.clearExpectedMessageAcknowledgements();
|
gdjs.multiplayerMessageManager.clearExpectedMessageAcknowledgements();
|
||||||
@@ -787,7 +787,7 @@ namespace gdjs {
|
|||||||
*/
|
*/
|
||||||
const handlePeerIdEvent = function (peerId: string) {
|
const handlePeerIdEvent = function (peerId: string) {
|
||||||
// When a peerId is received, trigger a P2P connection with the peer.
|
// When a peerId is received, trigger a P2P connection with the peer.
|
||||||
const currentPeerId = gdjs.evtTools.p2p.getCurrentId();
|
const currentPeerId = gdjs.multiplayerPeerJsHelper.getCurrentId();
|
||||||
if (!currentPeerId) {
|
if (!currentPeerId) {
|
||||||
logger.error(
|
logger.error(
|
||||||
'No peerId found, the player does not seem connected to the broker server.'
|
'No peerId found, the player does not seem connected to the broker server.'
|
||||||
@@ -800,7 +800,7 @@ namespace gdjs {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gdjs.evtTools.p2p.connect(peerId);
|
gdjs.multiplayerPeerJsHelper.connect(peerId);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -862,6 +862,8 @@ namespace gdjs {
|
|||||||
// Consider the game is ended, so that we don't listen to other players disconnecting.
|
// Consider the game is ended, so that we don't listen to other players disconnecting.
|
||||||
_isLobbyGameRunning = false;
|
_isLobbyGameRunning = false;
|
||||||
|
|
||||||
|
logger.info('Ending the lobby game.');
|
||||||
|
|
||||||
// Inform the players that the game has ended.
|
// Inform the players that the game has ended.
|
||||||
gdjs.multiplayerMessageManager.sendEndGameMessage();
|
gdjs.multiplayerMessageManager.sendEndGameMessage();
|
||||||
|
|
||||||
@@ -912,7 +914,7 @@ namespace gdjs {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const peerId = gdjs.evtTools.p2p.getCurrentId();
|
const peerId = gdjs.multiplayerPeerJsHelper.getCurrentId();
|
||||||
if (!peerId) {
|
if (!peerId) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"No peerId found, the player doesn't seem connected to the broker server."
|
"No peerId found, the player doesn't seem connected to the broker server."
|
||||||
|
10
Extensions/Multiplayer/peer.js
Normal file
10
Extensions/Multiplayer/peer.js
Normal file
File diff suppressed because one or more lines are too long
1
Extensions/Multiplayer/peer.js.map
Normal file
1
Extensions/Multiplayer/peer.js.map
Normal file
File diff suppressed because one or more lines are too long
414
Extensions/Multiplayer/peerJsHelper.ts
Normal file
414
Extensions/Multiplayer/peerJsHelper.ts
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
/// <reference path="peerjs.d.ts" />
|
||||||
|
namespace gdjs {
|
||||||
|
const logger = new gdjs.Logger('Multiplayer');
|
||||||
|
export namespace multiplayerPeerJsHelper {
|
||||||
|
/**
|
||||||
|
* The type of the data that is sent across peerjs.
|
||||||
|
* We use UInt8Array to send compressed data, but we only manipulate objects once received.
|
||||||
|
*/
|
||||||
|
type NetworkMessage = {
|
||||||
|
messageName: string;
|
||||||
|
data: Uint8Array;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to discard invalid messages when received.
|
||||||
|
*/
|
||||||
|
const isValidNetworkMessage = (
|
||||||
|
message: unknown
|
||||||
|
): message is NetworkMessage =>
|
||||||
|
typeof message === 'object' &&
|
||||||
|
message !== null &&
|
||||||
|
typeof message['messageName'] === 'string' &&
|
||||||
|
typeof message['data'] === 'object';
|
||||||
|
|
||||||
|
export interface IMessageData {
|
||||||
|
readonly data: any; // The data sent with the message, an object with unknown content.
|
||||||
|
readonly sender: String;
|
||||||
|
getData(): any;
|
||||||
|
getSender(): string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The data bound to a message name.
|
||||||
|
*/
|
||||||
|
export class MessageData implements IMessageData {
|
||||||
|
public readonly data: any;
|
||||||
|
public readonly sender: string;
|
||||||
|
constructor(data: object, sender: string) {
|
||||||
|
this.data = data;
|
||||||
|
this.sender = sender;
|
||||||
|
}
|
||||||
|
public getData(): any {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
public getSender(): string {
|
||||||
|
return this.sender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessagesList {
|
||||||
|
getName(): string;
|
||||||
|
getMessages(): IMessageData[];
|
||||||
|
pushMessage(data: object, sender: string): void;
|
||||||
|
}
|
||||||
|
export class MessagesList implements IMessagesList {
|
||||||
|
private readonly data: IMessageData[] = [];
|
||||||
|
private readonly messageName: string;
|
||||||
|
|
||||||
|
constructor(messageName: string) {
|
||||||
|
this.messageName = messageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): string {
|
||||||
|
return this.messageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMessages(): IMessageData[] {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pushMessage(data: object, sender: string): void {
|
||||||
|
this.data.push(new MessageData(data, sender));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The peer to peer configuration.
|
||||||
|
*/
|
||||||
|
let peerConfig: Peer.PeerJSOption = { debug: 1 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The p2p client.
|
||||||
|
*/
|
||||||
|
let peer: Peer<NetworkMessage> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All connected p2p clients, keyed by their ID.
|
||||||
|
*/
|
||||||
|
const connections = new Map<string, Peer.DataConnection<NetworkMessage>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains a map of message triggered by other p2p clients.
|
||||||
|
* It is keyed by the event name.
|
||||||
|
*/
|
||||||
|
const allMessages = new Map<string, IMessagesList>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if PeerJS is initialized and ready.
|
||||||
|
*/
|
||||||
|
let ready = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of IDs of peers that just disconnected.
|
||||||
|
*/
|
||||||
|
const justDisconnectedPeers: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of IDs of peers that just remotely initiated a connection.
|
||||||
|
*/
|
||||||
|
const justConnectedPeers: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to compress data sent over the network.
|
||||||
|
*/
|
||||||
|
async function compressData(data: object): Promise<Uint8Array> {
|
||||||
|
const jsonString = JSON.stringify(data); // Convert object to JSON string
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const array = encoder.encode(jsonString); // Convert string to Uint8Array
|
||||||
|
|
||||||
|
// @ts-ignore - As of 09/2023 the CompressionStream is now available in all browsers.
|
||||||
|
const cs = new CompressionStream('gzip'); // Create a CompressionStream with gzip
|
||||||
|
const writer = cs.writable.getWriter();
|
||||||
|
writer.write(array);
|
||||||
|
writer.close();
|
||||||
|
|
||||||
|
const compressedStream = cs.readable;
|
||||||
|
const reader = compressedStream.getReader();
|
||||||
|
const chunks: any[] = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
chunks.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const compressedData = new Uint8Array(
|
||||||
|
chunks.reduce((acc, chunk) => acc.concat(Array.from(chunk)), [])
|
||||||
|
);
|
||||||
|
return compressedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to decompress data received over the network.
|
||||||
|
* It returns the parsed JSON object, if valid, or undefined.
|
||||||
|
*/
|
||||||
|
async function decompressData(
|
||||||
|
compressedData: Uint8Array
|
||||||
|
): Promise<object | undefined> {
|
||||||
|
// @ts-ignore - As of 09/2023 the DecompressionStream is now available in all browsers.
|
||||||
|
const ds = new DecompressionStream('gzip'); // Create a DecompressionStream with gzip
|
||||||
|
const writer = ds.writable.getWriter();
|
||||||
|
writer.write(compressedData);
|
||||||
|
writer.close();
|
||||||
|
|
||||||
|
const decompressedStream = ds.readable;
|
||||||
|
const reader = decompressedStream.getReader();
|
||||||
|
const chunks: any[] = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
chunks.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decompressedData = new Uint8Array(
|
||||||
|
chunks.reduce((acc, chunk) => acc.concat(Array.from(chunk)), [])
|
||||||
|
);
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
const jsonStringData = decoder.decode(decompressedData); // Convert Uint8Array back to string
|
||||||
|
try {
|
||||||
|
const parsedData = JSON.parse(jsonStringData);
|
||||||
|
return parsedData;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Error while parsing message: ${e.toString()}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get the messages list for a given message name.
|
||||||
|
*/
|
||||||
|
export const getOrCreateMessagesList = (
|
||||||
|
messageName: string
|
||||||
|
): IMessagesList => {
|
||||||
|
const messagesList = allMessages.get(messageName);
|
||||||
|
if (messagesList) return messagesList;
|
||||||
|
const newMessagesList = new MessagesList(messageName);
|
||||||
|
allMessages.set(messageName, newMessagesList);
|
||||||
|
return newMessagesList;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function called when a connection with a remote peer is initiated.
|
||||||
|
* @param connection The DataConnection of the peer
|
||||||
|
*/
|
||||||
|
const _onConnect = (connection: Peer.DataConnection<NetworkMessage>) => {
|
||||||
|
connections.set(connection.peer, connection);
|
||||||
|
connection.on('data', async (data) => {
|
||||||
|
if (isValidNetworkMessage(data)) {
|
||||||
|
const messagesList = getOrCreateMessagesList(data.messageName);
|
||||||
|
const messageSender = connection.peer;
|
||||||
|
const decompressedData = await decompressData(data.data);
|
||||||
|
if (!decompressedData) return;
|
||||||
|
|
||||||
|
messagesList.pushMessage(decompressedData, messageSender);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close event is only for graceful disconnection,
|
||||||
|
// but we want onDisconnect to trigger for any type of disconnection,
|
||||||
|
// so we register a listener for both event types.
|
||||||
|
connection.on('error', () => {
|
||||||
|
_onDisconnect(connection.peer);
|
||||||
|
});
|
||||||
|
connection.on('close', () => {
|
||||||
|
_onDisconnect(connection.peer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Regularly check for disconnection as the built in way is not reliable.
|
||||||
|
(function disconnectChecker() {
|
||||||
|
if (
|
||||||
|
connection.peerConnection &&
|
||||||
|
(connection.peerConnection.connectionState === 'failed' ||
|
||||||
|
connection.peerConnection.connectionState === 'disconnected' ||
|
||||||
|
connection.peerConnection.connectionState === 'closed')
|
||||||
|
) {
|
||||||
|
_onDisconnect(connection.peer);
|
||||||
|
} else {
|
||||||
|
setTimeout(disconnectChecker, 1000);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function called when a remote client disconnects.
|
||||||
|
* @param connectionID The ID of the peer that disconnected.
|
||||||
|
*/
|
||||||
|
const _onDisconnect = (connectionID: string) => {
|
||||||
|
if (!connections.has(connectionID)) return;
|
||||||
|
justDisconnectedPeers.push(connectionID);
|
||||||
|
connections.delete(connectionID);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function called to initialize PeerJS after it
|
||||||
|
* has been configured.
|
||||||
|
*/
|
||||||
|
const loadPeerJS = () => {
|
||||||
|
if (peer !== null) return;
|
||||||
|
peer = new Peer(peerConfig);
|
||||||
|
peer.on('open', () => {
|
||||||
|
ready = true;
|
||||||
|
});
|
||||||
|
peer.on('error', (errorMessage) => {
|
||||||
|
logger.error('PeerJS error:', errorMessage);
|
||||||
|
});
|
||||||
|
peer.on('connection', (connection) => {
|
||||||
|
connection.on('open', () => {
|
||||||
|
_onConnect(connection);
|
||||||
|
justConnectedPeers.push(connection.peer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
peer.on('close', () => {
|
||||||
|
peer = null;
|
||||||
|
loadPeerJS();
|
||||||
|
});
|
||||||
|
peer.on('disconnected', peer.reconnect);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to another p2p client.
|
||||||
|
* @param id - The other client's ID.
|
||||||
|
*/
|
||||||
|
export const connect = (id: string) => {
|
||||||
|
if (peer === null) return;
|
||||||
|
const connection = peer.connect(id);
|
||||||
|
connection.on('open', () => {
|
||||||
|
_onConnect(connection);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects from all other p2p clients.
|
||||||
|
*/
|
||||||
|
export const disconnectFromAllPeers = () => {
|
||||||
|
for (const connection of connections.values()) connection.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to a specific peer.
|
||||||
|
* @param ids - The IDs of the clients to send the event to.
|
||||||
|
* @param messageName - The event to trigger.
|
||||||
|
* @param eventData - Additional data to send with the event.
|
||||||
|
*/
|
||||||
|
export const sendDataTo = async (
|
||||||
|
ids: string[],
|
||||||
|
messageName: string,
|
||||||
|
messageData: object
|
||||||
|
) => {
|
||||||
|
if (!ids.length) return;
|
||||||
|
|
||||||
|
const compressedData = await compressData(messageData);
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const connection = connections.get(id);
|
||||||
|
if (connection) {
|
||||||
|
connection.send({
|
||||||
|
messageName,
|
||||||
|
data: compressedData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAllMessagesMap = () => allMessages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to a custom broker server.
|
||||||
|
* @param host The host of the broker server.
|
||||||
|
* @param port The port of the broker server.
|
||||||
|
* @param path The path (part of the url after the host) to the broker server.
|
||||||
|
* @param key Optional password to connect to the broker server.
|
||||||
|
* @param ssl Use ssl?
|
||||||
|
*/
|
||||||
|
export const useCustomBrokerServer = (
|
||||||
|
host: string,
|
||||||
|
port: number,
|
||||||
|
path: string,
|
||||||
|
key: string,
|
||||||
|
ssl: boolean
|
||||||
|
) => {
|
||||||
|
Object.assign(peerConfig, {
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
path,
|
||||||
|
secure: ssl,
|
||||||
|
// All servers have "peerjs" as default key
|
||||||
|
key: key.length === 0 ? 'peerjs' : key,
|
||||||
|
});
|
||||||
|
loadPeerJS();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDefaultBrokerServer = loadPeerJS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an ICE server candidate, and removes the default ones provided by PeerJs. Must be called before connecting to a broker.
|
||||||
|
* @param urls The URL of the STUN/TURN server.
|
||||||
|
* @param username An optional username to send to the server.
|
||||||
|
* @param credential An optional password to send to the server.
|
||||||
|
*/
|
||||||
|
export const useCustomICECandidate = (
|
||||||
|
urls: string,
|
||||||
|
username?: string,
|
||||||
|
credential?: string
|
||||||
|
) => {
|
||||||
|
peerConfig.config = peerConfig.config || {};
|
||||||
|
peerConfig.config.iceServers = peerConfig.config.iceServers || [];
|
||||||
|
peerConfig.config.iceServers.push({
|
||||||
|
urls,
|
||||||
|
username,
|
||||||
|
credential,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the usage of a relay (TURN) server, to avoid sharing IP addresses with the other peers.
|
||||||
|
* @param shouldUseRelayServer Whether relay-only should be enabled or disabled.
|
||||||
|
*/
|
||||||
|
export const forceUseRelayServer = (shouldUseRelayServer: boolean) => {
|
||||||
|
peerConfig.config = peerConfig.config || {};
|
||||||
|
peerConfig.config.iceTransportPolicy = shouldUseRelayServer
|
||||||
|
? 'relay'
|
||||||
|
: 'all';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the own current peer ID.
|
||||||
|
* @see Peer.id
|
||||||
|
*/
|
||||||
|
export const getCurrentId = (): string => {
|
||||||
|
if (peer == undefined) return '';
|
||||||
|
return peer.id || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true once PeerJS finished initialization.
|
||||||
|
* @see ready
|
||||||
|
*/
|
||||||
|
export const isReady = () => ready;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return any disconnected peers.
|
||||||
|
*/
|
||||||
|
export const getJustDisconnectedPeers = () => justDisconnectedPeers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of all currently connected peers.
|
||||||
|
*/
|
||||||
|
export const getAllPeers = () => Array.from(connections.keys());
|
||||||
|
|
||||||
|
gdjs.callbacksRuntimeScenePostEvents.push(() => {
|
||||||
|
// Clear the list of messages at the end of the frame, assuming they've been all processed.
|
||||||
|
for (const messagesList of allMessages.values()) {
|
||||||
|
messagesList.getMessages().length = 0;
|
||||||
|
}
|
||||||
|
// Clear the list of just connected and disconnected peers.
|
||||||
|
if (justDisconnectedPeers.length > 0) {
|
||||||
|
justDisconnectedPeers.length = 0;
|
||||||
|
}
|
||||||
|
if (justConnectedPeers.length > 0) {
|
||||||
|
justConnectedPeers.length = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
509
Extensions/Multiplayer/peerjs.d.ts
vendored
Normal file
509
Extensions/Multiplayer/peerjs.d.ts
vendored
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
/**
|
||||||
|
* Minimal `EventEmitter` interface that is molded against the Node.js
|
||||||
|
* `EventEmitter` interface.
|
||||||
|
*/
|
||||||
|
declare class EventEmitter<
|
||||||
|
EventTypes extends EventEmitter.ValidEventTypes = string | symbol,
|
||||||
|
Context extends any = any
|
||||||
|
> {
|
||||||
|
static prefixed: string | boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array listing the events for which the emitter has registered
|
||||||
|
* listeners.
|
||||||
|
*/
|
||||||
|
eventNames(): Array<EventEmitter.EventNames<EventTypes>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the listeners registered for a given event.
|
||||||
|
*/
|
||||||
|
listeners<T extends EventEmitter.EventNames<EventTypes>>(
|
||||||
|
event: T
|
||||||
|
): Array<EventEmitter.EventListener<EventTypes, T>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of listeners listening to a given event.
|
||||||
|
*/
|
||||||
|
listenerCount(event: EventEmitter.EventNames<EventTypes>): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls each of the listeners registered for a given event.
|
||||||
|
*/
|
||||||
|
emit<T extends EventEmitter.EventNames<EventTypes>>(
|
||||||
|
event: T,
|
||||||
|
...args: EventEmitter.EventArgs<EventTypes, T>
|
||||||
|
): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for a given event.
|
||||||
|
*/
|
||||||
|
on<T extends EventEmitter.EventNames<EventTypes>>(
|
||||||
|
event: T,
|
||||||
|
fn: EventEmitter.EventListener<EventTypes, T>,
|
||||||
|
context?: Context
|
||||||
|
): this;
|
||||||
|
addListener<T extends EventEmitter.EventNames<EventTypes>>(
|
||||||
|
event: T,
|
||||||
|
fn: EventEmitter.EventListener<EventTypes, T>,
|
||||||
|
context?: Context
|
||||||
|
): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a one-time listener for a given event.
|
||||||
|
*/
|
||||||
|
once<T extends EventEmitter.EventNames<EventTypes>>(
|
||||||
|
event: T,
|
||||||
|
fn: EventEmitter.EventListener<EventTypes, T>,
|
||||||
|
context?: Context
|
||||||
|
): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the listeners of a given event.
|
||||||
|
*/
|
||||||
|
removeListener<T extends EventEmitter.EventNames<EventTypes>>(
|
||||||
|
event: T,
|
||||||
|
fn?: EventEmitter.EventListener<EventTypes, T>,
|
||||||
|
context?: Context,
|
||||||
|
once?: boolean
|
||||||
|
): this;
|
||||||
|
off<T extends EventEmitter.EventNames<EventTypes>>(
|
||||||
|
event: T,
|
||||||
|
fn?: EventEmitter.EventListener<EventTypes, T>,
|
||||||
|
context?: Context,
|
||||||
|
once?: boolean
|
||||||
|
): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all listeners, or those of the specified event.
|
||||||
|
*/
|
||||||
|
removeAllListeners(event?: EventEmitter.EventNames<EventTypes>): this;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace EventEmitter {
|
||||||
|
export interface ListenerFn<Args extends any[] = any[]> {
|
||||||
|
(...args: Args): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventEmitterStatic {
|
||||||
|
new <
|
||||||
|
EventTypes extends ValidEventTypes = string | symbol,
|
||||||
|
Context = any
|
||||||
|
>(): EventEmitter<EventTypes, Context>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `object` should be in either of the following forms:
|
||||||
|
* ```
|
||||||
|
* interface EventTypes {
|
||||||
|
* 'event-with-parameters': any[]
|
||||||
|
* 'event-with-example-handler': (...args: any[]) => void
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export type ValidEventTypes = string | symbol | object;
|
||||||
|
|
||||||
|
export type EventNames<T extends ValidEventTypes> = T extends string | symbol
|
||||||
|
? T
|
||||||
|
: keyof T;
|
||||||
|
|
||||||
|
export type ArgumentMap<T extends object> = {
|
||||||
|
[K in keyof T]: T[K] extends (...args: any[]) => void
|
||||||
|
? Parameters<T[K]>
|
||||||
|
: T[K] extends any[]
|
||||||
|
? T[K]
|
||||||
|
: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EventListener<
|
||||||
|
T extends ValidEventTypes,
|
||||||
|
K extends EventNames<T>
|
||||||
|
> = T extends string | symbol
|
||||||
|
? (...args: any[]) => void
|
||||||
|
: (
|
||||||
|
...args: ArgumentMap<Exclude<T, string | symbol>>[Extract<K, keyof T>]
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type EventArgs<
|
||||||
|
T extends ValidEventTypes,
|
||||||
|
K extends EventNames<T>
|
||||||
|
> = Parameters<EventListener<T, K>>;
|
||||||
|
|
||||||
|
export const EventEmitter: EventEmitterStatic;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace Peer {
|
||||||
|
export interface UtilSupportsObj {
|
||||||
|
browser: boolean;
|
||||||
|
webRTC: boolean;
|
||||||
|
audioVideo: boolean;
|
||||||
|
data: boolean;
|
||||||
|
binaryBlob: boolean;
|
||||||
|
reliable: boolean;
|
||||||
|
}
|
||||||
|
class Util {
|
||||||
|
noop(): void;
|
||||||
|
readonly CLOUD_HOST = '0.peerjs.com';
|
||||||
|
readonly CLOUD_PORT = 443;
|
||||||
|
readonly chunkedBrowsers: {
|
||||||
|
Chrome: number;
|
||||||
|
chrome: number;
|
||||||
|
};
|
||||||
|
readonly chunkedMTU = 16300;
|
||||||
|
readonly defaultConfig: {
|
||||||
|
iceServers: (
|
||||||
|
| {
|
||||||
|
urls: string;
|
||||||
|
username?: undefined;
|
||||||
|
credential?: undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
urls: string[];
|
||||||
|
username: string;
|
||||||
|
credential: string;
|
||||||
|
}
|
||||||
|
)[];
|
||||||
|
sdpSemantics: string;
|
||||||
|
};
|
||||||
|
readonly browser: string;
|
||||||
|
readonly browserVersion: number;
|
||||||
|
readonly supports: UtilSupportsObj;
|
||||||
|
validateId(id: string): boolean;
|
||||||
|
pack: any;
|
||||||
|
unpack: any;
|
||||||
|
chunk(
|
||||||
|
blob: Blob
|
||||||
|
): {
|
||||||
|
__peerData: number;
|
||||||
|
n: number;
|
||||||
|
total: number;
|
||||||
|
data: Blob;
|
||||||
|
}[];
|
||||||
|
blobToArrayBuffer(
|
||||||
|
blob: Blob,
|
||||||
|
cb: (arg: ArrayBuffer | null) => void
|
||||||
|
): FileReader;
|
||||||
|
binaryStringToArrayBuffer(binary: string): ArrayBuffer | SharedArrayBuffer;
|
||||||
|
randomToken(): string;
|
||||||
|
isSecure(): boolean;
|
||||||
|
}
|
||||||
|
export const util: Util;
|
||||||
|
export enum LogLevel {
|
||||||
|
Disabled = 0,
|
||||||
|
Errors = 1,
|
||||||
|
Warnings = 2,
|
||||||
|
All = 3,
|
||||||
|
}
|
||||||
|
export enum ConnectionType {
|
||||||
|
Data = 'data',
|
||||||
|
Media = 'media',
|
||||||
|
}
|
||||||
|
export enum PeerErrorType {
|
||||||
|
BrowserIncompatible = 'browser-incompatible',
|
||||||
|
Disconnected = 'disconnected',
|
||||||
|
InvalidID = 'invalid-id',
|
||||||
|
InvalidKey = 'invalid-key',
|
||||||
|
Network = 'network',
|
||||||
|
PeerUnavailable = 'peer-unavailable',
|
||||||
|
SslUnavailable = 'ssl-unavailable',
|
||||||
|
ServerError = 'server-error',
|
||||||
|
SocketError = 'socket-error',
|
||||||
|
SocketClosed = 'socket-closed',
|
||||||
|
UnavailableID = 'unavailable-id',
|
||||||
|
WebRTC = 'webrtc',
|
||||||
|
}
|
||||||
|
export enum SerializationType {
|
||||||
|
Binary = 'binary',
|
||||||
|
BinaryUTF8 = 'binary-utf8',
|
||||||
|
JSON = 'json',
|
||||||
|
}
|
||||||
|
export enum SocketEventType {
|
||||||
|
Message = 'message',
|
||||||
|
Disconnected = 'disconnected',
|
||||||
|
Error = 'error',
|
||||||
|
Close = 'close',
|
||||||
|
}
|
||||||
|
export enum ServerMessageType {
|
||||||
|
Heartbeat = 'HEARTBEAT',
|
||||||
|
Candidate = 'CANDIDATE',
|
||||||
|
Offer = 'OFFER',
|
||||||
|
Answer = 'ANSWER',
|
||||||
|
Open = 'OPEN',
|
||||||
|
Error = 'ERROR',
|
||||||
|
IdTaken = 'ID-TAKEN',
|
||||||
|
InvalidKey = 'INVALID-KEY',
|
||||||
|
Leave = 'LEAVE',
|
||||||
|
Expire = 'EXPIRE',
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* An abstraction on top of WebSockets to provide fastest
|
||||||
|
* possible connection for peers.
|
||||||
|
*/
|
||||||
|
class Socket extends EventEmitter {
|
||||||
|
constructor(
|
||||||
|
secure: any,
|
||||||
|
host: string,
|
||||||
|
port: number,
|
||||||
|
path: string,
|
||||||
|
key: string,
|
||||||
|
pingInterval?: number
|
||||||
|
);
|
||||||
|
start(id: string, token: string): void;
|
||||||
|
/** Exposed send for DC & Peer. */
|
||||||
|
send(data: any): void;
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
class ServerMessage {
|
||||||
|
type: ServerMessageType;
|
||||||
|
payload: any;
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
type BaseConnectionEvents = {
|
||||||
|
/**
|
||||||
|
* Emitted when either you or the remote peer closes the connection.
|
||||||
|
*/
|
||||||
|
close: () => void;
|
||||||
|
error: (error: Error) => void;
|
||||||
|
iceStateChanged: (state: RTCIceConnectionState) => void;
|
||||||
|
};
|
||||||
|
abstract class BaseConnection<
|
||||||
|
T extends EventEmitter.ValidEventTypes,
|
||||||
|
TT
|
||||||
|
> extends EventEmitter<T & BaseConnectionEvents> {
|
||||||
|
readonly peer: string;
|
||||||
|
provider: Peer<TT>;
|
||||||
|
readonly options: any;
|
||||||
|
protected _open: boolean;
|
||||||
|
readonly metadata: any;
|
||||||
|
connectionId: string;
|
||||||
|
peerConnection: RTCPeerConnection;
|
||||||
|
abstract get type(): ConnectionType;
|
||||||
|
get open(): boolean;
|
||||||
|
constructor(peer: string, provider: Peer<TT>, options: any);
|
||||||
|
abstract close(): void;
|
||||||
|
abstract handleMessage(message: ServerMessage): void;
|
||||||
|
}
|
||||||
|
type DataConnectionEvents<T> = {
|
||||||
|
/**
|
||||||
|
* Emitted when data is received from the remote peer.
|
||||||
|
*/
|
||||||
|
data: (data: T) => void;
|
||||||
|
/**
|
||||||
|
* Emitted when the connection is established and ready-to-use.
|
||||||
|
*/
|
||||||
|
open: () => void;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Wraps a DataChannel between two Peers.
|
||||||
|
*/
|
||||||
|
export class DataConnection<T> extends BaseConnection<
|
||||||
|
DataConnectionEvents<T>,
|
||||||
|
T
|
||||||
|
> {
|
||||||
|
readonly label: string;
|
||||||
|
readonly serialization: SerializationType;
|
||||||
|
readonly reliable: boolean;
|
||||||
|
stringify: (data: any) => string;
|
||||||
|
parse: (data: string) => any;
|
||||||
|
get type(): ConnectionType;
|
||||||
|
get dataChannel(): RTCDataChannel;
|
||||||
|
get bufferSize(): number;
|
||||||
|
constructor(peerId: string, provider: Peer<T>, options: any);
|
||||||
|
/** Called by the Negotiator when the DataChannel is ready. */
|
||||||
|
initialize(dc: RTCDataChannel): void;
|
||||||
|
/**
|
||||||
|
* Exposed functionality for users.
|
||||||
|
*/
|
||||||
|
/** Allows user to close connection. */
|
||||||
|
close(): void;
|
||||||
|
/** Allows user to send data. */
|
||||||
|
send(data: T, chunked?: boolean): void;
|
||||||
|
handleMessage(message: ServerMessage): void;
|
||||||
|
}
|
||||||
|
export interface AnswerOption {
|
||||||
|
sdpTransform?: Function;
|
||||||
|
}
|
||||||
|
export interface PeerJSOption {
|
||||||
|
key?: string;
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
path?: string;
|
||||||
|
secure?: boolean;
|
||||||
|
token?: string;
|
||||||
|
config?: RTCConfiguration;
|
||||||
|
debug?: number;
|
||||||
|
referrerPolicy?: ReferrerPolicy;
|
||||||
|
}
|
||||||
|
export interface PeerConnectOption {
|
||||||
|
label?: string;
|
||||||
|
metadata?: any;
|
||||||
|
serialization?: string;
|
||||||
|
reliable?: boolean;
|
||||||
|
}
|
||||||
|
export interface CallOption {
|
||||||
|
metadata?: any;
|
||||||
|
sdpTransform?: Function;
|
||||||
|
}
|
||||||
|
type MediaConnectionEvents = {
|
||||||
|
/**
|
||||||
|
* Emitted when a connection to the PeerServer is established.
|
||||||
|
*/
|
||||||
|
stream: (stream: MediaStream) => void;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Wraps the streaming interface between two Peers.
|
||||||
|
*/
|
||||||
|
export class MediaConnection<T> extends BaseConnection<
|
||||||
|
MediaConnectionEvents,
|
||||||
|
T
|
||||||
|
> {
|
||||||
|
get type(): ConnectionType;
|
||||||
|
get localStream(): MediaStream;
|
||||||
|
get remoteStream(): MediaStream;
|
||||||
|
constructor(peerId: string, provider: Peer<T>, options: any);
|
||||||
|
addStream(remoteStream: any): void;
|
||||||
|
handleMessage(message: ServerMessage): void;
|
||||||
|
answer(stream?: MediaStream, options?: AnswerOption): void;
|
||||||
|
/**
|
||||||
|
* Exposed functionality for users.
|
||||||
|
*/
|
||||||
|
/** Allows user to close connection. */
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
class PeerOptions implements PeerJSOption {
|
||||||
|
debug?: LogLevel;
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
path?: string;
|
||||||
|
key?: string;
|
||||||
|
token?: string;
|
||||||
|
config?: any;
|
||||||
|
secure?: boolean;
|
||||||
|
pingInterval?: number;
|
||||||
|
referrerPolicy?: ReferrerPolicy;
|
||||||
|
logFunction?: (logLevel: LogLevel, ...rest: any[]) => void;
|
||||||
|
}
|
||||||
|
type PeerEvents<T> = {
|
||||||
|
/**
|
||||||
|
* Emitted when a connection to the PeerServer is established.
|
||||||
|
*/
|
||||||
|
open: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* Emitted when a new data connection is established from a remote peer.
|
||||||
|
*/
|
||||||
|
connection: (dataConnection: DataConnection<T>) => void;
|
||||||
|
/**
|
||||||
|
* Emitted when a remote peer attempts to call you.
|
||||||
|
*/
|
||||||
|
call: (mediaConnection: MediaConnection<T>) => void;
|
||||||
|
/**
|
||||||
|
* Emitted when the peer is destroyed and can no longer accept or create any new connections.
|
||||||
|
*/
|
||||||
|
close: () => void;
|
||||||
|
/**
|
||||||
|
* Emitted when the peer is disconnected from the signalling server
|
||||||
|
*/
|
||||||
|
disconnected: (currentId: string) => void;
|
||||||
|
/**
|
||||||
|
* Errors on the peer are almost always fatal and will destroy the peer.
|
||||||
|
*/
|
||||||
|
error: (error: Error) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A peer who can initiate connections with other peers.
|
||||||
|
*/
|
||||||
|
declare class Peer<T> extends EventEmitter<Peer.PeerEvents<T>> {
|
||||||
|
/**
|
||||||
|
* The brokering ID of this peer
|
||||||
|
*/
|
||||||
|
get id(): string;
|
||||||
|
get options(): Peer.PeerOptions;
|
||||||
|
get open(): boolean;
|
||||||
|
get socket(): Peer.Socket;
|
||||||
|
/**
|
||||||
|
* A hash of all connections associated with this peer, keyed by the remote peer's ID.
|
||||||
|
* @deprecated
|
||||||
|
* Return type will change from Object to Map<string,[]>
|
||||||
|
*/
|
||||||
|
get connections(): Object;
|
||||||
|
/**
|
||||||
|
* true if this peer and all of its connections can no longer be used.
|
||||||
|
*/
|
||||||
|
get destroyed(): boolean;
|
||||||
|
/**
|
||||||
|
* false if there is an active connection to the PeerServer.
|
||||||
|
*/
|
||||||
|
get disconnected(): boolean;
|
||||||
|
/**
|
||||||
|
* A peer can connect to other peers and listen for connections.
|
||||||
|
*/
|
||||||
|
constructor();
|
||||||
|
/**
|
||||||
|
* A peer can connect to other peers and listen for connections.
|
||||||
|
* @param options for specifying details about PeerServer
|
||||||
|
*/
|
||||||
|
constructor(options: Peer.PeerOptions);
|
||||||
|
/**
|
||||||
|
* A peer can connect to other peers and listen for connections.
|
||||||
|
* @param id Other peers can connect to this peer using the provided ID.
|
||||||
|
* If no ID is given, one will be generated by the brokering server.
|
||||||
|
* @param options for specifying details about PeerServer
|
||||||
|
*/
|
||||||
|
constructor(id: string, options?: Peer.PeerOptions);
|
||||||
|
/** Retrieve messages from lost message store */
|
||||||
|
_getMessages(connectionId: string): Peer.ServerMessage[];
|
||||||
|
/**
|
||||||
|
* Connects to the remote peer specified by id and returns a data connection.
|
||||||
|
* @param peer The brokering ID of the remote peer (their peer.id).
|
||||||
|
* @param options for specifying details about Peer Connection
|
||||||
|
*/
|
||||||
|
connect(
|
||||||
|
peer: string,
|
||||||
|
options?: Peer.PeerConnectOption
|
||||||
|
): Peer.DataConnection<T>;
|
||||||
|
/**
|
||||||
|
* Calls the remote peer specified by id and returns a media connection.
|
||||||
|
* @param peer The brokering ID of the remote peer (their peer.id).
|
||||||
|
* @param stream The caller's media stream
|
||||||
|
* @param options Metadata associated with the connection, passed in by whoever initiated the connection.
|
||||||
|
*/
|
||||||
|
call(
|
||||||
|
peer: string,
|
||||||
|
stream: MediaStream,
|
||||||
|
options?: Peer.CallOption
|
||||||
|
): Peer.MediaConnection<T>;
|
||||||
|
_removeConnection(
|
||||||
|
connection: Peer.DataConnection<T> | Peer.MediaConnection<T>
|
||||||
|
): void;
|
||||||
|
/** Retrieve a data/media connection for this peer. */
|
||||||
|
getConnection(
|
||||||
|
peerId: string,
|
||||||
|
connectionId: string
|
||||||
|
): null | Peer.DataConnection<T> | Peer.MediaConnection<T>;
|
||||||
|
/** Emits a typed error message. */
|
||||||
|
emitError(type: Peer.PeerErrorType, err: string | Error): void;
|
||||||
|
/**
|
||||||
|
* Destroys the Peer: closes all active connections as well as the connection
|
||||||
|
* to the server.
|
||||||
|
* Warning: The peer can no longer create or accept connections after being
|
||||||
|
* destroyed.
|
||||||
|
*/
|
||||||
|
destroy(): void;
|
||||||
|
/**
|
||||||
|
* Disconnects the Peer's connection to the PeerServer. Does not close any
|
||||||
|
* active connections.
|
||||||
|
* Warning: The peer can no longer create or accept connections after being
|
||||||
|
* disconnected. It also cannot reconnect to the server.
|
||||||
|
*/
|
||||||
|
disconnect(): void;
|
||||||
|
/** Attempts to reconnect with the same ID. */
|
||||||
|
reconnect(): void;
|
||||||
|
/**
|
||||||
|
* Get a list of available peer IDs. If you're running your own server, you'll
|
||||||
|
* want to set allow_discovery: true in the PeerServer options. If you're using
|
||||||
|
* the cloud server, email team@peerjs.com to get the functionality enabled for
|
||||||
|
* your key.
|
||||||
|
*/
|
||||||
|
listAllPeers(cb?: (_: any[]) => void): void;
|
||||||
|
}
|
@@ -91,12 +91,12 @@ describe('Multiplayer', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mocked P2P event data.
|
* A mocked P2P message data.
|
||||||
* @implements {gdjs.evtTools.p2p.IEventData}
|
* @implements {gdjs.multiplayerPeerJsHelper.IMessageData}
|
||||||
*/
|
*/
|
||||||
class MockedEventData {
|
class MockedMessageData {
|
||||||
/**
|
/**
|
||||||
* @param {string} data
|
* @param {object} data
|
||||||
* @param {string} sender
|
* @param {string} sender
|
||||||
**/
|
**/
|
||||||
constructor(data, sender) {
|
constructor(data, sender) {
|
||||||
@@ -107,59 +107,63 @@ describe('Multiplayer', () => {
|
|||||||
/**
|
/**
|
||||||
* The data sent alongside the event.
|
* The data sent alongside the event.
|
||||||
*/
|
*/
|
||||||
data = '';
|
data = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the sender of the event.
|
* The ID of the sender of the event.
|
||||||
*/
|
*/
|
||||||
sender = '';
|
sender = '';
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mocked P2P event.
|
|
||||||
* @implements {gdjs.evtTools.p2p.IEvent}
|
|
||||||
*/
|
|
||||||
class MockedEvent {
|
|
||||||
data = [];
|
|
||||||
dataloss = false;
|
|
||||||
|
|
||||||
isTriggered() {
|
|
||||||
return this.data.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {gdjs.evtTools.p2p.IEventData} newData
|
|
||||||
*/
|
|
||||||
pushData(newData) {
|
|
||||||
if (this.dataloss && this.data.length > 0) this.data[0] = newData;
|
|
||||||
else this.data.push(newData);
|
|
||||||
}
|
|
||||||
|
|
||||||
popData() {
|
|
||||||
this.data.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
getData() {
|
getData() {
|
||||||
return this.data.length === 0 ? '' : this.data[0].data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSender() {
|
getSender() {
|
||||||
return this.data.length === 0 ? '' : this.data[0].sender;
|
return this.sender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mocked P2P messages list.
|
||||||
|
* @implements {gdjs.multiplayerPeerJsHelper.IMessagesList}
|
||||||
|
*/
|
||||||
|
class MockedMessagesList {
|
||||||
|
data = [];
|
||||||
|
messageName = 'some-message-name';
|
||||||
|
|
||||||
|
constructor(messageName) {
|
||||||
|
this.messageName = messageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this.messageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} newData
|
||||||
|
* @param {string} sender
|
||||||
|
*/
|
||||||
|
pushMessage(newData, sender) {
|
||||||
|
this.data.push(new MockedMessageData(newData, sender));
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessages() {
|
||||||
|
return this.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a mocked P2P handler.
|
* Create a mocked P2P handler.
|
||||||
* It stores the events sent to/from peers.
|
* It stores the messages sent to/from peers.
|
||||||
*/
|
*/
|
||||||
const createP2PAndMultiplayerManagersMock = () => {
|
const createMultiplayerManagersMock = () => {
|
||||||
const p2pState = {
|
const p2pState = {
|
||||||
currentPeerId: '',
|
currentPeerId: '',
|
||||||
otherPeerIds: [],
|
otherPeerIds: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {Record<string, Map<string, MockedEvent>>} */
|
/** @type {Record<string, Map<string, MockedMessagesList>>} */
|
||||||
const peerEvents = {};
|
const peerAllMessagesMap = {};
|
||||||
|
|
||||||
/** @type {Record<string, gdjs.MultiplayerMessageManager>} */
|
/** @type {Record<string, gdjs.MultiplayerMessageManager>} */
|
||||||
const peerMultiplayerMessageManager = {};
|
const peerMultiplayerMessageManager = {};
|
||||||
@@ -167,88 +171,68 @@ describe('Multiplayer', () => {
|
|||||||
/** @type {Record<string, gdjs.MultiplayerVariablesManager>} */
|
/** @type {Record<string, gdjs.MultiplayerVariablesManager>} */
|
||||||
const peerMultiplayerVariablesManager = {};
|
const peerMultiplayerVariablesManager = {};
|
||||||
|
|
||||||
const getPeerEvents = (peerId) =>
|
const getPeerMessages = (peerId) =>
|
||||||
(peerEvents[peerId] = peerEvents[peerId] || new Map());
|
(peerAllMessagesMap[peerId] = peerAllMessagesMap[peerId] || new Map());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} eventName
|
* @param {string} messageName
|
||||||
* @returns {gdjs.evtTools.p2p.IEvent}
|
* @returns {gdjs.multiplayerPeerJsHelper.IMessagesList}
|
||||||
*/
|
*/
|
||||||
const getEvent = (eventName) => {
|
const getOrCreateMessagesList = (messageName) => {
|
||||||
const events = getPeerEvents(p2pState.currentPeerId);
|
const allMessagesMap = getPeerMessages(p2pState.currentPeerId);
|
||||||
let event = events.get(eventName);
|
const messagesList = allMessagesMap.get(messageName);
|
||||||
if (!event) events.set(eventName, (event = new MockedEvent()));
|
if (messagesList) return messagesList;
|
||||||
return event;
|
const newMessagesList = new MockedMessagesList(messageName);
|
||||||
|
allMessagesMap.set(messageName, newMessagesList);
|
||||||
|
return newMessagesList;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} peerId
|
* @param {string[]} peerIds
|
||||||
* @param {string} eventName
|
* @param {string} messageName
|
||||||
* @param {string} eventData
|
* @param {object} messageData
|
||||||
*/
|
*/
|
||||||
const sendDataTo = (peerId, eventName, eventData) => {
|
const sendDataTo = async (peerIds, messageName, messageData) => {
|
||||||
// console.log(`## SENDING DATA TO ${peerId}:`, eventName, eventData);
|
for (const peerId of peerIds) {
|
||||||
const events = getPeerEvents(peerId);
|
// console.log(`## SENDING DATA TO ${peerId}:`, messageName, messageData);
|
||||||
let event = events.get(eventName);
|
const peerAllMessagesMap = getPeerMessages(peerId);
|
||||||
if (!event) events.set(eventName, (event = new MockedEvent()));
|
let peerMessagesList = peerAllMessagesMap.get(messageName);
|
||||||
event.pushData(new MockedEventData(eventData, peerId));
|
if (!peerMessagesList) {
|
||||||
|
peerMessagesList = new MockedMessagesList(messageName);
|
||||||
|
peerAllMessagesMap.set(messageName, peerMessagesList);
|
||||||
|
}
|
||||||
|
|
||||||
|
peerMessagesList.pushMessage(messageData, p2pState.currentPeerId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {typeof gdjs.evtTools.p2p} */
|
/** @type {typeof gdjs.multiplayerPeerJsHelper} */
|
||||||
const p2pMock = {
|
const peerJsHelperMock = {
|
||||||
// @ts-ignore - this is a mock so private properties can't be the same.
|
// @ts-ignore - this is a mock so private properties can't be the same.
|
||||||
Event: MockedEvent,
|
MessagesList: MockedMessagesList,
|
||||||
EventData: MockedEventData,
|
MessageData: MockedMessageData,
|
||||||
sendVariableTo: () => {},
|
getOrCreateMessagesList,
|
||||||
sendVariableToAll: () => {},
|
|
||||||
getEventVariable: (eventName, variable) => {
|
|
||||||
variable.fromJSON(getEvent(eventName).getData());
|
|
||||||
},
|
|
||||||
onEvent: (eventName, dataloss) => {
|
|
||||||
const event = getEvent(eventName);
|
|
||||||
event.dataloss = dataloss;
|
|
||||||
const isTriggered = event.isTriggered();
|
|
||||||
return isTriggered;
|
|
||||||
},
|
|
||||||
getEvent,
|
|
||||||
connect: (id) => {},
|
connect: (id) => {},
|
||||||
disconnectFromPeer: (id) => {},
|
|
||||||
disconnectFromAllPeers: () => {},
|
disconnectFromAllPeers: () => {},
|
||||||
disconnectFromAll: () => {},
|
|
||||||
disconnectFromBroker: () => {},
|
|
||||||
sendDataTo,
|
sendDataTo,
|
||||||
sendDataToAll: (eventName, eventData) => {
|
getAllMessagesMap: () => getPeerMessages(p2pState.currentPeerId),
|
||||||
p2pState.otherPeerIds.forEach((peerId) => {
|
|
||||||
sendDataTo(peerId, eventName, eventData);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getEventData: (eventName) => getEvent(eventName).getData(),
|
|
||||||
getEventSender: (eventName) => getEvent(eventName).getSender(),
|
|
||||||
getEvents: () => getPeerEvents(p2pState.currentPeerId),
|
|
||||||
useCustomBrokerServer: () => {},
|
useCustomBrokerServer: () => {},
|
||||||
useDefaultBrokerServer: () => {},
|
useDefaultBrokerServer: () => {},
|
||||||
useCustomICECandidate: () => {},
|
useCustomICECandidate: () => {},
|
||||||
forceUseRelayServer: (shouldUseRelayServer) => {},
|
forceUseRelayServer: (shouldUseRelayServer) => {},
|
||||||
overrideId: (id) => {},
|
|
||||||
getCurrentId: () => 'fake-current-id',
|
getCurrentId: () => 'fake-current-id',
|
||||||
isReady: () => true,
|
isReady: () => true,
|
||||||
onError: () => false,
|
getJustDisconnectedPeers: () => [],
|
||||||
getLastError: () => '',
|
|
||||||
onDisconnect: () => false,
|
|
||||||
getDisconnectedPeer: () => '',
|
|
||||||
onConnection: () => false,
|
|
||||||
getConnectedPeer: () => '',
|
|
||||||
getAllPeers: () => p2pState.otherPeerIds,
|
getAllPeers: () => p2pState.otherPeerIds,
|
||||||
getConnectionInstance: () => undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
gdjs.evtTools.p2p = p2pMock;
|
gdjs.multiplayerPeerJsHelper = peerJsHelperMock;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
switchToPeer: ({ peerId, otherPeerIds, playerNumber }) => {
|
switchToPeer: ({ peerId, otherPeerIds, playerNumber }) => {
|
||||||
// console.log('## SWITCHING TO PEER', peerId);
|
// console.log('## SWITCHING TO PEER', peerId);
|
||||||
|
|
||||||
// Switch the state of the P2P mock.
|
// Switch the state of the peerJs mock.
|
||||||
p2pState.currentPeerId = peerId;
|
p2pState.currentPeerId = peerId;
|
||||||
p2pState.otherPeerIds = otherPeerIds;
|
p2pState.otherPeerIds = otherPeerIds;
|
||||||
|
|
||||||
@@ -273,25 +257,29 @@ describe('Multiplayer', () => {
|
|||||||
// Switch the state of the game.
|
// Switch the state of the game.
|
||||||
gdjs.multiplayer.playerNumber = playerNumber;
|
gdjs.multiplayer.playerNumber = playerNumber;
|
||||||
},
|
},
|
||||||
logEvents: () => {
|
logMessages: () => {
|
||||||
Object.keys(peerEvents).forEach((peerId) => {
|
Object.keys(peerAllMessagesMap).forEach((peerId) => {
|
||||||
console.log(`## PEER ${peerId} events:`);
|
console.log(`## PEER ${peerId} messages:`);
|
||||||
for (const [eventName, event] of peerEvents[peerId]) {
|
for (const [messageName, messagesList] of peerAllMessagesMap[
|
||||||
console.log(`${eventName}: ${JSON.stringify(event.data)}`);
|
peerId
|
||||||
|
]) {
|
||||||
|
console.log(
|
||||||
|
`${messageName}: ${JSON.stringify(messagesList.getMessages())}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
markAllPeerEventsAsProcessed: () => {
|
markAllPeerMessagesAsProcessed: () => {
|
||||||
for (const events of Object.values(peerEvents)) {
|
for (const allMessagesList of Object.values(peerAllMessagesMap)) {
|
||||||
for (const event of events.values()) {
|
for (const messagesList of allMessagesList.values()) {
|
||||||
event.popData();
|
messagesList.data = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expectNoEventsToBeProcessed: () => {
|
expectNoMessagesToBeProcessed: () => {
|
||||||
for (const events of Object.values(peerEvents)) {
|
for (const allMessagesList of Object.values(peerAllMessagesMap)) {
|
||||||
for (const event of events.values()) {
|
for (const messagesList of allMessagesList.values()) {
|
||||||
expect(event.isTriggered()).to.be(false);
|
expect(messagesList.getMessages().length).to.be(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -337,8 +325,8 @@ describe('Multiplayer', () => {
|
|||||||
it('synchronizes scene/global variables from the host to other players', () => {
|
it('synchronizes scene/global variables from the host to other players', () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
peerId: 'player-1',
|
peerId: 'player-1',
|
||||||
@@ -370,7 +358,7 @@ describe('Multiplayer', () => {
|
|||||||
|
|
||||||
const p2RuntimeScene = makeTestRuntimeSceneWithNetworkId();
|
const p2RuntimeScene = makeTestRuntimeSceneWithNetworkId();
|
||||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expect(p2RuntimeScene.getVariables().has('MyString_Variable')).to.be(
|
expect(p2RuntimeScene.getVariables().has('MyString_Variable')).to.be(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@@ -459,7 +447,7 @@ describe('Multiplayer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expect(
|
expect(
|
||||||
p2RuntimeScene.getGame().getVariables().has('MyGlobalStringVariable')
|
p2RuntimeScene.getGame().getVariables().has('MyGlobalStringVariable')
|
||||||
).to.be(true);
|
).to.be(true);
|
||||||
@@ -529,9 +517,9 @@ describe('Multiplayer', () => {
|
|||||||
it('overrides a scene/global variable, modified by a player, when synchronized by the host', () => {
|
it('overrides a scene/global variable, modified by a player, when synchronized by the host', () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
expectNoEventsToBeProcessed,
|
expectNoMessagesToBeProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
peerId: 'player-1',
|
peerId: 'player-1',
|
||||||
@@ -566,7 +554,7 @@ describe('Multiplayer', () => {
|
|||||||
p2RuntimeScene.getVariables().add('MyOtherVariable', variable);
|
p2RuntimeScene.getVariables().add('MyOtherVariable', variable);
|
||||||
}
|
}
|
||||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expect(
|
expect(
|
||||||
p2RuntimeScene.getVariables().get('MyVariable').getAsString()
|
p2RuntimeScene.getVariables().get('MyVariable').getAsString()
|
||||||
).to.be('Hello from remote world');
|
).to.be('Hello from remote world');
|
||||||
@@ -574,7 +562,7 @@ describe('Multiplayer', () => {
|
|||||||
p2RuntimeScene.getVariables().get('MyOtherVariable').getAsString()
|
p2RuntimeScene.getVariables().get('MyOtherVariable').getAsString()
|
||||||
).to.be('Something else');
|
).to.be('Something else');
|
||||||
|
|
||||||
expectNoEventsToBeProcessed();
|
expectNoMessagesToBeProcessed();
|
||||||
|
|
||||||
// Check the host sends again the variable, even if not changed, for reliability
|
// Check the host sends again the variable, even if not changed, for reliability
|
||||||
// (allows to work around a dropped message, without using a real acknowledgement).
|
// (allows to work around a dropped message, without using a real acknowledgement).
|
||||||
@@ -605,16 +593,16 @@ describe('Multiplayer', () => {
|
|||||||
p2RuntimeScene.getVariables().get('MyOtherVariable').getAsString()
|
p2RuntimeScene.getVariables().get('MyOtherVariable').getAsString()
|
||||||
).to.be('Something else');
|
).to.be('Something else');
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expectNoEventsToBeProcessed();
|
expectNoMessagesToBeProcessed();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('synchronizes a scene/global variable from a player to the host to other players', () => {
|
it('synchronizes a scene/global variable from a player to the host to other players', () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
expectNoEventsToBeProcessed,
|
expectNoMessagesToBeProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
peerId: 'player-1',
|
peerId: 'player-1',
|
||||||
@@ -678,8 +666,8 @@ describe('Multiplayer', () => {
|
|||||||
p3GlobalVariable.setPlayerOwnership(3); // Ownership is given to player 3.
|
p3GlobalVariable.setPlayerOwnership(3); // Ownership is given to player 3.
|
||||||
p3RuntimeScene.renderAndStep(1000 / 60);
|
p3RuntimeScene.renderAndStep(1000 / 60);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expectNoEventsToBeProcessed();
|
expectNoMessagesToBeProcessed();
|
||||||
|
|
||||||
// Change the variables on player 3.
|
// Change the variables on player 3.
|
||||||
{
|
{
|
||||||
@@ -747,9 +735,9 @@ describe('Multiplayer', () => {
|
|||||||
it('does not synchronize a scene/global variable from players if defined as not synchronized', () => {
|
it('does not synchronize a scene/global variable from players if defined as not synchronized', () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
expectNoEventsToBeProcessed,
|
expectNoMessagesToBeProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
peerId: 'player-1',
|
peerId: 'player-1',
|
||||||
@@ -813,8 +801,8 @@ describe('Multiplayer', () => {
|
|||||||
p3GlobalVariable.disableSynchronization(); // Disable synchronization.
|
p3GlobalVariable.disableSynchronization(); // Disable synchronization.
|
||||||
p3RuntimeScene.renderAndStep(1000 / 60);
|
p3RuntimeScene.renderAndStep(1000 / 60);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expectNoEventsToBeProcessed();
|
expectNoMessagesToBeProcessed();
|
||||||
|
|
||||||
// Change the variables on player 3.
|
// Change the variables on player 3.
|
||||||
{
|
{
|
||||||
@@ -945,8 +933,8 @@ describe('Multiplayer', () => {
|
|||||||
it('synchronizes objects from the host to other players', () => {
|
it('synchronizes objects from the host to other players', () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
// Create an instance on the host's game:
|
// Create an instance on the host's game:
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
@@ -980,7 +968,7 @@ describe('Multiplayer', () => {
|
|||||||
if (!p2Objects) throw new Error('No objects found');
|
if (!p2Objects) throw new Error('No objects found');
|
||||||
expect(p2Objects.length).to.be(0);
|
expect(p2Objects.length).to.be(0);
|
||||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
object: p2SpriteObject,
|
object: p2SpriteObject,
|
||||||
@@ -1021,7 +1009,7 @@ describe('Multiplayer', () => {
|
|||||||
playerNumber: 2,
|
playerNumber: 2,
|
||||||
});
|
});
|
||||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
object: p2SpriteObject,
|
object: p2SpriteObject,
|
||||||
@@ -1061,7 +1049,7 @@ describe('Multiplayer', () => {
|
|||||||
playerNumber: 2,
|
playerNumber: 2,
|
||||||
});
|
});
|
||||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
const p2Objects = p2RuntimeScene.getObjects('MySpriteObject');
|
const p2Objects = p2RuntimeScene.getObjects('MySpriteObject');
|
||||||
if (!p2Objects) throw new Error('No objects found');
|
if (!p2Objects) throw new Error('No objects found');
|
||||||
@@ -1073,8 +1061,8 @@ describe('Multiplayer', () => {
|
|||||||
it('synchronizes objects from a player to the host to other players', () => {
|
it('synchronizes objects from a player to the host to other players', () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
// Create an instance on a player:
|
// Create an instance on a player:
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
@@ -1144,7 +1132,7 @@ describe('Multiplayer', () => {
|
|||||||
expect(p3SpriteObject.getX()).to.be(142);
|
expect(p3SpriteObject.getX()).to.be(142);
|
||||||
expect(p3SpriteObject.getY()).to.be(143);
|
expect(p3SpriteObject.getY()).to.be(143);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
// Move the object on the player:
|
// Move the object on the player:
|
||||||
{
|
{
|
||||||
@@ -1196,7 +1184,7 @@ describe('Multiplayer', () => {
|
|||||||
playerNumber: 3,
|
playerNumber: 3,
|
||||||
});
|
});
|
||||||
p3RuntimeScene.renderAndStep(1000 / 60);
|
p3RuntimeScene.renderAndStep(1000 / 60);
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
object: p3SpriteObject,
|
object: p3SpriteObject,
|
||||||
@@ -1270,15 +1258,15 @@ describe('Multiplayer', () => {
|
|||||||
expect(p3ObjectsAndBehaviorsUpdated.length).to.be(0);
|
expect(p3ObjectsAndBehaviorsUpdated.length).to.be(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows ownership to change from host to a player to another player', () => {
|
it('allows ownership to change from host to a player to another player', () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
expectNoEventsToBeProcessed,
|
expectNoMessagesToBeProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
// Create an instance on the host's game:
|
// Create an instance on the host's game:
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
@@ -1335,8 +1323,8 @@ describe('Multiplayer', () => {
|
|||||||
expect(p3SpriteObjectOriginal.getX()).to.be(142);
|
expect(p3SpriteObjectOriginal.getX()).to.be(142);
|
||||||
expect(p3SpriteObjectOriginal.getY()).to.be(143);
|
expect(p3SpriteObjectOriginal.getY()).to.be(143);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expectNoEventsToBeProcessed();
|
expectNoMessagesToBeProcessed();
|
||||||
|
|
||||||
// Check player 3 can get ownership (and can directly move the instance, without waiting for the
|
// Check player 3 can get ownership (and can directly move the instance, without waiting for the
|
||||||
// host to acknowledge the change).
|
// host to acknowledge the change).
|
||||||
@@ -1406,9 +1394,9 @@ describe('Multiplayer', () => {
|
|||||||
expect(p2SpriteObject.getX()).to.be(342);
|
expect(p2SpriteObject.getX()).to.be(342);
|
||||||
expect(p2SpriteObject.getY()).to.be(343);
|
expect(p2SpriteObject.getY()).to.be(343);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expectNoEventsToBeProcessed();
|
expectNoMessagesToBeProcessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check player 2 can get ownership.
|
// Check player 2 can get ownership.
|
||||||
@@ -1492,7 +1480,7 @@ describe('Multiplayer', () => {
|
|||||||
).to.be(2);
|
).to.be(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
// Check that the position given by player 2 is updated on the host and player 3.
|
// Check that the position given by player 2 is updated on the host and player 3.
|
||||||
{
|
{
|
||||||
@@ -1545,15 +1533,15 @@ describe('Multiplayer', () => {
|
|||||||
expect(p3SpriteObject.getY()).to.be(243);
|
expect(p3SpriteObject.getY()).to.be(243);
|
||||||
}
|
}
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
expectNoEventsToBeProcessed();
|
expectNoMessagesToBeProcessed();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reconciles an instance owned by a player with a "ghost" instance created on other peers without a network ID (as not owned by them)', () => {
|
it('reconciles an instance owned by a player with a "ghost" instance created on other peers without a network ID (as not owned by them)', () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
// Create an instance on a player:
|
// Create an instance on a player:
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
@@ -1625,7 +1613,7 @@ describe('Multiplayer', () => {
|
|||||||
expect(p3SpriteObject.getX()).to.be(142);
|
expect(p3SpriteObject.getX()).to.be(142);
|
||||||
expect(p3SpriteObject.getY()).to.be(143);
|
expect(p3SpriteObject.getY()).to.be(143);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
// Now, create a new instance on the host and player 3, but owned by player 2.
|
// Now, create a new instance on the host and player 3, but owned by player 2.
|
||||||
// We call this in this test a "ghost" instance as it would be deleted if not "reconcilied".
|
// We call this in this test a "ghost" instance as it would be deleted if not "reconcilied".
|
||||||
@@ -1756,12 +1744,12 @@ describe('Multiplayer', () => {
|
|||||||
expect(p3Object2.getX()).to.be(42);
|
expect(p3Object2.getX()).to.be(42);
|
||||||
expect(p3Object2.getY()).to.be(43);
|
expect(p3Object2.getY()).to.be(43);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes an instance owned by another player after a bit (if not "reconciled" in the meantime)', async () => {
|
it('deletes an instance owned by another player after a bit (if not "reconciled" in the meantime)', async () => {
|
||||||
const { switchToPeer } = createP2PAndMultiplayerManagersMock();
|
const { switchToPeer } = createMultiplayerManagersMock();
|
||||||
|
|
||||||
// Create an instance on a player (2), owned by another player (3).
|
// Create an instance on a player (2), owned by another player (3).
|
||||||
// We can assume it's because there is some common logic running for all players
|
// We can assume it's because there is some common logic running for all players
|
||||||
@@ -1813,8 +1801,8 @@ describe('Multiplayer', () => {
|
|||||||
it('gives priority to the first ownership change and revert the wrong one', async () => {
|
it('gives priority to the first ownership change and revert the wrong one', async () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
// Create an instance on the host's game:
|
// Create an instance on the host's game:
|
||||||
switchToPeer({
|
switchToPeer({
|
||||||
@@ -1887,7 +1875,7 @@ describe('Multiplayer', () => {
|
|||||||
expect(p3SpriteObject.getX()).to.be(142);
|
expect(p3SpriteObject.getX()).to.be(142);
|
||||||
expect(p3SpriteObject.getY()).to.be(143);
|
expect(p3SpriteObject.getY()).to.be(143);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
// Now, try to change ownership to player 2 and 3 at the "same time".
|
// Now, try to change ownership to player 2 and 3 at the "same time".
|
||||||
{
|
{
|
||||||
@@ -1953,7 +1941,7 @@ describe('Multiplayer', () => {
|
|||||||
p1SpriteMultiplayerObjectBehaviorUpdated.getPlayerObjectOwnership()
|
p1SpriteMultiplayerObjectBehaviorUpdated.getPlayerObjectOwnership()
|
||||||
).to.be(2);
|
).to.be(2);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait so that player 3 retries.
|
// Wait so that player 3 retries.
|
||||||
@@ -1980,7 +1968,7 @@ describe('Multiplayer', () => {
|
|||||||
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
||||||
).to.be(3);
|
).to.be(3);
|
||||||
|
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
await delay(210);
|
await delay(210);
|
||||||
}
|
}
|
||||||
@@ -2005,7 +1993,7 @@ describe('Multiplayer', () => {
|
|||||||
expect(
|
expect(
|
||||||
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
||||||
).to.be(0);
|
).to.be(0);
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the object on the player 2:
|
// Move the object on the player 2:
|
||||||
@@ -2072,7 +2060,7 @@ describe('Multiplayer', () => {
|
|||||||
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
||||||
).to.be(2);
|
).to.be(2);
|
||||||
}
|
}
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2091,7 +2079,7 @@ describe('Multiplayer', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it('synchronizes scenes from the host to other players', async () => {
|
it('synchronizes scenes from the host to other players', async () => {
|
||||||
const { switchToPeer } = createP2PAndMultiplayerManagersMock();
|
const { switchToPeer } = createMultiplayerManagersMock();
|
||||||
|
|
||||||
const gameLayoutData = [
|
const gameLayoutData = [
|
||||||
getFakeSceneAndExtensionData({ name: 'Scene1' }).sceneData,
|
getFakeSceneAndExtensionData({ name: 'Scene1' }).sceneData,
|
||||||
@@ -2218,8 +2206,8 @@ describe('Multiplayer', () => {
|
|||||||
it('reconciles a scene launched both by the host and by a player', async () => {
|
it('reconciles a scene launched both by the host and by a player', async () => {
|
||||||
const {
|
const {
|
||||||
switchToPeer,
|
switchToPeer,
|
||||||
markAllPeerEventsAsProcessed,
|
markAllPeerMessagesAsProcessed,
|
||||||
} = createP2PAndMultiplayerManagersMock();
|
} = createMultiplayerManagersMock();
|
||||||
|
|
||||||
const gameLayoutData = [
|
const gameLayoutData = [
|
||||||
getFakeSceneAndExtensionData({ name: 'Scene1' }).sceneData,
|
getFakeSceneAndExtensionData({ name: 'Scene1' }).sceneData,
|
||||||
@@ -2263,7 +2251,7 @@ describe('Multiplayer', () => {
|
|||||||
p2RuntimeGame.getSceneStack().step(1000 / 60);
|
p2RuntimeGame.getSceneStack().step(1000 / 60);
|
||||||
|
|
||||||
checkCurrentSceneIs(p2RuntimeGame, 'Scene1');
|
checkCurrentSceneIs(p2RuntimeGame, 'Scene1');
|
||||||
markAllPeerEventsAsProcessed();
|
markAllPeerMessagesAsProcessed();
|
||||||
|
|
||||||
// Launch a second scene, first on the player:
|
// Launch a second scene, first on the player:
|
||||||
p2RuntimeGame.getSceneStack().push('Scene2');
|
p2RuntimeGame.getSceneStack().push('Scene2');
|
||||||
|
Reference in New Issue
Block a user