Import s0600204's structree.

Taken from https://github.com/s0600204/0ad-structree-mod with some
improvements from my fork.
On small resolutions some buildings might not be fully visible (see
#3038).

This was SVN commit r16276.
This commit is contained in:
leper 2015-02-07 01:37:05 +00:00
parent f6903393bd
commit 932dbc7221
11 changed files with 1277 additions and 24 deletions

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/modern/setup.xml</include>
<include>common/modern/styles.xml</include>
<include>common/modern/sprites.xml</include>
<include>common/setup_resources.xml</include>
<include>common/sprite1.xml</include>
<include>common/styles.xml</include>
<include>common/common_sprites.xml</include>
<include>common/common_styles.xml</include>
<include>structree/styles.xml</include>
<include>structree/sprites.xml</include>
<include>structree/structree.xml</include>
<include>structree/setup.xml</include>
</page>

View File

@ -139,6 +139,66 @@
size="60 50%-100 300 50%+100"
hidden="true"
>
<!-- submenuSinglePlayer -->
<object name="submenuLearn"
type="image"
size="0 4 100%-4 100%-4"
tooltip_style="pgToolTip"
hidden="true"
>
<!-- LEARN TO PLAY BUTTON -->
<object name="menuManualButton"
type="button"
style="StoneButtonFancy"
size="0 0 100% 28"
tooltip_style="pgToolTip"
>
<translatableAttribute id="caption">Manual</translatableAttribute>
<translatableAttribute id="tooltip">Open the 0 A.D. Game Manual.</translatableAttribute>
<action on="Press">
closeMenu();
<![CDATA[
Engine.PushGuiPage("page_manual.xml", {"page":"manual/intro", "title":getManual(), "url":"http://trac.wildfiregames.com/wiki/0adManual"});
]]>
</action>
</object>
<!-- STRUCTREE BUTTON -->
<object name="menuStrucTreeButton"
style="StoneButtonFancy"
type="button"
size="0 32 100% 60"
tooltip_style="pgToolTip"
>
<translatableAttribute id="caption">Structure Tree</translatableAttribute>
<translatableAttribute id="tooltip">View the structure tree of civilizations featured in 0 A.D.</translatableAttribute>
<action on="Press">
closeMenu();
<![CDATA[
Engine.PushGuiPage("page_structree.xml");
]]>
</action>
</object>
<!-- HISTORY BUTTON -->
<object name="menuHistoryButton"
style="StoneButtonFancy"
type="button"
size="0 64 100% 92"
tooltip_style="pgToolTip"
>
<translatableAttribute id="caption">History</translatableAttribute>
<translatableAttribute id="tooltip">Learn about the many civilizations featured in 0 A.D.</translatableAttribute>
<action on="Press">
closeMenu();
<![CDATA[
Engine.PushGuiPage("page_civinfo.xml");
]]>
</action>
</object>
</object>
<!-- submenuSinglePlayer -->
<object name="submenuSinglePlayer"
type="image"
@ -374,20 +434,19 @@
size="8 156 100%-8 356"
ghost="false"
>
<!-- LEARN TO PLAY BUTTON -->
<!-- LEARN BUTTON -->
<object name="menuLearnToPlayButton"
type="button"
style="StoneButtonFancy"
type="button"
size="4 4 100%-4 32"
tooltip_style="pgToolTip"
>
<translatableAttribute id="caption">Learn To Play</translatableAttribute>
<translatableAttribute id="tooltip">Open the 0 A.D. Game Manual.</translatableAttribute>
<translatableAttribute id="tooltip">Learn how to play, discover the technology trees, and the history behind the civilizations</translatableAttribute>
<action on="Press">
closeMenu();
<![CDATA[
Engine.PushGuiPage("page_manual.xml", {"page":"manual/intro", "title":getManual(), "url":"http://trac.wildfiregames.com/wiki/0adManual"});
]]>
openMenu("submenuLearn", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
</action>
</object>
@ -436,28 +495,11 @@
</action>
</object>
<!-- HISTORY BUTTON -->
<object name="menuHistoryButton"
style="StoneButtonFancy"
type="button"
size="4 132 100%-4 160"
tooltip_style="pgToolTip"
>
<translatableAttribute id="caption">History</translatableAttribute>
<translatableAttribute id="tooltip">Learn about the many civilizations featured in 0 A.D.</translatableAttribute>
<action on="Press">
closeMenu();
<![CDATA[
Engine.PushGuiPage("page_civinfo.xml");
]]>
</action>
</object>
<!-- EXIT BUTTON -->
<object name="menuExitButton"
type="button"
style="StoneButtonFancy"
size="4 164 100%-4 192"
size="4 132 100%-4 160"
tooltip_style="pgToolTip"
>
<translatableAttribute id="caption">Exit</translatableAttribute>

View File

@ -0,0 +1,298 @@
var g_DrawLimits = {}; // GUI limits. Populated by predraw()
/**
* Draw the structree
*
* (Actually resizes and changes visibility of elements, and populates text)
*/
function draw()
{
// Set basic state (positioning of elements mainly), but only once
if (!Object.keys(g_DrawLimits).length)
predraw();
var defWidth = 96;
var defMargin = 4;
var phaseList = g_ParsedData.phaseList;
Engine.GetGUIObjectByName("civEmblem").sprite = "stretched:"+g_CivData[g_SelectedCiv].Emblem;
Engine.GetGUIObjectByName("civName").caption = g_CivData[g_SelectedCiv].Name;
Engine.GetGUIObjectByName("civHistory").caption = g_CivData[g_SelectedCiv].History;
let i = 0;
for (let pha of phaseList)
{
let s = 0;
let y = 0;
for (let stru of g_CivData[g_SelectedCiv].buildList[pha])
{
let thisEle = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]");
if (thisEle === undefined)
{
error("\""+g_SelectedCiv+"\" has more structures in phase "+pha+" than can be supported by the current GUI layout");
break;
}
let c = 0;
let rowCounts = [];
stru = g_ParsedData.structures[stru];
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_icon").sprite = "stretched:session/portraits/"+stru.icon;
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_icon").tooltip = assembleTooltip(stru);
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_name").caption = translate(stru.name.specific);
thisEle.hidden = false;
for (let r in g_DrawLimits[pha].prodQuant)
{
let p = 0;
r = +r; // force int
let prod_pha = phaseList[phaseList.indexOf(pha) + r];
if (stru.production.units[prod_pha])
{
for (let prod of stru.production.units[prod_pha])
{
prod = g_ParsedData.units[prod];
if (!drawProdIcon(i, s, r, p, prod))
break;
p++;
}
}
if (stru.wallset && prod_pha == pha)
{
for (let prod of [stru.wallset.gate, stru.wallset.tower])
{
if (!drawProdIcon(i, s, r, p, prod))
break;
p++;
}
}
if (stru.production.technology[prod_pha])
{
for (let prod of stru.production.technology[prod_pha])
{
prod = (prod.slice(0,5) == "phase") ? g_ParsedData.phases[prod] : g_ParsedData.techs[prod];
if (!drawProdIcon(i, s, r, p, prod))
break;
p++;
}
}
rowCounts[r] = p;
if (p>c)
c = p;
hideRemaining("phase["+i+"]_struct["+s+"]_row["+r+"]_prod[", p, "]");
}
let size = thisEle.size;
size.left = y;
size.right = size.left + ((c*24 < defWidth)?defWidth:c*24)+4;
y = size.right + defMargin;
thisEle.size = size;
let eleWidth = size.right - size.left;
let r;
for (r in rowCounts)
{
let wid = rowCounts[r] * 24 - 4;
let phaEle = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]");
size = phaEle.size;
size.left = (eleWidth - wid)/2;
phaEle.size = size;
}
++r;
hideRemaining("phase["+i+"]_struct["+s+"]_row[", r, "]");
++s;
}
hideRemaining("phase["+i+"]_struct[", s, "]");
++i;
}
}
function drawProdIcon(pha, s, r, p, prod)
{
var prodEle = Engine.GetGUIObjectByName("phase["+pha+"]_struct["+s+"]_row["+r+"]_prod["+p+"]");
if (prodEle === undefined)
{
error("The structures of \""+g_SelectedCiv+"\" have more production icons in phase "+pha+" than can be supported by the current GUI layout");
return false;
}
prodEle.sprite = "stretched:session/portraits/"+prod.icon;
prodEle.tooltip = assembleTooltip(prod);
prodEle.hidden = false;
return true;
}
/**
* Calculate row position offset (accounting for different number of prod rows per phase).
*/
function getPositionOffset(idx)
{
var phases = g_ParsedData.phaseList.length;
var size = 92*idx; // text, image and offset
size += 24 * (phases*idx - (idx-1)*idx/2); // phase rows (phase-currphase+1 per row)
return size;
}
function hideRemaining(prefix, idx, suffix)
{
let obj = Engine.GetGUIObjectByName(prefix+idx+suffix);
while (obj)
{
obj.hidden = true;
++idx;
obj = Engine.GetGUIObjectByName(prefix+idx+suffix);
}
}
/**
* Positions certain elements that only need to be positioned once
* (as <repeat> does not reposition automatically).
*
* Also detects limits on what the GUI can display by iterating through the set
* elements of the GUI. These limits are then used by draw().
*/
function predraw()
{
var phaseList = g_ParsedData.phaseList;
var initIconSize = Engine.GetGUIObjectByName("phase[0]_struct[0]_row[0]_prod[0]").size;
let phaseCount = phaseList.length;
let i = 0;
for (let pha of phaseList)
{
let offset = getPositionOffset(i);
// Align the phase row
Engine.GetGUIObjectByName("phase["+i+"]").size = "8 16+"+offset+" 100% 100%";
// Set phase icon
let phaseIcon = Engine.GetGUIObjectByName("phase["+i+"]_phase");
phaseIcon.sprite = "stretched:session/portraits/"+g_ParsedData.phases[pha].icon;
phaseIcon.size = "16 32+"+offset+" 48+16 48+32+"+offset;
// Position prod bars
let j = 1;
for (; j < phaseCount - i; ++j)
{
let prodBar = Engine.GetGUIObjectByName("phase["+i+"]_bar["+(j-1)+"]");
prodBar.size = "40 1+"+(24*j)+"+98+"+offset+" 100%-8 1+"+(24*j)+"+98+"+offset+"+22";
// Set phase icon
let prodBarIcon = Engine.GetGUIObjectByName("phase["+i+"]_bar["+(j-1)+"]_icon");
prodBarIcon.sprite = "stretched:session/portraits/"+g_ParsedData.phases[phaseList[i+j]].icon;
}
// Hide remaining prod bars
hideRemaining("phase["+i+"]_bar[", j-1, "]");
let s = 0;
let ele = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]");
g_DrawLimits[pha] = {
structQuant: 0,
prodQuant: []
};
do
{
// Position production icons
for (let r in phaseList.slice(phaseList.indexOf(pha)))
{
let p=1;
let prodEle = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]_prod["+p+"]");
do
{
let prodsize = prodEle.size;
prodsize.left = (initIconSize.right+4) * p;
prodsize.right = (initIconSize.right+4) * (p+1) - 4;
prodEle.size = prodsize;
p++;
prodEle = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]_prod["+p+"]");
} while (prodEle !== undefined);
// Set quantity of productions in this row
g_DrawLimits[pha].prodQuant[r] = p;
// Position the prod row
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+r+"]").size = "4 100%-"+24*(phaseCount - i - r)+" 100%-4 100%";
}
// Hide unused struct rows
for (let j = phaseCount - i; j < phaseCount; ++j)
Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]_row["+j+"]").hidden = true;
let size = ele.size;
size.bottom += Object.keys(g_DrawLimits[pha].prodQuant).length*24;
ele.size = size;
s++;
ele = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]");
} while (ele !== undefined);
// Set quantity of structures in each phase
g_DrawLimits[pha].structQuant = s;
++i;
}
hideRemaining("phase[", i, "]");
hideRemaining("phase[", i, "]_bar");
}
/**
* Assemble a tooltip text
*
* @param template Information about a Unit, a Structure or a Technology
*
* @return The tooltip text, formatted.
*/
function assembleTooltip(template)
{
var txt = getEntityNamesFormatted(template);
txt += '\n' + getEntityCostTooltip(template, 1);
if (template.tooltip)
txt += '\n' + txtFormats.body[0] + translate(template.tooltip) + txtFormats.body[1];
if (template.auras)
for (let aura in template.auras)
txt += '\n' + sprintf(translate("%(auralabel)s %(aurainfo)s"), {
auralabel: txtFormats.header[0] + sprintf(translate("%(auraname)s:"), {
auraname: translate(aura)
}) + txtFormats.header[1],
aurainfo: txtFormats.body[0] + translate(template.auras[aura]) + txtFormats.body[1]
});
if (template.health)
txt += '\n' + sprintf(translate("%(label)s %(details)s"), {
label: txtFormats.header[0] + translate("Health:") + txtFormats.header[1],
details: template.health
});
if (template.healer)
txt += '\n' + getHealerTooltip(template);
if (template.attack)
txt += '\n' + getAttackTooltip(template);
if (template.armour)
txt += '\n' + getArmorTooltip(template.armour);
txt += '\n' + getSpeedTooltip(template);
if (template.gather)
{
var rates = [];
for (let type in template.gather)
rates.push(sprintf(translate("%(resourceIcon)s %(rate)s"), {
resourceIcon: getCostComponentDisplayName(type),
rate: template.gather[type]
}));
txt += '\n' + sprintf(translate("%(label)s %(details)s"), {
label: txtFormats.header[0] + translate("Gather Rates:") + txtFormats.header[1],
details: rates.join(" ")
});
}
return txt;
}

