1
0
forked from 0ad/0ad

Rewrite the civinfo page to use OOP principles

Tested by: Nescio, Freagarach
Refs: #5387
Differential Revision: https://code.wildfiregames.com/D2822
This was SVN commit r24289.
This commit is contained in:
s0600204 2020-11-29 04:20:17 +00:00
parent 416cc90c3e
commit 7cde8a9df4
26 changed files with 504 additions and 299 deletions

View File

@ -1,153 +0,0 @@
/**
* Display selectable civs only.
*/
const g_CivData = loadCivData(true, false);
var g_SelectedCiv = "";
/**
* Initialize the dropdown containing all the available civs.
*/
function init(data = {})
{
var civList = Object.keys(g_CivData).map(civ => ({ "name": g_CivData[civ].Name, "code": civ })).sort(sortNameIgnoreCase);
var civSelection = Engine.GetGUIObjectByName("civSelection");
civSelection.list = civList.map(civ => civ.name);
civSelection.list_data = civList.map(civ => civ.code);
civSelection.selected = data.civ ? civSelection.list_data.indexOf(data.civ) : 0;
Engine.GetGUIObjectByName("structreeButton").tooltip = colorizeHotkey(translate("%(hotkey)s: Switch to Structure Tree."), "structree");
Engine.GetGUIObjectByName("close").tooltip = colorizeHotkey(translate("%(hotkey)s: Close Civilization Overview."), "cancel");
}
/**
* Give the first character a larger font.
*/
function bigFirstLetter(str, size)
{
return '[font="sans-bold-' + (size + 6) + '"]' + str[0] + '[/font]' + '[font="sans-bold-' + size + '"]' + str.substring(1) + '[/font]';
}
/**
* Set heading font - bold and mixed caps
*
* @param string {string}
* @param size {number} - Font size
* @returns {string}
*/
function heading(string, size)
{
var textArray = string.split(" ");
for (let i in textArray)
{
var word = textArray[i];
var wordCaps = word.toUpperCase();
// Check if word is capitalized, if so assume it needs a big first letter
// Check if toLowerCase changes the character to avoid false positives from special signs
if (word.length && word[0].toLowerCase() != word[0])
textArray[i] = bigFirstLetter(wordCaps, size);
else
textArray[i] = '[font="sans-bold-' + size + '"]' + wordCaps + '[/font]'; // TODO: Would not be necessary if we could do nested tags
}
return textArray.join(" ");
}
/**
* Prepends a backslash to all quotation marks.
* @param str {string}
* @returns {string}
*/
function escapeQuotation(str)
{
return str.replace(/"/g, "\\\"");
}
/**
* Returns a styled concatenation of Name, History and Description of the given object.
*
* @param obj {Object}
* @returns {string}
*/
function subHeading(obj)
{
if (!obj.Name)
return "";
let string = '[font="sans-bold-14"]' + obj.Name + '[/font] ';
if (obj.History)
string += '[icon="iconInfo" tooltip="' + escapeQuotation(obj.History) + '" tooltip_style="civInfoTooltip"]';
if (obj.Description)
string += '\n ' + obj.Description;
// Translation: insert an itemization symbol for each entry.
return sprintf(translate("• %(string)s"), { "string": string }) + "\n";
}
function switchToStrucTreePage()
{
Engine.PopGuiPage({ "civ": g_SelectedCiv, "nextPage": "page_structree.xml" });
}
function closePage()
{
Engine.PopGuiPage({ "civ": g_SelectedCiv, "page": "page_civinfo.xml" });
}
/**
* Updates the GUI after the user selected a civ from dropdown.
*
* @param code {string}
*/
function selectCiv(code)
{
var civInfo = g_CivData[code];
g_SelectedCiv = code;
if(!civInfo)
error(sprintf("Error loading civ data for \"%(code)s\"", { "code": code }));
// Update civ gameplay display
Engine.GetGUIObjectByName("civGameplayHeading").caption = heading(sprintf(translate("%(civilization)s Gameplay"), { "civilization": civInfo.Name }), 16);
// Bonuses
var bonusCaption = heading(translatePlural("Civilization Bonus", "Civilization Bonuses", civInfo.CivBonuses.length), 12) + '\n';
for (let bonus of civInfo.CivBonuses)
bonusCaption += subHeading(bonus);
// Team bonuses
bonusCaption += heading(translatePlural("Team Bonus", "Team Bonuses", civInfo.TeamBonuses.length), 12) + '\n';
for (let bonus of civInfo.TeamBonuses)
bonusCaption += subHeading(bonus);
Engine.GetGUIObjectByName("civBonuses").caption = bonusCaption;
// Special technologies
var techCaption = heading(translate("Special Technologies"), 12) + '\n';
for (let faction of civInfo.Factions)
for (let technology of faction.Technologies)
techCaption += subHeading(technology);
// Special structures
techCaption += heading(translatePlural("Special Structure", "Special Structures", civInfo.Structures.length), 12) + '\n';
for (let structure of civInfo.Structures)
techCaption += subHeading(structure);
Engine.GetGUIObjectByName("civTechs").caption = techCaption;
// Heroes
var heroCaption = heading(translate("Heroes"), 12) + '\n';
for (let faction of civInfo.Factions)
{
for (let hero of faction.Heroes)
heroCaption += subHeading(hero);
heroCaption += '\n';
}
Engine.GetGUIObjectByName("civHeroes").caption = heroCaption;
// Update civ history display
Engine.GetGUIObjectByName("civHistoryHeading").caption = heading(sprintf(translate("History of the %(civilization)s"), { "civilization": civInfo.Name }), 16);
Engine.GetGUIObjectByName("civHistoryText").caption = civInfo.History;
}

