gdcore-tools 2.0.0-gd-v5.5.235-autobuild → 2.0.0-gd-v5.5.237-autobuild

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/Runtime/CustomRuntimeObject.js +1 -1
  2. package/dist/Runtime/CustomRuntimeObject.js.map +2 -2
  3. package/dist/Runtime/CustomRuntimeObjectInstanceContainer.js +1 -1
  4. package/dist/Runtime/CustomRuntimeObjectInstanceContainer.js.map +2 -2
  5. package/dist/Runtime/Extensions/3D/A_RuntimeObject3D.js +1 -1
  6. package/dist/Runtime/Extensions/3D/A_RuntimeObject3D.js.map +2 -2
  7. package/dist/Runtime/Extensions/3D/Cube3DRuntimeObjectPixiRenderer.js +1 -1
  8. package/dist/Runtime/Extensions/3D/Cube3DRuntimeObjectPixiRenderer.js.map +2 -2
  9. package/dist/Runtime/Extensions/3D/CustomRuntimeObject3D.js +1 -1
  10. package/dist/Runtime/Extensions/3D/CustomRuntimeObject3D.js.map +2 -2
  11. package/dist/Runtime/Extensions/3D/DirectionalLight.js +1 -1
  12. package/dist/Runtime/Extensions/3D/DirectionalLight.js.map +2 -2
  13. package/dist/Runtime/Extensions/3D/HemisphereLight.js +1 -1
  14. package/dist/Runtime/Extensions/3D/HemisphereLight.js.map +2 -2
  15. package/dist/Runtime/Extensions/3D/JsExtension.js +10 -8
  16. package/dist/Runtime/Extensions/Multiplayer/JsExtension.js +67 -0
  17. package/dist/Runtime/Extensions/Multiplayer/multiplayerobjectruntimebehavior.js +1 -1
  18. package/dist/Runtime/Extensions/Multiplayer/multiplayerobjectruntimebehavior.js.map +2 -2
  19. package/dist/Runtime/Extensions/Multiplayer/multiplayertools.js +1 -1
  20. package/dist/Runtime/Extensions/Multiplayer/multiplayertools.js.map +2 -2
  21. package/dist/Runtime/Extensions/PanelSpriteObject/panelspriteruntimeobject.js +1 -1
  22. package/dist/Runtime/Extensions/PanelSpriteObject/panelspriteruntimeobject.js.map +2 -2
  23. package/dist/Runtime/Extensions/Physics3DBehavior/JsExtension.js +48 -0
  24. package/dist/Runtime/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.js +1 -1
  25. package/dist/Runtime/Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.js.map +2 -2
  26. package/dist/Runtime/Extensions/Physics3DBehavior/PhysicsCar3DRuntimeBehavior.js +1 -1
  27. package/dist/Runtime/Extensions/Physics3DBehavior/PhysicsCar3DRuntimeBehavior.js.map +2 -2
  28. package/dist/Runtime/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.js +1 -1
  29. package/dist/Runtime/Extensions/Physics3DBehavior/PhysicsCharacter3DRuntimeBehavior.js.map +2 -2
  30. package/dist/Runtime/Extensions/Spine/spineruntimeobject.js +1 -1
  31. package/dist/Runtime/Extensions/Spine/spineruntimeobject.js.map +2 -2
  32. package/dist/Runtime/Extensions/TextInput/JsExtension.js +26 -0
  33. package/dist/Runtime/Extensions/TextInput/textinputruntimeobject-pixi-renderer.js +1 -1
  34. package/dist/Runtime/Extensions/TextInput/textinputruntimeobject-pixi-renderer.js.map +2 -2
  35. package/dist/Runtime/Extensions/TextInput/textinputruntimeobject.js +1 -1
  36. package/dist/Runtime/Extensions/TextInput/textinputruntimeobject.js.map +2 -2
  37. package/dist/Runtime/Extensions/TileMap/simpletilemapruntimeobject.js +1 -1
  38. package/dist/Runtime/Extensions/TileMap/simpletilemapruntimeobject.js.map +2 -2
  39. package/dist/Runtime/Extensions/TileMap/tilemapcollisionmaskruntimeobject.js +1 -1
  40. package/dist/Runtime/Extensions/TileMap/tilemapcollisionmaskruntimeobject.js.map +2 -2
  41. package/dist/Runtime/Extensions/TileMap/tilemapruntimeobject.js +1 -1
  42. package/dist/Runtime/Extensions/TileMap/tilemapruntimeobject.js.map +2 -2
  43. package/dist/Runtime/Extensions/TiledSpriteObject/tiledspriteruntimeobject.js +1 -1
  44. package/dist/Runtime/Extensions/TiledSpriteObject/tiledspriteruntimeobject.js.map +2 -2
  45. package/dist/Runtime/Extensions/Video/videoruntimeobject.js +1 -1
  46. package/dist/Runtime/Extensions/Video/videoruntimeobject.js.map +2 -2
  47. package/dist/Runtime/debugger-client/hot-reloader.js +1 -1
  48. package/dist/Runtime/debugger-client/hot-reloader.js.map +2 -2
  49. package/dist/Runtime/events-tools/inputtools.js +1 -1
  50. package/dist/Runtime/events-tools/inputtools.js.map +2 -2
  51. package/dist/Runtime/runtimeobject.js +1 -1
  52. package/dist/Runtime/runtimeobject.js.map +2 -2
  53. package/dist/Runtime/runtimescene.js.map +2 -2
  54. package/dist/Runtime/types/project-data.d.ts +4 -0
  55. package/dist/lib/libGD.cjs +1 -1
  56. package/dist/lib/libGD.wasm +0 -0
  57. package/gd.d.ts +3 -0
  58. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../GDevelop/Extensions/Multiplayer/multiplayerobjectruntimebehavior.ts"],