View File

@ -0,0 +1,89 @@
var g_TemplateData = {};
var g_TechnologyData = {};
function loadTemplate(templateName)
{
if (!(templateName in g_TemplateData))
{
// We need to clone the template because we want to perform some translations.
var data = clone(Engine.GetTemplate(templateName));
translateObjectKeys(data, ["GenericName", "Tooltip"]);
g_TemplateData[templateName] = data;
}
return g_TemplateData[templateName];
}
function loadTechData(templateName)
{
if (!(templateName in g_TechnologyData))
{
var filename = "simulation/data/technologies/" + templateName + ".json";
var data = Engine.ReadJSONFile(filename);
translateObjectKeys(data, ["genericName", "tooltip"]);
g_TechnologyData[templateName] = data;
}
return g_TechnologyData[templateName];
}
/**
* Fetch a value from an entity's template
*
* @param templateName The template to retreive the value from
* @param keypath The path to the value to be fetched. "Identity/GenericName"
* is equivalent to {"Identity":{"GenericName":"FOOBAR"}}
*
* @return The content requested at the key-path defined, or a blank array if
* not found
*/
function fetchValue(templateName, keypath)
{
var keys = keypath.split("/");
var template = loadTemplate(templateName);
let k = 0;
for (; k < keys.length-1; ++k)
{
if (template[keys[k]] === undefined)
return [];
template = template[keys[k]];
}
if (template[keys[k]] === undefined)
return [];
return template[keys[k]];
}
/**
* Fetch tokens from an entity's template
* @return An array containing all tokens if found, else an empty array
* @see fetchValue
*/
function fetchTokens(templateName, keypath)
{
var val = fetchValue(templateName, keypath);
if (!("_string" in val))
return [];
return val._string.split(" ");
}
function depath(path)
{
return path.slice(path.lastIndexOf("/")+1);
}
/**
* This is needed because getEntityCostTooltip in tooltip.js needs to get
* the template data of the different wallSet pieces. In the session this
* function does some caching, but here we do that in loadTemplate already.
*/
function GetTemplateData(templateName)
{
var template = loadTemplate(templateName);
return GetTemplateDataHelper(template);
}