View File

@ -1,141 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script directory="gui/common/"/>
<script directory="gui/civinfo/"/>
<object hotkey="civinfo">
<action on="Press">closePage();</action>
</object>
<!-- Add a translucent black background to fade out the menu page -->
<object type="image" sprite="BackgroundTranslucent"/>
<object type="image" style="ModernDialog" size="50%-500 50%-368 50%+500 50%+370">
<object style="TitleText" type="text" size="50%-128 -18 50%+128 14">
<translatableAttribute id="caption">Civilization Overview</translatableAttribute>
</object>
<!-- Civ selection -->
<object size="25 10 100% 30">
<object
name="civSelectionHeading"
type="text"
font="sans-bold-20"
textcolor="white"
text_align="left"
size="50%-420 10 50%-96 48">
<translatableAttribute id="caption">Civilization Selection</translatableAttribute>
</object>
<object name="civSelection" type="dropdown" style="ModernDropDown" size="50%-96 10 50%+96 40" dropdown_size="424">
<action on="SelectionChange">selectCiv(this.list_data[this.selected]);</action>
</object>
</object>
<!-- Civ data display -->
<object size="25 50 100%-25 65%-5">
<object type="image" sprite="TranslucentPanelThinBorder" size="0 5 100% 40">
<object
name="civGameplayHeading"
type="text"
font="sans-bold-16"
textcolor="white"
text_align="left"
text_valign="center"
size="10 0 100% 100%"
/>
</object>
<object type="image" sprite="ModernDarkBoxGold" size="0 45 33%-5 100%">
<object
name="civBonuses"
type="text"
font="sans-14"
textcolor="white"
text_align="left"
size="10 0 100%-10 100%-10"
/>
</object>
<object type="image" sprite="ModernDarkBoxGold" size="33%+5 45 75%-5 100%">
<object
name="civTechs"
type="text"
font="sans-14"
textcolor="white"
text_align="left"
size="10 0 100%-10 100%-10"
/>
</object>
<object type="image" sprite="ModernDarkBoxGold" size="75%+5 45 100% 100%">
<object
name="civHeroes"
type="text"
font="sans-14"
textcolor="white"
text_align="left"
size="10 0 100%-10 100%-10"
/>
</object>
</object>
<!-- Civ history display -->
<object size="25 65% 100%-25 100%-56">
<object type="image" sprite="TranslucentPanelThinBorder" size="0 0 100% 35">
<object
name="civHistoryHeading"
type="text"
font="sans-bold-16"
textcolor="white"
text_align="left"
text_valign="center"
size="10 0 100% 100%"
/>
</object>
<object type="image" sprite="ModernDarkBoxGold" size="0 35 100% 100%">
<object
name="civHistoryText"
type="text"
font="sans-14"
textcolor="white"
text_align="left"
size="10 0 100% 100%"
/>
</object>
</object>
<!-- Structure tree page -->
<object
type="button"
style="StoneButton"
size="100%-429 100%-52 100%-229 100%-24"
name="structreeButton"
hotkey="structree"
>
<translatableAttribute id="caption">Structure Tree</translatableAttribute>
<action on="Press">switchToStrucTreePage();</action>
</object>
<!-- Close dialog -->
<object
type="button"
style="StoneButton"
size="100%-224 100%-52 100%-24 100%-24"
name="close"
hotkey="cancel"
>
<translatableAttribute id="caption">Close</translatableAttribute>
<action on="Press">closePage();</action>
</object>
</object>
</objects>

