0ad/binaries/data/mods/public/gui/campaigns/default_menu/CampaignMenu.js
Dunedan 93ce94655d
Use @stylistic/brace-style for eslint
Up to now `eslint-plugin-brace-rules` was used to enforce a common brace
style for JavaScript code. This plugin was however updated the last time
over 9 years ago and will be incompatible with ESLint v10, as that
[removes `context.getSourceCode()`][1], the plugin relies on.

To keep the eslint config working with ESLint v10, this replaces
`eslint-plugin-brace-rules` with the [`@stylistic/brace-style`][2] rule
from `@stylistic/eslint-plugin`, a package we already use.

While `@stylistic/brace-style` doesn't offer an option to format braces
in exactly the same way as before, the "allman" style seems to be the
one closest to the existing code.

[1]: https://eslint.org/blog/2025/11/eslint-v10.0.0-alpha.0-released/#removed-deprecated-rule-context-members
[2]: https://eslint.style/rules/brace-style
2026-01-12 21:33:52 +01:00

252 lines
6.7 KiB
JavaScript

/**
* This is the main menu screen of the campaign.
* It shows you the currently available scenarios, scenarios you've already completed, etc.
* This particular variant is extremely simple and shows a list similar to Age 1's campaigns,
* but conceptually nothing really prevents more complex systems.
*/
class CampaignMenu extends AutoWatcher
{
constructor(campaignRun)
{
super("render");
this.run = campaignRun;
this.selectedLevel = -1;
this.levelSelection = Engine.GetGUIObjectByName("levelSelection");
this.levelSelection.onSelectionChange = () => { this.selectedLevel = this.levelSelection.selected; };
this.levelSelection.onMouseLeftDoubleClickItem = () => this.startScenario();
Engine.GetGUIObjectByName('startButton').onPress = () => this.startScenario();
Engine.GetGUIObjectByName('backToMain').onPress = () => this.goBackToMainMenu();
Engine.GetGUIObjectByName('savedGamesButton').onPress = this.loadSavegame.bind(this);
this.mapCache = new MapCache();
this._ready = true;
}
async loadSavegame()
{
const gameId = await Engine.OpenChildPage(
'page_loadgame.xml',
{
"campaignRun": this.run.filename
});
if (!gameId)
return;
const metadata = Engine.StartSavedGame(gameId);
if (!metadata)
{
error("Could not load saved game: " + gameId);
return;
}
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": metadata.initAttributes,
"playerAssignments": {
"local": {
"name":
metadata.initAttributes.settings.PlayerData[metadata.playerID]?.Name ??
singleplayerName(),
"player": metadata.playerID
}
},
"savedGUIData": metadata.gui
});
}
goBackToMainMenu()
{
this.run.save();
Engine.SwitchGuiPage("page_pregame.xml", {});
}
startScenario()
{
const level = this.getSelectedLevelData();
if (!meetsRequirements(this.run, level))
return;
// TODO: level description should also be passed, ideally.
const settings = {
"mapType": level.MapType,
"map": "maps/" + level.Map,
"settings": {
// TODO: don't translate this here.
"mapName": this.getLevelName(level),
"mapPreview": level.Preview && "cropped:" + 400/512 + "," + 300/512 + ":" + level.Preview,
"CheatsEnabled": true
},
"campaignData": {
"run": this.run.filename,
"levelID": this.levelSelection.list_data[this.selectedLevel],
"data": this.run.data
}
};
const assignments = {
"local": {
"player": 1,
"name": Engine.ConfigDB_GetValue("user", "playername.singleplayer") || Engine.GetSystemUsername()
}
};
const gameSettings = new GameSettings().init();
gameSettings.fromInitAttributes(settings);
if (level.useGameSetup)
{
// Setup some default AI on the non-human players.
for (let i = 1; i < gameSettings.playerCount.nbPlayers; ++i)
gameSettings.playerAI.set(i, {
"bot": g_Settings.PlayerDefaults[i + 1].AI,
"difficulty": +Engine.ConfigDB_GetValue("user", "gui.gamesetup.aidifficulty"),
"behavior": Engine.ConfigDB_GetValue("user", "gui.gamesetup.aibehavior"),
});
const attributes = gameSettings.toInitAttributes();
attributes.guiData = {
"lockSettings": {
"map": true,
},
};
Engine.SwitchGuiPage("page_gamesetup.xml", {
"backPage": {
"page": this.run.getMenuPath(),
"data": {
"filename": this.run.filename
}
},
"gameSettings": attributes,
});
return;
}
gameSettings.launchGame(assignments, true);
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": gameSettings.finalizedAttributes,
"playerAssignments": assignments
});
}
getSelectedLevelData()
{
if (this.selectedLevel === -1)
return undefined;
return this.run.template.Levels[this.levelSelection.list_data[this.selectedLevel]];
}
shouldShowLevel(levelData)
{
if (this.run.template.ShowUnavailable)
return true;
return meetsRequirements(this.run, levelData);
}
getLevelName(levelData)
{
if (levelData.Name)
return translateWithContext("Campaign Template", levelData.Name);
return translate(this.mapCache.getTranslatableMapName(levelData.MapType, "maps/" + levelData.Map));
}
getLevelDescription(levelData)
{
if (levelData.Description)
return translateWithContext("Campaign Template", levelData.Description);
return this.mapCache.getTranslatedMapDescription(levelData.MapType, "maps/" + levelData.Map);
}
getLevelPreview(levelData)
{
if (levelData.Preview)
return "cropped:" + 400/512 + "," + 300/512 + ":" + levelData.Preview;
return this.mapCache.getMapPreview(levelData.MapType, "maps/" + levelData.Map);
}
displayLevelsList()
{
let list = [];
for (const key in this.run.template.Levels)
{
const level = this.run.template.Levels[key];
if (!this.shouldShowLevel(level))
continue;
let status = "";
let name = this.getLevelName(level);
if (isCompleted(this.run, key))
status = translateWithContext("campaign status", "Completed");
else if (meetsRequirements(this.run, level))
status = coloredText(translateWithContext("campaign status", "Available"), "green");
else
name = coloredText(name, "gray");
list.push({ "ID": key, "name": name, "status": status });
}
list.sort((a, b) => this.run.template.Order.indexOf(a.ID) - this.run.template.Order.indexOf(b.ID));
list = prepareForDropdown(list);
this.levelSelection.list_name = list.name || [];
this.levelSelection.list_status = list.status || [];
// COList needs these changed last or crashes.
this.levelSelection.list = list.ID || [];
this.levelSelection.list_data = list.ID || [];
}
displayLevelDetails()
{
if (this.selectedLevel === -1)
{
Engine.GetGUIObjectByName("startButton").enabled = false;
Engine.GetGUIObjectByName("startButton").hidden = false;
return;
}
const level = this.getSelectedLevelData();
Engine.GetGUIObjectByName("scenarioName").caption = this.getLevelName(level);
Engine.GetGUIObjectByName("scenarioDesc").caption = this.getLevelDescription(level);
Engine.GetGUIObjectByName('levelPreviewBox').sprite = this.getLevelPreview(level);
Engine.GetGUIObjectByName("startButton").enabled = meetsRequirements(this.run, level);
Engine.GetGUIObjectByName("startButton").hidden = false;
Engine.GetGUIObjectByName("loadSavedButton").hidden = true;
}
render()
{
Engine.GetGUIObjectByName("campaignTitle").caption = this.run.getLabel();
this.displayLevelDetails();
this.displayLevelsList();
}
}
var g_CampaignMenu;
function init(initData)
{
let run = initData?.filename || CampaignRun.getCurrentRunFilename();
try
{
run = new CampaignRun(run).load();
if (!run.isCurrent())
run.setCurrent();
g_CampaignMenu = new CampaignMenu(run);
}
catch(err)
{
error(sprintf("Error loading campaign run %s: %s.", CampaignRun.getCurrentRunFilename(), err));
Engine.SwitchGuiPage("page_pregame.xml", {});
}
}