4
- "sourcesContent": ["/*\n GDevelop - Multiplayer Object Behavior Extension\n Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)\n*/\n\nnamespace gdjs {\n const logger = new gdjs.Logger('Multiplayer');\n const debugLogger = new gdjs.Logger('Multiplayer - Debug');\n const getTimeNow =\n window.performance && typeof window.performance.now === 'function'\n ? window.performance.now.bind(window.performance)\n : Date.now;\n\n /**\n * The MultiplayerObjectRuntimeBehavior represents a behavior that can be added to objects\n * to make them synchronized over the network.\n */\n export class MultiplayerObjectRuntimeBehavior extends gdjs.RuntimeBehavior {\n // Which player is the owner of the object.\n // If 0, then the object is not owned by any player, so the host is the owner.\n playerNumber: number = 0;\n\n // The action to be executed when the player disconnects.\n actionOnPlayerDisconnect: string;\n\n // The last time the object has been synchronized.\n // This is to avoid synchronizing the object too often, see _objectMaxSyncRate.\n _lastObjectSyncTimestamp: number = 0;\n\n // The last time the basic object info has been synchronized.\n _lastBasicObjectSyncTimestamp: number = 0;\n // The number of times per second the object basic info should be synchronized when it doesn't change.\n _objectBasicInfoSyncRate: number = 5;\n // The last data sent to synchronize the basic info of the object.\n _lastSentBasicObjectSyncData: BasicObjectNetworkSyncData | undefined;\n // When we know that the basic info of the object has been updated, we can force sending them\n // on the max SyncRate for a number of times to ensure they are received, without the need of an acknowledgment.\n _numberOfForcedBasicObjectUpdates: number = 0;\n\n // The last time the variables have been synchronized.\n _lastVariablesSyncTimestamp: number = 0;\n // The number of times per second the variables should be synchronized.\n _variablesSyncRate: number = 1;\n // The last data sent to synchronize the variables.\n _lastSentVariableSyncData: VariableNetworkSyncData[] | undefined;\n // When we know that the variables have been updated, we can force sending them\n // on the same syncRate as the object update for a number of times\n // to ensure they are received, without the need of an acknowledgment.\n _numberOfForcedVariablesUpdates: number = 0;\n\n // The last time the effects have been synchronized.\n _lastEffectsSyncTimestamp: number = 0;\n // The number of times per second the effects should be synchronized.\n _effectsSyncRate: number = 1;\n // The last data sent to synchronize the effects.\n _lastSentEffectSyncData:\n | { [effectName: string]: EffectNetworkSyncData }\n | undefined;\n // When we know that the effects have been updated, we can force sending them\n // on the same syncRate as the object update for a number of times\n // to ensure they are received, without the need of an acknowledgment.\n _numberOfForcedEffectsUpdates: number = 0;\n\n // To avoid seeing too many logs.\n _lastLogTimestamp: number = 0;\n _logSyncRate: number = 1;\n // Clock to be incremented every time we send a message, to ensure they are ordered\n // and old messages are ignored.\n _clock: number = 0;\n _destroyInstanceTimeoutId: NodeJS.Timeout | null = null;\n _timeBeforeDestroyingObjectWithoutNetworkIdInMs = 500;\n\n constructor(\n instanceContainer: gdjs.RuntimeInstanceContainer,\n behaviorData,\n owner: RuntimeObject\n ) {\n super(instanceContainer, behaviorData, owner);\n this.playerNumber =\n behaviorData.playerNumber === 'Host'\n ? 0\n : parseInt(behaviorData.playerNumber, 10);\n this.actionOnPlayerDisconnect = behaviorData.actionOnPlayerDisconnect;\n\n // When a synchronized object is created, we assume it will be assigned a networkId quickly if:\n // - It is a new object created by the current player. -> will be assigned a networkId when sending the update message.\n // - It is an object created by another player. -> will be assigned a networkId when receiving the update message.\n // There is a small risk that the object is created by us after we receive an update message from the host,\n // ending up with 2 objects created, one with a networkId (from the host) and one without (from us).\n // To handle this case and avoid having an object not synchronized, we set a timeout to destroy the object\n // if it has not been assigned a networkId after a short delay.\n this._destroyInstanceTimeoutId = setTimeout(() => {\n const sceneNetworkId = this.owner.getRuntimeScene().networkId;\n if (\n !owner.networkId &&\n gdjs.multiplayer.isLobbyGameRunning() &&\n sceneNetworkId\n ) {\n debugLogger.info(\n `Lobby game is running on a synced scene and object ${owner.getName()} has not been assigned a networkId after a short delay, destroying it.`\n );\n owner.deleteFromScene();\n }\n }, this._timeBeforeDestroyingObjectWithoutNetworkIdInMs);\n }\n\n private _sendDataToPeersWithIncreasedClock = async (\n messageName: string,\n data: Object\n ) => {\n this._clock++;\n data['_clock'] = this._clock;\n const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();\n await gdjs.multiplayerMessageManager.sendDataTo(\n connectedPeerIds,\n messageName,\n data\n );\n };\n\n private _isOwnerAsPlayerOrHost() {\n const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();\n const isHost = gdjs.multiplayer.isCurrentPlayerHost();\n\n const isOwnerOfObject =\n currentPlayerNumber === this.playerNumber || // Player as owner.\n (isHost && this.playerNumber === 0); // Host as owner.\n\n return isOwnerOfObject;\n }\n\n private _hasObjectBeenSyncedWithinMaxRate() {\n const objectMaxSyncRate =\n gdjs.multiplayer.getObjectsSynchronizationRate();\n return (\n getTimeNow() - this._lastObjectSyncTimestamp < 1000 / objectMaxSyncRate\n );\n }\n\n private _hasObjectBasicInfoBeenSyncedRecently() {\n return (\n getTimeNow() - this._lastBasicObjectSyncTimestamp <\n 1000 / this._objectBasicInfoSyncRate\n );\n }\n\n private _haveVariablesBeenSyncedRecently() {\n return (\n getTimeNow() - this._lastVariablesSyncTimestamp <\n 1000 / this._variablesSyncRate\n );\n }\n\n private _haveEffectsBeenSyncedRecently() {\n return (\n getTimeNow() - this._lastEffectsSyncTimestamp <\n 1000 / this._effectsSyncRate\n );\n }\n\n // private _logToConsoleWithThrottle(message: string) {\n // if (getTimeNow() - this._lastLogTimestamp > 1000 / this._logSyncRate) {\n // logger.info(message);\n // this._lastLogTimestamp = getTimeNow();\n // }\n // }\n\n private _getOrCreateInstanceNetworkId() {\n if (!this.owner.networkId) {\n // No ID for this object, let's generate one so it can be identified by other players.\n // Keep it short to avoid sending too much data.\n const newID = gdjs.makeUuid().substring(0, 8);\n this.owner.networkId = newID;\n }\n\n return this.owner.networkId;\n }\n\n private _isBasicObjectNetworkSyncDataDifferentFromLastSync(\n basicObjectNetworkSyncData: BasicObjectNetworkSyncData\n ) {\n if (!this._lastSentBasicObjectSyncData) {\n return true;\n }\n\n // Compare the json of the basicObjectNetworkSyncData to check if they are different.\n // This is not the most efficient way to do it, but it's simple and should work.\n const haveBasicObjectNetworkSyncDataChanged =\n JSON.stringify(basicObjectNetworkSyncData) !==\n JSON.stringify(this._lastSentBasicObjectSyncData);\n\n return haveBasicObjectNetworkSyncDataChanged;\n }\n\n private _areVariablesDifferentFromLastSync(\n variablesSyncData: VariableNetworkSyncData[]\n ) {\n if (!this._lastSentVariableSyncData) {\n return true;\n }\n\n // Compare the json of the variables to check if they are different.\n // This is not the most efficient way to do it, but it's simple and should work.\n const haveVariableSyncDataChanged =\n JSON.stringify(variablesSyncData) !==\n JSON.stringify(this._lastSentVariableSyncData);\n\n return haveVariableSyncDataChanged;\n }\n\n private _areEffectsDifferentFromLastSync(effectsSyncData: {\n [effectName: string]: EffectNetworkSyncData;\n }) {\n if (!this._lastSentEffectSyncData) {\n return true;\n }\n\n for (const effectName in effectsSyncData) {\n if (!effectsSyncData.hasOwnProperty(effectName)) {\n continue;\n }\n\n const effectSyncData = effectsSyncData[effectName];\n const effectEnabled = effectSyncData.ena;\n const effectFilterCreator = effectSyncData.fc;\n\n const effectInLastSync = this._lastSentEffectSyncData[effectName];\n\n if (!effectInLastSync || effectInLastSync.ena !== effectEnabled) {\n return true;\n }\n\n for (const parameterName in effectFilterCreator) {\n if (!effectFilterCreator.hasOwnProperty(parameterName)) {\n continue;\n }\n\n const parameterValue = effectFilterCreator[parameterName];\n const lastParameterValueSent = effectInLastSync.fc[parameterName];\n if (lastParameterValueSent !== parameterValue) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n doStepPostEvents() {\n // Before doing anything, check if the game is running, if not, return.\n if (!gdjs.multiplayer.isLobbyGameRunning()) {\n return;\n }\n\n // If game is running and the object belongs to a player who is not connected, destroy the object.\n // As the game may create objects before the lobby game starts, we don't want to destroy them if it's not running.\n if (\n this.actionOnPlayerDisconnect !== 'DoNothing' && // Should not delete if flagged as such.\n this.playerNumber !== 0 && // Host is always connected.\n !gdjs.multiplayerMessageManager.isPlayerConnected(this.playerNumber)\n ) {\n debugLogger.info(\n `Player number ${this.playerNumber} does not exist in the lobby at the moment. Destroying the object.`\n );\n this.owner.deleteFromScene();\n return;\n }\n\n if (!this._isOwnerAsPlayerOrHost()) {\n return;\n }\n\n // If the object has been synchronized recently at the max rate, then return.\n // This is to avoid sending data on every frame, which would be too much.\n if (this._hasObjectBeenSyncedWithinMaxRate()) {\n return;\n }\n\n const instanceNetworkId = this._getOrCreateInstanceNetworkId();\n const objectName = this.owner.getName();\n const objectNetworkSyncData = this.owner.getNetworkSyncData();\n\n // this._logToConsoleWithThrottle(\n // `Synchronizing object ${this.owner.getName()} (instance ${\n // this.owner.networkId\n // }) with player ${this.playerNumber} and data ${JSON.stringify(\n // objectNetworkSyncData\n // )}`\n // );\n\n const areBasicObjectNetworkSyncDataDifferent =\n this._isBasicObjectNetworkSyncDataDifferentFromLastSync({\n x: objectNetworkSyncData.x,\n y: objectNetworkSyncData.y,\n z: objectNetworkSyncData.z,\n zo: objectNetworkSyncData.zo,\n a: objectNetworkSyncData.a,\n hid: objectNetworkSyncData.hid,\n lay: objectNetworkSyncData.lay,\n if: objectNetworkSyncData.if,\n pfx: objectNetworkSyncData.pfx,\n pfy: objectNetworkSyncData.pfy,\n });\n const shouldSyncObjectBasicInfo =\n !this._hasObjectBasicInfoBeenSyncedRecently() ||\n areBasicObjectNetworkSyncDataDifferent ||\n this._numberOfForcedBasicObjectUpdates > 0;\n if (areBasicObjectNetworkSyncDataDifferent) {\n this._numberOfForcedBasicObjectUpdates = 3;\n }\n if (!shouldSyncObjectBasicInfo) {\n // If the basic info has not changed, assume we don't need to sync the whole object data at a high rate.\n // TODO: allow sending the variables, behaviors and effects still?\n return;\n }\n\n const areVariablesDifferent =\n objectNetworkSyncData.var &&\n this._areVariablesDifferentFromLastSync(objectNetworkSyncData.var);\n const shouldSyncVariables =\n !this._haveVariablesBeenSyncedRecently() ||\n areVariablesDifferent ||\n this._numberOfForcedVariablesUpdates > 0;\n if (areVariablesDifferent) {\n this._numberOfForcedVariablesUpdates = 3;\n }\n if (!shouldSyncVariables) {\n delete objectNetworkSyncData.var;\n }\n\n const areEffectsDifferent =\n objectNetworkSyncData.eff &&\n this._areEffectsDifferentFromLastSync(objectNetworkSyncData.eff);\n const shoundSyncEffects =\n !this._haveEffectsBeenSyncedRecently() ||\n areEffectsDifferent ||\n this._numberOfForcedEffectsUpdates > 0;\n if (areEffectsDifferent) {\n this._numberOfForcedEffectsUpdates = 3;\n }\n if (!shoundSyncEffects) {\n delete objectNetworkSyncData.eff;\n }\n\n const sceneNetworkId = this.owner.getRuntimeScene().networkId;\n if (!sceneNetworkId) {\n // No networkId for the scene yet, it will be set soon, let's not sync the object yet.\n return;\n }\n\n const { messageName: updateMessageName, messageData: updateMessageData } =\n gdjs.multiplayerMessageManager.createUpdateInstanceMessage({\n objectOwner: this.playerNumber,\n objectName,\n instanceNetworkId,\n objectNetworkSyncData,\n sceneNetworkId,\n });\n this._sendDataToPeersWithIncreasedClock(\n updateMessageName,\n updateMessageData\n );\n\n const now = getTimeNow();\n\n this._lastObjectSyncTimestamp = now;\n if (shouldSyncObjectBasicInfo) {\n this._lastBasicObjectSyncTimestamp = now;\n this._lastSentBasicObjectSyncData = {\n x: objectNetworkSyncData.x,\n y: objectNetworkSyncData.y,\n zo: objectNetworkSyncData.zo,\n a: objectNetworkSyncData.a,\n hid: objectNetworkSyncData.hid,\n lay: objectNetworkSyncData.lay,\n if: objectNetworkSyncData.if,\n pfx: objectNetworkSyncData.pfx,\n pfy: objectNetworkSyncData.pfy,\n };\n this._numberOfForcedBasicObjectUpdates = Math.max(\n this._numberOfForcedBasicObjectUpdates - 1,\n 0\n );\n }\n if (shouldSyncVariables) {\n this._lastVariablesSyncTimestamp = now;\n this._lastSentVariableSyncData = objectNetworkSyncData.var;\n this._numberOfForcedVariablesUpdates = Math.max(\n this._numberOfForcedVariablesUpdates - 1,\n 0\n );\n }\n if (shoundSyncEffects) {\n this._lastEffectsSyncTimestamp = now;\n this._lastSentEffectSyncData = objectNetworkSyncData.eff;\n this._numberOfForcedEffectsUpdates = Math.max(\n this._numberOfForcedEffectsUpdates - 1,\n 0\n );\n }\n }\n\n onDestroy() {\n if (this._destroyInstanceTimeoutId) {\n clearTimeout(this._destroyInstanceTimeoutId);\n this._destroyInstanceTimeoutId = null;\n }\n\n // If the lobby game is not running, no need to send a message to destroy the object.\n if (!gdjs.multiplayer.isLobbyGameRunning()) {\n return;\n }\n\n // For destruction of objects, we allow the host to destroy the object even if it is not the owner.\n // This is particularly helpful when a player disconnects, so the host can destroy the object they were owning.\n if (\n !this._isOwnerAsPlayerOrHost() &&\n !gdjs.multiplayer.isCurrentPlayerHost()\n ) {\n return;\n }\n\n const instanceNetworkId = this.owner.networkId;\n const objectName = this.owner.getName();\n\n // If it had no networkId, then it was not synchronized and we don't need to send a message.\n if (!instanceNetworkId) {\n debugLogger.info(\n `Destroying object ${objectName} without networkId, no need to send a message.`\n );\n return;\n }\n\n const sceneNetworkId = this.owner.getRuntimeScene().networkId;\n if (!sceneNetworkId) {\n // No networkId for the scene yet, it will be set soon, let's not sync the object yet.\n return;\n }\n\n // Ensure we send a final update before the object is destroyed, if it had a networkId.\n const { messageName: updateMessageName, messageData: updateMessageData } =\n gdjs.multiplayerMessageManager.createUpdateInstanceMessage({\n objectOwner: this.playerNumber,\n objectName,\n instanceNetworkId,\n objectNetworkSyncData: this.owner.getNetworkSyncData(),\n sceneNetworkId,\n });\n this._sendDataToPeersWithIncreasedClock(\n updateMessageName,\n updateMessageData\n );\n\n // Before sending the destroy message, we set up the object representing the peers\n // that we need an acknowledgment from.\n // If we are the host, we are connected to everyone, so we expect an acknowledgment from everyone.\n // If we are another player, we are only connected to the host, so we expect an acknowledgment from the host.\n // In both cases, this represents the list of peers the current user is connected to.\n const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();\n const {\n messageName: destroyMessageName,\n messageData: destroyMessageData,\n } = gdjs.multiplayerMessageManager.createDestroyInstanceMessage({\n objectOwner: this.playerNumber,\n objectName,\n instanceNetworkId,\n sceneNetworkId,\n });\n const destroyedMessageName =\n gdjs.multiplayerMessageManager.createInstanceDestroyedMessageNameFromDestroyInstanceMessage(\n destroyMessageName\n );\n gdjs.multiplayerMessageManager.addExpectedMessageAcknowledgement({\n originalMessageName: destroyMessageName,\n originalData: {\n ...destroyMessageData,\n // As the method `sendDataToPeersWithIncreasedClock` will increment the clock,\n // we increment it here to ensure we can resend the same message if we don't receive an acknowledgment.\n _clock: this._clock + 1,\n },\n expectedMessageName: destroyedMessageName,\n otherPeerIds,\n // Destruction of objects are not reverted, as they will eventually be recreated by an update message.\n shouldCancelMessageIfTimesOut: false,\n });\n\n this._sendDataToPeersWithIncreasedClock(\n destroyMessageName,\n destroyMessageData\n );\n }\n\n setPlayerObjectOwnership(newObjectPlayerNumber: number) {\n debugLogger.info(\n `Setting ownership of object ${this.owner.getName()} (networkId: ${\n this.owner.networkId\n } to player ${newObjectPlayerNumber}.`\n );\n if (newObjectPlayerNumber < 0) {\n logger.error(\n 'Invalid player number (' +\n newObjectPlayerNumber +\n ') when setting ownership of an object.'\n );\n return;\n }\n\n // Update the ownership locally, so the object can be used immediately.\n // This is a prediction to allow snappy interactions.\n // If we are host, we will have the ownership immediately anyway.\n // If we are another player, we will have the ownership as soon as the host acknowledges the change.\n // If the host does not send an acknowledgment, we will revert the ownership.\n const previousObjectPlayerNumber = this.playerNumber;\n this.playerNumber = newObjectPlayerNumber;\n const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();\n\n // If the lobby game is not running, do not try to update the ownership over the network,\n // as the game may create & update objects before the lobby game starts.\n if (!gdjs.multiplayer.isLobbyGameRunning()) {\n return;\n }\n\n let instanceNetworkId = this.owner.networkId;\n if (!instanceNetworkId) {\n debugLogger.info(\n 'Object has no networkId, we change the ownership locally, but it will not be synchronized yet if we are not the owner.'\n );\n if (newObjectPlayerNumber !== currentPlayerNumber) {\n // If we are not the new owner, we should not send a message to the host to change the ownership.\n // Just return and wait to receive an update message to reconcile this object.\n return;\n }\n // If we don't have a networkId, we need to create one now that we are the owner.\n // We are probably in a case where we created the object and then changed the ownership.\n debugLogger.info(\n 'We are the new owner, creating a networkId for the object.'\n );\n instanceNetworkId = this._getOrCreateInstanceNetworkId();\n }\n\n const sceneNetworkId = this.owner.getRuntimeScene().networkId;\n if (!sceneNetworkId) {\n // No networkId for the scene yet, it will be set soon, let's not sync the object yet.\n return;\n }\n\n const objectName = this.owner.getName();\n\n // When changing the ownership of an object with a networkId, we send a message to the host to ensure it is aware of the change,\n // and can either accept it and broadcast it to other players, or reject it and do nothing with it.\n // We expect an acknowledgment from the host, if not, we will retry and eventually revert the ownership.\n const { messageName, messageData } =\n gdjs.multiplayerMessageManager.createChangeInstanceOwnerMessage({\n objectOwner: previousObjectPlayerNumber,\n objectName,\n instanceNetworkId,\n newObjectOwner: newObjectPlayerNumber,\n instanceX: this.owner.getX(),\n instanceY: this.owner.getY(),\n sceneNetworkId,\n });\n // Before sending the changeOwner message, if we are becoming the new owner,\n // we want to ensure this message is acknowledged, by everyone we're connected to.\n // If we are the host, we are connected to everyone, so we expect an acknowledgment from everyone.\n // If we are another player, we are only connected to the host, so we expect an acknowledgment from the host.\n // In both cases, this represents the list of peers the current user is connected to.\n if (newObjectPlayerNumber === currentPlayerNumber) {\n const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();\n const changeOwnerAcknowledgedMessageName =\n gdjs.multiplayerMessageManager.createInstanceOwnerChangedMessageNameFromChangeInstanceOwnerMessage(\n messageName\n );\n gdjs.multiplayerMessageManager.addExpectedMessageAcknowledgement({\n originalMessageName: messageName,\n originalData: {\n ...messageData,\n _clock: this._clock + 1, // Will be incremented by the time the message is sent.\n },\n expectedMessageName: changeOwnerAcknowledgedMessageName,\n otherPeerIds,\n // If we are not the host, we should revert the ownership if the host does not acknowledge the change.\n shouldCancelMessageIfTimesOut:\n !gdjs.multiplayer.isCurrentPlayerHost(),\n });\n }\n\n debugLogger.info('Sending change owner message', messageName);\n this._sendDataToPeersWithIncreasedClock(messageName, messageData);\n\n // If we are the new owner, also send directly an update of the position,\n // so that the object is immediately moved on the screen and we don't wait for the next tick.\n if (newObjectPlayerNumber === currentPlayerNumber) {\n debugLogger.info(\n 'Sending update message to move the object immediately.'\n );\n const objectNetworkSyncData = this.owner.getNetworkSyncData();\n const {\n messageName: updateMessageName,\n messageData: updateMessageData,\n } = gdjs.multiplayerMessageManager.createUpdateInstanceMessage({\n objectOwner: this.playerNumber,\n objectName,\n instanceNetworkId,\n objectNetworkSyncData,\n sceneNetworkId,\n });\n this._sendDataToPeersWithIncreasedClock(\n updateMessageName,\n updateMessageData\n );\n }\n }\n\n getPlayerObjectOwnership(): number {\n return this.playerNumber;\n }\n\n isObjectOwnedByCurrentPlayer(): boolean {\n return this._isOwnerAsPlayerOrHost();\n }\n\n removeObjectOwnership() {\n // 0 means the host is the owner.\n this.setPlayerObjectOwnership(0);\n }\n\n takeObjectOwnership() {\n this.setPlayerObjectOwnership(gdjs.multiplayer.getCurrentPlayerNumber());\n }\n\n getActionOnPlayerDisconnect() {\n return this.actionOnPlayerDisconnect;\n }\n\n enableBehaviorSynchronization(behaviorName: string, enable: boolean) {\n const behavior = this.owner.getBehavior(behaviorName);\n if (!behavior) {\n logger.error(\n `Behavior ${behaviorName} does not exist on object ${this.owner.getName()}.`\n );\n return;\n }\n\n behavior.enableSynchronization(enable);\n }\n }\n gdjs.registerBehavior(\n 'Multiplayer::MultiplayerObjectBehavior',\n gdjs.MultiplayerObjectRuntimeBehavior\n );\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/*\n GDevelop - Multiplayer Object Behavior Extension\n Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)\n*/\n\nnamespace gdjs {\n const logger = new gdjs.Logger('Multiplayer');\n const debugLogger = new gdjs.Logger('Multiplayer - Debug');\n const getTimeNow =\n window.performance && typeof window.performance.now === 'function'\n ? window.performance.now.bind(window.performance)\n : Date.now;\n\n /**\n * The MultiplayerObjectRuntimeBehavior represents a behavior that can be added to objects\n * to make them synchronized over the network.\n */\n export class MultiplayerObjectRuntimeBehavior extends gdjs.RuntimeBehavior {\n // Which player is the owner of the object.\n // If 0, then the object is not owned by any player, so the host is the owner.\n playerNumber: number = 0;\n\n // The action to be executed when the player disconnects.\n actionOnPlayerDisconnect: string;\n\n // The last time the object has been synchronized.\n // This is to avoid synchronizing the object too often, see _objectMaxSyncRate.\n _lastObjectSyncTimestamp: number = 0;\n\n // The last time the basic object info has been synchronized.\n _lastBasicObjectSyncTimestamp: number = 0;\n // The number of times per second the object basic info should be synchronized when it doesn't change.\n _objectBasicInfoSyncRate: number = 5;\n // The last data sent to synchronize the basic info of the object.\n _lastSentBasicObjectSyncData: BasicObjectNetworkSyncData | undefined;\n // When we know that the basic info of the object has been updated, we can force sending them\n // on the max SyncRate for a number of times to ensure they are received, without the need of an acknowledgment.\n _numberOfForcedBasicObjectUpdates: number = 0;\n\n // The last time the variables have been synchronized.\n _lastVariablesSyncTimestamp: number = 0;\n // The number of times per second the variables should be synchronized.\n _variablesSyncRate: number = 1;\n // The last data sent to synchronize the variables.\n _lastSentVariableSyncData: VariableNetworkSyncData[] | undefined;\n // When we know that the variables have been updated, we can force sending them\n // on the same syncRate as the object update for a number of times\n // to ensure they are received, without the need of an acknowledgment.\n _numberOfForcedVariablesUpdates: number = 0;\n\n // The last time the effects have been synchronized.\n _lastEffectsSyncTimestamp: number = 0;\n // The number of times per second the effects should be synchronized.\n _effectsSyncRate: number = 1;\n // The last data sent to synchronize the effects.\n _lastSentEffectSyncData:\n | { [effectName: string]: EffectNetworkSyncData }\n | undefined;\n // When we know that the effects have been updated, we can force sending them\n // on the same syncRate as the object update for a number of times\n // to ensure they are received, without the need of an acknowledgment.\n _numberOfForcedEffectsUpdates: number = 0;\n\n // To avoid seeing too many logs.\n _lastLogTimestamp: number = 0;\n _logSyncRate: number = 1;\n // Clock to be incremented every time we send a message, to ensure they are ordered\n // and old messages are ignored.\n _clock: number = 0;\n _destroyInstanceTimeoutId: NodeJS.Timeout | null = null;\n _timeBeforeDestroyingObjectWithoutNetworkIdInMs = 500;\n\n constructor(\n instanceContainer: gdjs.RuntimeInstanceContainer,\n behaviorData,\n owner: RuntimeObject\n ) {\n super(instanceContainer, behaviorData, owner);\n this.playerNumber =\n behaviorData.playerNumber === 'Host'\n ? 0\n : parseInt(behaviorData.playerNumber, 10);\n this.actionOnPlayerDisconnect = behaviorData.actionOnPlayerDisconnect;\n\n // When a synchronized object is created, we assume it will be assigned a networkId quickly if:\n // - It is a new object created by the current player. -> will be assigned a networkId when sending the update message.\n // - It is an object created by another player. -> will be assigned a networkId when receiving the update message.\n // There is a small risk that the object is created by us after we receive an update message from the host,\n // ending up with 2 objects created, one with a networkId (from the host) and one without (from us).\n // To handle this case and avoid having an object not synchronized, we set a timeout to destroy the object\n // if it has not been assigned a networkId after a short delay.\n this._destroyInstanceTimeoutId = setTimeout(() => {\n const sceneNetworkId = this.owner.getRuntimeScene().networkId;\n if (\n !owner.networkId &&\n gdjs.multiplayer.isLobbyGameRunning() &&\n sceneNetworkId\n ) {\n debugLogger.info(\n `Lobby game is running on a synced scene and object ${owner.getName()} has not been assigned a networkId after a short delay, destroying it.`\n );\n owner.deleteFromScene();\n }\n }, this._timeBeforeDestroyingObjectWithoutNetworkIdInMs);\n }\n\n private _sendDataToPeersWithIncreasedClock = async (\n messageName: string,\n data: Object\n ) => {\n this._clock++;\n data['_clock'] = this._clock;\n const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();\n await gdjs.multiplayerMessageManager.sendDataTo(\n connectedPeerIds,\n messageName,\n data\n );\n };\n\n private _isOwnerAsPlayerOrHost() {\n const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();\n const isHost = gdjs.multiplayer.isCurrentPlayerHost();\n\n const isOwnerOfObject =\n currentPlayerNumber === this.playerNumber || // Player as owner.\n (isHost && this.playerNumber === 0); // Host as owner.\n\n return isOwnerOfObject;\n }\n\n private _hasObjectBeenSyncedWithinMaxRate() {\n const objectMaxSyncRate =\n gdjs.multiplayer.getObjectsSynchronizationRate();\n return (\n getTimeNow() - this._lastObjectSyncTimestamp < 1000 / objectMaxSyncRate\n );\n }\n\n private _hasObjectBasicInfoBeenSyncedRecently() {\n return (\n getTimeNow() - this._lastBasicObjectSyncTimestamp <\n 1000 / this._objectBasicInfoSyncRate\n );\n }\n\n private _haveVariablesBeenSyncedRecently() {\n return (\n getTimeNow() - this._lastVariablesSyncTimestamp <\n 1000 / this._variablesSyncRate\n );\n }\n\n private _haveEffectsBeenSyncedRecently() {\n return (\n getTimeNow() - this._lastEffectsSyncTimestamp <\n 1000 / this._effectsSyncRate\n );\n }\n\n // private _logToConsoleWithThrottle(message: string) {\n // if (getTimeNow() - this._lastLogTimestamp > 1000 / this._logSyncRate) {\n // logger.info(message);\n // this._lastLogTimestamp = getTimeNow();\n // }\n // }\n\n private _getOrCreateInstanceNetworkId() {\n if (!this.owner.networkId) {\n // No ID for this object, let's generate one so it can be identified by other players.\n // Keep it short to avoid sending too much data.\n const newID = gdjs.makeUuid().substring(0, 8);\n this.owner.networkId = newID;\n }\n\n return this.owner.networkId;\n }\n\n private _isBasicObjectNetworkSyncDataDifferentFromLastSync(\n basicObjectNetworkSyncData: BasicObjectNetworkSyncData\n ) {\n if (!this._lastSentBasicObjectSyncData) {\n return true;\n }\n\n // Compare the json of the basicObjectNetworkSyncData to check if they are different.\n // This is not the most efficient way to do it, but it's simple and should work.\n const haveBasicObjectNetworkSyncDataChanged =\n JSON.stringify(basicObjectNetworkSyncData) !==\n JSON.stringify(this._lastSentBasicObjectSyncData);\n\n return haveBasicObjectNetworkSyncDataChanged;\n }\n\n private _areVariablesDifferentFromLastSync(\n variablesSyncData: VariableNetworkSyncData[]\n ) {\n if (!this._lastSentVariableSyncData) {\n return true;\n }\n\n // Compare the json of the variables to check if they are different.\n // This is not the most efficient way to do it, but it's simple and should work.\n const haveVariableSyncDataChanged =\n JSON.stringify(variablesSyncData) !==\n JSON.stringify(this._lastSentVariableSyncData);\n\n return haveVariableSyncDataChanged;\n }\n\n private _areEffectsDifferentFromLastSync(effectsSyncData: {\n [effectName: string]: EffectNetworkSyncData;\n }) {\n if (!this._lastSentEffectSyncData) {\n return true;\n }\n\n for (const effectName in effectsSyncData) {\n if (!effectsSyncData.hasOwnProperty(effectName)) {\n continue;\n }\n\n const effectSyncData = effectsSyncData[effectName];\n const effectEnabled = effectSyncData.ena;\n const effectFilterCreator = effectSyncData.fc;\n\n const effectInLastSync = this._lastSentEffectSyncData[effectName];\n\n if (!effectInLastSync || effectInLastSync.ena !== effectEnabled) {\n return true;\n }\n\n for (const parameterName in effectFilterCreator) {\n if (!effectFilterCreator.hasOwnProperty(parameterName)) {\n continue;\n }\n\n const parameterValue = effectFilterCreator[parameterName];\n const lastParameterValueSent = effectInLastSync.fc[parameterName];\n if (lastParameterValueSent !== parameterValue) {\n return true;\n }\n }\n }\n\n return false;\n }\n\n doStepPostEvents() {\n // Before doing anything, check if the game is running, if not, return.\n if (!gdjs.multiplayer.isLobbyGameRunning()) {\n return;\n }\n\n // If game is running and the object belongs to a player who is not connected, destroy the object.\n // As the game may create objects before the lobby game starts, we don't want to destroy them if it's not running.\n if (\n this.actionOnPlayerDisconnect !== 'DoNothing' && // Should not delete if flagged as such.\n this.playerNumber !== 0 && // Host is always connected.\n !gdjs.multiplayerMessageManager.isPlayerConnected(this.playerNumber)\n ) {\n debugLogger.info(\n `Player number ${this.playerNumber} does not exist in the lobby at the moment. Destroying the object.`\n );\n this.owner.deleteFromScene();\n return;\n }\n\n if (!this._isOwnerAsPlayerOrHost()) {\n return;\n }\n\n // If the object has been synchronized recently at the max rate, then return.\n // This is to avoid sending data on every frame, which would be too much.\n if (this._hasObjectBeenSyncedWithinMaxRate()) {\n return;\n }\n\n const instanceNetworkId = this._getOrCreateInstanceNetworkId();\n const objectName = this.owner.getName();\n const objectNetworkSyncData = this.owner.getNetworkSyncData();\n\n // this._logToConsoleWithThrottle(\n // `Synchronizing object ${this.owner.getName()} (instance ${\n // this.owner.networkId\n // }) with player ${this.playerNumber} and data ${JSON.stringify(\n // objectNetworkSyncData\n // )}`\n // );\n\n const areBasicObjectNetworkSyncDataDifferent =\n this._isBasicObjectNetworkSyncDataDifferentFromLastSync({\n x: objectNetworkSyncData.x,\n y: objectNetworkSyncData.y,\n z: objectNetworkSyncData.z,\n w: objectNetworkSyncData.w,\n h: objectNetworkSyncData.h,\n zo: objectNetworkSyncData.zo,\n a: objectNetworkSyncData.a,\n hid: objectNetworkSyncData.hid,\n lay: objectNetworkSyncData.lay,\n if: objectNetworkSyncData.if,\n pfx: objectNetworkSyncData.pfx,\n pfy: objectNetworkSyncData.pfy,\n });\n const shouldSyncObjectBasicInfo =\n !this._hasObjectBasicInfoBeenSyncedRecently() ||\n areBasicObjectNetworkSyncDataDifferent ||\n this._numberOfForcedBasicObjectUpdates > 0;\n if (areBasicObjectNetworkSyncDataDifferent) {\n this._numberOfForcedBasicObjectUpdates = 3;\n }\n if (!shouldSyncObjectBasicInfo) {\n // If the basic info has not changed, assume we don't need to sync the whole object data at a high rate.\n // TODO: allow sending the variables, behaviors and effects still?\n return;\n }\n\n const areVariablesDifferent =\n objectNetworkSyncData.var &&\n this._areVariablesDifferentFromLastSync(objectNetworkSyncData.var);\n const shouldSyncVariables =\n !this._haveVariablesBeenSyncedRecently() ||\n areVariablesDifferent ||\n this._numberOfForcedVariablesUpdates > 0;\n if (areVariablesDifferent) {\n this._numberOfForcedVariablesUpdates = 3;\n }\n if (!shouldSyncVariables) {\n delete objectNetworkSyncData.var;\n }\n\n const areEffectsDifferent =\n objectNetworkSyncData.eff &&\n this._areEffectsDifferentFromLastSync(objectNetworkSyncData.eff);\n const shoundSyncEffects =\n !this._haveEffectsBeenSyncedRecently() ||\n areEffectsDifferent ||\n this._numberOfForcedEffectsUpdates > 0;\n if (areEffectsDifferent) {\n this._numberOfForcedEffectsUpdates = 3;\n }\n if (!shoundSyncEffects) {\n delete objectNetworkSyncData.eff;\n }\n\n const sceneNetworkId = this.owner.getRuntimeScene().networkId;\n if (!sceneNetworkId) {\n // No networkId for the scene yet, it will be set soon, let's not sync the object yet.\n return;\n }\n\n const { messageName: updateMessageName, messageData: updateMessageData } =\n gdjs.multiplayerMessageManager.createUpdateInstanceMessage({\n objectOwner: this.playerNumber,\n objectName,\n instanceNetworkId,\n objectNetworkSyncData,\n sceneNetworkId,\n });\n this._sendDataToPeersWithIncreasedClock(\n updateMessageName,\n updateMessageData\n );\n\n const now = getTimeNow();\n\n this._lastObjectSyncTimestamp = now;\n if (shouldSyncObjectBasicInfo) {\n this._lastBasicObjectSyncTimestamp = now;\n this._lastSentBasicObjectSyncData = {\n x: objectNetworkSyncData.x,\n y: objectNetworkSyncData.y,\n z: objectNetworkSyncData.z,\n w: objectNetworkSyncData.w,\n h: objectNetworkSyncData.h,\n zo: objectNetworkSyncData.zo,\n a: objectNetworkSyncData.a,\n hid: objectNetworkSyncData.hid,\n lay: objectNetworkSyncData.lay,\n if: objectNetworkSyncData.if,\n pfx: objectNetworkSyncData.pfx,\n pfy: objectNetworkSyncData.pfy,\n };\n this._numberOfForcedBasicObjectUpdates = Math.max(\n this._numberOfForcedBasicObjectUpdates - 1,\n 0\n );\n }\n if (shouldSyncVariables) {\n this._lastVariablesSyncTimestamp = now;\n this._lastSentVariableSyncData = objectNetworkSyncData.var;\n this._numberOfForcedVariablesUpdates = Math.max(\n this._numberOfForcedVariablesUpdates - 1,\n 0\n );\n }\n if (shoundSyncEffects) {\n this._lastEffectsSyncTimestamp = now;\n this._lastSentEffectSyncData = objectNetworkSyncData.eff;\n this._numberOfForcedEffectsUpdates = Math.max(\n this._numberOfForcedEffectsUpdates - 1,\n 0\n );\n }\n }\n\n onDestroy() {\n if (this._destroyInstanceTimeoutId) {\n clearTimeout(this._destroyInstanceTimeoutId);\n this._destroyInstanceTimeoutId = null;\n }\n\n // If the lobby game is not running, no need to send a message to destroy the object.\n if (!gdjs.multiplayer.isLobbyGameRunning()) {\n return;\n }\n\n // For destruction of objects, we allow the host to destroy the object even if it is not the owner.\n // This is particularly helpful when a player disconnects, so the host can destroy the object they were owning.\n if (\n !this._isOwnerAsPlayerOrHost() &&\n !gdjs.multiplayer.isCurrentPlayerHost()\n ) {\n return;\n }\n\n const instanceNetworkId = this.owner.networkId;\n const objectName = this.owner.getName();\n\n // If it had no networkId, then it was not synchronized and we don't need to send a message.\n if (!instanceNetworkId) {\n debugLogger.info(\n `Destroying object ${objectName} without networkId, no need to send a message.`\n );\n return;\n }\n\n const sceneNetworkId = this.owner.getRuntimeScene().networkId;\n if (!sceneNetworkId) {\n // No networkId for the scene yet, it will be set soon, let's not sync the object yet.\n return;\n }\n\n // Ensure we send a final update before the object is destroyed, if it had a networkId.\n const { messageName: updateMessageName, messageData: updateMessageData } =\n gdjs.multiplayerMessageManager.createUpdateInstanceMessage({\n objectOwner: this.playerNumber,\n objectName,\n instanceNetworkId,\n objectNetworkSyncData: this.owner.getNetworkSyncData(),\n sceneNetworkId,\n });\n this._sendDataToPeersWithIncreasedClock(\n updateMessageName,\n updateMessageData\n );\n\n // Before sending the destroy message, we set up the object representing the peers\n // that we need an acknowledgment from.\n // If we are the host, we are connected to everyone, so we expect an acknowledgment from everyone.\n // If we are another player, we are only connected to the host, so we expect an acknowledgment from the host.\n // In both cases, this represents the list of peers the current user is connected to.\n const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();\n const {\n messageName: destroyMessageName,\n messageData: destroyMessageData,\n } = gdjs.multiplayerMessageManager.createDestroyInstanceMessage({\n objectOwner: this.playerNumber,\n objectName,\n instanceNetworkId,\n sceneNetworkId,\n });\n const destroyedMessageName =\n gdjs.multiplayerMessageManager.createInstanceDestroyedMessageNameFromDestroyInstanceMessage(\n destroyMessageName\n );\n gdjs.multiplayerMessageManager.addExpectedMessageAcknowledgement({\n originalMessageName: destroyMessageName,\n originalData: {\n ...destroyMessageData,\n // As the method `sendDataToPeersWithIncreasedClock` will increment the clock,\n // we increment it here to ensure we can resend the same message if we don't receive an acknowledgment.\n _clock: this._clock + 1,\n },\n expectedMessageName: destroyedMessageName,\n otherPeerIds,\n // Destruction of objects are not reverted, as they will eventually be recreated by an update message.\n shouldCancelMessageIfTimesOut: false,\n });\n\n this._sendDataToPeersWithIncreasedClock(\n destroyMessageName,\n destroyMessageData\n );\n }\n\n setPlayerObjectOwnership(newObjectPlayerNumber: number) {\n debugLogger.info(\n `Setting ownership of object ${this.owner.getName()} (networkId: ${\n this.owner.networkId\n } to player ${newObjectPlayerNumber}.`\n );\n if (newObjectPlayerNumber < 0) {\n logger.error(\n 'Invalid player number (' +\n newObjectPlayerNumber +\n ') when setting ownership of an object.'\n );\n return;\n }\n\n // Update the ownership locally, so the object can be used immediately.\n // This is a prediction to allow snappy interactions.\n // If we are host, we will have the ownership immediately anyway.\n // If we are another player, we will have the ownership as soon as the host acknowledges the change.\n // If the host does not send an acknowledgment, we will revert the ownership.\n const previousObjectPlayerNumber = this.playerNumber;\n this.playerNumber = newObjectPlayerNumber;\n const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();\n\n // If the lobby game is not running, do not try to update the ownership over the network,\n // as the game may create & update objects before the lobby game starts.\n if (!gdjs.multiplayer.isLobbyGameRunning()) {\n return;\n }\n\n let instanceNetworkId = this.owner.networkId;\n if (!instanceNetworkId) {\n debugLogger.info(\n 'Object has no networkId, we change the ownership locally, but it will not be synchronized yet if we are not the owner.'\n );\n if (newObjectPlayerNumber !== currentPlayerNumber) {\n // If we are not the new owner, we should not send a message to the host to change the ownership.\n // Just return and wait to receive an update message to reconcile this object.\n return;\n }\n // If we don't have a networkId, we need to create one now that we are the owner.\n // We are probably in a case where we created the object and then changed the ownership.\n debugLogger.info(\n 'We are the new owner, creating a networkId for the object.'\n );\n instanceNetworkId = this._getOrCreateInstanceNetworkId();\n }\n\n const sceneNetworkId = this.owner.getRuntimeScene().networkId;\n if (!sceneNetworkId) {\n // No networkId for the scene yet, it will be set soon, let's not sync the object yet.\n return;\n }\n\n const objectName = this.owner.getName();\n\n // When changing the ownership of an object with a networkId, we send a message to the host to ensure it is aware of the change,\n // and can either accept it and broadcast it to other players, or reject it and do nothing with it.\n // We expect an acknowledgment from the host, if not, we will retry and eventually revert the ownership.\n const { messageName, messageData } =\n gdjs.multiplayerMessageManager.createChangeInstanceOwnerMessage({\n objectOwner: previousObjectPlayerNumber,\n objectName,\n instanceNetworkId,\n newObjectOwner: newObjectPlayerNumber,\n instanceX: this.owner.getX(),\n instanceY: this.owner.getY(),\n sceneNetworkId,\n });\n // Before sending the changeOwner message, if we are becoming the new owner,\n // we want to ensure this message is acknowledged, by everyone we're connected to.\n // If we are the host, we are connected to everyone, so we expect an acknowledgment from everyone.\n // If we are another player, we are only connected to the host, so we expect an acknowledgment from the host.\n // In both cases, this represents the list of peers the current user is connected to.\n if (newObjectPlayerNumber === currentPlayerNumber) {\n const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();\n const changeOwnerAcknowledgedMessageName =\n gdjs.multiplayerMessageManager.createInstanceOwnerChangedMessageNameFromChangeInstanceOwnerMessage(\n messageName\n );\n gdjs.multiplayerMessageManager.addExpectedMessageAcknowledgement({\n originalMessageName: messageName,\n originalData: {\n ...messageData,\n _clock: this._clock + 1, // Will be incremented by the time the message is sent.\n },\n expectedMessageName: changeOwnerAcknowledgedMessageName,\n otherPeerIds,\n // If we are not the host, we should revert the ownership if the host does not acknowledge the change.\n shouldCancelMessageIfTimesOut:\n !gdjs.multiplayer.isCurrentPlayerHost(),\n });\n }\n\n debugLogger.info('Sending change owner message', messageName);\n this._sendDataToPeersWithIncreasedClock(messageName, messageData);\n\n // If we are the new owner, also send directly an update of the position,\n // so that the object is immediately moved on the screen and we don't wait for the next tick.\n if (newObjectPlayerNumber === currentPlayerNumber) {\n debugLogger.info(\n 'Sending update message to move the object immediately.'\n );\n const objectNetworkSyncData = this.owner.getNetworkSyncData();\n const {\n messageName: updateMessageName,\n messageData: updateMessageData,\n } = gdjs.multiplayerMessageManager.createUpdateInstanceMessage({\n objectOwner: this.playerNumber,\n objectName,\n instanceNetworkId,\n objectNetworkSyncData,\n sceneNetworkId,\n });\n this._sendDataToPeersWithIncreasedClock(\n updateMessageName,\n updateMessageData\n );\n }\n }\n\n getPlayerObjectOwnership(): number {\n return this.playerNumber;\n }\n\n isObjectOwnedByCurrentPlayer(): boolean {\n return this._isOwnerAsPlayerOrHost();\n }\n\n removeObjectOwnership() {\n // 0 means the host is the owner.\n this.setPlayerObjectOwnership(0);\n }\n\n takeObjectOwnership() {\n this.setPlayerObjectOwnership(gdjs.multiplayer.getCurrentPlayerNumber());\n }\n\n getActionOnPlayerDisconnect() {\n return this.actionOnPlayerDisconnect;\n }\n\n enableBehaviorSynchronization(behaviorName: string, enable: boolean) {\n const behavior = this.owner.getBehavior(behaviorName);\n if (!behavior) {\n logger.error(\n `Behavior ${behaviorName} does not exist on object ${this.owner.getName()}.`\n );\n return;\n }\n\n behavior.enableSynchronization(enable);\n }\n }\n gdjs.registerBehavior(\n 'Multiplayer::MultiplayerObjectBehavior',\n gdjs.MultiplayerObjectRuntimeBehavior\n );\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,2 +1,2 @@
1
- var gdjs;(function(n){const r=new n.Logger("Multiplayer"),B=window.performance&&typeof window.performance.now=="function"?window.performance.now.bind(window.performance):Date.now,T=async({relativeUrl:G,method:A,body:L,dev:D})=>{const P=n.playerAuthentication.getUserId(),k=n.playerAuthentication.getUserToken();if(!P||!k)throw r.warn("Cannot fetch as a player if the player is not connected."),new Error("Cannot fetch as a player if the player is not connected.");const y=D?"https://api-dev.gdevelop.io":"https://api.gdevelop.io",N=new URL(`${y}${G}`);N.searchParams.set("playerId",P);const _=N.toString(),R={"Content-Type":"application/json",Authorization:`player-game-token ${k}`},u=await fetch(_,{method:A,headers:R,body:L});if(!u.ok)throw new Error(`Error while fetching as a player: ${u.status} ${u.statusText}`);const f=await u.text();if(f!=="OK")try{return JSON.parse(f)}catch(q){throw new Error(`Error while parsing the response: ${q}`)}};let Ie;(function(t){t.disableMultiplayerForTesting=!1,t._isReadyToSendOrReceiveGameUpdateMessages=!1;let L=null,D=!1,P=!1,k=!1;t._isLobbyGameRunning=!1;let N=!1,_=!1,R=null,u=null,f=null,q=!1,h=null,F=null,Z=!1,Y=null,M=null,U=!1,V=null,H=null,l=null,ee=null,j=null;const Me=1e4,ae=3e4;let te=ae;const Ce=1e3,ve=1e4,Ee=1e3,Pe=1e4;let Q=null;const Le=12e3;t.DEFAULT_OBJECT_MAX_SYNC_RATE=30,t._objectMaxSyncRate=t.DEFAULT_OBJECT_MAX_SYNC_RATE;let m=!1;t.playerNumber=null,t.hostPeerId=null,n.registerRuntimeScenePreEventsCallback(e=>{m=e.getGame().isUsingGDevelopDevelopmentEnvironment(),!t.disableMultiplayerForTesting&&(n.multiplayerMessageManager.handleHeartbeatsToSend(),n.multiplayerMessageManager.handleJustDisconnectedPeers(e),n.multiplayerMessageManager.handleChangeInstanceOwnerMessagesReceived(e),n.multiplayerMessageManager.handleUpdateInstanceMessagesReceived(e),n.multiplayerMessageManager.handleCustomMessagesReceived(),n.multiplayerMessageManager.handleAcknowledgeMessagesReceived(),n.multiplayerMessageManager.resendClearOrCancelAcknowledgedMessages(e),n.multiplayerMessageManager.handleChangeVariableOwnerMessagesReceived(e),t._isLobbyGameRunning&&n.multiplayerMessageManager.handleSavedUpdateMessages(e),n.multiplayerMessageManager.handleUpdateGameMessagesReceived(e),n.multiplayerMessageManager.handleUpdateSceneMessagesReceived(e))}),n.registerRuntimeScenePostEventsCallback(e=>{t.disableMultiplayerForTesting||(Ae(e),ke(e),n.multiplayerMessageManager.handleHeartbeatsReceived(),n.multiplayerMessageManager.handleEndGameMessagesReceived(),n.multiplayerMessageManager.handleResumeGameMessagesReceived(e),n.multiplayerMessageManager.handleDestroyInstanceMessagesReceived(e),n.multiplayerVariablesManager.handleChangeVariableOwnerMessagesToSend(),n.multiplayerMessageManager.handleUpdateGameMessagesToSend(e),n.multiplayerMessageManager.handleUpdateSceneMessagesToSend(e))}),n.registerRuntimeScenePostEventsCallback(()=>{t.disableMultiplayerForTesting||(k=!1,N=!1,_=!1)});const Re=({runtimeGame:e,gameId:o})=>{const s="https://gd.games",a=new URL(`${s}/games/${o}/lobbies${u?`/${u}`:""}`);a.searchParams.set("gameVersion",e.getGameData().properties.version),e.getAdditionalOptions().nativeMobileApp&&a.searchParams.set("nativeMobileApp","true"),a.searchParams.set("isPreview",e.isPreview()?"true":"false"),m&&a.searchParams.set("dev","true"),f&&a.searchParams.set("connectionId",f),t.playerNumber&&a.searchParams.set("positionInLobby",t.playerNumber.toString());const g=n.playerAuthentication.getUserId();g&&a.searchParams.set("playerId",g);const c=n.playerAuthentication.getUserToken();c&&a.searchParams.set("playerToken",c);const i=e.getPlatformInfo();return a.searchParams.set("scm",i.supportedCompressionMethods.join(",")),a.searchParams.set("multiplayerVersion","2"),a.toString()};t.setObjectsSynchronizationRate=e=>{e<1||e>60?(r.warn(`Invalid rate ${e} for object synchronization. Defaulting to ${t.DEFAULT_OBJECT_MAX_SYNC_RATE}.`),t._objectMaxSyncRate=t.DEFAULT_OBJECT_MAX_SYNC_RATE):t._objectMaxSyncRate=e},t.getObjectsSynchronizationRate=()=>t._objectMaxSyncRate,t.hasLobbyGameJustStarted=()=>k,t.isLobbyGameRunning=()=>t._isLobbyGameRunning,t.isReadyToSendOrReceiveGameUpdateMessages=()=>t._isReadyToSendOrReceiveGameUpdateMessages,t.hasLobbyGameJustEnded=()=>N,t.getPlayersInLobbyCount=()=>n.multiplayerMessageManager.getNumberOfConnectedPlayers(),t.isPlayerConnected=e=>n.multiplayerMessageManager.isPlayerConnected(e),t.getCurrentPlayerNumber=()=>t.playerNumber||0,t.isCurrentPlayerHost=()=>!!t.hostPeerId&&t.hostPeerId===n.multiplayerPeerJsHelper.getCurrentId(),t.isMigratingHost=()=>!!Z,t.endLobbyWhenHostLeaves=e=>{q=e},t.shouldEndLobbyWhenHostLeaves=()=>q,t.getPlayerUsername=e=>n.multiplayerMessageManager.getPlayerUsername(e),t.getCurrentPlayerUsername=()=>{const e=t.getCurrentPlayerNumber();return t.getPlayerUsername(e)};const Ae=e=>{const o=n.multiplayerMessageManager.getLatestPlayerWhoJustLeft();if(o){const s=t.getPlayerUsername(o);n.multiplayerComponents.displayPlayerLeftNotification(e,s),n.multiplayerMessageManager.removePlayerWhoJustLeft(),t.isCurrentPlayerHost()&&t.isReadyToSendOrReceiveGameUpdateMessages()&&x()}},ke=e=>{const o=n.multiplayerMessageManager.getLatestPlayerWhoJustJoined();if(o){const s=t.getPlayerUsername(o);n.multiplayerComponents.displayPlayerJoinedNotification(e,s),t.isCurrentPlayerHost()&&t.isReadyToSendOrReceiveGameUpdateMessages()&&x()}n.multiplayerMessageManager.removePlayerWhoJustJoined()},ce=(e,o,s=0)=>{const g=`${m?"https://api-dev.gdevelop.io":"https://api.gdevelop.io"}/game/public-game/${o}`;return fetch(g,{method:"HEAD"}).then(c=>c.status!==200?(r.warn(`Error while fetching the game: ${c.status} ${c.statusText}`),c.status===404||s>2?!1:ce(e,o,s+1)):!0,c=>(r.error("Error while fetching game:",c),!1))},de=function(e,o){if(f){r.info("Already connected to a lobby.");return}l&&(r.warn("Already connected to a lobby. Closing the previous one."),l.close(),f=null,t.playerNumber=null,t.hostPeerId=null,u=null,l=null);const s=n.projectData.properties.projectUuid,a=n.playerAuthentication.getUserId(),g=n.playerAuthentication.getUserToken();if(!s){r.error("Cannot open lobbies if the project has no ID.");return}if(!a||!g){r.warn("Cannot open lobbies if the player is not connected.");return}const c=m?"wss://api-ws-dev.gdevelop.io/play":"wss://api-ws.gdevelop.io/play",i=new URL(c);i.searchParams.set("gameId",s),i.searchParams.set("lobbyId",o),i.searchParams.set("playerId",a),i.searchParams.set("connectionType","lobby"),i.searchParams.set("playerGameToken",g),l=new WebSocket(i.toString()),l.onopen=()=>{if(r.info("Connected to the lobby."),ee=setInterval(()=>{l&&l.send(JSON.stringify({action:"heartbeat",connectionType:"lobby"}))},Me),l){l.send(JSON.stringify({action:"getConnectionId"}));const d=e.getGame().getPlatformInfo();l.send(JSON.stringify({action:"sessionInformation",connectionType:"lobby",isCordova:d.isCordova,devicePlatform:d.devicePlatform,navigatorPlatform:d.navigatorPlatform,hasTouch:d.hasTouch,supportedCompressionMethods:d.supportedCompressionMethods}))}},l.onmessage=d=>{if(d.data){const p=JSON.parse(d.data);switch(p.type){case"connectionId":{const b=p.data,I=b.connectionId,$=b.positionInLobby,W=b.validIceServers||[],we=b.brokerServerConfig;if(!I||!$){r.error("No connectionId or position received"),n.multiplayerComponents.displayErrorNotification(e),l&&l.close();return}Ne({runtimeScene:e,connectionId:I,positionInLobby:$,lobbyId:o,playerId:a,playerToken:g,validIceServers:W,brokerServerConfig:we});break}case"lobbyUpdated":{const I=p.data.positionInLobby;_e({runtimeScene:e,positionInLobby:I});break}case"gameCountdownStarted":{const I=p.data.compressionMethod||"none";Ue({runtimeScene:e,compressionMethod:I});break}case"gameStarted":{te=p.data.heartbeatInterval||ae,Je({runtimeScene:e});break}case"peerId":{const b=p.data;if(!b){r.error("No message received");return}const I=b.peerId,$=b.compressionMethod;if(!I||!$){r.error("Malformed message received");return}const W={times:2,delayInMs:500};try{n.evtTools.network.retryIfFailed(W,async()=>{Oe({peerId:I,compressionMethod:$})})}catch{r.error(`Handling peerId message from websocket failed (after {${W.times}} times with a delay of ${W.delayInMs}ms). Not trying anymore.`)}break}}}},l.onclose=()=>{if(t._isLobbyGameRunning||r.info("Disconnected from the lobby."),f=null,l=null,ee&&clearInterval(ee),t._isLobbyGameRunning)return;const d=n.multiplayerComponents.getLobbiesIframe(e);!d||!d.contentWindow||d.contentWindow.postMessage({id:"lobbyLeft"},"*")}},ge=e=>{n.multiplayerComponents.displayConnectionErrorNotification(e),K(),M=null,R=null,U&&O(e)},Ne=function({runtimeScene:e,connectionId:o,positionInLobby:s,lobbyId:a,playerId:g,playerToken:c,validIceServers:i,brokerServerConfig:d}){if(i.length)for(const b of i)n.multiplayerPeerJsHelper.useCustomICECandidate(b.urls,b.username,b.credential);if(d?n.multiplayerPeerJsHelper.useCustomBrokerServer(d.hostname,d.port,d.path,d.key,d.secure,{onPeerUnavailable:()=>ge(e)}):n.multiplayerPeerJsHelper.useDefaultBrokerServer({onPeerUnavailable:()=>ge(e)}),f=o,t.playerNumber=s,u=a,M==="OPEN_LOBBY_PAGE"){t.openLobbiesWindow(e),O(e);return}else if(M==="JOIN_GAME"){ue();return}else if(M==="START_GAME"){const b={times:2,delayInMs:500};try{n.evtTools.network.retryIfFailed(b,async()=>{ye(),be()})}catch{r.error(`Sending of peerId message from websocket failed (after {${b.times}} times with a delay of ${b.delayInMs}ms). Not trying anymore.`)}return}const p=n.multiplayerComponents.getLobbiesIframe(e);if(!p||!p.contentWindow){r.error("The lobbies iframe is not opened, cannot send the join message.");return}p.contentWindow.postMessage({id:"lobbyJoined",lobbyId:a,playerId:g,playerToken:c,connectionId:f,positionInLobby:s},"https://gd.games")},K=function(){l&&l.close(),f=null,t.playerNumber=null,t.hostPeerId=null,u=null,l=null},_e=function({runtimeScene:e,positionInLobby:o}){t.playerNumber=o;const s=n.multiplayerComponents.getLobbiesIframe(e);!s||!s.contentWindow||s.contentWindow.postMessage({id:"lobbyUpdated",positionInLobby:o},"*")},Ue=function({runtimeScene:e,compressionMethod:o}){n.multiplayerPeerJsHelper.setCompressionMethod(o),t.getCurrentPlayerNumber()===1&&ye();const s=n.multiplayerComponents.getLobbiesIframe(e);if(!s||!s.contentWindow){r.info("The lobbies iframe is not opened, not sending message.");return}s.contentWindow.postMessage({id:"gameCountdownStarted"},"*"),n.multiplayerComponents.hideLobbiesCloseButtonTemporarily(e)},x=async function(){const e=n.projectData.properties.projectUuid;if(!e||!u){r.error("Cannot keep the lobby playing without the game ID or lobby ID.");return}const o=`/play/game/${e}/public-lobby/${u}/action/heartbeat`,s=n.multiplayerMessageManager.getConnectedPlayers();try{await T({relativeUrl:o,method:"POST",body:JSON.stringify({players:s}),dev:m})}catch(a){r.error("Error while sending heartbeat, retrying:",a);try{await T({relativeUrl:o,method:"POST",body:JSON.stringify({players:s}),dev:m})}catch(g){r.error("Error while sending heartbeat a second time. Giving up:",g)}}},Je=function({runtimeScene:e}){const o=n.multiplayerPeerJsHelper.getAllPeers();if(!t.isCurrentPlayerHost()&&o.length===0){n.multiplayerComponents.displayConnectionErrorNotification(e),K(),t.removeLobbiesContainer(e),re(e);return}t.isCurrentPlayerHost()&&(j=setInterval(async()=>{await x()},te)),r.info("Lobby game has started."),n.multiplayerMessageManager.handleSavedUpdateMessages(e),U&&O(e),t._isReadyToSendOrReceiveGameUpdateMessages=!0,k=!0,t._isLobbyGameRunning=!0,t.removeLobbiesContainer(e),l&&l.close(),re(e)};t.handleLobbyGameEnded=function(){r.info("Lobby game has ended."),N=!0,t._isLobbyGameRunning=!1,u=null,t.playerNumber=null,t.hostPeerId=null,t._isReadyToSendOrReceiveGameUpdateMessages=!1,j&&(clearInterval(j),j=null),n.multiplayerPeerJsHelper.disconnectFromAllPeers(),n.multiplayerMessageManager.clearAllMessagesTempData()};const Oe=function({peerId:e,compressionMethod:o}){n.multiplayerPeerJsHelper.setCompressionMethod(o);const s=n.multiplayerPeerJsHelper.getCurrentId();if(!s)throw r.error("No peerId found, the player does not seem connected to the broker server."),new Error("Missing player peerId.");if(s===e){r.info("Received our own peerId, ignoring.");return}t.hostPeerId=e,n.multiplayerPeerJsHelper.connect(e)},Ge=function(){if(!l){r.error("No connection to send the start countdown message. Are you connected to a lobby?");return}l.send(JSON.stringify({action:"startGameCountdown",connectionType:"lobby"}))},be=function(){if(!l){r.error("No connection to send the start countdown message. Are you connected to a lobby?");return}l.send(JSON.stringify({action:"startGame",connectionType:"lobby"})),t._isReadyToSendOrReceiveGameUpdateMessages=!0},ue=function(){if(!l){r.error("No connection to send the join game message. Are you connected to a lobby?");return}l.send(JSON.stringify({action:"joinGame",connectionType:"lobby"}))};t.markConnectionAsConnected=function(){!l||l.send(JSON.stringify({action:"updateConnection",connectionType:"lobby",status:"connected",peerId:n.multiplayerPeerJsHelper.getCurrentId()}))};const E=function(e){h=null,F=null,Y=null,Q&&(clearTimeout(Q),Q=null),Z=!1,t.hostPeerId?n.multiplayerComponents.showHostMigrationFinishedNotification(e):n.multiplayerComponents.showHostMigrationFailedNotification(e)};t.resumeGame=async function(e){t.isCurrentPlayerHost()&&(n.multiplayerMessageManager.sendResumeGameMessage(),await x(),j=setInterval(async()=>{await x()},te)),E(e)};const he=async function({runtimeScene:e}){if(!h||!F)return;try{const s=`/play/game/${h.gameId}/public-lobby/${h.lobbyId}/lobby-change-host-request?peerId=${n.multiplayerPeerJsHelper.getCurrentId()}`;h=await T({relativeUrl:s,method:"GET",dev:m})}catch(s){r.error("Error while trying to retrieve the lobby change host request:",s),t.handleLobbyGameEnded(),E(e);return}if(!h)throw new Error("No lobby change host request received.");const o=h.newHostPeerId;if(!o){if(r.info("No new host picked yet."),B()-F>ve){r.error("Timeout while waiting for the lobby host change. Giving up."),t.handleLobbyGameEnded(),E(e);return}r.info("Retrying..."),setTimeout(()=>{he({runtimeScene:e})},Ce);return}try{const s=h.newLobbyId,a=h.newPlayers;if(!s||!a){r.error("Change host request is incomplete. Cannot change host."),t.handleLobbyGameEnded(),E(e);return}t.hostPeerId=o,Y=B(),u=s,o===n.multiplayerPeerJsHelper.getCurrentId()?(r.info(`We are the new host. Switching to lobby ${s} and awaiting for ${a.length-1} player(s) to connect.`),await fe({runtimeScene:e})):(r.info(`Connecting to new host and switching lobby to ${s}.`),n.multiplayerPeerJsHelper.connect(o),Q=setTimeout(()=>{r.error("Timeout while waiting for the game to resume. Leaving the lobby."),t.handleLobbyGameEnded(),E(e)},Le))}catch(s){r.error("Error while trying to change host:",s),t.handleLobbyGameEnded(),E(e)}},fe=async function({runtimeScene:e}){if(!h)return;const o=h.newPlayers;if(!o){r.error("No expected players in the lobby change host request."),t.handleLobbyGameEnded(),E(e);return}const s=o.map(i=>i.playerNumber);n.multiplayerMessageManager.getConnectedPlayers().map(i=>i.playerNumber).filter(i=>!s.includes(i)).map(i=>{r.info(`Player ${i} left during the host migration. Marking as disconnected.`),n.multiplayerMessageManager.markPlayerAsDisconnected({runtimeScene:e,playerNumber:i})});const c=s.filter(i=>i!==t.playerNumber&&!n.multiplayerMessageManager.hasReceivedHeartbeatFromPlayer(i));if(c.length===0){r.info("All expected players are connected. Resuming the game."),await t.resumeGame(e);return}if(Y&&B()-Y>Pe&&c.length>0){r.error(`Timeout while waiting for players ${c.join(", ")} to connect. Assume they disconnected.`),c.map(i=>{n.multiplayerMessageManager.markPlayerAsDisconnected({runtimeScene:e,playerNumber:i})}),await t.resumeGame(e);return}setTimeout(()=>{fe({runtimeScene:e})},Ee)};t.handleHostDisconnected=async function({runtimeScene:e}){if(!t._isLobbyGameRunning)return;h&&(t.handleLobbyGameEnded(),E(e));const o=n.projectData.properties.projectUuid;if(!o||!u){r.error("Cannot ask for a host change without the game ID or lobby ID.");return}try{Z=!0,n.multiplayerComponents.displayHostMigrationNotification(e);const s=`/play/game/${o}/public-lobby/${u}/lobby-change-host-request`,a=n.multiplayerMessageManager.getPlayersInfo(),g=Object.keys(a).map(d=>({playerNumber:parseInt(d,10),playerId:a[d].playerId,ping:a[d].ping})),c=JSON.stringify({playersInfo:g,peerId:n.multiplayerPeerJsHelper.getCurrentId()});h=await T({relativeUrl:s,method:"POST",body:c,dev:m}),F=B(),await he({runtimeScene:e})}catch(s){r.error("Error while trying to change host:",s),t.handleLobbyGameEnded(),E(e)}},t.endLobbyGame=async function(){if(!t.isLobbyGameRunning())return;if(!t.isCurrentPlayerHost()){r.error("Only the host can end the game.");return}t._isLobbyGameRunning=!1,r.info("Ending the lobby game."),n.multiplayerMessageManager.sendEndGameMessage();const e=n.projectData.properties.projectUuid;if(!e||!u){r.error("Cannot end the lobby without the game ID or lobby ID.");return}const o=`/play/game/${e}/public-lobby/${u}/action/end`;try{await T({relativeUrl:o,method:"POST",body:JSON.stringify({}),dev:m})}catch(s){r.error("Error while ending the game:",s)}t.handleLobbyGameEnded()};const ye=function(){if(!l){r.error("No connection to send the message. Are you connected to a lobby?");return}const e=n.multiplayerPeerJsHelper.getCurrentId();if(!e)throw r.error("No peerId found, the player doesn't seem connected to the broker server."),new Error("Missing player peerId.");l.send(JSON.stringify({action:"sendPeerId",connectionType:"lobby",peerId:e})),t.hostPeerId=e},De=function(e,o,{checkOrigin:s}){if(!(s&&!["https://gd.games","http://localhost:4000"].includes(o.origin))){if(!o.data.id)throw new Error("Malformed message");switch(o.data.id){case"lobbiesListenerReady":{He(e);break}case"joinLobby":{if(!o.data.lobbyId)throw new Error("Malformed message.");M=null,de(e,o.data.lobbyId);break}case"startGameCountdown":{Ge();break}case"startGame":{be();break}case"leaveLobby":{K();break}case"joinGame":{ue();break}}}},z=function(e,o){r.error(o),t.removeLobbiesContainer(e),re(e)},He=e=>{const o=n.multiplayerComponents.getLobbiesIframe(e);if(!o||!o.contentWindow)return;const s=e.getGame().getPlatformInfo();o.contentWindow.postMessage({id:"sessionInformation",isCordova:s.isCordova,devicePlatform:s.devicePlatform,navigatorPlatform:s.navigatorPlatform,hasTouch:s.hasTouch},"*")},je=(e,o)=>{const s=Re({runtimeGame:e.getGame(),gameId:o});H=a=>{De(e,a,{checkOrigin:!0})},window.addEventListener("message",H,!0),n.multiplayerComponents.displayIframeInsideLobbiesContainer(e,s)},O=e=>{U=!1,M=null,n.multiplayerComponents.displayLoader(e,!1)},me=async(e,o,s)=>{if(U)return;const a=n.projectData.properties.projectUuid;if(!a){z(e,"The game ID is missing, the quick join lobby action cannot continue.");return}R=null,U=!0,o&&n.multiplayerComponents.displayLoader(e,!0);const g=`/play/game/${a}/public-lobby/action/quick-join`,c=e.getGame().getPlatformInfo();try{const i=await n.evtTools.network.retryIfFailed({times:2},()=>T({relativeUrl:g,method:"POST",dev:m,body:JSON.stringify({isPreview:e.getGame().isPreview(),gameVersion:e.getGame().getGameData().properties.version,supportedCompressionMethods:c.supportedCompressionMethods})}));if(i.status==="full"||i.status==="not-enough-players"){_=!0,R=i.status==="full"?"FULL":"NOT_ENOUGH_PLAYERS",O(e),s&&t.openLobbiesWindow(e);return}if(i.status==="join-game")if(i.lobby.status==="waiting")M="START_GAME";else if(i.lobby.status==="playing")M="JOIN_GAME";else throw new Error(`Lobby in wrong status: ${i.status}`);else if(f){O(e),t.openLobbiesWindow(e);return}else M="OPEN_LOBBY_PAGE";de(e,i.lobby.id)}catch(i){r.error("An error occurred while joining a lobby:",i),_=!0,R="UNKNOWN",O(e),s&&t.openLobbiesWindow(e)}};t.authenticateAndQuickJoinLobby=async(e,o,s)=>{const a=Date.now();if(V){if(a-V<500){V=a,r.warn("Last request to quick join a lobby was sent too little time ago. Ignoring this one.");return}}else V=a;const g=n.playerAuthentication.getUserId(),c=n.playerAuthentication.getUserToken();if(!g||!c){P=!0;const{status:i}=await n.playerAuthentication.openAuthenticationWindow(e).promise;P=!1,i==="logged"&&await me(e,o,s);return}await me(e,o,s)},t.isSearchingForLobbyToJoin=e=>U,t.hasQuickJoinJustFailed=e=>_,t.getQuickJoinFailureReason=()=>R,t.openLobbiesWindow=async e=>{if(t.isLobbiesWindowOpen(e)||n.playerAuthentication.isAuthenticationWindowOpen())return;const o=n.projectData.properties.projectUuid;if(!o){z(e,"The game ID is missing, the lobbies window cannot be opened.");return}if(D||P)return;if(!e.getGame().getRenderer().getDomElementContainer()){z(e,"The div element covering the game couldn't be found, the lobbies window cannot be displayed.");return}const a=()=>{t.removeLobbiesContainer(e)},g=n.playerAuthentication.getUserId(),c=n.playerAuthentication.getUserToken();if(!g||!c){P=!0;const{status:p}=await n.playerAuthentication.openAuthenticationWindow(e).promise;P=!1,p==="logged"&&t.openLobbiesWindow(e);return}if(n.multiplayerComponents.displayLobbies(e,a),L===null){D=!0;try{L=await ce(e.getGame(),o)}catch(p){L=!1,r.error("Error while checking if the game is registered:",p),z(e,"Error while checking if the game is registered.");return}finally{D=!1}}const i=e.getGame().getRenderer().getElectron(),d=i?()=>i.shell.openExternal("https://wiki.gdevelop.io/gdevelop5/publishing/web"):()=>window.open("https://wiki.gdevelop.io/gdevelop5/publishing/web","_blank");n.multiplayerComponents.addTextsToLoadingContainer(e,L,d),L&&je(e,o)},t.isLobbiesWindowOpen=function(e){return!!n.multiplayerComponents.getLobbiesRootContainer(e)},t.showLobbiesCloseButton=function(e,o){n.multiplayerComponents.changeLobbiesWindowCloseActionVisibility(e,o)},t.removeLobbiesContainer=function(e){Se(),n.multiplayerComponents.removeLobbiesContainer(e)};const Se=function(){H&&(window.removeEventListener("message",H,!0),H=null)},re=function(e){const o=e.getGame().getRenderer().getCanvas();o&&o.focus()};t.leaveGameLobby=async()=>{K(),t.handleLobbyGameEnded()}})(Ie=n.multiplayer||(n.multiplayer={}))})(gdjs||(gdjs={}));
1
+ var gdjs;(function(n){const r=new n.Logger("Multiplayer"),F=window.performance&&typeof window.performance.now=="function"?window.performance.now.bind(window.performance):Date.now,k=async({relativeUrl:D,method:J,body:N,dev:j})=>{const P=n.playerAuthentication.getUserId(),U=n.playerAuthentication.getUserToken();if(!P||!U)throw r.warn("Cannot fetch as a player if the player is not connected."),new Error("Cannot fetch as a player if the player is not connected.");const I=j?"https://api-dev.gdevelop.io":"https://api.gdevelop.io",O=new URL(`${I}${D}`);O.searchParams.set("playerId",P);const T=O.toString(),m={"Content-Type":"application/json",Authorization:`player-game-token ${U}`},b=await fetch(T,{method:J,headers:m,body:N});if(!b.ok)throw new Error(`Error while fetching as a player: ${b.status} ${b.statusText}`);const h=await b.text();if(h!=="OK")try{return JSON.parse(h)}catch(q){throw new Error(`Error while parsing the response: ${q}`)}};let Me;(function(t){t.disableMultiplayerForTesting=!1,t._isReadyToSendOrReceiveGameUpdateMessages=!1;let N=null,j=!1,P=!1,U=!1;t._isLobbyGameRunning=!1;let O=!1,T=!1,m=null,b=null,h=null,q=!1,y=null,B=null,Z=!1,Y=null,f=null,R=!1,Q=null,H=null,l=null,ee=null,x=null;const Ce=1e4,ie=3e4;let te=ie;const ve=1e3,Le=1e4,Ee=1e3,Pe=1e4;let V=null;const Te=12e3;t.DEFAULT_OBJECT_MAX_SYNC_RATE=30,t._objectMaxSyncRate=t.DEFAULT_OBJECT_MAX_SYNC_RATE;let w=!1;t.playerNumber=null,t.hostPeerId=null,n.registerRuntimeScenePreEventsCallback(e=>{w=e.getGame().isUsingGDevelopDevelopmentEnvironment(),!t.disableMultiplayerForTesting&&(n.multiplayerMessageManager.handleHeartbeatsToSend(),n.multiplayerMessageManager.handleJustDisconnectedPeers(e),n.multiplayerMessageManager.handleChangeInstanceOwnerMessagesReceived(e),n.multiplayerMessageManager.handleUpdateInstanceMessagesReceived(e),n.multiplayerMessageManager.handleCustomMessagesReceived(),n.multiplayerMessageManager.handleAcknowledgeMessagesReceived(),n.multiplayerMessageManager.resendClearOrCancelAcknowledgedMessages(e),n.multiplayerMessageManager.handleChangeVariableOwnerMessagesReceived(e),t._isLobbyGameRunning&&n.multiplayerMessageManager.handleSavedUpdateMessages(e),n.multiplayerMessageManager.handleUpdateGameMessagesReceived(e),n.multiplayerMessageManager.handleUpdateSceneMessagesReceived(e))}),n.registerRuntimeScenePostEventsCallback(e=>{t.disableMultiplayerForTesting||(ke(e),Ne(e),n.multiplayerMessageManager.handleHeartbeatsReceived(),n.multiplayerMessageManager.handleEndGameMessagesReceived(),n.multiplayerMessageManager.handleResumeGameMessagesReceived(e),n.multiplayerMessageManager.handleDestroyInstanceMessagesReceived(e),n.multiplayerVariablesManager.handleChangeVariableOwnerMessagesToSend(),n.multiplayerMessageManager.handleUpdateGameMessagesToSend(e),n.multiplayerMessageManager.handleUpdateSceneMessagesToSend(e))}),n.registerRuntimeScenePostEventsCallback(()=>{t.disableMultiplayerForTesting||(U=!1,O=!1,T=!1)});const Re=({runtimeGame:e,gameId:o})=>{const s="https://gd.games",i=new URL(`${s}/games/${o}/lobbies${b?`/${b}`:""}`);i.searchParams.set("gameVersion",e.getGameData().properties.version),e.getAdditionalOptions().nativeMobileApp&&i.searchParams.set("nativeMobileApp","true"),i.searchParams.set("isPreview",e.isPreview()?"true":"false"),w&&i.searchParams.set("dev","true"),h&&i.searchParams.set("connectionId",h),t.playerNumber&&i.searchParams.set("positionInLobby",t.playerNumber.toString());const g=n.playerAuthentication.getUserId();g&&i.searchParams.set("playerId",g);const d=n.playerAuthentication.getUserToken();d&&i.searchParams.set("playerToken",d);const a=e.getPlatformInfo();return i.searchParams.set("scm",a.supportedCompressionMethods.join(",")),i.searchParams.set("multiplayerVersion","2"),i.toString()};t.setObjectsSynchronizationRate=e=>{e<1||e>60?(r.warn(`Invalid rate ${e} for object synchronization. Defaulting to ${t.DEFAULT_OBJECT_MAX_SYNC_RATE}.`),t._objectMaxSyncRate=t.DEFAULT_OBJECT_MAX_SYNC_RATE):t._objectMaxSyncRate=e},t.getObjectsSynchronizationRate=()=>t._objectMaxSyncRate,t.hasLobbyGameJustStarted=()=>U,t.isLobbyGameRunning=()=>t._isLobbyGameRunning,t.isReadyToSendOrReceiveGameUpdateMessages=()=>t._isReadyToSendOrReceiveGameUpdateMessages,t.hasLobbyGameJustEnded=()=>O,t.getPlayersInLobbyCount=()=>n.multiplayerMessageManager.getNumberOfConnectedPlayers(),t.isPlayerConnected=e=>n.multiplayerMessageManager.isPlayerConnected(e),t.getCurrentPlayerNumber=()=>t.playerNumber||0,t.isCurrentPlayerHost=()=>!!t.hostPeerId&&t.hostPeerId===n.multiplayerPeerJsHelper.getCurrentId(),t.isMigratingHost=()=>!!Z,t.endLobbyWhenHostLeaves=e=>{q=e},t.shouldEndLobbyWhenHostLeaves=()=>q,t.getPlayerUsername=e=>n.multiplayerMessageManager.getPlayerUsername(e),t.getCurrentPlayerUsername=()=>{const e=t.getCurrentPlayerNumber();return t.getPlayerUsername(e)};const ke=e=>{const o=n.multiplayerMessageManager.getLatestPlayerWhoJustLeft();if(o){const s=t.getPlayerUsername(o);n.multiplayerComponents.displayPlayerLeftNotification(e,s),n.multiplayerMessageManager.removePlayerWhoJustLeft(),t.isCurrentPlayerHost()&&t.isReadyToSendOrReceiveGameUpdateMessages()&&S()}},Ne=e=>{const o=n.multiplayerMessageManager.getLatestPlayerWhoJustJoined();if(o){const s=t.getPlayerUsername(o);n.multiplayerComponents.displayPlayerJoinedNotification(e,s),t.isCurrentPlayerHost()&&t.isReadyToSendOrReceiveGameUpdateMessages()&&S()}n.multiplayerMessageManager.removePlayerWhoJustJoined()},de=(e,o,s=0)=>{const g=`${w?"https://api-dev.gdevelop.io":"https://api.gdevelop.io"}/game/public-game/${o}`;return fetch(g,{method:"HEAD"}).then(d=>d.status!==200?(r.warn(`Error while fetching the game: ${d.status} ${d.statusText}`),d.status===404||s>2?!1:de(e,o,s+1)):!0,d=>(r.error("Error while fetching game:",d),!1))},re=function(e,o){if(h){r.info("Already connected to a lobby.");return}l&&(r.warn("Already connected to a lobby. Closing the previous one."),l.close(),h=null,t.playerNumber=null,t.hostPeerId=null,b=null,l=null);const s=n.projectData.properties.projectUuid,i=n.playerAuthentication.getUserId(),g=n.playerAuthentication.getUserToken();if(!s){r.error("Cannot open lobbies if the project has no ID.");return}if(!i||!g){r.warn("Cannot open lobbies if the player is not connected.");return}const d=w?"wss://api-ws-dev.gdevelop.io/play":"wss://api-ws.gdevelop.io/play",a=new URL(d);a.searchParams.set("gameId",s),a.searchParams.set("lobbyId",o),a.searchParams.set("playerId",i),a.searchParams.set("connectionType","lobby"),a.searchParams.set("playerGameToken",g),l=new WebSocket(a.toString()),l.onopen=()=>{if(r.info("Connected to the lobby."),ee=setInterval(()=>{l&&l.send(JSON.stringify({action:"heartbeat",connectionType:"lobby"}))},Ce),l){l.send(JSON.stringify({action:"getConnectionId"}));const c=e.getGame().getPlatformInfo();l.send(JSON.stringify({action:"sessionInformation",connectionType:"lobby",isCordova:c.isCordova,devicePlatform:c.devicePlatform,navigatorPlatform:c.navigatorPlatform,hasTouch:c.hasTouch,supportedCompressionMethods:c.supportedCompressionMethods}))}},l.onmessage=c=>{if(c.data){const p=JSON.parse(c.data);switch(p.type){case"connectionId":{const u=p.data,C=u.connectionId,W=u.positionInLobby,$=u.validIceServers||[],Ie=u.brokerServerConfig;if(!C||!W){r.error("No connectionId or position received"),n.multiplayerComponents.displayErrorNotification(e),l&&l.close();return}_e({runtimeScene:e,connectionId:C,positionInLobby:W,lobbyId:o,playerId:i,playerToken:g,validIceServers:$,brokerServerConfig:Ie});break}case"lobbyUpdated":{const C=p.data.positionInLobby;Je({runtimeScene:e,positionInLobby:C});break}case"gameCountdownStarted":{const C=p.data.compressionMethod||"none";Ue({runtimeScene:e,compressionMethod:C});break}case"gameStarted":{te=p.data.heartbeatInterval||ie,Oe({runtimeScene:e});break}case"peerId":{const u=p.data;if(!u){r.error("No message received");return}const C=u.peerId,W=u.compressionMethod;if(!C||!W){r.error("Malformed message received");return}const $={times:2,delayInMs:500};try{n.evtTools.network.retryIfFailed($,async()=>{Ge({peerId:C,compressionMethod:W})})}catch{r.error(`Handling peerId message from websocket failed (after {${$.times}} times with a delay of ${$.delayInMs}ms). Not trying anymore.`)}break}}}},l.onclose=()=>{if(t._isLobbyGameRunning||r.info("Disconnected from the lobby."),h=null,l=null,ee&&clearInterval(ee),t._isLobbyGameRunning)return;const c=n.multiplayerComponents.getLobbiesIframe(e);!c||!c.contentWindow||c.contentWindow.postMessage({id:"lobbyLeft"},"*")}},ge=e=>{n.multiplayerComponents.displayConnectionErrorNotification(e),K(),f=null,m=null,R&&A(e)},_e=function({runtimeScene:e,connectionId:o,positionInLobby:s,lobbyId:i,playerId:g,playerToken:d,validIceServers:a,brokerServerConfig:c}){if(a.length)for(const u of a)n.multiplayerPeerJsHelper.useCustomICECandidate(u.urls,u.username,u.credential);if(c?n.multiplayerPeerJsHelper.useCustomBrokerServer(c.hostname,c.port,c.path,c.key,c.secure,{onPeerUnavailable:()=>ge(e)}):n.multiplayerPeerJsHelper.useDefaultBrokerServer({onPeerUnavailable:()=>ge(e)}),h=o,t.playerNumber=s,b=i,f==="OPEN_LOBBY_PAGE"){t.openLobbiesWindow(e),A(e);return}else if(f==="JOIN_GAME"){ue();return}else if(f==="START_GAME"){const u={times:2,delayInMs:500};try{n.evtTools.network.retryIfFailed(u,async()=>{fe(),be()})}catch{r.error(`Sending of peerId message from websocket failed (after {${u.times}} times with a delay of ${u.delayInMs}ms). Not trying anymore.`)}return}const p=n.multiplayerComponents.getLobbiesIframe(e);if(!p||!p.contentWindow){r.error("The lobbies iframe is not opened, cannot send the join message.");return}p.contentWindow.postMessage({id:"lobbyJoined",lobbyId:i,playerId:g,playerToken:d,connectionId:h,positionInLobby:s},"https://gd.games")},K=function(){l&&l.close(),h=null,t.playerNumber=null,t.hostPeerId=null,b=null,l=null},Je=function({runtimeScene:e,positionInLobby:o}){t.playerNumber=o;const s=n.multiplayerComponents.getLobbiesIframe(e);!s||!s.contentWindow||s.contentWindow.postMessage({id:"lobbyUpdated",positionInLobby:o},"*")},Ue=function({runtimeScene:e,compressionMethod:o}){n.multiplayerPeerJsHelper.setCompressionMethod(o),t.getCurrentPlayerNumber()===1&&fe();const s=n.multiplayerComponents.getLobbiesIframe(e);if(!s||!s.contentWindow){r.info("The lobbies iframe is not opened, not sending message.");return}s.contentWindow.postMessage({id:"gameCountdownStarted"},"*"),n.multiplayerComponents.hideLobbiesCloseButtonTemporarily(e)},S=async function(){const e=n.projectData.properties.projectUuid;if(!e||!b){r.error("Cannot keep the lobby playing without the game ID or lobby ID.");return}const o=`/play/game/${e}/public-lobby/${b}/action/heartbeat`,s=n.multiplayerMessageManager.getConnectedPlayers();try{await k({relativeUrl:o,method:"POST",body:JSON.stringify({players:s}),dev:w})}catch(i){r.error("Error while sending heartbeat, retrying:",i);try{await k({relativeUrl:o,method:"POST",body:JSON.stringify({players:s}),dev:w})}catch(g){r.error("Error while sending heartbeat a second time. Giving up:",g)}}},Oe=function({runtimeScene:e}){const o=n.multiplayerPeerJsHelper.getAllPeers();if(!t.isCurrentPlayerHost()&&o.length===0){n.multiplayerComponents.displayConnectionErrorNotification(e),K(),t.removeLobbiesContainer(e),ae(e);return}t.isCurrentPlayerHost()&&(x=setInterval(async()=>{await S()},te)),r.info("Lobby game has started."),n.multiplayerMessageManager.handleSavedUpdateMessages(e),R&&A(e),t._isReadyToSendOrReceiveGameUpdateMessages=!0,U=!0,t._isLobbyGameRunning=!0,t.removeLobbiesContainer(e),l&&l.close(),ae(e)};t.handleLobbyGameEnded=function(){r.info("Lobby game has ended."),O=!0,t._isLobbyGameRunning=!1,b=null,t.playerNumber=null,t.hostPeerId=null,t._isReadyToSendOrReceiveGameUpdateMessages=!1,x&&(clearInterval(x),x=null),n.multiplayerPeerJsHelper.disconnectFromAllPeers(),n.multiplayerMessageManager.clearAllMessagesTempData()};const Ge=function({peerId:e,compressionMethod:o}){n.multiplayerPeerJsHelper.setCompressionMethod(o);const s=n.multiplayerPeerJsHelper.getCurrentId();if(!s)throw r.error("No peerId found, the player does not seem connected to the broker server."),new Error("Missing player peerId.");if(s===e){r.info("Received our own peerId, ignoring.");return}t.hostPeerId=e,n.multiplayerPeerJsHelper.connect(e)},De=function(){if(!l){r.error("No connection to send the start countdown message. Are you connected to a lobby?");return}l.send(JSON.stringify({action:"startGameCountdown",connectionType:"lobby"}))},be=function(){if(!l){r.error("No connection to send the start countdown message. Are you connected to a lobby?");return}l.send(JSON.stringify({action:"startGame",connectionType:"lobby"})),t._isReadyToSendOrReceiveGameUpdateMessages=!0},ue=function(){if(!l){r.error("No connection to send the join game message. Are you connected to a lobby?");return}l.send(JSON.stringify({action:"joinGame",connectionType:"lobby"}))};t.markConnectionAsConnected=function(){!l||l.send(JSON.stringify({action:"updateConnection",connectionType:"lobby",status:"connected",peerId:n.multiplayerPeerJsHelper.getCurrentId()}))};const E=function(e){y=null,B=null,Y=null,V&&(clearTimeout(V),V=null),Z=!1,t.hostPeerId?n.multiplayerComponents.showHostMigrationFinishedNotification(e):n.multiplayerComponents.showHostMigrationFailedNotification(e)};t.resumeGame=async function(e){t.isCurrentPlayerHost()&&(n.multiplayerMessageManager.sendResumeGameMessage(),await S(),x=setInterval(async()=>{await S()},te)),E(e)};const ye=async function({runtimeScene:e}){if(!y||!B)return;try{const s=`/play/game/${y.gameId}/public-lobby/${y.lobbyId}/lobby-change-host-request?peerId=${n.multiplayerPeerJsHelper.getCurrentId()}`;y=await k({relativeUrl:s,method:"GET",dev:w})}catch(s){r.error("Error while trying to retrieve the lobby change host request:",s),t.handleLobbyGameEnded(),E(e);return}if(!y)throw new Error("No lobby change host request received.");const o=y.newHostPeerId;if(!o){if(r.info("No new host picked yet."),F()-B>Le){r.error("Timeout while waiting for the lobby host change. Giving up."),t.handleLobbyGameEnded(),E(e);return}r.info("Retrying..."),setTimeout(()=>{ye({runtimeScene:e})},ve);return}try{const s=y.newLobbyId,i=y.newPlayers;if(!s||!i){r.error("Change host request is incomplete. Cannot change host."),t.handleLobbyGameEnded(),E(e);return}t.hostPeerId=o,Y=F(),b=s,o===n.multiplayerPeerJsHelper.getCurrentId()?(r.info(`We are the new host. Switching to lobby ${s} and awaiting for ${i.length-1} player(s) to connect.`),await he({runtimeScene:e})):(r.info(`Connecting to new host and switching lobby to ${s}.`),n.multiplayerPeerJsHelper.connect(o),V=setTimeout(()=>{r.error("Timeout while waiting for the game to resume. Leaving the lobby."),t.handleLobbyGameEnded(),E(e)},Te))}catch(s){r.error("Error while trying to change host:",s),t.handleLobbyGameEnded(),E(e)}},he=async function({runtimeScene:e}){if(!y)return;const o=y.newPlayers;if(!o){r.error("No expected players in the lobby change host request."),t.handleLobbyGameEnded(),E(e);return}const s=o.map(a=>a.playerNumber);n.multiplayerMessageManager.getConnectedPlayers().map(a=>a.playerNumber).filter(a=>!s.includes(a)).map(a=>{r.info(`Player ${a} left during the host migration. Marking as disconnected.`),n.multiplayerMessageManager.markPlayerAsDisconnected({runtimeScene:e,playerNumber:a})});const d=s.filter(a=>a!==t.playerNumber&&!n.multiplayerMessageManager.hasReceivedHeartbeatFromPlayer(a));if(d.length===0){r.info("All expected players are connected. Resuming the game."),await t.resumeGame(e);return}if(Y&&F()-Y>Pe&&d.length>0){r.error(`Timeout while waiting for players ${d.join(", ")} to connect. Assume they disconnected.`),d.map(a=>{n.multiplayerMessageManager.markPlayerAsDisconnected({runtimeScene:e,playerNumber:a})}),await t.resumeGame(e);return}setTimeout(()=>{he({runtimeScene:e})},Ee)};t.handleHostDisconnected=async function({runtimeScene:e}){if(!t._isLobbyGameRunning)return;y&&(t.handleLobbyGameEnded(),E(e));const o=n.projectData.properties.projectUuid;if(!o||!b){r.error("Cannot ask for a host change without the game ID or lobby ID.");return}try{Z=!0,n.multiplayerComponents.displayHostMigrationNotification(e);const s=`/play/game/${o}/public-lobby/${b}/lobby-change-host-request`,i=n.multiplayerMessageManager.getPlayersInfo(),g=Object.keys(i).map(c=>({playerNumber:parseInt(c,10),playerId:i[c].playerId,ping:i[c].ping})),d=JSON.stringify({playersInfo:g,peerId:n.multiplayerPeerJsHelper.getCurrentId()});y=await k({relativeUrl:s,method:"POST",body:d,dev:w}),B=F(),await ye({runtimeScene:e})}catch(s){r.error("Error while trying to change host:",s),t.handleLobbyGameEnded(),E(e)}},t.endLobbyGame=async function(){if(!t.isLobbyGameRunning())return;if(!t.isCurrentPlayerHost()){r.error("Only the host can end the game.");return}t._isLobbyGameRunning=!1,r.info("Ending the lobby game."),n.multiplayerMessageManager.sendEndGameMessage();const e=n.projectData.properties.projectUuid;if(!e||!b){r.error("Cannot end the lobby without the game ID or lobby ID.");return}const o=`/play/game/${e}/public-lobby/${b}/action/end`;try{await k({relativeUrl:o,method:"POST",body:JSON.stringify({}),dev:w})}catch(s){r.error("Error while ending the game:",s)}t.handleLobbyGameEnded()};const fe=function(){if(!l){r.error("No connection to send the message. Are you connected to a lobby?");return}const e=n.multiplayerPeerJsHelper.getCurrentId();if(!e)throw r.error("No peerId found, the player doesn't seem connected to the broker server."),new Error("Missing player peerId.");l.send(JSON.stringify({action:"sendPeerId",connectionType:"lobby",peerId:e})),t.hostPeerId=e},je=function(e,o,{checkOrigin:s}){if(!(s&&!["https://gd.games","http://localhost:4000"].includes(o.origin))){if(!o.data.id)throw new Error("Malformed message");switch(o.data.id){case"lobbiesListenerReady":{He(e);break}case"joinLobby":{if(!o.data.lobbyId)throw new Error("Malformed message.");f=null,re(e,o.data.lobbyId);break}case"startGameCountdown":{De();break}case"startGame":{be();break}case"leaveLobby":{K();break}case"joinGame":{ue();break}}}},X=function(e,o){r.error(o),t.removeLobbiesContainer(e),ae(e)},He=e=>{const o=n.multiplayerComponents.getLobbiesIframe(e);if(!o||!o.contentWindow)return;const s=e.getGame().getPlatformInfo();o.contentWindow.postMessage({id:"sessionInformation",isCordova:s.isCordova,devicePlatform:s.devicePlatform,navigatorPlatform:s.navigatorPlatform,hasTouch:s.hasTouch},"*")},xe=(e,o)=>{const s=Re({runtimeGame:e.getGame(),gameId:o});H=i=>{je(e,i,{checkOrigin:!0})},window.addEventListener("message",H,!0),n.multiplayerComponents.displayIframeInsideLobbiesContainer(e,s)},A=e=>{R=!1,f=null,n.multiplayerComponents.displayLoader(e,!1)},Se=async(e,o,s)=>{if(R)return;const i=n.projectData.properties.projectUuid;if(!i){X(e,"The game ID is missing, the quick join lobby action cannot continue.");return}m=null,R=!0,o&&n.multiplayerComponents.displayLoader(e,!0);const g=`/play/game/${i}/public-lobby/action/quick-join`,d=e.getGame().getPlatformInfo();try{const a=await n.evtTools.network.retryIfFailed({times:2},()=>k({relativeUrl:g,method:"POST",dev:w,body:JSON.stringify({isPreview:e.getGame().isPreview(),gameVersion:e.getGame().getGameData().properties.version,supportedCompressionMethods:d.supportedCompressionMethods})}));if(a.status==="full"||a.status==="not-enough-players"){T=!0,m=a.status==="full"?"FULL":"NOT_ENOUGH_PLAYERS",A(e),s&&t.openLobbiesWindow(e);return}if(a.status==="join-game")if(a.lobby.status==="waiting")f="START_GAME";else if(a.lobby.status==="playing")f="JOIN_GAME";else throw new Error(`Lobby in wrong status: ${a.status}`);else if(h){A(e),t.openLobbiesWindow(e);return}else f="OPEN_LOBBY_PAGE";re(e,a.lobby.id)}catch(a){r.error("An error occurred while joining a lobby:",a),T=!0,m="UNKNOWN",A(e),s&&t.openLobbiesWindow(e)}};t.getLobbyID=()=>b||"";const We=async(e,o,s,i)=>{if(R)return;const g=n.projectData.properties.projectUuid;if(!g){r.error("The game ID is missing, the quick join lobby action cannot continue.");return}m=null,R=!0,s&&n.multiplayerComponents.displayLoader(e,!0);const d=`/play/game/${g}/public-lobby/${o}`;try{const a=await n.evtTools.network.retryIfFailed({times:2},()=>k({relativeUrl:d,method:"GET",dev:w}));if(a.players.length===a.maxPlayers){r.error("Lobby is full - cannot quick join it."),T=!0,m="FULL",A(e),i&&t.openLobbiesWindow(e);return}if(a.status==="playing")f="JOIN_GAME";else if(a.status==="waiting")a.players.length===0?f="START_GAME":f="OPEN_LOBBY_PAGE";else throw new Error(`Lobby in wrong status: ${a.status}`);re(e,o)}catch(a){parseInt(a.message.match(/\d{3}/)?.[0])===404?(r.error("Lobby does not exist."),m="DOES_NOT_EXIST"):(r.error("An error occurred while joining a lobby:",a),m="UNKNOWN"),T=!0,A(e),i&&t.openLobbiesWindow(e)}},me=()=>{const e=Date.now();if(Q){if(e-Q<500)return Q=e,r.warn("Last request to quick join a lobby was sent too little time ago. Ignoring this one."),!0}else Q=e;return!1},we=async e=>{const o=n.playerAuthentication.getUserId(),s=n.playerAuthentication.getUserToken();if(!o||!s){P=!0;const{status:i}=await n.playerAuthentication.openAuthenticationWindow(e).promise;if(P=!1,i!=="logged")return!0}return!1};t.authenticateAndQuickJoinWithLobbyID=async(e,o,s,i)=>{me()||await we(e)||await We(e,o,s,i)},t.authenticateAndQuickJoinLobby=async(e,o,s)=>{me()||await we(e)||await Se(e,o,s)},t.isSearchingForLobbyToJoin=e=>R,t.hasQuickJoinJustFailed=e=>T,t.getQuickJoinFailureReason=()=>m,t.openLobbiesWindow=async e=>{if(t.isLobbiesWindowOpen(e)||n.playerAuthentication.isAuthenticationWindowOpen())return;const o=n.projectData.properties.projectUuid;if(!o){X(e,"The game ID is missing, the lobbies window cannot be opened.");return}if(j||P)return;if(!e.getGame().getRenderer().getDomElementContainer()){X(e,"The div element covering the game couldn't be found, the lobbies window cannot be displayed.");return}const i=()=>{t.removeLobbiesContainer(e)},g=n.playerAuthentication.getUserId(),d=n.playerAuthentication.getUserToken();if(!g||!d){P=!0;const{status:p}=await n.playerAuthentication.openAuthenticationWindow(e).promise;P=!1,p==="logged"&&t.openLobbiesWindow(e);return}if(n.multiplayerComponents.displayLobbies(e,i),N===null){j=!0;try{N=await de(e.getGame(),o)}catch(p){N=!1,r.error("Error while checking if the game is registered:",p),X(e,"Error while checking if the game is registered.");return}finally{j=!1}}const a=e.getGame().getRenderer().getElectron(),c=a?()=>a.shell.openExternal("https://wiki.gdevelop.io/gdevelop5/publishing/web"):()=>window.open("https://wiki.gdevelop.io/gdevelop5/publishing/web","_blank");n.multiplayerComponents.addTextsToLoadingContainer(e,N,c),N&&xe(e,o)},t.isLobbiesWindowOpen=function(e){return!!n.multiplayerComponents.getLobbiesRootContainer(e)},t.showLobbiesCloseButton=function(e,o){n.multiplayerComponents.changeLobbiesWindowCloseActionVisibility(e,o)},t.removeLobbiesContainer=function(e){Fe(),n.multiplayerComponents.removeLobbiesContainer(e)};const Fe=function(){H&&(window.removeEventListener("message",H,!0),H=null)},ae=function(e){const o=e.getGame().getRenderer().getCanvas();o&&o.focus()};t.leaveGameLobby=async()=>{K(),t.handleLobbyGameEnded()}})(Me=n.multiplayer||(n.multiplayer={}))})(gdjs||(gdjs={}));
2
2
  //# sourceMappingURL=multiplayertools.js.map