View File

@ -70,6 +70,14 @@ function unescapeText(text)
return text.replace(/\\\\/g, "\\").replace(/\\\[/g, "\[");
}
/**
* Prepends a backslash to all quotation marks.
*/
function escapeQuotation(text)
{
return text.replace(/"/g, "\\\"");
}
/**
* Merge players by team to remove duplicate Team entries, thus reducing the packet size of the lobby report.
*/

View File

@ -8,7 +8,13 @@
<include>common/sprites.xml</include>
<include>common/styles.xml</include>
<include>civinfo/civinfo.xml</include>
<include>civinfo/setup.xml</include>
<include>civinfo/sprites.xml</include>
<include>reference/common/sprites.xml</include>
<include>reference/common/styles.xml</include>
<include>reference/civinfo/sprites.xml</include>
<include>reference/civinfo/styles.xml</include>
<include>reference/civinfo/civinfo.xml</include>
<include>reference/civinfo/setup.xml</include>
<include>reference/common/setup.xml</include>
</page>

View File

@ -0,0 +1,122 @@
class CivInfoPage extends ReferencePage
{
constructor(data)
{
super();
this.civSelection = new CivSelectDropdown(this.civData);
this.civSelection.registerHandler(this.selectCiv.bind(this));
this.gameplaySection = new GameplaySection(this);
this.historySection = new HistorySection(this);
let structreeButton = new StructreeButton(this);
let closeButton = new CloseButton(this);
Engine.SetGlobalHotkey("civinfo", "Press", this.closePage.bind(this));
}
switchToStructreePage()
{
Engine.PopGuiPage({ "civ": this.activeCiv, "nextPage": "page_structree.xml" });
}
closePage()
{
Engine.PopGuiPage({ "civ": this.activeCiv, "page": "page_civinfo.xml" });
}
/**
* Updates the GUI after the user selected a civ from dropdown.
*
* @param code {string}
*/
selectCiv(civCode)
{
this.setActiveCiv(civCode);
let civInfo = this.civData[civCode];
if(!civInfo)
error(sprintf("Error loading civ data for \"%(code)s\"", { "code": civCode }));
// Update civ gameplay display
this.gameplaySection.update(civInfo);
// Update civ history display
this.historySection.update(civInfo);
}
/**
* Give the first character a larger font.
*/
bigFirstLetter(text, size)
{
return setStringTags(text[0], { "font": "sans-bold-" + (size + 6) }) + text.substring(1);
}
/**
* Set heading font - bold and mixed caps
*
* @param text {string}
* @param size {number} - Font size
* @returns {string}
*/
formatHeading(text, size)
{
let textArray = [];
for (let word of text.split(" "))
{
let wordCaps = word.toUpperCase();
// Usually we wish a big first letter, however this isn't always desirable. Check if
// `.toLowerCase()` changes the character to avoid false positives from special characters.
if (word.length && word[0].toLowerCase() != word[0])
word = this.bigFirstLetter(wordCaps, size);
textArray.push(setStringTags(word, { "font": "sans-bold-" + size }));
}
return textArray.join(" ");
}
/**
* Returns a styled concatenation of the Name, History, and Description of the given object.
*
* @param obj {Object}
* @returns {string}
*/
formatEntry(obj)
{
if (!obj.Name)
return "";
let history_icon = "";
if (obj.History)
history_icon = '[icon="iconInfo" tooltip="' + escapeQuotation(obj.History) + '" tooltip_style="civInfoTooltip"]';
let description = "";
if (obj.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, });
return sprintf(
// Translation: An entry in the CivInfo Page. The newline and indentation of the description is handled elsewhere.
// Example:
// > • Name of a Special Something (i)
// > 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,
}
);
}
}
CivInfoPage.prototype.CloseButtonTooltip =
translate("%(hotkey)s: Close Civilization Overview.");
CivInfoPage.prototype.SectionHeaderSize = 16;
CivInfoPage.prototype.SubsectionHeaderSize = 12;

