mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-21 17:45:25 -04:00
282 lines
9.6 KiB
TypeScript
282 lines
9.6 KiB
TypeScript
namespace gdjs {
|
|
const logger = new gdjs.Logger('Multiplayer');
|
|
const debugLogger = new gdjs.Logger('Multiplayer - Debug');
|
|
|
|
/** @category Multiplayer */
|
|
export type MultiplayerVariablesManager = ReturnType<
|
|
typeof makeMultiplayerVariablesManager
|
|
>;
|
|
|
|
/**
|
|
* @category Multiplayer
|
|
*/
|
|
export const makeMultiplayerVariablesManager = () => {
|
|
const variableOwnershipChangesToSyncAtEndOfFrame: {
|
|
[variableNetworkId: string]: {
|
|
variableName: string;
|
|
sceneNetworkId?: string; // If not defined, the variable is global.
|
|
previousVariableOwner: number;
|
|
newVariableOwner: number;
|
|
};
|
|
} = {};
|
|
|
|
const addVariableOwnershipChangeToSync = function ({
|
|
variableNetworkId,
|
|
previousVariableOwner,
|
|
newVariableOwner,
|
|
}: {
|
|
variableNetworkId: string;
|
|
previousVariableOwner: number;
|
|
newVariableOwner: number;
|
|
}) {
|
|
// If the variable is already planned to be synchronized, update it with the new owner.
|
|
if (variableOwnershipChangesToSyncAtEndOfFrame[variableNetworkId]) {
|
|
variableOwnershipChangesToSyncAtEndOfFrame[
|
|
variableNetworkId
|
|
].newVariableOwner = newVariableOwner;
|
|
return;
|
|
}
|
|
|
|
variableOwnershipChangesToSyncAtEndOfFrame[variableNetworkId] = {
|
|
variableName: variableNetworkId,
|
|
previousVariableOwner: previousVariableOwner,
|
|
newVariableOwner: newVariableOwner,
|
|
};
|
|
};
|
|
|
|
const getVariableTypeAndNameFromNetworkId = function (
|
|
variableNetworkId: string
|
|
): { type: string; name: string; containerId: string } {
|
|
const parts = variableNetworkId.split('_');
|
|
if (parts.length < 2) {
|
|
throw new Error(
|
|
'Trying to get the variable type from a network id that is not a valid variable network id.'
|
|
);
|
|
}
|
|
|
|
return {
|
|
type: parts[0] === 'game' ? 'global' : 'scene',
|
|
name: parts.slice(1).join('_'),
|
|
containerId: parts[0],
|
|
};
|
|
};
|
|
|
|
// A variable network id is a combination of the scene network id if it's a scene variable,
|
|
// and the variable name, or "game" and the variable name if it's a global variable.
|
|
const _guessVariableNetworkIdFromSceneAndGame = function (
|
|
variable: gdjs.Variable,
|
|
currentScene: gdjs.RuntimeScene
|
|
): string | undefined {
|
|
const currentSceneVariables = currentScene.getVariables();
|
|
|
|
if (currentSceneVariables.hasVariable(variable)) {
|
|
// Scene variable.
|
|
const sceneNetworkId = currentScene.networkId;
|
|
if (!sceneNetworkId) {
|
|
// Variable is being synchronized but the scene has no networkId yet.
|
|
// It should have one assigned as soon as the scene is synchronized.
|
|
// Skipping.
|
|
debugLogger.info(
|
|
'Variable is being synchronized but the scene has no networkId yet.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
const variableName =
|
|
currentSceneVariables.getVariableNameInContainerByLoopingThroughAllVariables(
|
|
variable
|
|
);
|
|
|
|
if (!variableName) {
|
|
logger.error('Variable is being synchronized but has no name.');
|
|
return;
|
|
}
|
|
|
|
return sceneNetworkId + '_' + variableName;
|
|
}
|
|
|
|
const runtimeGame = currentScene.getGame();
|
|
const runtimeGameVariables = runtimeGame.getVariables();
|
|
|
|
if (runtimeGameVariables.hasVariable(variable)) {
|
|
// Global variable.
|
|
|
|
// TODO: prevent returning a networkID if this is not a root variable.
|
|
|
|
const variableName =
|
|
runtimeGameVariables.getVariableNameInContainerByLoopingThroughAllVariables(
|
|
variable
|
|
);
|
|
if (!variableName) {
|
|
logger.error('Variable is being synchronized but has no name.');
|
|
return;
|
|
}
|
|
|
|
return 'game_' + variableName;
|
|
}
|
|
|
|
logger.error(
|
|
'Trying to modify synchronization of a variable that is not a scene or global variable.'
|
|
);
|
|
return;
|
|
};
|
|
|
|
const getPlayerVariableOwnership = function (
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
variable: gdjs.Variable
|
|
) {
|
|
return variable.getPlayerOwnership();
|
|
};
|
|
|
|
const setPlayerVariableOwnership = function (
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
variable: gdjs.Variable,
|
|
newVariablePlayerNumber: number
|
|
) {
|
|
debugLogger.info(
|
|
`Setting ownership of variable to player ${newVariablePlayerNumber}.`
|
|
);
|
|
if (newVariablePlayerNumber < 0) {
|
|
logger.error(
|
|
'Invalid player number (' +
|
|
newVariablePlayerNumber +
|
|
') when setting ownership of a variable.'
|
|
);
|
|
return;
|
|
}
|
|
const previousVariablePlayerNumber = variable.getPlayerOwnership();
|
|
if (previousVariablePlayerNumber === null) {
|
|
logger.error(
|
|
'Cannot change ownership of a variable that is not synchronized.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
variable.setPlayerOwnership(newVariablePlayerNumber);
|
|
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
|
|
|
|
// If the lobby game is not running, do not try to update the ownership over the network,
|
|
// as the game may update variable ownerships before the lobby game starts.
|
|
if (!gdjs.multiplayer.isLobbyGameRunning()) {
|
|
return;
|
|
}
|
|
|
|
if (newVariablePlayerNumber !== currentPlayerNumber) {
|
|
// If we are not the new owner, we should not send a message to the host to change the ownership.
|
|
// Just return and wait to receive an update message to reconcile the variable.
|
|
return;
|
|
}
|
|
|
|
const variableNetworkId = _guessVariableNetworkIdFromSceneAndGame(
|
|
variable,
|
|
runtimeScene
|
|
);
|
|
const sceneNetworkId = runtimeScene.networkId;
|
|
if (!variableNetworkId || !sceneNetworkId) {
|
|
// An error was already logged.
|
|
return;
|
|
}
|
|
|
|
const { type: variableType } =
|
|
getVariableTypeAndNameFromNetworkId(variableNetworkId);
|
|
|
|
debugLogger.info(
|
|
`Adding variable to be synchronized: ${variableNetworkId} (type: ${variableType}) from owner ${previousVariablePlayerNumber} to ${newVariablePlayerNumber}.`
|
|
);
|
|
addVariableOwnershipChangeToSync({
|
|
variableNetworkId,
|
|
previousVariableOwner: previousVariablePlayerNumber,
|
|
newVariableOwner: newVariablePlayerNumber,
|
|
});
|
|
};
|
|
|
|
const takeVariableOwnership = function (
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
variable: gdjs.Variable
|
|
) {
|
|
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
|
|
setPlayerVariableOwnership(runtimeScene, variable, currentPlayerNumber);
|
|
};
|
|
|
|
const removeVariableOwnership = function (
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
variable: gdjs.Variable
|
|
) {
|
|
setPlayerVariableOwnership(runtimeScene, variable, 0);
|
|
};
|
|
|
|
const disableVariableSynchronization = function (
|
|
runtimeScene: gdjs.RuntimeScene,
|
|
variable: gdjs.Variable
|
|
) {
|
|
variable.disableSynchronization();
|
|
};
|
|
|
|
const handleChangeVariableOwnerMessagesToSend = function () {
|
|
if (
|
|
!gdjs.multiplayer.isLobbyGameRunning() ||
|
|
!gdjs.multiplayer.isReadyToSendOrReceiveGameUpdateMessages()
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
|
|
|
|
for (const variableNetworkId in variableOwnershipChangesToSyncAtEndOfFrame) {
|
|
const variableData =
|
|
variableOwnershipChangesToSyncAtEndOfFrame[variableNetworkId];
|
|
const { messageName, messageData } =
|
|
gdjs.multiplayerMessageManager.createChangeVariableOwnerMessage({
|
|
variableNetworkId,
|
|
variableOwner: variableData.previousVariableOwner,
|
|
newVariableOwner: variableData.newVariableOwner,
|
|
});
|
|
// Before sending the change owner message, if we are becoming the new owner,
|
|
// we want to ensure this message is acknowledged, by everyone we're connected to.
|
|
if (variableData.newVariableOwner === currentPlayerNumber) {
|
|
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
|
const variableOwnerChangedMessageName =
|
|
gdjs.multiplayerMessageManager.createVariableOwnerChangedMessageNameFromChangeVariableOwnerMessage(
|
|
messageName
|
|
);
|
|
gdjs.multiplayerMessageManager.addExpectedMessageAcknowledgement({
|
|
originalMessageName: messageName,
|
|
originalData: messageData,
|
|
expectedMessageName: variableOwnerChangedMessageName,
|
|
otherPeerIds,
|
|
// If we are not the host and don't receive an acknowledgement from the host, we should cancel the ownership change.
|
|
shouldCancelMessageIfTimesOut: currentPlayerNumber !== 1,
|
|
});
|
|
}
|
|
|
|
debugLogger.info('Sending change owner message', messageName);
|
|
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
|
gdjs.multiplayerMessageManager.sendDataTo(
|
|
connectedPeerIds,
|
|
messageName,
|
|
messageData
|
|
);
|
|
|
|
// Remove the variable from the list of variables ownership changes to sync.
|
|
delete variableOwnershipChangesToSyncAtEndOfFrame[variableNetworkId];
|
|
}
|
|
};
|
|
|
|
return {
|
|
getVariableTypeAndNameFromNetworkId,
|
|
getPlayerVariableOwnership,
|
|
setPlayerVariableOwnership,
|
|
takeVariableOwnership,
|
|
removeVariableOwnership,
|
|
disableVariableSynchronization,
|
|
handleChangeVariableOwnerMessagesToSend,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* The MultiplayerVariablesManager used by the game.
|
|
* @category Multiplayer
|
|
*/
|
|
export let multiplayerVariablesManager = makeMultiplayerVariablesManager();
|
|
}
|