View File

@ -0,0 +1,333 @@
/**
* Calculates gather rates.
*
* All available rates that have a value greater than 0 are summed and averaged
*/
function getGatherRates(templateName)
{
// TODO: It would be nice to use the gather rates present in the templates
// instead of hard-coding the possible rates here.
// We ignore ruins here, as those are not that common and would skew the results
var types = {
"food": ["food", "food.fish", "food.fruit", "food.grain", "food.meat", "food.milk"],
"wood": ["wood", "wood.tree"],
"stone": ["stone", "stone.rock"],
"metal": ["metal", "metal.ore"]
};
var rates = {};
for (let type in types)
{
let count, rate;
[rate, count] = types[type].reduce(function(sum, t) {
let r = +fetchValue(templateName, "ResourceGatherer/Rates/"+t);
return [sum[0] + (r > 0 ? r : 0), sum[1] + (r > 0 ? 1 : 0)];
}, [0, 0]);
if (rate > 0)
rates[type] = Math.round(rate / count * 100) / 100;
}
if (!Object.keys(rates).length)
return null;
return rates;
}
function loadUnit(templateName)
{
var template = loadTemplate(templateName);
var unit = GetTemplateDataHelper(template);
unit.phase = false;
if (unit.requiredTechnology)
{
if (unit.requiredTechnology.slice(0, 5) == "phase")
unit.phase = unit.requiredTechnology;
else if (unit.requiredTechnology.length)
unit.required = unit.requiredTechnology;
}
unit.gather = getGatherRates(templateName);
if (template.Heal)
unit.healer = {
"Range": +template.Heal.Range || 0,
"HP": +template.Heal.HP || 0,
"Rate": +template.Heal.Rate || 0
};
if (template.Builder && template.Builder.Entities._string)
for (let build of template.Builder.Entities._string.split(" "))
{
build = build.replace("{civ}", g_SelectedCiv);
if (g_Lists.structures.indexOf(build) < 0)
g_Lists.structures.push(build);
}
return unit;
}
function loadStructure(templateName)
{
var template = loadTemplate(templateName);
var structure = GetTemplateDataHelper(template);
structure.phase = false;
if (structure.requiredTechnology)
{
if (structure.requiredTechnology.slice(0, 5) == "phase")
structure.phase = structure.requiredTechnology;
else if (structure.requiredTechnology.length)
structure.required = structure.requiredTechnology;
}
structure.production = {
"technology": [],
"units": []
};
if (template.ProductionQueue)
{
if (template.ProductionQueue.Entities && template.ProductionQueue.Entities._string)
for (let build of template.ProductionQueue.Entities._string.split(" "))
{
build = build.replace("{civ}", g_SelectedCiv);
structure.production.units.push(build);
if (g_Lists.units.indexOf(build) < 0)
g_Lists.units.push(build);
}
if (template.ProductionQueue.Technologies && template.ProductionQueue.Technologies._string)
for (let research of template.ProductionQueue.Technologies._string.split(" "))
{
structure.production.technology.push(research);
if (g_Lists.techs.indexOf(research) < 0)
g_Lists.techs.push(research);
}
}
if (structure.wallSet)
{
structure.wallset = {};
// Note: Assume wall segments of all lengths have the same armor
structure.armour = loadStructure(structure.wallSet.templates["long"]).armour;
let health;
for (let wSegm in structure.wallSet.templates)
{
let wPart = loadStructure(structure.wallSet.templates[wSegm]);
structure.wallset[wSegm] = wPart;
for (let research of wPart.production.technology)
structure.production.technology.push(research);
if (["gate", "tower"].indexOf(wSegm) != -1)
continue;
if (!health)
{
health = { "min": wPart.health, "max": wPart.health };
continue;
}
if (health.min > wPart.health)
health.min = wPart.health;
else if (health.max < wPart.health)
health.max = wPart.health;
}
if (health.min == health.max)
structure.health = health.min;
else
structure.health = sprintf(translate("%(val1)s to %(val2)s"), {
val1: health.min,
val2: health.max
});
}
return structure;
}
function loadTechnology(techName)
{
var template = loadTechData(techName);
var tech = GetTechnologyDataHelper(template, g_SelectedCiv);
tech.reqs = {};
if (template.pair !== undefined)
tech.pair = template.pair;
if (template.requirements !== undefined)
{
for (let op in template.requirements)
{
let val = template.requirements[op];
let req = calcReqs(op, val);
switch (op)
{
case "tech":
tech.reqs.generic = [ req ];
break;
case "civ":
tech.reqs[req] = [];
break;
case "any":
if (req[0].length > 0)
for (let r of req[0])
{
let v = req[0][r];
if (typeof r == "number")
tech.reqs[v] = [];
else
tech.reqs[r] = v;
}
if (req[1].length > 0)
tech.reqs.generic = req[1];
break;
case "all":
for (let r of req[0])
tech.reqs[r] = req[1];
break;
}
}
}
if (template.supersedes !== undefined)
{
if (tech.reqs.generic !== undefined)
tech.reqs.generic.push(template.supersedes);
else
for (let ck of Object.keys(tech.reqs))
tech.reqs[ck].push(template.supersedes);
}
return tech;
}
function loadPhase(phaseCode)
{
var template = loadTechData(phaseCode);
var phase = GetTechnologyDataHelper(template, g_SelectedCiv);
phase.actualPhase = "";
return phase;
}
function loadTechnologyPair(pairCode)
{
var pairInfo = loadTechData(pairCode);
return {
"techs": [ pairInfo.top, pairInfo.bottom ],
"req": (pairInfo.supersedes !== undefined) ? pairInfo.supersedes : ""
};
}
/**
* Calculate the prerequisite requirements of a technology.
* Works recursively if needed.
*
* @param op The base operation. Can be "civ", "tech", "all" or "any".
* @param val The value associated with the above operation.
*
* @return Sorted requirments.
*/
function calcReqs(op, val)
{
switch (op)
{
case "civ":
case "tech":
// nothing needs doing
break;
case "all":
case "any":
let t = [];
let c = [];
for (let nv of val)
{
for (let o in nv)
{
let v = nv[o];
let r = calcReqs(o, v);
switch (o)
{
case "civ":
c.push(r);
break;
case "tech":
t.push(r);
break;
case "any":
c = c.concat(r[0]);
t = t.concat(r[1]);
break;
case "all":
for (let ci in r[0])
c[ci] = r[1];
t = t;
}
}
}
return [ c, t ];
default:
warn("Unknown reqs operator: "+op);
}
return val;
}
/**
* Unravel phases
*
* @param techs The current available store of techs
*
* @return List of phases
*/
function unravelPhases(techs)
{
var phaseList = [];
for (let techcode in techs)
{
let techdata = techs[techcode];
if (!("generic" in techdata.reqs) || techdata.reqs.generic.length < 2)
continue;
let reqTech = techs[techcode].reqs.generic[1];
// Tech that can't be researched anywhere
if (!(reqTech in techs))
continue;
if (!("generic" in techs[reqTech].reqs))
continue;
let reqPhase = techs[reqTech].reqs.generic[0];
let myPhase = techs[techcode].reqs.generic[0];
if (reqPhase == myPhase || depath(reqPhase).slice(0,5) !== "phase" || depath(myPhase).slice(0,5) !== "phase")
continue;
let reqPhasePos = phaseList.indexOf(reqPhase);
let myPhasePos = phaseList.indexOf(myPhase);
if (phaseList.length === 0)
phaseList = [reqPhase, myPhase];
else if (reqPhasePos < 0 && myPhasePos > -1)
phaseList.splice(myPhasePos, 0, reqPhase);
else if (myPhasePos < 0 && reqPhasePos > -1)
phaseList.splice(reqPhasePos+1, 0, myPhase);
}
return phaseList;
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="phase_rows">
<repeat count="4" var="k">
<object name="phase[k]">
<repeat count="11" var="s">
<object type="image" style="StructBox" name="phase[k]_struct[s]">
<object type="text" style="StructNameSpecific" name="phase[k]_struct[s]_name"/>
<object type="image" style="StructIcon" name="phase[k]_struct[s]_icon"
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
/>
<repeat count="4" var="r">
<object name="phase[k]_struct[s]_row[r]">
<repeat count="16" var="p">
<object type="image" style="ProdBox" name="phase[k]_struct[s]_row[r]_prod[p]"/>
</repeat>
</object>
</repeat>
</object>
</repeat>
</object>
</repeat>
</object>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<setup>
<tooltip name="treeTooltip"
anchor="top"
buffer_zone="4"
delay="50"
font="sans-14"
maxwidth="480"
offset="16 24"
sprite="bkTooltip"
textcolor="white"
/>
</setup>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<sprites>
<sprite name="ProdBar">
<image
backcolor="136 136 136 102"
size="0 0 100% 100%"
/>
</sprite>
<sprite name="bkTooltip">
<image
backcolor="0 0 0 192"
size="0 0 100% 100%"
/>
<!-- sides -->
<image
texture="global/border/line_horiz.png"
texture_size="0 0 64 4"
size="2 0 100%-2 2"
/>
<image
texture="global/border/line_vert.png"
texture_size="0 0 4 64"
size="100%-2 2 100% 100%-2"
/>
<image
texture="global/border/line_horiz.png"
texture_size="0 0 64 4"
size="2 100%-2 100%-2 100%"
/>
<image
texture="global/border/line_vert.png"
texture_size="0 0 4 64"
size="0 2 2 100%-2"
/>
<!-- corners -->
<image
texture="global/border/line_corner_top_right.png"
texture_size="0 0 4 4"
size="100%-2 0 100% 2"
/>
<image
texture="global/border/line_corner_bottom_right.png"
texture_size="0 0 4 4"
size="100%-2 100%-2 100% 100%"
/>
<image
texture="global/border/line_corner_bottom_left.png"
texture_size="0 0 4 4"
size="0 100%-2 2 100%"
/>
<image
texture="global/border/line_corner_top_left.png"
texture_size="0 0 4 4"
size="0 0 2 2"
/>
</sprite>
</sprites>

View File

@ -0,0 +1,250 @@
var g_ParsedData = {
units: {},
structures: {},
techs: {},
phases: {}
};
var g_Lists = {};
var g_CivData = {};
var g_SelectedCiv = "";
/**
* Initialize the dropdown containing all the available civs
*/
function init()
{
g_CivData = loadCivData(true);
if (!Object.keys(g_CivData).length)
return;
var civList = [ { "name": civ.Name, "code": civ.Code } for each (civ in g_CivData) ];
// Alphabetically sort the list, ignoring case
civList.sort(sortNameIgnoreCase);
var civListNames = [ civ.name for each (civ in civList) ];
var civListCodes = [ civ.code for each (civ in civList) ];
// Set civ control
var civSelection = Engine.GetGUIObjectByName("civSelection");
civSelection.list = civListNames;
civSelection.list_data = civListCodes;
civSelection.selected = 0;
}
function selectCiv(civCode)
{
if (civCode === g_SelectedCiv || !g_CivData[civCode])
return;
g_SelectedCiv = civCode;
// If a buildList already exists, then this civ has already been parsed
if (g_CivData[g_SelectedCiv].buildList)
{
draw();
return;
}
g_Lists.units = [];
g_Lists.structures = [];
g_Lists.techs = [];
// get initial units
var startStructs = [];
for (let entity of g_CivData[civCode].StartEntities)
{
if (entity.Template.slice(0, 5) == "units")
g_Lists.units.push(entity.Template);
else if (entity.Template.slice(0, 6) == "struct")
{
g_Lists.structures.push(entity.Template);
startStructs.push(entity.Template);
}
}
// Load units and structures
var unitCount = 0;
do
{
for (let u of g_Lists.units)
if (!g_ParsedData.units[u])
g_ParsedData.units[u] = loadUnit(u);
unitCount = g_Lists.units.length;
for (let s of g_Lists.structures)
if (!g_ParsedData.structures[s])
g_ParsedData.structures[s] = loadStructure(s);
} while (unitCount < g_Lists.units.length);
// Load technologies
var techPairs = {};
for (let techcode of g_Lists.techs)
{
let realcode = depath(techcode);
if (realcode.slice(0,4) == "pair")
techPairs[techcode] = loadTechnologyPair(techcode);
else if (realcode.slice(0,5) == "phase")
g_ParsedData.phases[techcode] = loadPhase(techcode);
else
g_ParsedData.techs[techcode] = loadTechnology(techcode);
}
// Expand tech pairs
for (let paircode in techPairs)
{
let pair = techPairs[paircode];
for (let techcode of pair.techs)
{
let newTech = loadTechnology(techcode);
if (pair.req !== "")
{
if ("generic" in newTech.reqs)
newTech.reqs.generic.concat(techPairs[pair.req].techs);
else
for (let civkey of Object.keys(newTech.reqs))
newTech.reqs[civkey].concat(techPairs[pair.req].techs);
}
g_ParsedData.techs[techcode] = newTech;
}
}
// Establish phase order
g_ParsedData.phaseList = unravelPhases(g_ParsedData.techs);
for (let phasecode of g_ParsedData.phaseList)
{
let phaseInfo = loadTechData(phasecode);
g_ParsedData.phases[phasecode] = loadPhase(phasecode);
if ("requirements" in phaseInfo)
for (let op in phaseInfo.requirements)
{
let val = phaseInfo.requirements[op];
if (op == "any")
for (let v of val)
{
let k = Object.keys(v);
k = k[0];
v = v[k];
if (k == "tech" && v in g_ParsedData.phases)
g_ParsedData.phases[v].actualPhase = phasecode;
}
}
}
// Group production lists of structures by phase
for (let structCode of g_Lists.structures)
{
let structInfo = g_ParsedData.structures[structCode];
// If this building is shared with another civ,
// it may have already gone through the grouping process already
if (!Array.isArray(structInfo.production.technology))
continue;
// Expand tech pairs
for (let prod of structInfo.production.technology)
if (prod in techPairs)
structInfo.production.technology.splice(
structInfo.production.technology.indexOf(prod), 1,
techPairs[prod].techs[0], techPairs[prod].techs[1]
);
// Sort techs by phase
let newProdTech = {};
for (let prod of structInfo.production.technology)
{
let phase = "";
if (prod.slice(0,5) === "phase")
{
phase = g_ParsedData.phaseList.indexOf(g_ParsedData.phases[prod].actualPhase);
if (phase > 0)
phase = g_ParsedData.phaseList[phase - 1];
}
else if (g_SelectedCiv in g_ParsedData.techs[prod].reqs)
{
if (g_ParsedData.techs[prod].reqs[g_SelectedCiv].length > 0)
phase = g_ParsedData.techs[prod].reqs[g_SelectedCiv][0];
}
else if ("generic" in g_ParsedData.techs[prod].reqs)
{
phase = g_ParsedData.techs[prod].reqs.generic[0];
}
if (depath(phase).slice(0,5) !== "phase")
{
warn(prod+" doesn't have a specific phase set ("+structCode+")");
phase = structInfo.phase;
}
if (!(phase in newProdTech))
newProdTech[phase] = [];
newProdTech[phase].push(prod);
}
// Determine phase for units
let newProdUnits = {};
for (let prod of structInfo.production.units)
{
if (!(prod in g_ParsedData.units))
{
error(prod+" doesn't exist! ("+structCode+")");
continue;
}
let unit = g_ParsedData.units[prod];
let phase = "";
if (unit.phase !== false)
phase = unit.phase;
else if (unit.required !== undefined)
{
let reqs = g_ParsedData.techs[unit.required].reqs;
if (g_SelectedCiv in reqs)
phase = reqs[g_SelectedCiv][0];
else
phase = reqs.generic[0];
}
else if (structInfo.phase !== false)
phase = structInfo.phase;
else
phase = g_ParsedData.phaseList[0];
if (!(phase in newProdUnits))
newProdUnits[phase] = [];
newProdUnits[phase].push(prod);
}
g_ParsedData.structures[structCode].production = {
"technology": newProdTech,
"units": newProdUnits
};
}
// Determine the buildList for the civ (grouped by phase)
var buildList = {};
for (let structCode of g_Lists.structures)
{
if (!g_ParsedData.structures[structCode].phase || startStructs.indexOf(structCode) > -1)
g_ParsedData.structures[structCode].phase = g_ParsedData.phaseList[0];
let myPhase = g_ParsedData.structures[structCode].phase;
if (!(myPhase in buildList))
buildList[myPhase] = [];
buildList[myPhase].push(structCode);
}
g_CivData[g_SelectedCiv].buildList = buildList;
// Draw tree
draw();
}

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<objects>
<script file="gui/common/functions_civinfo.js"/>
<script file="gui/common/functions_utility.js"/>
<script file="gui/common/tooltips.js"/>
<script file="gui/structree/structree.js"/>
<script file="gui/structree/draw.js"/>
<script file="gui/structree/helper.js"/>
<script file="gui/structree/load.js"/>
<!-- Add a translucent black background to fade out the menu page -->
<object type="image" z="0" sprite="bkTranslucent"/>
<object type="image" style="ModernDialog" size="16 24 100%-16 100%-24">
<object style="TitleText" type="text" size="50%-128 -18 50%+128 14">
<translatableAttribute id="caption">Structure Tree</translatableAttribute>
</object>
<!-- Civ selection -->
<object size="16 10 100%-16 30">
<object
name="civSelectionHeading"
type="text"
font="sans-bold-16"
textcolor="white"
text_align="left"
size="100%-264 10 100%-160 48">
<translatableAttribute id="caption">Civilization:</translatableAttribute>
</object>
<object name="civSelection" type="dropdown" style="ModernDropDown" size="100%-160 8 100% 34">
<action on="SelectionChange">selectCiv(this.list_data[this.selected]);</action>
</object>
</object>
<object
name="civEmblem"
type="image"
size="6 6 96+6 96+6"
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
/>
<object
name="civName"
type="text"
font="sans-bold-20"
textcolor="white"
text_align="left"
text_valign="top"
size="104 32 100%-8 96"
/>
<object
name="civHistory"
type="text"
font="sans-12"
textcolor="white"
text_align="left"
text_valign="top"
size="104 52 100%-8 100%"
/>
<!-- Data display -->
<object size="0 54+64 100% 100%-54">
<repeat count="4" var="n">
<object name="phase[n]_phase" type="image"/>
<object name="phase[n]_bar">
<repeat count="4" var="k">
<object name="phase[n]_bar[k]" type="image" sprite="ProdBar">
<object name="phase[n]_bar[k]_icon" type="image" size="2 2 20 20"/>
</object>
</repeat>
</object>
</repeat>
<object type="image" style="TreeDisplay" size="48+16+8 0 100%-16 100%">
<include file="gui/structree/rows.xml"/>
</object>
</object>
<!-- Close dialog -->
<object
type="button"
style="StoneButton"
size="100%-164 100%-44 100%-16 100%-16"
>
<translatableAttribute id="caption">Close</translatableAttribute>
<action on="Press">Engine.PopGuiPage();</action>
</object>
</object>
</objects>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<styles>
<style name="TreeDisplay"
sprite="ModernDarkBoxGold"
scrollbar="true"
scrollbar_style="ModernScrollBar"
/>
<style name="StructNameSpecific"
textcolor="white"
font="sans-12"
text_align="center"
size="8 0 100%-8 20"
/>
<style name="StructBox"
sprite="ModernDarkBoxGold"
size="4 4 68 68+16"
/>
<style name="StructIcon"
size="50%-24 8+16 50%+24 8+16+48"
tooltip_style="treeTooltip"
/>
<style name="ProdBox"
size="0 0 20 20"
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
tooltip_style="treeTooltip"
/>
</styles>