View File

@ -0,0 +1,30 @@
class GameplaySection
{
constructor(page)
{
this.page = page;
this.CivGameplayHeading = Engine.GetGUIObjectByName('civGameplayHeading');
this.BonusesSubsection = new BonusesSubsection(this.page);
this.HeroesSubsection = new HeroesSubsection(this.page);
this.StructuresSubsection = new StructuresSubsection(this.page);
this.TechnologiesSubsection = new TechnologiesSubsection(this.page);
}
update(civInfo)
{
this.CivGameplayHeading.caption =
this.page.formatHeading(
sprintf(this.headingCaption, { "civilization": civInfo.Name }),
this.page.SectionHeaderSize
);
this.BonusesSubsection.update(civInfo);
this.TechnologiesSubsection.update(civInfo);
this.StructuresSubsection.update(civInfo);
this.HeroesSubsection.update(civInfo);
}
}
GameplaySection.prototype.headingCaption =
translate("%(civilization)s Gameplay");

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<object size="25 50 100%-25 65%-5">
<object type="image" sprite="TranslucentPanelThinBorder" size="0 5 100% 40">
<object
name="civGameplayHeading"
type="text"
font="sans-bold-16"
textcolor="white"
text_align="left"
text_valign="center"
size="10 0 100% 100%"
/>
</object>
<include file="gui/reference/civinfo/Sections/Subsections/BonusesSubsection.xml"/>
<include file="gui/reference/civinfo/Sections/Subsections/TechnologiesSubsection.xml"/>
<include file="gui/reference/civinfo/Sections/Subsections/StructuresSubsection.xml"/>
<include file="gui/reference/civinfo/Sections/Subsections/HeroesSubsection.xml"/>
</object>

View File

@ -0,0 +1,23 @@
class HistorySection
{
constructor(page)
{
this.page = page;
this.CivHistoryHeading = Engine.GetGUIObjectByName('civHistoryHeading');
this.CivHistoryText = Engine.GetGUIObjectByName('civHistoryText');
}
update(civInfo)
{
this.CivHistoryHeading.caption =
this.page.formatHeading(
sprintf(this.headingCaption, { "civilization": civInfo.Name }),
this.page.SectionHeaderSize
);
this.CivHistoryText.caption = civInfo.History;
}
}
HistorySection.prototype.headingCaption =
translate("History of the %(civilization)s");

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<object size="25 65% 100%-25 100%-56">
<object type="image" sprite="TranslucentPanelThinBorder" size="0 0 100% 35">
<object
name="civHistoryHeading"
type="text"
font="sans-bold-16"
textcolor="white"
text_align="left"
text_valign="center"
size="10 0 100% 100%"
/>
</object>
<object type="image" sprite="ModernDarkBoxGold" size="0 35 100% 100%">
<object
name="civHistoryText"
type="text"
font="sans-14"
scrollbar="true"
scrollbar_style="ModernScrollBar"
textcolor="white"
text_align="left"
size="10 0 100% 100%"
/>
</object>
</object>

