Compare commits

...

5 Commits

Author SHA1 Message Date
AlexandreSi
96d33a7ab8 Add query param to lobbies iframe url 2024-06-14 16:51:26 +02:00
AlexandreSi
15cb723af9 Add nativeMobileApp attribute tu PreviewExportOptions 2024-06-14 16:41:35 +02:00
AlexandreSi
f0899e45e7 Review changes 2024-06-14 15:35:25 +02:00
AlexandreSi
1be148d5ee Add multiplayer configurator 2024-06-14 15:25:10 +02:00
AlexandreSi
a383c1e896 Rename file 2024-06-14 11:04:03 +02:00
12 changed files with 117 additions and 6 deletions

View File

@@ -104,6 +104,9 @@ namespace gdjs {
'gameVersion',
runtimeGame.getGameData().properties.version
);
if (runtimeGame.getAdditionalOptions().nativeMobileApp) {
url.searchParams.set('nativeMobileApp', 'true');
}
url.searchParams.set(
'isPreview',
runtimeGame.isPreview() ? 'true' : 'false'

View File

@@ -222,6 +222,8 @@ bool ExporterHelper::ExportProjectForPixiPreview(
}
runtimeGameOptions.AddChild("projectDataOnlyExport")
.SetBoolValue(options.projectDataOnlyExport);
runtimeGameOptions.AddChild("nativeMobileApp")
.SetBoolValue(options.nativeMobileApp);
runtimeGameOptions.AddChild("websocketDebuggerServerAddress")
.SetStringValue(options.websocketDebuggerServerAddress);
runtimeGameOptions.AddChild("websocketDebuggerServerPort")

View File

@@ -37,6 +37,7 @@ struct PreviewExportOptions {
: project(project_),
exportPath(exportPath_),
useWindowMessageDebuggerClient(false),
nativeMobileApp(false),
projectDataOnlyExport(false),
fullLoadingScreen(false),
isDevelopmentEnvironment(false),
@@ -76,6 +77,15 @@ struct PreviewExportOptions {
return *this;
}
/**
* \brief Set that the preview is launched from a GDevelop native mobile app
* (iOS or Android).
*/
PreviewExportOptions &SetNativeMobileApp(bool enable) {
nativeMobileApp = enable;
return *this;
}
/**
* \brief Set the layout to be run first in the previewed game
*/
@@ -186,6 +196,7 @@ struct PreviewExportOptions {
gd::String externalLayoutName;
gd::String fallbackAuthorUsername;
gd::String fallbackAuthorId;
bool nativeMobileApp;
std::map<gd::String, int> includeFileHashes;
bool projectDataOnlyExport;
bool fullLoadingScreen;

View File

@@ -32,6 +32,8 @@ namespace gdjs {
scriptFiles?: Array<RuntimeGameOptionsScriptFile>;
/** if true, export is a partial preview without events. */
projectDataOnlyExport?: boolean;
/** if true, preview is launched from GDevelop native mobile app. */
nativeMobileApp?: boolean;
/** The address of the debugger server, to reach out using WebSocket. */
websocketDebuggerServerAddress?: string;
/** The port of the debugger server, to reach out using WebSocket. */
@@ -267,7 +269,7 @@ namespace gdjs {
* Return the additional options passed to the RuntimeGame when created.
* @returns The additional options, if any.
*/
getAdditionalOptions(): RuntimeGameOptions | null {
getAdditionalOptions(): RuntimeGameOptions {
return this._options;
}

View File

@@ -3569,6 +3569,7 @@ interface PreviewExportOptions {
[Ref] PreviewExportOptions SetExternalLayoutName([Const] DOMString externalLayoutName);
[Ref] PreviewExportOptions SetIncludeFileHash([Const] DOMString includeFile, long hash);
[Ref] PreviewExportOptions SetProjectDataOnlyExport(boolean enable);
[Ref] PreviewExportOptions SetNativeMobileApp(boolean enable);
[Ref] PreviewExportOptions SetFullLoadingScreen(boolean enable);
[Ref] PreviewExportOptions SetIsDevelopmentEnvironment(boolean enable);
[Ref] PreviewExportOptions SetNonRuntimeScriptsCacheBurst(unsigned long value);

View File

@@ -2732,6 +2732,7 @@ export class PreviewExportOptions extends EmscriptenObject {
setExternalLayoutName(externalLayoutName: string): PreviewExportOptions;
setIncludeFileHash(includeFile: string, hash: number): PreviewExportOptions;
setProjectDataOnlyExport(enable: boolean): PreviewExportOptions;
setNativeMobileApp(enable: boolean): PreviewExportOptions;
setFullLoadingScreen(enable: boolean): PreviewExportOptions;
setIsDevelopmentEnvironment(enable: boolean): PreviewExportOptions;
setNonRuntimeScriptsCacheBurst(value: number): PreviewExportOptions;

View File

@@ -8,6 +8,7 @@ declare class gdPreviewExportOptions {
setExternalLayoutName(externalLayoutName: string): gdPreviewExportOptions;
setIncludeFileHash(includeFile: string, hash: number): gdPreviewExportOptions;
setProjectDataOnlyExport(enable: boolean): gdPreviewExportOptions;
setNativeMobileApp(enable: boolean): gdPreviewExportOptions;
setFullLoadingScreen(enable: boolean): gdPreviewExportOptions;
setIsDevelopmentEnvironment(enable: boolean): gdPreviewExportOptions;
setNonRuntimeScriptsCacheBurst(value: number): gdPreviewExportOptions;

View File

@@ -22,7 +22,7 @@ import { duplicateLeaderboard } from '../Utils/GDevelopServices/Play';
import { registerGame } from '../Utils/GDevelopServices/Game';
import { toNewGdMapStringString } from '../Utils/MapStringString';
const gd = global.gd;
const gd: libGDevelop = global.gd;
type ReplacePromptDialogProps = {|
leaderboardsToReplace: ?Array<string>,
@@ -181,7 +181,7 @@ export const LeaderboardReplacerProgressDialog = ({
type RetryOrAbandonCallback = () => void;
type UseLeaderboardReplacerOutput = {
type UseLeaderboardReplacerOutput = {|
/**
* Launch search through the whole project for leaderboard ids to replace.
*/
@@ -191,7 +191,7 @@ type UseLeaderboardReplacerOutput = {
* Render, if needed, the dialog that will show the progress of leaderboard replacement.
*/
renderLeaderboardReplacerDialog: () => React.Node,
};
|};
type ErroredLeaderboard = {
leaderboardId: string,
@@ -319,6 +319,7 @@ export const useLeaderboardReplacer = (): UseLeaderboardReplacerOutput => {
gd.ProjectBrowserHelper.exposeProjectEvents(
project,
// $FlowIgnore - eventsLeaderboardReplacer inherits from ArbitraryEventsWorker
eventsLeaderboardReplacer
);
eventsLeaderboardReplacer.delete();
@@ -373,6 +374,7 @@ export const useLeaderboardReplacer = (): UseLeaderboardReplacerOutput => {
setGameId(sourceGameId);
const leaderboardsLister = new gd.EventsLeaderboardsLister(project);
// $FlowIgnore - leaderboardsLister inherits from ArbitraryEventsWorker
gd.ProjectBrowserHelper.exposeProjectEvents(project, leaderboardsLister);
const leaderboardIds = leaderboardsLister.getLeaderboardIds();
setLeaderboardsToReplace(leaderboardIds.toNewVectorString().toJSArray());

View File

@@ -0,0 +1,60 @@
// @flow
import * as React from 'react';
import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
import { duplicateLobbyConfiguration } from '../Utils/GDevelopServices/Play';
const gd: libGDevelop = global.gd;
type UseMultiplayerLobbyConfiguratorOutput = {|
configureMultiplayerLobbiesIfNeeded: (
project: gdProject,
sourceGameId: string
) => Promise<void>,
|};
/**
* Hook allowing to duplicate lobby configuration from another project, useful after
* opening a project from an example.
*/
export const useMultiplayerLobbyConfigurator = (): UseMultiplayerLobbyConfiguratorOutput => {
const { profile, getAuthorizationHeader } = React.useContext(
AuthenticatedUserContext
);
const configureMultiplayerLobbiesIfNeeded = React.useCallback(
async (project: gdProject, sourceGameId: string) => {
const isMultiplayerExtensionUsed = gd.UsedExtensionsFinder.scanProject(
project
)
.getUsedExtensions()
.toNewVectorString()
.toJSArray()
.some(extensionName => extensionName === 'Multiplayer');
if (!isMultiplayerExtensionUsed) return;
if (!profile) {
console.warn(
'User is not connected. Aborting multiplayer lobby configuration.'
);
return;
}
try {
await duplicateLobbyConfiguration({
userId: profile.id,
getAuthorizationHeader,
gameId: project.getProjectUuid(),
sourceGameId,
});
} catch (error) {
console.error(
`An error occurred while copying lobby configuration from game with id ${sourceGameId}. Failing silently: `,
error
);
}
},
[getAuthorizationHeader, profile]
);
return {
configureMultiplayerLobbiesIfNeeded,
};
};

View File

@@ -142,7 +142,7 @@ import {
sendInAppTutorialStarted,
sendEventsExtractedAsFunction,
} from '../Utils/Analytics/EventSender';
import { useLeaderboardReplacer } from '../Leaderboard/useLeaderboardReplacer';
import { useLeaderboardReplacer } from '../Leaderboard/UseLeaderboardReplacer';
import useAlertDialog from '../UI/Alert/useAlertDialog';
import NewProjectSetupDialog from '../ProjectCreation/NewProjectSetupDialog';
import {
@@ -178,6 +178,7 @@ import useVersionHistory from '../VersionHistory/UseVersionHistory';
import { ProjectManagerDrawer } from '../ProjectManager/ProjectManagerDrawer';
import DiagnosticReportDialog from '../ExportAndShare/DiagnosticReportDialog';
import useSaveReminder from './UseSaveReminder';
import { useMultiplayerLobbyConfigurator } from './UseMultiplayerLobbyConfigurator';
const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || [];
@@ -410,6 +411,9 @@ const MainFrame = (props: Props) => {
findLeaderboardsToReplace,
renderLeaderboardReplacerDialog,
} = useLeaderboardReplacer();
const {
configureMultiplayerLobbiesIfNeeded,
} = useMultiplayerLobbyConfigurator();
const eventsFunctionsExtensionsState = React.useContext(
EventsFunctionsExtensionsContext
);
@@ -1148,6 +1152,7 @@ const MainFrame = (props: Props) => {
setNewProjectSetupDialogOpen(false);
closeExampleStoreDialog({ deselectExampleAndGameTemplate: true });
findLeaderboardsToReplace(project, oldProjectId);
configureMultiplayerLobbiesIfNeeded(project, oldProjectId);
openSceneOrProjectManager({
currentProject: project,
editorTabs: editorTabs,

View File

@@ -502,3 +502,26 @@ export const updateLobbyConfiguration = async (
);
return response.data;
};
export const duplicateLobbyConfiguration = async ({
getAuthorizationHeader,
userId,
gameId,
sourceGameId,
}: {|
getAuthorizationHeader: () => Promise<string>,
userId: string,
gameId: string,
sourceGameId: string,
|}): Promise<LobbyConfiguration> => {
const authorizationHeader = await getAuthorizationHeader();
const response = await axios.post(
`${GDevelopPlayApi.baseUrl}/game/${gameId}/lobby-configuration/action/copy`,
{ sourceGameId },
{
headers: { Authorization: authorizationHeader },
params: { userId },
}
);
return response.data;
};

View File

@@ -6,7 +6,7 @@ import paperDecorator from '../../PaperDecorator';
import {
LeaderboardReplacerProgressDialog,
ReplacePromptDialog,
} from '../../../Leaderboard/useLeaderboardReplacer';
} from '../../../Leaderboard/UseLeaderboardReplacer';
import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
import { fakeSilverAuthenticatedUser } from '../../../fixtures/GDevelopServicesTestData';