0ad/binaries/data/mods/public/gui/common/settings.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

379 lines
9.7 KiB
JavaScript

/**
* The maximum number of players that the engine supports.
* TODO: Maybe we can support more than 8 players sometime.
*/
const g_MaxPlayers = 8;
/**
* The maximum number of teams allowed.
*/
const g_MaxTeams = 4;
/**
* Directory containing all editable settings.
*/
const g_SettingsDirectory = "simulation/data/settings/";
/**
* Directory containing all biomes supported for random map scripts.
*/
const g_BiomesDirectory = "maps/random/rmbiome/";
/**
* An object containing all values given by setting name.
* Used by lobby, game setup, session, summary screen, and replay menu.
*/
const g_Settings = loadSettingsValues();
/**
* Loads and translates all values of all settings which
* can be configured by dropdowns in the game setup.
*
* @returns {Object|undefined}
*/
function loadSettingsValues()
{
var settings = {
"AIDescriptions": loadAIDescriptions(),
"AIDifficulties": loadAIDifficulties(),
"AIBehaviors": loadAIBehaviors(),
"GameSpeeds": loadSettingValuesFile("game_speeds.json"),
"MapTypes": loadMapTypes(),
"MapSizes": loadSettingValuesFile("map_sizes.json"),
"PlayerPlacements": loadSettingValuesFile("player_placements.json"),
"Biomes": loadBiomes(),
"PlayerDefaults": loadPlayerDefaults(),
"PopulationCapacities": loadSettingValuesFile("population_capacities.json"),
"StartingResources": loadSettingValuesFile("starting_resources.json"),
"VictoryConditions": loadVictoryConditions(),
"TriggerDifficulties": loadSettingValuesFile("trigger_difficulties.json")
};
if (Object.keys(settings).some(key => settings[key] === undefined))
return undefined;
return deepfreeze(settings);
}
/**
* Returns an array of objects reflecting all possible values for a given setting.
*
* @param {string} filename
* @see simulation/data/settings/
* @returns {Array|undefined}
*/
function loadSettingValuesFile(filename)
{
var json = Engine.ReadJSONFile(g_SettingsDirectory + filename);
if (!json || !json.Data)
{
error("Could not load " + filename + "!");
return undefined;
}
if (json.TranslatedKeys)
{
let keyContext = json.TranslatedKeys;
if (json.TranslationContext)
{
keyContext = {};
for (const key of json.TranslatedKeys)
keyContext[key] = json.TranslationContext;
}
translateObjectKeys(json.Data, keyContext);
}
return json.Data;
}
/**
* Loads the descriptions as defined in simulation/ai/.../data.json and loaded by ICmpAIManager.cpp.
*
* @returns {Array}
*/
function loadAIDescriptions()
{
var ais = Engine.GetAIs();
translateObjectKeys(ais, ["name", "description"]);
return ais.sort((a, b) => a.data.name.localeCompare(b.data.name));
}
/**
* Hardcoded, as modding is not supported without major changes.
* Notice the AI code parses the difficulty level by the index, not by name.
*
* @returns {Array}
*/
function loadAIDifficulties()
{
return [
{
"Name": "sandbox",
"Title": translateWithContext("aiDiff", "Sandbox")
},
{
"Name": "very easy",
"Title": translateWithContext("aiDiff", "Very Easy")
},
{
"Name": "easy",
"Title": translateWithContext("aiDiff", "Easy")
},
{
"Name": "medium",
"Title": translateWithContext("aiDiff", "Medium"),
"Default": true
},
{
"Name": "hard",
"Title": translateWithContext("aiDiff", "Hard")
},
{
"Name": "very hard",
"Title": translateWithContext("aiDiff", "Very Hard")
}
];
}
function loadAIBehaviors()
{
return [
{
"Name": "random",
"Title": translateWithContext("aiBehavior", "Random"),
"Default": true
},
{
"Name": "balanced",
"Title": translateWithContext("aiBehavior", "Balanced"),
},
{
"Name": "defensive",
"Title": translateWithContext("aiBehavior", "Defensive")
},
{
"Name": "aggressive",
"Title": translateWithContext("aiBehavior", "Aggressive")
}
];
}
/**
* Hardcoded, as modding is not supported without major changes.
*/
function loadMapTypes()
{
return [
{
"Name": "skirmish",
"Title": translateWithContext("map", "Skirmish"),
"Description": translate("A map with a predefined landscape and number of players. Freely select the other game settings."),
"Default": true,
"Path": "maps/skirmishes/",
"Suffix": ".xml",
"GetData": Engine.LoadMapSettings,
"CheckIfExists": mapPath => Engine.FileExists(mapPath + ".xml")
},
{
"Name": "random",
"Title": translateWithContext("map", "Random"),
"Description": translate("Create a unique map with a different resource distribution each time. Freely select the number of players and teams."),
"Path": "maps/random/",
"Suffix": ".json",
"GetData": mapPath => Engine.ReadJSONFile(mapPath + ".json"),
"CheckIfExists": mapPath => Engine.FileExists(mapPath + ".json")
},
{
"Name": "scenario",
"Title": translateWithContext("map", "Scenario"),
"Description": translate("A map with a predefined landscape and matchsettings."),
"Path": "maps/scenarios/",
"Suffix": ".xml",
"GetData": Engine.LoadMapSettings,
"CheckIfExists": mapPath => Engine.FileExists(mapPath + ".xml")
}
];
}
function loadBiomes()
{
return listFiles(g_BiomesDirectory, ".json", true).filter(biomeID => biomeID != "defaultbiome").map(biomeID =>
{
const description = Engine.ReadJSONFile(g_BiomesDirectory + biomeID + ".json").Description;
return {
"Id": biomeID,
"Title": translateWithContext("biome definition", description.Title),
"Description": description.Description ? translateWithContext("biome definition", description.Description) : "",
"Preview": description.Preview || undefined
};
});
}
/**
* Loads available victoryCondtions from json files.
*
* @returns {Array|undefined}
*/
function loadVictoryConditions()
{
const subdir = "victory_conditions/";
const victoryConditions = listFiles(g_SettingsDirectory + subdir, ".json", false).map(victoryScriptName =>
{
const victoryCondition = loadSettingValuesFile(subdir + victoryScriptName + ".json");
if (victoryCondition)
victoryCondition.Name = victoryScriptName;
return victoryCondition;
});
if (victoryConditions.some(victoryCondition => victoryCondition == undefined))
return undefined;
return victoryConditions.sort((a, b) => a.GUIOrder - b.GUIOrder || (a.Title > b.Title ? 1 : a.Title > b.Title ? -1 : 0));
}
/**
* Loads the default player settings (like civs and colors).
*
* @returns {Array|undefined}
*/
function loadPlayerDefaults()
{
var json = Engine.ReadJSONFile(g_SettingsDirectory + "player_defaults.json");
if (!json || !json.PlayerData)
{
error("Could not load player_defaults.json");
return undefined;
}
return json.PlayerData;
}
/**
* Creates an object with all values of that property of the given setting and
* finds the index of the default value.
*
* This allows easy copying of setting values to dropdown lists.
*
* @param {Array} settingValues
* @returns {Object|undefined}
*/
function prepareForDropdown(settingValues)
{
if (!settingValues)
return undefined;
const settings = { "Default": 0 };
for (const index in settingValues)
{
for (const property in settingValues[index])
{
if (property == "Default")
continue;
if (!settings[property])
settings[property] = [];
// Switch property and index
settings[property][index] = settingValues[index][property];
}
// Copy default value
if (settingValues[index].Default)
settings.Default = +index;
}
return deepfreeze(settings);
}
/**
* Returns title or placeholder.
*
* @param {string} aiName - for example "petra"
*/
function translateAIName(aiName)
{
const description = g_Settings.AIDescriptions.find(ai => ai.id == aiName);
return description ? translate(description.data.name) : translateWithContext("AI name", "Unknown");
}
/**
* Returns title or placeholder.
*
* @param {number} index - index of AIDifficulties
*/
function translateAIDifficulty(index)
{
const difficulty = g_Settings.AIDifficulties[index];
return difficulty ? difficulty.Title : translateWithContext("AI difficulty", "Unknown");
}
/**
* Returns title or placeholder.
*
* @param {string} aiBehavior - for example "defensive"
*/
function translateAIBehavior(aiBehavior)
{
const behavior = g_Settings.AIBehaviors.find(b => b.Name == aiBehavior);
return behavior ? behavior.Title : translateWithContext("AI behavior", "Default");
}
/**
* Returns title or placeholder.
*
* @param {string} mapType - for example "skirmish"
* @returns {string}
*/
function translateMapType(mapType)
{
const type = g_Settings.MapTypes.find(t => t.Name == mapType);
return type ? type.Title : translateWithContext("map type", "Unknown");
}
/**
* Returns title or placeholder "Default".
*
* @param {number} mapSize - tilecount
* @returns {string}
*/
function translateMapSize(tiles)
{
const mapSize = g_Settings.MapSizes.find(size => size.Tiles == +tiles);
return mapSize ? mapSize.Name : translateWithContext("map size", "Default");
}
/**
* Returns title or placeholder.
*
* @param {number} popCap
* @param {string} popCapType - "player", "team", or "world"
* @returns {string}
*/
function translatePopulationCapacity(popCap, popCapType)
{
const popCapTypeData = g_Settings.PopulationCapacities.find(type => type.Name == popCapType);
if (!popCapTypeData)
return translateWithContext("population capacity", "Unknown");
return popCap >= 10000 ?
translateWithContext("population capacity", "Unlimited") :
sprintf(translate("%(populationCapacity)s (%(populationCapacityType)s)"), {
"populationCapacity": popCap,
"populationCapacityType": popCapType
});
}
/**
* Returns title or placeholder.
*
* @param {string} victoryConditionName - For example "conquest".
* @returns {string}
*/
function translateVictoryCondition(victoryConditionName)
{
const victoryCondition = g_Settings.VictoryConditions.find(condition => condition.Name == victoryConditionName);
return victoryCondition ? victoryCondition.Title : translate("Unknown Victory Condition");
}