View File

@ -0,0 +1,7 @@
class Subsection
{
constructor(page)
{
this.page = page;
}
}

View File

@ -0,0 +1,35 @@
class BonusesSubsection extends Subsection
{
constructor(page)
{
super(page);
this.CivBonuses = Engine.GetGUIObjectByName("civBonuses");
}
update(civInfo)
{
let civBonuses = civInfo.CivBonuses.map(bonus => this.page.formatEntry(bonus));
civBonuses.unshift(
this.page.formatHeading(
this.HeadingCivBonusCaption(civBonuses.length),
this.page.SubsectionHeaderSize
)
);
let teamBonuses = civInfo.TeamBonuses.map(bonus => this.page.formatEntry(bonus));
teamBonuses.unshift(
this.page.formatHeading(
this.HeadingTeamBonusCaption(teamBonuses.length),
this.page.SubsectionHeaderSize
)
);
this.CivBonuses.caption = civBonuses.join("\n") + "\n\n" + teamBonuses.join("\n");
}
}
BonusesSubsection.prototype.HeadingCivBonusCaption =
count => translatePlural("Civilization Bonus", "Civilization Bonuses", count);
BonusesSubsection.prototype.HeadingTeamBonusCaption =
count => translatePlural("Team Bonus", "Team Bonuses", count);

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<object type="image" sprite="ModernDarkBoxGold" size="0 45 33%-5 100%">
<object
name="civBonuses"
type="text"
style="SubsectionText"
size="10 1 100%-10 100%-1"
/>
</object>

View File

@ -0,0 +1,30 @@
class HeroesSubsection extends Subsection
{
constructor(page)
{
super(page);
this.CivHeroes = Engine.GetGUIObjectByName("civHeroes");
}
update(civInfo)
{
let heroes = [];
for (let faction of civInfo.Factions)
Array.prototype.push.apply(
heroes,
faction.Heroes.map(hero => this.page.formatEntry(hero))
);
heroes.unshift(
this.page.formatHeading(
this.HeadingCaption(heroes.length),
this.page.SubsectionHeaderSize
)
);
this.CivHeroes.caption = heroes.join("\n");
}
}
HeroesSubsection.prototype.HeadingCaption =
count => translatePlural("Hero", "Heroes", count);

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<object type="image" sprite="ModernDarkBoxGold" size="75%+5 45 100% 100%">
<object
name="civHeroes"
type="text"
style="SubsectionText"
size="10 1 100%-10 100%-1"
/>
</object>

View File

@ -0,0 +1,24 @@
class StructuresSubsection extends Subsection
{
constructor(page)
{
super(page);
this.CivStructures = Engine.GetGUIObjectByName("civStructures");
}
update(civInfo)
{
let structures = civInfo.Structures.map(bonus => this.page.formatEntry(bonus));
structures.unshift(
this.page.formatHeading(
this.HeadingCaption(structures.length),
this.page.SubsectionHeaderSize
)
);
this.CivStructures.caption = structures.join("\n");
}
}
StructuresSubsection.prototype.HeadingCaption =
count => translatePlural("Special Structure", "Special Structures", count);

View File

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

View File

@ -0,0 +1,30 @@
class TechnologiesSubsection extends Subsection
{
constructor(page)
{
super(page);
this.CivTechs = Engine.GetGUIObjectByName("civTechs");
}
update(civInfo)
{
let techs = [];
for (let faction of civInfo.Factions)
Array.prototype.push.apply(
techs,
faction.Technologies.map(tech => this.page.formatEntry(tech))
);
techs.unshift(
this.page.formatHeading(
this.HeadingCaption(techs.length),
this.page.SubsectionHeaderSize
)
);
this.CivTechs.caption = techs.join("\n");
}
}
TechnologiesSubsection.prototype.HeadingCaption =
count => translatePlural("Special Technology", "Special Technologies", count);

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<object type="image" sprite="ModernDarkBoxGold" size="33%+5 45 75%-5 70%-5">
<object
name="civTechs"
type="text"
style="SubsectionText"
size="10 1 100%-10 100%-1"
/>
</object>

