Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml (revision 16550)
@@ -1,102 +1,102 @@
5
20
1
1
5
1
0.0
20.0
0.0
55.0
13.0
75.0
1200
2000
2.0
1
own neutral
House
40
80
0
15.0
1
0.1
Unit
Support Infantry
0
2
800
rubble/rubble_stone_2x2
Outpost
Build in neutral and own territories to scout areas of the map. Slowly loses health while in neutral territory.
-ConquestCritical
Village Outpost
structures/outpost.png
100
0
8
0
0
0.7
vision_outpost
decay_outpost
interface/complete/building/complete_tower.xml
attack/destruction/building_collapse_large.xml
6.0
0.6
18.0
- 2
+ 2
80
props/special/palisade_rocks_outpost.xml
structures/fndn_2x2.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml (revision 16550)
@@ -1,94 +1,95 @@
0.0
20.0
0.0
88.0
8.0
75.0
1200
2000
1.5
Human
0
1
Infantry
land-shore
Wall
+
120
100
8.0
2
0.1
Unit
Support Infantry
0
2
5000
rubble/rubble_stone_wall_tower
Wall Turret
Shoots arrows. Garrison to defend a city wall against attackers.
-ConquestCritical StoneWall Tower
structures/tower.png
phase_town
100
0
10
15
0
0.8
pair_walls_01
interface/complete/building/complete_tower.xml
attack/weapon/arrowfly.xml
attack/destruction/building_collapse_large.xml
20.0
false
20
65536
60
structures/fndn_2x2.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml (revision 16550)
@@ -1,108 +1,111 @@
0.0
20.0
0.0
72.0
8.0
75.0
1200
2000
1.5
Human
3
1
Infantry Ranged
Fortress
Fortress
80
+
+ 4000
+
10
300
0
650
8.0
20
0.075
Unit
Support Infantry Cavalry Siege
0
6
4200
rubble/rubble_stone_6x6
Fortress
Train heroes, champions, and siege weapons. Research siege weapon improvements. Garrison: 20.
GarrisonFortress
Defensive
City
Fortress
structures/fortress.png
phase_city
100
0
0
65
0
0.8
units/{civ}_mechanical_siege_ballista_packed
units/{civ}_mechanical_siege_scorpio_packed
units/{civ}_mechanical_siege_oxybeles_packed
units/{civ}_mechanical_siege_lithobolos_packed
units/{civ}_mechanical_siege_ram
units/{civ}_mechanical_siege_tower
attack_soldiers_will
interface/complete/building/complete_fortress.xml
attack/weapon/arrowfly.xml
attack/destruction/building_collapse_large.xml
false
100
40000
80
structures/fndn_6x6.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml (revision 16550)
@@ -1,106 +1,111 @@
3
1
15
+
+ 3
+ 4
+ 1000
+
100.0
0.0
0.0
4.0
1
12
80
7.5
150
CitizenSoldier Human Organic
Cavalry Citizen Soldier
Cavalry
Basic
formations/wedge
130
10
0
0
0
pitch
15000000
2.0
1.0
5
20
20
20
20
circle/128x128.png
circle/128x128_mask.png
voice/hellenes/civ/civ_male_ack.xml
voice/hellenes/civ/civ_male_attack.xml
voice/hellenes/civ/civ_male_ack.xml
actor/mounted/movement/walk.xml
actor/mounted/movement/walk.xml
attack/weapon/sword.xml
actor/fauna/death/death_horse.xml
interface/alarm/alarm_create_cav.xml
2000
6.5
16.5
26.0
600.0
5.0
92
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml (revision 16550)
@@ -1,75 +1,75 @@
15.0
35.0
5.0
4.0
7.0
3.0
own neutral enemy
60
0
7.0
05.70
85.70
-85.70
45.70
-45.70
2000
rome
structures/rome_wallset_siege
Siege Wall
Murus Circummunitionis
SiegeWall
structures/palisade_wall.png
A wooden and turf palisade buildable in enemy and neutral territories.
Convert Siege Wall into Siege Wall Gate
Quick building, but expensive wooden and earthen walls used to surround and siege an enemy town or fortified position. The most famous examples are the Roman sieges of the Iberian stronghold of Numantia and the Gallic stronghold of Alesia.
- 1
+ 1
structures/romans/siege_wall_long.xml
structures/fndn_wall.xml
36.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_short.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_short.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_short.xml (revision 16550)
@@ -1,55 +1,55 @@
15.0
35.0
5.0
4.0
7.0
3.0
own neutral enemy
20
0
7.0
1000
rome
structures/rome_wallset_siege
Siege Wall
Murus Circummunitionis
SiegeWall
structures/palisade_wall.png
A wooden and turf palisade buildable in enemy and neutral territories.
Quick building, but expensive wooden and earthen walls used to surround and siege an enemy town or fortified position. The most famous examples are the Roman sieges of the Iberian stronghold of Numantia and the Gallic stronghold of Alesia.
- 1
+ 1
structures/romans/siege_wall_short.xml
structures/fndn_1x1.xml
12.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_tent.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_tent.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_tent.xml (revision 16550)
@@ -1,59 +1,59 @@
own neutral enemy
30
50
5.0
5
0.1
Unit
Support Infantry
0
2
200
rome
Tent
Tabernāculum
-Village
A temporary shelter for soldiers. +5 population bonus.
1.0
-units/{civ}_support_female_citizen_house
interface/complete/building/complete_universal.xml
attack/destruction/building_collapse_large.xml
- 1
+ 1
props/structures/romans/rome_tent.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml (revision 16550)
@@ -1,119 +1,124 @@
1
1
1
1
1
1
0
0
Ranged Infantry
land
own
+
+ 1000
+ 0
+ 3
+
0
0
10
0
0
0
0
false
false
0.0
3.0
9.8
corpse
0
true
true
Structure
Structure ConquestCritical
structure
true
true
true
true
true
false
false
special/rallypoint
art/textures/misc/rallypoint_line.png
art/textures/misc/rallypoint_line_mask.png
0.2
square
round
default
default
outline_border.png
outline_border_mask.png
0.4
interface/complete/building/complete_universal.xml
attack/destruction/building_collapse_large.xml
interface/alarm/alarm_attackplayer.xml
attack/weapon/arrowfly.xml
attack/impact/arrow_metal.xml
6.0
0.6
12.0
- 5
+ 5
true
false
false
40
false
true
false
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml (revision 16550)
@@ -1,80 +1,83 @@
House
+
+ 300
+
5
30
75
5.0
3
0
0.1
Unit
Support
1
800
rubble/rubble_stone_3x3
House
Increase the population limit.
-ConquestCritical
Village House
structures/house.png
100
0
10
10
0
1.0
units/{civ}_support_female_citizen_house
health_females_01
pop_house_01
unlock_females_house
interface/complete/building/complete_house.xml
attack/destruction/building_collapse_large.xml
6.0
0.6
8.0
false
20
40000
20
structures/fndn_3x3.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_gate.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_gate.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_gate.xml (revision 16550)
@@ -1,67 +1,68 @@
5.0
Wall
+
0
60
8.0
20
3000
rubble/rubble_stone_wall_long
City Gate
Allow units access through a city wall. Can be locked to prevent access.
-ConquestCritical StoneWall Gates
structures/gate.png
phase_town
100
0
20
20
0
interface/complete/building/complete_gate.xml
attack/destruction/building_collapse_large.xml
actor/gate/stonegate_close.xml
actor/gate/stonegate_open.xml
interface/select/building/sel_gate.xml
interface/select/building/sel_gate.xml
false
20
65536
structures/fndn_wall.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_storehouse.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_storehouse.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_storehouse.xml (revision 16550)
@@ -1,81 +1,84 @@
Storehouse
+
+ 300
+
40
100
8.0
800
rubble/rubble_stone_3x3
Storehouse
Dropsite for wood, stone, and metal resources. Research gathering improvements for these resources.
DropsiteWood
DropsiteMetal
DropsiteStone
-ConquestCritical
Village Storehouse
structures/storehouse.png
100
0
10
0
0
0.7
gather_lumbering_ironaxes
gather_lumbering_strongeraxes
gather_lumbering_sharpaxes
gather_mining_servants
gather_mining_serfs
gather_mining_slaves
gather_mining_wedgemallet
gather_mining_shaftmining
gather_mining_silvermining
gather_capacity_basket
gather_capacity_wheelbarrow
gather_capacity_carts
wood stone metal
interface/complete/building/complete_storehouse.xml
attack/destruction/building_collapse_large.xml
false
16
30000
20
structures/fndn_3x3.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml (revision 16550)
@@ -1,87 +1,90 @@
5
25
5
2
10
2
Wonder
+
+ 5000
+
1000
0
1000
1000
1000
10.0
5000
rubble/rubble_stone_6x6
Wonder
Bring glory to your civilization and add large tracts of land to your empire.
City
Wonder
structures/wonder.png
phase_city
200
0
100
100
100
0.7
pop_wonder
interface/complete/building/complete_wonder.xml
attack/destruction/building_collapse_large.xml
6.0
0.6
12.0
true
100
65536
72
structures/fndn_6x6.xml
300
Index: ps/trunk/binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/Templates.js (revision 16549)
+++ ps/trunk/binaries/data/mods/public/globalscripts/Templates.js (revision 16550)
@@ -1,303 +1,313 @@
/**
* Gets an array of all classes for this identity template
*/
function GetIdentityClasses(template)
{
var classList = [];
if (template.Classes && template.Classes._string)
classList = classList.concat(template.Classes._string.split(/\s+/));
if (template.VisibleClasses && template.VisibleClasses._string)
classList = classList.concat(template.VisibleClasses._string.split(/\s+/));
if (template.Rank)
classList = classList.concat(template.Rank);
return classList;
}
/**
* Gets an array with all classes for this identity template
* that should be shown in the GUI
*/
function GetVisibleIdentityClasses(template)
{
if (template.VisibleClasses && template.VisibleClasses._string)
return template.VisibleClasses._string.split(/\s+/);
return [];
}
/**
* Check if the classes given in the identity template
* match a list of classes
* @param classes List of the classes to check against
* @param match Either a string in the form
* "Class1 Class2+Class3"
* where spaces are handled as OR and '+'-signs as AND,
* Or a list in the form
* [["Class1"], ["Class2", "Class3"]]
* where the outer list is combined as OR, and the inner lists are AND-ed
* Or a hybrid format containing a list of strings, where the list is
* combined as OR, and the strings are split by space and '+' and AND-ed
*
* @return undefined if there are no classes or no match object
* true if the the logical combination in the match object matches the classes
* false otherwise
*/
function MatchesClassList(classes, match)
{
if (!match || !classes)
return undefined;
// transform the string to an array
if (typeof match == "string")
match = match.split(/\s+/);
for (var sublist of match)
{
// if the elements are still strings, split them by space or by '+'
if (typeof sublist == "string")
sublist = sublist.split(/[+\s]+/);
if (sublist.every(function(c) { return classes.indexOf(c) != -1; }))
return true;
}
return false;
}
/**
* Get information about a template with or without technology modifications.
* @param template A valid template as returned by the template loader.
* @param player An optional player id to get the technology modifications
* of properties.
*/
function GetTemplateDataHelper(template, player)
{
var ret = {};
var func;
if (player)
func = ApplyValueModificationsToTemplate;
else
func = function(a, val, c, d) { return val; }
if (template.Armour)
{
ret.armour = {
"hack": func("Armour/Hack", +template.Armour.Hack, player, template),
"pierce": func("Armour/Pierce", +template.Armour.Pierce, player, template),
"crush": func("Armour/Crush", +template.Armour.Crush, player, template),
};
}
if (template.Attack)
{
+ let getAttackStat = function(type, stat)
+ {
+ return func("Attack/"+type+"/"+stat, +(template.Attack[type][stat] || 0), player, template);
+ };
+
ret.attack = {};
- for (var type in template.Attack)
+ for (let type in template.Attack)
{
- ret.attack[type] = {
- "hack": func("Attack/"+type+"/Hack", +(template.Attack[type].Hack || 0), player, template),
- "pierce": func("Attack/"+type+"/Pierce", +(template.Attack[type].Pierce || 0), player, template),
- "crush": func("Attack/"+type+"/Crush", +(template.Attack[type].Crush || 0), player, template),
- "minRange": func("Attack/"+type+"/MinRange", +(template.Attack[type].MinRange || 0), player, template),
- "maxRange": func("Attack/"+type+"/MaxRange", +template.Attack[type].MaxRange, player, template),
- "elevationBonus": func("Attack/"+type+"/ElevationBonus", +(template.Attack[type].ElevationBonus || 0), player, template),
- "repeatTime": +(template.Attack[type].RepeatTime || 0),
- };
+ if (type == "Capture")
+ ret.attack.Capture = {
+ "value": getAttackStat(type,"Value"),
+ };
+ else
+ ret.attack[type] = {
+ "hack": getAttackStat(type, "Hack"),
+ "pierce": getAttackStat(type, "Pierce"),
+ "crush": getAttackStat(type, "Crush"),
+ "minRange": getAttackStat(type, "MinRange"),
+ "maxRange": getAttackStat(type, "MaxRange"),
+ "elevationBonus": getAttackStat(type, "ElevationBonus"),
+ };
+ ret.attack[type].repeatTime = +(template.Attack[type].RepeatTime || 0);
}
}
if (template.Auras)
{
ret.auras = {};
for each (var aura in template.Auras)
if (aura.AuraName)
ret.auras[aura.AuraName] = aura.AuraDescription || null;
}
if (template.BuildRestrictions)
{
// required properties
ret.buildRestrictions = {
"placementType": template.BuildRestrictions.PlacementType,
"territory": template.BuildRestrictions.Territory,
"category": template.BuildRestrictions.Category,
};
// optional properties
if (template.BuildRestrictions.Distance)
{
ret.buildRestrictions.distance = {
"fromCategory": template.BuildRestrictions.Distance.FromCategory,
};
if (template.BuildRestrictions.Distance.MinDistance) ret.buildRestrictions.distance.min = +template.BuildRestrictions.Distance.MinDistance;
if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance;
}
}
if (template.TrainingRestrictions)
{
ret.trainingRestrictions = {
"category": template.TrainingRestrictions.Category,
};
}
if (template.Cost)
{
ret.cost = {};
if (template.Cost.Resources.food) ret.cost.food = func("Cost/Resources/food", +template.Cost.Resources.food, player, template);
if (template.Cost.Resources.wood) ret.cost.wood = func("Cost/Resources/wood", +template.Cost.Resources.wood, player, template);
if (template.Cost.Resources.stone) ret.cost.stone = func("Cost/Resources/stone", +template.Cost.Resources.stone, player, template);
if (template.Cost.Resources.metal) ret.cost.metal = func("Cost/Resources/metal", +template.Cost.Resources.metal, player, template);
if (template.Cost.Population) ret.cost.population = func("Cost/Population", +template.Cost.Population, player, template);
if (template.Cost.PopulationBonus) ret.cost.populationBonus = func("Cost/PopulationBonus", +template.Cost.PopulationBonus, player, template);
if (template.Cost.BuildTime) ret.cost.time = func("Cost/BuildTime", +template.Cost.BuildTime, player, template);
}
if (template.Footprint)
{
ret.footprint = {"height": template.Footprint.Height};
if (template.Footprint.Square)
ret.footprint.square = {"width": +template.Footprint.Square["@width"], "depth": +template.Footprint.Square["@depth"]};
else if (template.Footprint.Circle)
ret.footprint.circle = {"radius": +template.Footprint.Circle["@radius"]};
else
warn("GetTemplateDataHelper(): Unrecognized Footprint type");
}
if (template.Obstruction)
{
ret.obstruction = {
"active": ("" + template.Obstruction.Active == "true"),
"blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
"blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"),
"blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"),
"blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"),
"disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"),
"disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
"shape": {}
};
if (template.Obstruction.Static)
{
ret.obstruction.shape.type = "static";
ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
}
else if (template.Obstruction.Unit)
{
ret.obstruction.shape.type = "unit";
ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
}
else
{
ret.obstruction.shape.type = "cluster";
}
}
if (template.Pack)
{
ret.pack = {
"state": template.Pack.State,
"time": func("Pack/Time", +template.Pack.Time, player, template),
};
}
if (template.Health)
ret.health = Math.round(func("Health/Max", +template.Health.Max, player, template));
if (template.Identity)
{
ret.selectionGroupName = template.Identity.SelectionGroupName;
ret.name = {
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
"generic": template.Identity.GenericName
};
ret.icon = template.Identity.Icon;
ret.tooltip = template.Identity.Tooltip;
ret.gateConversionTooltip = template.Identity.GateConversionTooltip;
ret.requiredTechnology = template.Identity.RequiredTechnology;
ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
}
if (template.UnitMotion)
{
ret.speed = {
"walk": func("UnitMotion/WalkSpeed", +template.UnitMotion.WalkSpeed, player, template),
};
if (template.UnitMotion.Run)
ret.speed.run = func("UnitMotion/Run/Speed", +template.UnitMotion.Run.Speed, player, template);
}
if (template.Trader)
ret.trader = template.Trader;
if (template.WallSet)
{
ret.wallSet = {
"templates": {
"tower": template.WallSet.Templates.Tower,
"gate": template.WallSet.Templates.Gate,
"long": template.WallSet.Templates.WallLong,
"medium": template.WallSet.Templates.WallMedium,
"short": template.WallSet.Templates.WallShort,
},
"maxTowerOverlap": +template.WallSet.MaxTowerOverlap,
"minTowerOverlap": +template.WallSet.MinTowerOverlap,
};
}
if (template.WallPiece)
ret.wallPiece = {"length": +template.WallPiece.Length};
return ret;
}
/**
* Get information about a technology template.
* @param template A valid template as obtained by loading the tech JSON file.
* @param civ Civilization for which the specific name should be returned.
*/
function GetTechnologyDataHelper(template, civ)
{
var ret = {};
// Get specific name for this civ or else the generic specific name
var specific = undefined;
if (template.specificName)
{
if (template.specificName[civ])
specific = template.specificName[civ];
else
specific = template.specificName['generic'];
}
ret.name = {
"specific": specific,
"generic": template.genericName,
};
if (template.icon)
ret.icon = "technologies/" + template.icon;
else
ret.icon = null;
ret.cost = {
"food": template.cost ? (+template.cost.food) : 0,
"wood": template.cost ? (+template.cost.wood) : 0,
"metal": template.cost ? (+template.cost.metal) : 0,
"stone": template.cost ? (+template.cost.stone) : 0,
"time": template.researchTime ? (+template.researchTime) : 0,
}
ret.tooltip = template.tooltip;
if (template.requirementsTooltip)
ret.requirementsTooltip = template.requirementsTooltip;
else
ret.requirementsTooltip = "";
if (template.requirements && template.requirements.class)
ret.classRequirements = {"class": template.requirements.class, "number": template.requirements.number};
ret.description = template.description;
return ret;
}
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js (revision 16549)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js (revision 16550)
@@ -1,286 +1,290 @@
/*
DESCRIPTION : Generic utility functions.
NOTES :
*/
// ====================================================================
function getRandom(randomMin, randomMax)
{
// Returns a random whole number in a min..max range.
// NOTE: There should probably be an engine function for this,
// since we'd need to keep track of random seeds for replays.
var randomNum = randomMin + (randomMax-randomMin)*Math.random(); // num is random, from A to B
return Math.round(randomNum);
}
// ====================================================================
// Get list of XML files in pathname with recursion, excepting those starting with _
function getXMLFileList(pathname)
{
var files = Engine.BuildDirEntList(pathname, "*.xml", true);
var result = [];
// Get only subpath from filename and discard extension
for (var i = 0; i < files.length; ++i)
{
var file = files[i];
file = file.substring(pathname.length, file.length-4);
// Split path into directories so we can check for beginning _ character
var tokens = file.split("/");
if (tokens[tokens.length-1][0] != "_")
result.push(file);
}
return result;
}
// ====================================================================
// Get list of JSON files in pathname
function getJSONFileList(pathname)
{
var files = Engine.BuildDirEntList(pathname, "*.json", false);
// Remove the path and extension from each name, since we just want the filename
files = [ n.substring(pathname.length, n.length-5) for each (n in files) ];
return files;
}
// ====================================================================
// A sorting function for arrays of objects with 'name' properties, ignoring case
function sortNameIgnoreCase(x, y)
{
var lowerX = x.name.toLowerCase();
var lowerY = y.name.toLowerCase();
if (lowerX < lowerY)
return -1;
else if (lowerX > lowerY)
return 1;
else
return 0;
}
// ====================================================================
/**
* Escape tag start and escape characters, so users cannot use special formatting.
* Also limit string length to 256 characters (not counting escape characters).
*/
function escapeText(text)
{
if (!text)
return text;
return text.substr(0, 255).replace(/\\/g, "\\\\").replace(/\[/g, "\\[");
}
// ====================================================================
// Load default player data, for when it's not otherwise specified
function initPlayerDefaults()
{
var data = Engine.ReadJSONFile("simulation/data/player_defaults.json");
if (!data || !data.PlayerData)
{
error("Failed to parse player defaults in player_defaults.json (check for valid JSON data)");
return [];
}
return data.PlayerData;
}
// ====================================================================
// Load map size data
function initMapSizes()
{
var sizes = {
"shortNames":[],
"names":[],
"tiles": [],
"default": 0
};
var data = Engine.ReadJSONFile("simulation/data/map_sizes.json");
if (!data || !data.Sizes)
{
error("Failed to parse map sizes in map_sizes.json (check for valid JSON data)");
return sizes;
}
translateObjectKeys(data, ["Name", "LongName"]);
for (var i = 0; i < data.Sizes.length; ++i)
{
sizes.shortNames.push(data.Sizes[i].Name);
sizes.names.push(data.Sizes[i].LongName);
sizes.tiles.push(data.Sizes[i].Tiles);
if (data.Sizes[i].Default)
sizes["default"] = i;
}
return sizes;
}
// ====================================================================
// Load game speed data
function initGameSpeeds()
{
var gameSpeeds = {
"names": [],
"speeds": [],
"default": 0
};
var data = Engine.ReadJSONFile("simulation/data/game_speeds.json");
if (!data || !data.Speeds)
{
error("Failed to parse game speeds in game_speeds.json (check for valid JSON data)");
return gameSpeeds;
}
translateObjectKeys(data, ["Name"]);
for (var i = 0; i < data.Speeds.length; ++i)
{
gameSpeeds.names.push(data.Speeds[i].Name);
gameSpeeds.speeds.push(data.Speeds[i].Speed);
if (data.Speeds[i].Default)
gameSpeeds["default"] = i;
}
return gameSpeeds;
}
// ====================================================================
// Convert integer color values to string (for use in GUI objects)
-function rgbToGuiColor(color)
+function rgbToGuiColor(color, alpha)
{
+ var ret;
if (color && ("r" in color) && ("g" in color) && ("b" in color))
- return color.r + " " + color.g + " " + color.b;
-
- return "0 0 0";
+ ret = color.r + " " + color.g + " " + color.b;
+ else
+ ret = "0 0 0";
+ if (alpha)
+ ret += " " + alpha;
+ return ret;
}
// ====================================================================
/**
* Convert time in milliseconds to [hh:]mm:ss string representation.
* @param time Time period in milliseconds (integer)
* @return String representing time period
*/
function timeToString(time)
{
if (time < 1000 * 60 * 60)
var format = translate("mm:ss");
else
var format = translate("HH:mm:ss");
return Engine.FormatMillisecondsIntoDateString(time, format);
}
// ====================================================================
function removeDupes(array)
{
// loop backwards to make splice operations cheaper
var i = array.length;
while (i--)
{
if (array.indexOf(array[i]) != i)
array.splice(i, 1);
}
}
// ====================================================================
// "Inside-out" implementation of Fisher-Yates shuffle
function shuffleArray(source)
{
if (!source.length)
return [];
var result = [source[0]];
for (var i = 1; i < source.length; ++i)
{
var j = Math.floor(Math.random() * i);
result[i] = result[j];
result[j] = source[i];
}
return result;
}
// ====================================================================
// Filter out conflicting characters and limit the length of a given name.
// @param name Name to be filtered.
// @param stripUnicode Whether or not to remove unicode characters.
// @param stripSpaces Whether or not to remove whitespace.
function sanitizePlayerName(name, stripUnicode, stripSpaces)
{
// We delete the '[', ']' characters (GUI tags) and delete the ',' characters (player name separators) by default.
var sanitizedName = name.replace(/[\[\],]/g, "");
// Optionally strip unicode
if (stripUnicode)
sanitizedName = sanitizedName.replace(/[^\x20-\x7f]/g, "");
// Optionally strip whitespace
if (stripSpaces)
sanitizedName = sanitizedName.replace(/\s/g, "");
// Limit the length to 20 characters
return sanitizedName.substr(0,20);
}
function tryAutoComplete(text, autoCompleteList)
{
if (!text.length)
return text;
var wordSplit = text.split(/\s/g);
if (!wordSplit.length)
return text;
var lastWord = wordSplit.pop();
if (!lastWord.length)
return text;
for (var word of autoCompleteList)
{
if (word.toLowerCase().indexOf(lastWord.toLowerCase()) != 0)
continue;
text = wordSplit.join(" ")
if (text.length > 0)
text += " ";
text += word;
break;
}
return text;
}
function autoCompleteNick(guiName, playerList)
{
var input = Engine.GetGUIObjectByName(guiName);
var text = input.caption;
if (!text.length)
return;
var autoCompleteList = [];
for (var player of playerList)
autoCompleteList.push(player.name);
var bufferPosition = input.buffer_position;
var textTillBufferPosition = text.substring(0, bufferPosition);
var newText = tryAutoComplete(textTillBufferPosition, autoCompleteList);
input.caption = newText + text.substring(bufferPosition);
input.buffer_position = bufferPosition + (newText.length - textTillBufferPosition.length);
}
Index: ps/trunk/binaries/data/mods/public/gui/common/tooltips.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/tooltips.js (revision 16549)
+++ ps/trunk/binaries/data/mods/public/gui/common/tooltips.js (revision 16550)
@@ -1,472 +1,482 @@
const COST_DISPLAY_NAMES = {
"food": '[icon="iconFood"]',
"wood": '[icon="iconWood"]',
"stone": '[icon="iconStone"]',
"metal": '[icon="iconMetal"]',
"population": '[icon="iconPopulation"]',
"time": '[icon="iconTime"]'
};
const txtFormats = {
"unit": ['[font="sans-10"][color="orange"]', '[/color][/font]'],
"header": ['[font="sans-bold-13"]', '[/font]'],
"body": ['[font="sans-13"]', '[/font]']
};
function damageValues(dmg)
{
if (!dmg)
return [0, 0, 0];
return [dmg.hack || 0, dmg.pierce || 0, dmg.crush || 0];
}
function damageTypeDetails(dmg)
{
if (!dmg)
return '[font="sans-12"]' + translate("(None)") + '[/font]';
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.hack.toFixed(1),
damageType: txtFormats.unit[0] + translate("Hack") + txtFormats.unit[1]
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.pierce.toFixed(1),
damageType: txtFormats.unit[0] + translate("Pierce") + txtFormats.unit[1]
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.crush.toFixed(1),
damageType: txtFormats.unit[0] + translate("Crush") + txtFormats.unit[1]
}));
return dmgArray.join(translate(", "));
}
function attackRateDetails(entState, type)
{
var time = entState.attack[type].repeatTime / 1000;
var timeString = sprintf(translate("%(time)s %(second)s"), {
time: time,
second: txtFormats.unit[0] + translatePlural("second", "seconds", time) + txtFormats.unit[1]
});
if (!entState.buildingAI)
return timeString;
var arrows = Math.max(entState.buildingAI.arrowCount, entState.buildingAI.defaultArrowCount);
var arrowString = sprintf(translate("%(arrowcount)s %(arrow)s"), {
arrowcount: arrows,
arrow: txtFormats.unit[0] + translatePlural("arrow", "arrows", arrows) + txtFormats.unit[1]
});
return sprintf(translate("%(arrowString)s / %(timeString)s"), {
arrowString: arrowString,
timeString: timeString
});
}
// Converts an armor level into the actual reduction percentage
function armorLevelToPercentageString(level)
{
return (100 - Math.round(Math.pow(0.9, level) * 100)) + "%";
// return sprintf(translate("%(armorPercentage)s%"), { armorPercentage: (100 - Math.round(Math.pow(0.9, level) * 100)) }); // Not supported by our sprintf implementation.
}
function getArmorTooltip(dmg)
{
var label = txtFormats.header[0] + translate("Armor:") + txtFormats.header[1];
if (!dmg)
return sprintf(translate("%(label)s %(details)s"), {
"label": label,
"details": '[font="sans-12"]' + translate("(None)") + '[/font]'
});
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
damage: dmg.hack,
damageType: txtFormats.unit[0] + translate("Hack") + txtFormats.unit[1],
armorPercentage: '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.hack) }) + '[/font]'
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
damage: dmg.pierce,
damageType: txtFormats.unit[0] + translate("Pierce") + txtFormats.unit[1],
armorPercentage: '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.pierce) }) + '[/font]'
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
damage: dmg.crush,
damageType: txtFormats.unit[0] + translate("Crush") + txtFormats.unit[1],
armorPercentage: '[font="sans-10"]' + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + '[/font]'
}));
return sprintf(translate("%(label)s %(details)s"), {
"label": label,
"details": dmgArray.join('[font="sans-12"]' + translate(", ") + '[/font]')
});
}
function damageTypesToText(dmg)
{
if (!dmg)
return '[font="sans-12"]' + translate("(None)") + '[/font]';
var dmgArray = [];
if (dmg.hack)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.hack.toFixed(1),
damageType: txtFormats.unit[0] + translate("Hack") + txtFormats.unit[1]
}));
if (dmg.pierce)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.pierce.toFixed(1),
damageType: txtFormats.unit[0] + translate("Pierce") + txtFormats.unit[1]
}));
if (dmg.crush)
dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
damage: dmg.crush.toFixed(1),
damageType: txtFormats.unit[0] + translate("Crush") + txtFormats.unit[1]
}));
return dmgArray.join('[font="sans-12"]' + translate(", ") + '[/font]');
}
function getAttackTypeLabel(type)
{
if (type === "Charge") return translate("Charge Attack:");
if (type === "Melee") return translate("Melee Attack:");
if (type === "Ranged") return translate("Ranged Attack:");
+ if (type === "Capture") return translate("Capture Attack:");
warn(sprintf("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized.", { attackType: type }));
return translate("Attack:");
}
function getAttackTooltip(template)
{
var attacks = [];
if (!template.attack)
return "";
if (template.buildingAI)
var rateLabel = txtFormats.header[0] + translate("Interval:") + txtFormats.header[1];
else
var rateLabel = txtFormats.header[0] + translate("Rate:") + txtFormats.header[1];
for (var type in template.attack)
{
if (type == "Slaughter")
continue; // Slaughter is not a real attack, so do not show it.
if (type == "Charge")
continue; // Charging isn't implemented yet and shouldn't be displayed.
var rate = sprintf(translate("%(label)s %(details)s"), {
label: rateLabel,
details: attackRateDetails(template, type)
});
var attackLabel = txtFormats.header[0] + getAttackTypeLabel(type) + txtFormats.header[1];
+ if (type == "Capture")
+ {
+ attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
+ attackLabel: attackLabel,
+ details: template.attack[type].value,
+ rate: rate
+ }));
+ continue;
+ }
if (type != "Ranged")
{
attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
attackLabel: attackLabel,
details: damageTypesToText(template.attack[type]),
rate: rate
}));
continue;
}
var realRange = template.attack[type].elevationAdaptedRange;
var range = Math.round(template.attack[type].maxRange);
var rangeLabel = txtFormats.header[0] + translate("Range:") + txtFormats.header[1];
var relativeRange = Math.round((realRange - range));
var meters = txtFormats.unit[0] + translate("meters") + txtFormats.unit[1];
if (relativeRange) // show if it is non-zero
attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rangeLabel)s %(range)s %(meters)s (%(relative)s), %(rate)s"), {
attackLabel: attackLabel,
details: damageTypesToText(template.attack[type]),
rangeLabel: rangeLabel,
range: range,
meters: meters,
relative: relativeRange > 0 ? "+" + relativeRange : relativeRange,
rate: rate
}));
else
attacks.push(sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(range)s %(meters)s, %(rate)s"), {
attackLabel: attackLabel,
damageTypes: damageTypesToText(template.attack[type]),
rangeLabel: rangeLabel,
range: range,
meters: meters,
rate: rate
}));
}
- return attacks.join(translate(", "));
+ return attacks.join("\n");
}
/**
* Translates a cost component identifier as they are used internally
* (e.g. "population", "food", etc.) to proper display names.
*/
function getCostComponentDisplayName(costComponentName)
{
if (costComponentName in COST_DISPLAY_NAMES)
return COST_DISPLAY_NAMES[costComponentName];
warn(sprintf("The specified cost component, ‘%(component)s’, is not currently supported.", { component: costComponentName }));
return "";
}
/**
* Multiplies the costs for a template by a given batch size.
*/
function multiplyEntityCosts(template, trainNum)
{
var totalCosts = {};
for (var r in template.cost)
totalCosts[r] = Math.floor(template.cost[r] * trainNum);
return totalCosts;
}
/**
* Helper function for getEntityCostTooltip.
*/
function getEntityCostComponentsTooltipString(template, trainNum, entity)
{
if (!trainNum)
trainNum = 1;
var totalCosts = multiplyEntityCosts(template, trainNum);
totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", {"entity": entity, "batchSize": trainNum}) : 1));
var costs = [];
if (totalCosts.food) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("food"), cost: totalCosts.food }));
if (totalCosts.wood) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("wood"), cost: totalCosts.wood }));
if (totalCosts.metal) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("metal"), cost: totalCosts.metal }));
if (totalCosts.stone) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("stone"), cost: totalCosts.stone }));
if (totalCosts.population) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("population"), cost: totalCosts.population }));
if (totalCosts.time) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("time"), cost: totalCosts.time }));
return costs;
}
/**
* Returns an array of strings for a set of wall pieces. If the pieces share
* resource type requirements, output will be of the form '10 to 30 Stone',
* otherwise output will be, e.g. '10 Stone, 20 Stone, 30 Stone'.
*/
function getWallPieceTooltip(wallTypes)
{
var out = [];
var resourceCount = {};
// Initialize the acceptable types for '$x to $y $resource' mode.
for (var resource in wallTypes[0].cost)
if (wallTypes[0].cost[resource])
resourceCount[resource] = [wallTypes[0].cost[resource]];
var sameTypes = true;
for (var i = 1; i < wallTypes.length; ++i)
{
for (var resource in wallTypes[i].cost)
{
// Break out of the same-type mode if this wall requires
// resource types that the first didn't.
if (wallTypes[i].cost[resource] && !resourceCount[resource])
{
sameTypes = false;
break;
}
}
for (var resource in resourceCount)
{
if (wallTypes[i].cost[resource])
resourceCount[resource].push(wallTypes[i].cost[resource]);
else
{
sameTypes = false;
break;
}
}
}
if (sameTypes)
{
for (var resource in resourceCount)
{
var resourceMin = Math.min.apply(Math, resourceCount[resource]);
var resourceMax = Math.max.apply(Math, resourceCount[resource]);
// Translation: This string is part of the resources cost string on
// the tooltip for wall structures.
out.push(sprintf(translate("%(resourceIcon)s %(minimum)s to %(resourceIcon)s %(maximum)s"), {
resourceIcon: getCostComponentDisplayName(resource),
minimum: resourceMin,
maximum: resourceMax
}));
}
}
else
for (var i = 0; i < wallTypes.length; ++i)
out.push(getEntityCostComponentsTooltipString(wallTypes[i]).join(", "));
return out;
}
/**
* Returns the cost information to display in the specified entity's construction button tooltip.
*/
function getEntityCostTooltip(template, trainNum, entity)
{
// Entities with a wallset component are proxies for initiating wall placement and as such do not have a cost of
// their own; the individual wall pieces within it do.
if (template.wallSet)
{
var templateLong = GetTemplateData(template.wallSet.templates.long);
var templateMedium = GetTemplateData(template.wallSet.templates.medium);
var templateShort = GetTemplateData(template.wallSet.templates.short);
var templateTower = GetTemplateData(template.wallSet.templates.tower);
var wallCosts = getWallPieceTooltip([templateShort, templateMedium, templateLong]);
var towerCosts = getEntityCostComponentsTooltipString(templateTower);
return sprintf(translate("Walls: %(costs)s"), { costs: wallCosts.join(" ") }) + "\n"
+ sprintf(translate("Towers: %(costs)s"), { costs: towerCosts.join(" ") });
}
if (template.cost)
return getEntityCostComponentsTooltipString(template, trainNum, entity).join(" ");
return "";
}
/**
* Returns the population bonus information to display in the specified entity's construction button tooltip.
*/
function getPopulationBonusTooltip(template)
{
var popBonus = "";
if (template.cost && template.cost.populationBonus)
popBonus = "\n" + sprintf(translate("%(label)s %(populationBonus)s"), {
label: txtFormats.header[0] + translate("Population Bonus:") + txtFormats.header[1],
populationBonus: template.cost.populationBonus
});
return popBonus;
}
/**
* Returns a message with the amount of each resource needed to create an entity.
*/
function getNeededResourcesTooltip(resources)
{
var formatted = [];
for (var resource in resources)
formatted.push(sprintf(translate("%(component)s %(cost)s"), {
component: '[font="sans-12"]' + getCostComponentDisplayName(resource) + '[/font]',
cost: resources[resource]
}));
return '\n\n[font="sans-bold-13"][color="red"]' + translate("Insufficient resources:") + '[/color][/font]\n' + formatted.join(" ");
}
function getSpeedTooltip(template)
{
if (!template.speed)
return "";
var label = txtFormats.header[0] + translate("Speed:") + txtFormats.header[1];
var speeds = [];
if (template.speed.walk)
speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.walk, movementType: txtFormats.unit[0] + translate("Walk") + txtFormats.unit[1]}));
if (template.speed.run)
speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.run, movementType: txtFormats.unit[0] + translate("Run") + txtFormats.unit[1]}));
return sprintf(translate("%(label)s %(speeds)s"), { label: label, speeds: speeds.join(translate(", ")) });
}
function getHealerTooltip(template)
{
if (!template.healer)
return "";
var healer = [
sprintf(translate("%(label)s %(val)s %(unit)s"), {
label: txtFormats.header[0] + translate("Heal:") + txtFormats.header[1],
val: template.healer.HP,
// Translation: Short for Health Points (that are healed in one healing action)
unit: txtFormats.unit[0] + translate("HP") + txtFormats.unit[1]
}),
sprintf(translate("%(label)s %(val)s %(unit)s"), {
label: txtFormats.header[0] + translate("Range:") + txtFormats.header[1],
val: template.healer.Range,
unit: txtFormats.unit[0] + translate("meters") + txtFormats.unit[1]
}),
sprintf(translate("%(label)s %(val)s %(unit)s"), {
label: txtFormats.header[0] + translate("Rate:") + txtFormats.header[1],
val: template.healer.Rate/1000,
unit: txtFormats.unit[0] + translatePlural("second", "seconds", template.healer.Rate/1000) + txtFormats.unit[1]
})
];
return healer.join(translate(", "));
}
function getEntityNames(template)
{
if (template.name.specific)
{
if (template.name.generic && template.name.specific != template.name.generic)
return sprintf(translate("%(specificName)s (%(genericName)s)"), {
specificName: template.name.specific,
genericName: template.name.generic
});
return template.name.specific;
}
if (template.name.generic)
return template.name.generic;
warn("Entity name requested on an entity without a name, specific or generic.");
return translate("???");
}
function getEntityNamesFormatted(template)
{
var names = "";
var generic = template.name.generic;
var specific = template.name.specific;
if (specific)
{
// drop caps for specific name
names += '[font="sans-bold-16"]' + specific[0] + '[/font]' +
'[font="sans-bold-12"]' + specific.slice(1).toUpperCase() + '[/font]';
if (generic)
names += '[font="sans-bold-16"] (' + generic + ')[/font]';
}
else if (generic)
names = '[font="sans-bold-16"]' + generic + "[/font]";
else
names = "???";
return names;
}
function getVisibleEntityClassesFormatted(template)
{
var r = ""
if (template.visibleIdentityClasses && template.visibleIdentityClasses.length)
{
r += '\n' + txtFormats.header[0] + translate("Classes:") + txtFormats.header[1];
var classes = [];
for (var c of template.visibleIdentityClasses)
classes.push(translate(c));
r += ' ' + txtFormats.body[0] + classes.join(translate(", ")) + txtFormats.body[1];
}
return r;
}
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 16549)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 16550)
@@ -1,386 +1,434 @@
function layoutSelectionSingle()
{
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}
function layoutSelectionMultiple()
{
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}
function getResourceTypeDisplayName(resourceType)
{
var resourceCode = resourceType["generic"];
var displayName = "";
if (resourceCode == "treasure")
displayName = getLocalizedResourceName(resourceType["specific"], "firstWord");
else
displayName = getLocalizedResourceName(resourceCode, "firstWord");
return displayName;
}
// Fills out information that most entities have
function displaySingle(entState, template)
{
// Get general unit and player data
var specificName = template.name.specific;
var genericName = template.name.generic != template.name.specific ? template.name.generic : "";
// If packed, add that to the generic name (reduces template clutter)
if (genericName && template.pack && template.pack.state == "packed")
genericName = sprintf(translate("%(genericName)s — Packed"), { genericName: genericName });
var playerState = g_Players[entState.player];
var civName = g_CivData[playerState.civ].Name;
var civEmblem = g_CivData[playerState.civ].Emblem;
var playerName = playerState.name;
var playerColor = playerState.color.r + " " + playerState.color.g + " " + playerState.color.b + " 128";
// Indicate disconnected players by prefixing their name
if (g_Players[entState.player].offline)
{
playerName = sprintf(translate("\\[OFFLINE] %(player)s"), { player: playerName });
}
// Rank
if (entState.identity && entState.identity.rank && entState.identity.classes)
{
Engine.GetGUIObjectByName("rankIcon").tooltip = sprintf(translate("%(rank)s Rank"), { rank: translateWithContext("Rank", entState.identity.rank) });
Engine.GetGUIObjectByName("rankIcon").sprite = getRankIconSprite(entState);
Engine.GetGUIObjectByName("rankIcon").hidden = false;
}
else
{
Engine.GetGUIObjectByName("rankIcon").hidden = true;
Engine.GetGUIObjectByName("rankIcon").tooltip = "";
}
// Hitpoints
+ Engine.GetGUIObjectByName("healthSection").hidden = !entState.hitpoints;
if (entState.hitpoints)
{
var unitHealthBar = Engine.GetGUIObjectByName("healthBar");
var healthSize = unitHealthBar.size;
healthSize.rright = 100*Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
unitHealthBar.size = healthSize;
if (entState.foundation && entState.visibility == "visible" && entState.foundation.numBuilders !== 0)
{
// logic comes from Foundation component.
var speed = Math.pow(entState.foundation.numBuilders, 0.7);
var timeLeft = (1.0 - entState.foundation.progress / 100.0) * template.cost.time;
Engine.GetGUIObjectByName("health").tooltip = sprintf(translate("This foundation will be completed in %(numb)s seconds."), { numb : Math.ceil(timeLeft/speed) });
}
else
Engine.GetGUIObjectByName("health").tooltip = "";
Engine.GetGUIObjectByName("healthStats").caption = sprintf(translate("%(hitpoints)s / %(maxHitpoints)s"), {
hitpoints: Math.ceil(entState.hitpoints),
maxHitpoints: entState.maxHitpoints
});
- Engine.GetGUIObjectByName("healthSection").hidden = false;
}
- else
+
+ // CapturePoints
+ Engine.GetGUIObjectByName("captureSection").hidden = !entState.capturePoints;
+ if (entState.capturePoints)
{
- Engine.GetGUIObjectByName("healthSection").hidden = true;
+ let setCaptureBarPart = function(playerID, startSize)
+ {
+ var unitCaptureBar = Engine.GetGUIObjectByName("captureBar["+playerID+"]");
+ var sizeObj = unitCaptureBar.size;
+ sizeObj.rleft = startSize;
+
+ var size = 100*Math.max(0, Math.min(1, entState.capturePoints[playerID] / entState.maxCapturePoints));
+ sizeObj.rright = startSize + size;
+ unitCaptureBar.size = sizeObj;
+ unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[playerID].color, 128);
+ unitCaptureBar.hidden=false;
+ return startSize + size;
+ }
+
+ // first handle the owner's points, to keep those points on the left for clarity
+ let size = setCaptureBarPart(entState.player, 0);
+
+ for (let i in entState.capturePoints)
+ if (i != entState.player)
+ size = setCaptureBarPart(i, size);
+
+
+ Engine.GetGUIObjectByName("captureStats").caption = sprintf(translate("%(capturePoints)s / %(maxCapturePoints)s"), {
+ capturePoints: Math.ceil(entState.capturePoints[entState.player]),
+ maxCapturePoints: entState.maxCapturePoints
+ });
}
// TODO: Stamina
- var player = Engine.GetPlayerID();
- if (entState.stamina && (entState.player == player || g_DevSettings.controlAll))
- Engine.GetGUIObjectByName("staminaSection").hidden = false;
- else
- Engine.GetGUIObjectByName("staminaSection").hidden = true;
// Experience
+ Engine.GetGUIObjectByName("experience").hidden = !entState.promotion;
if (entState.promotion)
{
var experienceBar = Engine.GetGUIObjectByName("experienceBar");
var experienceSize = experienceBar.size;
experienceSize.rtop = 100 - (100 * Math.max(0, Math.min(1, 1.0 * +entState.promotion.curr / +entState.promotion.req)));
experienceBar.size = experienceSize;
if (entState.promotion.curr < entState.promotion.req)
Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s / %(required)s"), {
experience: "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
current: Math.floor(entState.promotion.curr),
required: entState.promotion.req
});
else
Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s"), {
experience: "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
current: Math.floor(entState.promotion.curr)
});
- Engine.GetGUIObjectByName("experience").hidden = false;
- }
- else
- {
- Engine.GetGUIObjectByName("experience").hidden = true;
}
// Resource stats
+ Engine.GetGUIObjectByName("resourceSection").hidden = !entState.resourceSupply;
if (entState.resourceSupply)
{
var resources = entState.resourceSupply.isInfinite ? translate("∞") : // Infinity symbol
sprintf(translate("%(amount)s / %(max)s"), { amount: Math.ceil(+entState.resourceSupply.amount), max: entState.resourceSupply.max });
var resourceType = getResourceTypeDisplayName(entState.resourceSupply.type);
var unitResourceBar = Engine.GetGUIObjectByName("resourceBar");
var resourceSize = unitResourceBar.size;
resourceSize.rright = entState.resourceSupply.isInfinite ? 100 :
100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
unitResourceBar.size = resourceSize;
Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), { resource: resourceType });
Engine.GetGUIObjectByName("resourceStats").caption = resources;
if (entState.hitpoints)
- Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("staminaSection").size;
+ Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("captureSection").size;
else
Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("healthSection").size;
- Engine.GetGUIObjectByName("resourceSection").hidden = false;
- }
- else
- {
- Engine.GetGUIObjectByName("resourceSection").hidden = true;
}
// Resource carrying
if (entState.resourceCarrying && entState.resourceCarrying.length)
{
// We should only be carrying one resource type at once, so just display the first
var carried = entState.resourceCarrying[0];
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+carried.type+".png";
Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { amount: carried.amount, max: carried.max });
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "";
}
// Use the same indicators for traders
else if (entState.trader && entState.trader.goods.amount)
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+entState.trader.goods.type+".png";
var totalGain = entState.trader.goods.amount.traderGain;
if (entState.trader.goods.amount.market1Gain)
totalGain += entState.trader.goods.amount.market1Gain;
if (entState.trader.goods.amount.market2Gain)
totalGain += entState.trader.goods.amount.market2Gain;
Engine.GetGUIObjectByName("resourceCarryingText").caption = totalGain;
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(amount)s"), { amount: getTradingTooltip(entState.trader.goods.amount) });
}
// And for number of workers
else if (entState.foundation && entState.visibility == "visible")
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + " ";
if (entState.foundation.numBuilders !== 0)
{
var speedup = Math.pow((entState.foundation.numBuilders+1)/entState.foundation.numBuilders, 0.7);
var timeLeft = (1.0 - entState.foundation.progress / 100.0) * template.cost.time;
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Number of builders.\nTasking another to this foundation would speed construction up by %(numb)s seconds."), { numb : Math.ceil(timeLeft - timeLeft/speedup) });
}
else
{
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
}
}
else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints) && entState.visibility == "visible")
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { amount: entState.resourceSupply.gatherers.length, max: entState.resourceSupply.maxGatherers }) + " ";
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Current/max gatherers");
}
else
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = true;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = true;
}
// Set Player details
Engine.GetGUIObjectByName("specific").caption = specificName;
Engine.GetGUIObjectByName("player").caption = playerName;
Engine.GetGUIObjectByName("playerColorBackground").sprite = "color: " + playerColor;
if (genericName)
{
Engine.GetGUIObjectByName("generic").caption = sprintf(translate("(%(genericName)s)"), { genericName: genericName });
}
else
{
Engine.GetGUIObjectByName("generic").caption = "";
}
if ("gaia" != playerState.civ)
{
Engine.GetGUIObjectByName("playerCivIcon").sprite = "stretched:grayscale:" + civEmblem;
Engine.GetGUIObjectByName("player").tooltip = civName;
}
else
{
Engine.GetGUIObjectByName("playerCivIcon").sprite = "";
Engine.GetGUIObjectByName("player").tooltip = "";
}
// Icon image
if (template.icon)
{
Engine.GetGUIObjectByName("icon").sprite = "stretched:session/portraits/" + template.icon;
}
else
{
// TODO: we should require all entities to have icons, so this case never occurs
Engine.GetGUIObjectByName("icon").sprite = "bkFillBlack";
}
var armorString = getArmorTooltip(entState.armour);
// Attack and Armor
if ("attack" in entState && entState.attack)
Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = getAttackTooltip(entState) + "\n" + armorString;
else
Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = armorString;
// Icon Tooltip
var iconTooltip = "";
if (genericName)
iconTooltip = "[font=\"sans-bold-16\"]" + genericName + "[/font]";
if (template.visibleIdentityClasses && template.visibleIdentityClasses.length)
{
iconTooltip += "\n[font=\"sans-bold-13\"]" + translate("Classes:") + "[/font] ";
iconTooltip += "[font=\"sans-13\"]" + translate(template.visibleIdentityClasses[0]) ;
for (var i = 1; i < template.visibleIdentityClasses.length; i++)
iconTooltip += ", " + translate(template.visibleIdentityClasses[i]);
iconTooltip += "[/font]";
}
if (template.auras)
{
for (var auraName in template.auras)
{
iconTooltip += "\n[font=\"sans-bold-13\"]" + translate(auraName) + "[/font]";
if (template.auras[auraName])
iconTooltip += ": " + translate(template.auras[auraName]);
}
}
if (template.tooltip)
iconTooltip += "\n[font=\"sans-13\"]" + template.tooltip + "[/font]";
Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltip;
// Unhide Details Area
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}
// Fills out information for multiple entities
function displayMultiple(selection, template)
{
var averageHealth = 0;
var maxHealth = 0;
+ var maxCapturePoints = 0;
+ var capturePoints = (new Array(9)).fill(0);
+ var playerID = 0;
for (var i = 0; i < selection.length; i++)
{
var entState = GetEntityState(selection[i])
- if (entState)
+ if (!entState)
+ continue;
+ playerID = entState.player; // trust that all selected entities have the same owner
+ if (entState.hitpoints)
+ {
+ averageHealth += entState.hitpoints;
+ maxHealth += entState.maxHitpoints;
+ }
+ if (entState.capturePoints)
{
- if (entState.hitpoints)
- {
- averageHealth += entState.hitpoints;
- maxHealth += entState.maxHitpoints;
- }
+ maxCapturePoints += entState.maxCapturePoints;
+ capturePoints = entState.capturePoints.map(function(v, i) { return v + capturePoints[i]; });
}
}
+ Engine.GetGUIObjectByName("healthMultiple").hidden = averageHealth <= 0;
if (averageHealth > 0)
{
var unitHealthBar = Engine.GetGUIObjectByName("healthBarMultiple");
var healthSize = unitHealthBar.size;
healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
unitHealthBar.size = healthSize;
var hitpointsLabel = "[font=\"sans-bold-13\"]" + translate("Hitpoints:") + "[/font]"
var hitpoints = sprintf(translate("%(label)s %(current)s / %(max)s"), { label: hitpointsLabel, current: averageHealth, max: maxHealth });
- var healthMultiple = Engine.GetGUIObjectByName("healthMultiple");
- healthMultiple.tooltip = hitpoints;
- healthMultiple.hidden = false;
+ Engine.GetGUIObjectByName("healthMultiple").tooltip = hitpoints;
}
- else
+
+ Engine.GetGUIObjectByName("captureMultiple").hidden = maxCapturePoints <= 0;
+ if (maxCapturePoints > 0)
{
- Engine.GetGUIObjectByName("healthMultiple").hidden = true;
+ let setCaptureBarPart = function(playerID, startSize)
+ {
+ var unitCaptureBar = Engine.GetGUIObjectByName("captureBarMultiple["+playerID+"]");
+ var sizeObj = unitCaptureBar.size;
+ sizeObj.rtop = startSize;
+
+ var size = 100*Math.max(0, Math.min(1, capturePoints[playerID] / maxCapturePoints));
+ sizeObj.rbottom = startSize + size;
+ unitCaptureBar.size = sizeObj;
+ unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[playerID].color, 128);
+ unitCaptureBar.hidden=false;
+ return startSize + size;
+ }
+
+ let size = 0;
+ for (let i in entState.capturePoints)
+ if (i != playerID)
+ size = setCaptureBarPart(i, size);
+
+ // last handle the owner's points, to keep those points on the bottom for clarity
+ setCaptureBarPart(playerID, size);
+
+ var capturePointsLabel = "[font=\"sans-bold-13\"]" + translate("Capture points:") + "[/font]"
+ var capturePointsTooltip = sprintf(translate("%(label)s %(current)s / %(max)s"), { label: capturePointsLabel, current: Math.ceil(capturePoints[playerID]), max: Math.ceil(maxCapturePoints) });
+ Engine.GetGUIObjectByName("captureMultiple").tooltip = capturePointsTooltip;
}
// TODO: Stamina
// Engine.GetGUIObjectByName("staminaBarMultiple");
Engine.GetGUIObjectByName("numberOfUnits").caption = selection.length;
// Unhide Details Area
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}
// Updates middle entity Selection Details Panel
function updateSelectionDetails()
{
var supplementalDetailsPanel = Engine.GetGUIObjectByName("supplementalSelectionDetails");
var detailsPanel = Engine.GetGUIObjectByName("selectionDetails");
var commandsPanel = Engine.GetGUIObjectByName("unitCommands");
g_Selection.update();
var selection = g_Selection.toList();
if (selection.length == 0)
{
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
hideUnitCommands();
supplementalDetailsPanel.hidden = true;
detailsPanel.hidden = true;
commandsPanel.hidden = true;
return;
}
/* If the unit has no data (e.g. it was killed), don't try displaying any
data for it. (TODO: it should probably be removed from the selection too;
also need to handle multi-unit selections) */
var entState = GetExtendedEntityState(selection[0]);
if (!entState)
return;
var template = GetTemplateData(entState.template);
// Fill out general info and display it
if (selection.length == 1)
displaySingle(entState, template);
else
displayMultiple(selection, template);
// Show basic details.
detailsPanel.hidden = false;
if (g_IsObserver)
{
// Observers don't need these displayed.
supplementalDetailsPanel.hidden = true;
commandsPanel.hidden = true;
}
else
{
// Fill out commands panel for specific unit selected (or first unit of primary group)
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
}
}
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/multiple_details_area.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/multiple_details_area.xml (revision 16549)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/multiple_details_area.xml (revision 16550)
@@ -1,46 +1,47 @@