1
0
forked from 0ad/0ad

Adapt the civinfo page to read from templates

Instead of loading information about heroes, special buildings, et al.,
from the
`{civ}.json` files, load the information desired from templates.

Determination of "specialness" works as follows:

 * Heroes are detected via the `Hero` class.
 * Special Structures are detected via the `SpecialBuilding` or `Wonder`
class.
 * Technologies are determined to be "special" when they can be
researched by
   the currently selected civ, but are not open to be researched by
every civ.
 * Team Bonuses are any applicable aura that reside in the appropriate
folder.


Accepted by: Freagarach
Comments by: Stan
Differential Revision: https://code.wildfiregames.com/D759
This was SVN commit r24492.
This commit is contained in:
s0600204 2021-01-01 01:55:13 +00:00
parent 3ec8a91f30
commit 190d6e7cf5
25 changed files with 189 additions and 43 deletions

View File

@ -40,7 +40,7 @@ class CivInfoPage extends ReferencePage
error(sprintf("Error loading civ data for \"%(code)s\"", { "code": civCode }));
// Update civ gameplay display
this.gameplaySection.update(civInfo);
this.gameplaySection.update(this.activeCiv, civInfo);
// Update civ history display
this.historySection.update(civInfo);
@ -81,24 +81,18 @@ class CivInfoPage extends ReferencePage
}
/**
* Returns a styled concatenation of the Name, History, and Description of the given object.
*
* @param obj {Object}
* @returns {string}
*/
formatEntry(obj)
formatEntry(name, tooltip, description)
{
if (!obj.Name)
return "";
let tooltip_icon = "";
if (tooltip)
tooltip_icon = '[icon="iconInfo" tooltip="' + escapeQuotation(tooltip) + '" tooltip_style="civInfoTooltip"]';
let history_icon = "";
if (obj.History)
history_icon = '[icon="iconInfo" tooltip="' + escapeQuotation(obj.History) + '" tooltip_style="civInfoTooltip"]';
let description = "";
if (obj.Description)
let description_text = "";
if (description)
// Translation: Description of an item in the CivInfo page, on a new line and indented.
description = sprintf(translate('\n %(description)s'), { "description": obj.Description, });
description_text = sprintf(translate('\n %(description)s'), { "description": description, });
return sprintf(
// Translation: An entry in the CivInfo Page. The newline and indentation of the description is handled elsewhere.
@ -107,9 +101,9 @@ class CivInfoPage extends ReferencePage
// > A brief description of the aforementioned something.
translate("• %(name)s %(info_icon)s%(description)s"),
{
"name": setStringTags(obj.Name, { "font": "sans-bold-14" }),
"info_icon": history_icon,
"description": description,
"name": setStringTags(name, { "font": "sans-bold-14" }),
"info_icon": tooltip_icon,
"description": description_text,
}
);
}

View File

@ -11,7 +11,7 @@ class GameplaySection
this.TechnologiesSubsection = new TechnologiesSubsection(this.page);
}
update(civInfo)
update(civCode, civInfo)
{
this.CivGameplayHeading.caption =
this.page.formatHeading(
@ -19,10 +19,10 @@ class GameplaySection
this.page.SectionHeaderSize
);
this.BonusesSubsection.update(civInfo);
this.TechnologiesSubsection.update(civInfo);
this.StructuresSubsection.update(civInfo);
this.HeroesSubsection.update(civInfo);
this.BonusesSubsection.update(civCode, civInfo);
this.TechnologiesSubsection.update(civCode);
this.StructuresSubsection.update(civCode);
this.HeroesSubsection.update(civCode);
}
}

View File

@ -4,4 +4,67 @@ class Subsection
{
this.page = page;
}
getAuraCaptions(auraList, civCode)
{
let captions = [];
for (let auraCode of auraList)
{
let aura = this.page.TemplateParser.getAura(auraCode);
if (!aura.civ || aura.civ != civCode)
continue;
captions.push(this.page.formatEntry(
getEntityNames(aura),
false,
getDescriptionTooltip(aura)
));
}
return captions;
}
getEntityCaptions(entityList, classList, civCode)
{
let captions = [];
for (let entityCode of entityList)
{
// Acquire raw template as we need to compare all classes an entity has, not just the visible ones.
let template = this.page.TemplateLoader.loadEntityTemplate(entityCode, civCode);
let classListFull = GetIdentityClasses(template.Identity);
if (!MatchesClassList(classListFull, classList))
continue;
let entity = this.page.TemplateParser.getEntity(entityCode, civCode);
captions.push(this.page.formatEntry(
getEntityNames(entity),
getDescriptionTooltip(entity),
getEntityTooltip(entity)
));
}
return captions;
}
getTechnologyCaptions(technologyList, civCode)
{
let captions = [];
for (let techCode of technologyList)
{
let technology = this.page.TemplateParser.getTechnology(techCode, civCode);
// We deliberately pass an invalid civ code here.
// If it returns with a value other than false, then
// we know that this tech can be researched by any civ
let genericReqs = this.page.TemplateParser.getTechnology(techCode, "anyciv").reqs;
if (!technology.reqs || genericReqs)
continue;
captions.push(this.page.formatEntry(
getEntityNames(technology),
getDescriptionTooltip(technology),
getEntityTooltip(technology)
));
}
return captions;
}
}