View File

@ -0,0 +1,12 @@
/**
* Initialize the dropdown containing all the available civs.
*/
function init(data = {})
{
g_Page = new CivInfoPage(data);
if (data.civ)
g_Page.civSelection.selectCiv(data.civ);
else
g_Page.civSelection.selectFirstCiv();
}

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script directory="gui/common/"/>
<script directory="gui/reference/common/"/>
<script directory="gui/reference/common/Buttons/"/>
<script directory="gui/reference/common/Dropdowns/"/>
<script directory="gui/reference/civinfo/"/>
<script directory="gui/reference/civinfo/Sections/"/>
<script directory="gui/reference/civinfo/Sections/Subsections/"/>
<!-- Add a translucent black background to fade out the menu page -->
<object type="image" sprite="BackgroundTranslucent"/>
<object type="image" style="ModernDialog" size="50%-500 50%-368 50%+500 50%+370">
<object style="TitleText" type="text" size="50%-128 -18 50%+128 14">
<translatableAttribute id="caption">Civilization Overview</translatableAttribute>
</object>
<!-- Civ selection -->
<include file="gui/reference/common/Dropdowns/CivSelectDropdown.xml"/>
<!-- Civ gameplay data display -->
<include file="gui/reference/civinfo/Sections/GameplaySection.xml"/>
<!-- Civ history display -->
<include file="gui/reference/civinfo/Sections/HistorySection.xml"/>
<!-- Buttons -->
<include file="gui/reference/common/Buttons/StructreeButton.xml"/>
<include file="gui/reference/common/Buttons/CloseButton.xml"/>
</object>
</objects>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<styles>
<style name="SubsectionText"
font="sans-14"
scrollbar="true"
scrollbar_style="ModernScrollBar"
textcolor="white"
text_align="left"
/>
</styles>

View File

@ -0,0 +1,27 @@
class StructreeButton
{
constructor(parentPage)
{
this.parentPage = parentPage;
this.civInfoButton = Engine.GetGUIObjectByName("structreeButton");
this.civInfoButton.onPress = this.onPress.bind(this);
this.civInfoButton.caption = this.Caption;
this.civInfoButton.tooltip = colorizeHotkey(this.Tooltip, this.Hotkey);
}
onPress()
{
Engine.PopGuiPage({ "civ": this.parentPage.activeCiv, "nextPage": "page_structree.xml" });
}
}
StructreeButton.prototype.Caption =
translate("Structure Tree");
StructreeButton.prototype.Hotkey =
"structree";
StructreeButton.prototype.Tooltip =
translate("%(hotkey)s: Switch to Structure Tree.");

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<object
name="structreeButton"
type="button"
style="StoneButton"
size="100%-421 100%-44 100%-221 100%-16"
hotkey="structree"
/>

View File

@ -250,13 +250,13 @@
"extractor": "javascript",
"filemasks": [
"globalscripts/**.js",
"gui/civinfo/**.js",
"gui/common/**.js",
"gui/credits/**.js",
"gui/loadgame/**.js",
"gui/locale/**.js",
"gui/options/**.js",
"gui/pregame/**.js",
"gui/reference/civinfo/**.js",
"gui/reference/common/**.js",
"gui/reference/structree/**.js",
"gui/reference/viewer/**.js",
@ -285,13 +285,13 @@
"extractor": "xml",
"filemasks": [
"globalscripts/**.xml",
"gui/civinfo/**.xml",
"gui/common/**.xml",
"gui/credits/**.xml",
"gui/loadgame/**.xml",
"gui/locale/**.xml",
"gui/options/**.xml",
"gui/pregame/**.xml",
"gui/reference/civinfo/**.xml",
"gui/reference/structree/**.xml",
"gui/reference/viewer/**.xml",
"gui/replaymenu/**.xml",