0ad/binaries/data/mods/public/gui/session/MenuButtons.js
Vantha 2520c45220 Only call Engine.EndGame right before closing
Since #7786, the GUI tick completed normally after calling
`closePageCallback` instead of being cancelled like previously. Since
`Engine.EndGame` deletes the internal `g_Game` pointer, every Engine call
after it that tried to access `g_Game` caused a segfault.
This patch solves it by moving `Engine.EndGame` to the very end, right
before closing the session page.
In order to follow the convention that only functions directly closing
the page are called `closePageCallback`, that function is in turn
renamed to `closeSession`. Additionally, this allows only resolving
the promise with a bool (`showSummary`) and then only calling
`getNextPageOpenRequest` at the end of `init` for reasons of simplicity.
2026-07-22 23:51:49 +02:00

295 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* This class is extended in subclasses.
* Each subclass represents one button in the session menu.
* All subclasses store the button member so that mods can change it easily.
*/
class MenuButtons
{
}
MenuButtons.prototype.Manual = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate(translate("Manual"));
this.button.hotkey = "manual";
this.pauseControl = pauseControl;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("%(hotkey)s: Open the 0 A.D. game manual."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
async onPress()
{
closeOpenDialogs();
this.pauseControl.implicitPause();
await Engine.OpenChildPage("page_manual.xml");
resumeGame();
}
};
MenuButtons.prototype.Chat = class
{
constructor(button, pauseControl, playerViewControl, chat)
{
this.button = button;
this.button.caption = translate("Chat");
this.chat = chat;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = this.chat.getOpenHotkeyTooltip().trim();
}
onPress()
{
this.chat.openPage();
}
};
MenuButtons.prototype.Save = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Save");
this.pauseControl = pauseControl;
}
async onPress()
{
closeOpenDialogs();
this.pauseControl.implicitPause();
await Engine.OpenChildPage(
"page_loadgame.xml",
{
"savedGameData": getSavedGameData(),
"campaignRun": g_CampaignSession ? g_CampaignSession.run.filename : null
});
resumeGame();
}
};
MenuButtons.prototype.Summary = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Summary");
this.button.hotkey = "summary";
// TODO: Atlas should pass g_InitAttributes.settings
this.button.enabled = !Engine.IsAtlasRunning();
this.pauseControl = pauseControl;
this.selectedData = undefined;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("%(hotkey)s: Open the summary screen."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
async onPress()
{
if (Engine.IsAtlasRunning())
return;
closeOpenDialogs();
this.pauseControl.implicitPause();
// Allows players to see their own summary.
// If they have shared ally vision researched, they are able to see the summary of there allies too.
const simState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
const data = await Engine.OpenChildPage(
"page_summary.xml",
{
"sim": {
"mapSettings": g_InitAttributes.settings,
"playerStates": simState.players.filter((state, player) =>
g_IsObserver || g_ViewedPlayer == 0 || player == 0 || player == g_ViewedPlayer ||
simState.players[g_ViewedPlayer].hasSharedLos && g_Players[player].isMutualAlly[g_ViewedPlayer]),
"timeElapsed": simState.timeElapsed
},
"gui": {
"dialog": true,
"isInGame": true,
"summarySelection": this.summarySelection
},
});
this.summarySelection = data.summarySelection;
this.pauseControl.implicitResume();
}
};
MenuButtons.prototype.Lobby = class
{
constructor(button)
{
this.button = button;
this.button.caption = translate("Lobby");
this.button.hotkey = "lobby";
this.button.enabled = Engine.HasXmppClient();
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("%(hotkey)s: Open the multiplayer lobby page without leaving the game."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
onPress()
{
if (!Engine.HasXmppClient())
return;
closeOpenDialogs();
Engine.OpenChildPage("page_lobby.xml", { "dialog": true });
}
};
MenuButtons.prototype.Options = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Options");
this.button.hotkey = "options";
this.pauseControl = pauseControl;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("%(hotkey)s: Adjust game settings."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
async onPress()
{
closeOpenDialogs();
this.pauseControl.implicitPause();
fireConfigChangeHandlers(await Engine.OpenChildPage("page_options.xml"));
resumeGame();
}
};
MenuButtons.prototype.Hotkeys = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Hotkeys");
this.button.hotkey = "hotkeys";
this.pauseControl = pauseControl;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("%(hotkey)s: Adjust hotkeys."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
async onPress()
{
closeOpenDialogs();
this.pauseControl.implicitPause();
await Engine.OpenChildPage("page_hotkeys.xml");
resumeGame();
}
};
MenuButtons.prototype.Pause = class
{
constructor(button, pauseControl, playerViewControl)
{
this.button = button;
this.button.hotkey = "pause";
this.pauseControl = pauseControl;
registerPlayersInitHandler(this.rebuild.bind(this));
registerPlayersFinishedHandler(this.rebuild.bind(this));
playerViewControl.registerPlayerIDChangeHandler(this.rebuild.bind(this));
pauseControl.registerPauseHandler(this.rebuild.bind(this));
registerHotkeyChangeHandler(this.rebuild.bind(this));
registerNetworkStatusChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.enabled = this.pauseControl.canPause(true);
this.button.caption = this.pauseControl.explicitPause ? translate("Resume") : translate("Pause");
this.button.tooltip = sprintf(translate("%(hotkey)s: Pause or resume the game."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
onPress()
{
this.pauseControl.setPaused(!g_PauseControl.explicitPause, true);
}
};
MenuButtons.prototype.Resign = class
{
constructor(button, pauseControl, playerViewControl)
{
this.button = button;
this.button.caption = translate("Resign");
this.pauseControl = pauseControl;
registerPlayersInitHandler(this.rebuild.bind(this));
registerPlayersFinishedHandler(this.rebuild.bind(this));
playerViewControl.registerPlayerIDChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.enabled = !g_IsObserver;
}
onPress()
{
(new ResignConfirmation()).display();
}
};
MenuButtons.prototype.Exit = class
{
constructor(button, pauseControl)
{
this.button = button;
this.button.caption = translate("Exit");
this.button.enabled = !Engine.IsAtlasRunning();
this.pauseControl = pauseControl;
}
onPress(closeSession)
{
for (const name in QuitConfirmationMenu.prototype)
{
const quitConfirmation = new QuitConfirmationMenu.prototype[name]();
if (quitConfirmation.enabled())
quitConfirmation.display(closeSession);
}
}
};