View File

@ -6,9 +6,21 @@ class BonusesSubsection extends Subsection
this.CivBonuses = Engine.GetGUIObjectByName("civBonuses");
}
update(civInfo)
update(civCode, civInfo)
{
let civBonuses = civInfo.CivBonuses.map(bonus => this.page.formatEntry(bonus));
// Not all civ bonuses can be represented by a single auto-researched technology (e.g.
// Athenian "Silver Owls", Roman "Testudo Formation"). Thus we also display descriptions
// of civ bonuses as written in the {civ}.json files.
let civBonuses = this.getTechnologyCaptions(
this.page.TemplateLoader.autoResearchTechList,
civCode
);
for (let bonus of civInfo.CivBonuses)
civBonuses.push(this.page.formatEntry(
bonus.Name,
bonus.History || false,
bonus.Description || false
));
civBonuses.unshift(
this.page.formatHeading(
this.HeadingCivBonusCaption(civBonuses.length),
@ -16,7 +28,10 @@ class BonusesSubsection extends Subsection
)
);
let teamBonuses = civInfo.TeamBonuses.map(bonus => this.page.formatEntry(bonus));
let teamBonuses = this.getAuraCaptions(
this.page.TemplateLoader.teamBonusAuraList,
civCode
);
teamBonuses.unshift(
this.page.formatHeading(
this.HeadingTeamBonusCaption(teamBonuses.length),

View File

@ -6,14 +6,13 @@ class HeroesSubsection extends Subsection
this.CivHeroes = Engine.GetGUIObjectByName("civHeroes");
}
update(civInfo)
update(civCode)
{
let heroes = [];
for (let faction of civInfo.Factions)
Array.prototype.push.apply(
heroes,
faction.Heroes.map(hero => this.page.formatEntry(hero))
);
let heroes = this.getEntityCaptions(
this.page.TemplateLister.getTemplateLists(civCode).units.keys(),
this.IdentifyingClassList,
civCode
);
heroes.unshift(
this.page.formatHeading(
@ -28,3 +27,6 @@ class HeroesSubsection extends Subsection
HeroesSubsection.prototype.HeadingCaption =
count => translatePlural("Hero", "Heroes", count);
HeroesSubsection.prototype.IdentifyingClassList =
["Hero"];

View File

@ -6,9 +6,14 @@ class StructuresSubsection extends Subsection
this.CivStructures = Engine.GetGUIObjectByName("civStructures");
}
update(civInfo)
update(civCode)
{
let structures = civInfo.Structures.map(bonus => this.page.formatEntry(bonus));
let structures = this.getEntityCaptions(
this.page.TemplateLister.getTemplateLists(civCode).structures.keys(),
this.IdentifyingClassList,
civCode
);
structures.unshift(
this.page.formatHeading(
this.HeadingCaption(structures.length),
@ -22,3 +27,6 @@ class StructuresSubsection extends Subsection
StructuresSubsection.prototype.HeadingCaption =
count => translatePlural("Special Structure", "Special Structures", count);
StructuresSubsection.prototype.IdentifyingClassList =
[["SpecialBuilding"], ["Wonder"]];

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<object type="image" sprite="ModernDarkBoxGold" size="33%+5 70%+5 75%-5 100%">
<object type="image" sprite="ModernDarkBoxGold" size="33%+5 60%+5 75%-5 100%">
<object
name="civStructures"
type="text"

View File

@ -6,14 +6,12 @@ class TechnologiesSubsection extends Subsection
this.CivTechs = Engine.GetGUIObjectByName("civTechs");
}
update(civInfo)
update(civCode)
{
let techs = [];
for (let faction of civInfo.Factions)
Array.prototype.push.apply(
techs,
faction.Technologies.map(tech => this.page.formatEntry(tech))
);
let techs = this.getTechnologyCaptions(
this.page.TemplateLister.getTemplateLists(civCode).techs.keys(),
civCode
);
techs.unshift(
this.page.formatHeading(

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<object type="image" sprite="ModernDarkBoxGold" size="33%+5 45 75%-5 70%-5">
<object type="image" sprite="ModernDarkBoxGold" size="33%+5 45 75%-5 60%-5">
<object
name="civTechs"
type="text"

View File

@ -16,6 +16,7 @@ class TemplateLoader
* Partly-composed data.
*/
this.autoResearchTechList = this.findAllAutoResearchedTechs();
this.teamBonusAuraList = this.findAllTeamBonusAuras();
}
/**
@ -196,6 +197,33 @@ class TemplateLoader
return techList;
}
/**
* Iterates through and loads all team bonus auras.
*
* We make an assumption in this method: that all team bonus auras are
* in a single folder.
*
* Team bonuses must have a "civ" attribute to indicate what civ they
* belong to.
*
* @return {array} List of teambonus auras
*/
findAllTeamBonusAuras()
{
let auraList = [];
let path = this.AuraPath + TemplateLoader.prototype.AuraTeamBonusSubpath;
for (let templateName of listFiles(path, ".json", true))
{
let filename = TemplateLoader.prototype.AuraTeamBonusSubpath + templateName;
let data = this.loadAuraTemplate(filename);
if (!data || !data.civ)
continue;
auraList.push(filename);
}
return auraList;
}
/**
* Returns the name of a template's base form (without `_house`, `_trireme`, or similar),
* or the template's own name if the base is of a different promotion rank.
@ -252,6 +280,7 @@ class TemplateLoader
* It might be nice if we could get these from somewhere, instead of having them hardcoded here.
*/
TemplateLoader.prototype.AuraPath = "simulation/data/auras/";
TemplateLoader.prototype.AuraTeamBonusSubpath = "teambonuses/";
TemplateLoader.prototype.TechnologyPath = "simulation/data/technologies/";
TemplateLoader.prototype.DefaultCiv = "gaia";

View File

@ -10,6 +10,7 @@ class TemplateParser
/**
* Parsed Data Stores
*/
this.auras = {};
this.entities = {};
this.techs = {};
this.phases = {};
@ -18,6 +19,24 @@ class TemplateParser
this.phaseList = [];
}
getAura(auraName)
{
if (auraName in this.auras)
return this.auras[auraName];
if (!AuraTemplateExists(auraName))
return null;
let template = this.TemplateLoader.loadAuraTemplate(auraName);
let parsed = GetAuraDataHelper(template);
if (template.civ)
parsed.civ = template.civ;
this.auras[auraName] = parsed;
return this.auras[auraName];
}
/**
* Load and parse a structure, unit, resource, etc from its entity template file.
*

View File

@ -26,3 +26,8 @@ function TechnologyTemplateExists(templateName)
{
return Engine.FileExists(g_Page.TemplateLoader.TechnologyPath + templateName + ".json");
}
function AuraTemplateExists(templateName)
{
return Engine.FileExists(g_Page.TemplateLoader.AuraPath + templateName + ".json");
}

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Warship"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "athen",
"modifications": [
{ "value": "Cost/BuildTime", "multiply": 0.75 }
],

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Healer"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "brit",
"modifications": [
{ "value": "Cost/Resources/food", "multiply": 0.8 },
{ "value": "Cost/Resources/wood", "multiply": 0.8 },

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Trade"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "cart",
"modifications": [
{ "value": "Market/InternationalBonus", "add": 0.1 }
],

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Forge"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "gaul",
"modifications": [
{ "value": "ProductionQueue/TechCostMultiplier/food", "multiply": 0.85 },
{ "value": "ProductionQueue/TechCostMultiplier/wood", "multiply": 0.85 },

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Citizen Javelineer"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "iber",
"modifications": [
{ "value": "Cost/Resources/food", "multiply": 0.9 },
{ "value": "Cost/Resources/wood", "multiply": 0.9 },

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Elephant"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "kush",
"modifications": [
{ "value": "Cost/BuildTime", "multiply": 0.8 },
{ "value": "Cost/Resources/food", "multiply": 0.8 },

View File

@ -2,6 +2,7 @@
"type": "player",
"affects": ["Player"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "mace",
"modifications": [
{ "value": "Player/BarterMultiplier/Sell/food", "multiply": 1.2 },
{ "value": "Player/BarterMultiplier/Sell/wood", "multiply": 1.2 },

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Temple"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "maur",
"modifications": [
{ "value": "Cost/BuildTime", "multiply": 0.5 },
{ "value": "Cost/Resources/wood", "multiply": 0.5 },

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Trader !Ship"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "pers",
"modifications": [
{ "value": "Trader/GainMultiplier", "multiply": 1.15 }
],

View File

@ -2,6 +2,7 @@
"type": "player",
"affects": ["Player"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "ptol",
"modifications": [
{ "value": "ResourceTrickle/Rates/food", "add": 1 }
],

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Citizen Infantry"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "rome",
"modifications": [
{ "value": "Cost/BuildTime", "multiply": 0.9 }
],

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["CivilCentre"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "sele",
"modifications": [
{ "value": "Cost/Resources/food", "multiply": 0.8 },
{ "value": "Cost/Resources/wood", "multiply": 0.8 },

View File

@ -2,6 +2,7 @@
"type": "global",
"affects": ["Citizen Infantry Spearman"],
"affectedPlayers": ["ExclusiveMutualAlly"],
"civ": "spart",
"modifications": [
{ "value": "Health/Max", "multiply": 1.1 }
],