General scrollbar improvements. Patch by Josh. Fixes #2080.
- Don't show the scrollbar when the contents aren't overflowing the visible area. - Fix bugs in minimum bar sizing. - Add capability to set a maximum bar size. - Correct outdated/incorrect comments. This was SVN commit r13771.
This commit is contained in:
parent
2dc0c40ff0
commit
f5ab6255d0
@ -135,6 +135,7 @@
|
||||
sprite_bar_vertical_pressed CDATA #IMPLIED
|
||||
sprite_back_vertical CDATA #IMPLIED
|
||||
minimum_bar_size CDATA #IMPLIED
|
||||
maximum_bar_size CDATA #IMPLIED
|
||||
>
|
||||
|
||||
<!--
|
||||
|
@ -1,211 +1,221 @@
|
||||
function layoutSelectionSingle()
|
||||
{
|
||||
getGUIObjectByName("detailsAreaSingle").hidden = false;
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = true;
|
||||
}
|
||||
|
||||
function layoutSelectionMultiple()
|
||||
{
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = false;
|
||||
getGUIObjectByName("detailsAreaSingle").hidden = true;
|
||||
}
|
||||
|
||||
// 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 += " -- Packed";
|
||||
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 = "[OFFLINE] " + playerName;
|
||||
}
|
||||
|
||||
// Rank
|
||||
if (entState.identity && entState.identity.rank && entState.identity.classes)
|
||||
{
|
||||
getGUIObjectByName("rankIcon").tooltip = entState.identity.rank + " Rank";
|
||||
getGUIObjectByName("rankIcon").sprite = getRankIconSprite(entState);
|
||||
getGUIObjectByName("rankIcon").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("rankIcon").hidden = true;
|
||||
getGUIObjectByName("rankIcon").tooltip = "";
|
||||
}
|
||||
|
||||
// Hitpoints
|
||||
if (entState.hitpoints)
|
||||
{
|
||||
var unitHealthBar = getGUIObjectByName("healthBar");
|
||||
var healthSize = unitHealthBar.size;
|
||||
healthSize.rright = 100*Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
|
||||
unitHealthBar.size = healthSize;
|
||||
|
||||
var hitpoints = Math.ceil(entState.hitpoints) + " / " + entState.maxHitpoints;
|
||||
getGUIObjectByName("healthStats").caption = hitpoints;
|
||||
getGUIObjectByName("healthSection").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("healthSection").hidden = true;
|
||||
}
|
||||
|
||||
// TODO: Stamina
|
||||
var player = Engine.GetPlayerID();
|
||||
if (entState.stamina && (entState.player == player || g_DevSettings.controlAll))
|
||||
{
|
||||
getGUIObjectByName("staminaSection").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("staminaSection").hidden = true;
|
||||
}
|
||||
|
||||
// Experience
|
||||
if (entState.promotion)
|
||||
{
|
||||
var experienceBar = 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;
|
||||
|
||||
var experience = "[font=\"serif-bold-13\"]Experience: [/font]" + Math.floor(entState.promotion.curr);
|
||||
if (entState.promotion.curr < entState.promotion.req)
|
||||
experience += " / " + entState.promotion.req;
|
||||
getGUIObjectByName("experience").tooltip = experience;
|
||||
getGUIObjectByName("experience").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("experience").hidden = true;
|
||||
}
|
||||
|
||||
// Resource stats
|
||||
if (entState.resourceSupply)
|
||||
{
|
||||
var resources = entState.resourceSupply.isInfinite ? "\u221E" : // Infinity symbol
|
||||
Math.ceil(+entState.resourceSupply.amount) + " / " + entState.resourceSupply.max;
|
||||
var resourceType = entState.resourceSupply.type["generic"];
|
||||
if (resourceType == "treasure")
|
||||
resourceType = entState.resourceSupply.type["specific"];
|
||||
|
||||
var unitResourceBar = 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;
|
||||
getGUIObjectByName("resourceLabel").caption = toTitleCase(resourceType) + ":";
|
||||
getGUIObjectByName("resourceStats").caption = resources;
|
||||
|
||||
if (entState.hitpoints)
|
||||
getGUIObjectByName("resourceSection").size = getGUIObjectByName("staminaSection").size;
|
||||
else
|
||||
getGUIObjectByName("resourceSection").size = getGUIObjectByName("healthSection").size;
|
||||
|
||||
getGUIObjectByName("resourceSection").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
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];
|
||||
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+carried.type+".png";
|
||||
getGUIObjectByName("resourceCarryingText").caption = carried.amount + " / " + carried.max;
|
||||
getGUIObjectByName("resourceCarryingIcon").tooltip = "";
|
||||
}
|
||||
// Use the same indicators for traders
|
||||
else if (entState.trader && entState.trader.goods.amount)
|
||||
{
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = false;
|
||||
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;
|
||||
getGUIObjectByName("resourceCarryingText").caption = totalGain;
|
||||
getGUIObjectByName("resourceCarryingIcon").tooltip = "Gain: " + getTradingTooltip(entState.trader.goods.amount);
|
||||
}
|
||||
// And for number of workers
|
||||
else if (entState.foundation)
|
||||
{
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
|
||||
getGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + " ";
|
||||
getGUIObjectByName("resourceCarryingIcon").tooltip = "Number of builders";
|
||||
}
|
||||
else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
|
||||
{
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
|
||||
getGUIObjectByName("resourceCarryingText").caption = entState.resourceSupply.gatherers.length + " / " + entState.resourceSupply.maxGatherers + " ";
|
||||
getGUIObjectByName("resourceCarryingIcon").tooltip = "Current/max gatherers";
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = true;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = true;
|
||||
}
|
||||
|
||||
// Set Player details
|
||||
getGUIObjectByName("specific").caption = specificName;
|
||||
getGUIObjectByName("player").caption = playerName;
|
||||
getGUIObjectByName("playerColorBackground").sprite = "colour: " + playerColor;
|
||||
|
||||
if (genericName)
|
||||
{
|
||||
getGUIObjectByName("generic").caption = "(" + genericName + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("generic").caption = "";
|
||||
|
||||
}
|
||||
|
||||
if ("Gaia" != civName)
|
||||
{
|
||||
getGUIObjectByName("playerCivIcon").sprite = "stretched:grayscale:" + civEmblem;
|
||||
getGUIObjectByName("player").tooltip = civName;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("playerCivIcon").sprite = "";
|
||||
getGUIObjectByName("player").tooltip = "";
|
||||
}
|
||||
|
||||
// Icon image
|
||||
if (template.icon)
|
||||
{
|
||||
getGUIObjectByName("icon").sprite = "stretched:session/portraits/" + template.icon;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: we should require all entities to have icons, so this case never occurs
|
||||
function layoutSelectionSingle()
|
||||
{
|
||||
getGUIObjectByName("detailsAreaSingle").hidden = false;
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = true;
|
||||
}
|
||||
|
||||
function layoutSelectionMultiple()
|
||||
{
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = false;
|
||||
getGUIObjectByName("detailsAreaSingle").hidden = true;
|
||||
}
|
||||
|
||||
// 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 += " -- Packed";
|
||||
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 = "[OFFLINE] " + playerName;
|
||||
}
|
||||
|
||||
// Rank
|
||||
if (entState.identity && entState.identity.rank && entState.identity.classes)
|
||||
{
|
||||
getGUIObjectByName("rankIcon").tooltip = entState.identity.rank + " Rank";
|
||||
getGUIObjectByName("rankIcon").sprite = getRankIconSprite(entState);
|
||||
getGUIObjectByName("rankIcon").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("rankIcon").hidden = true;
|
||||
getGUIObjectByName("rankIcon").tooltip = "";
|
||||
}
|
||||
|
||||
// Hitpoints
|
||||
if (entState.hitpoints)
|
||||
{
|
||||
var unitHealthBar = getGUIObjectByName("healthBar");
|
||||
var healthSize = unitHealthBar.size;
|
||||
healthSize.rright = 100*Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
|
||||
unitHealthBar.size = healthSize;
|
||||
|
||||
var hitpoints = Math.ceil(entState.hitpoints) + " / " + entState.maxHitpoints;
|
||||
getGUIObjectByName("healthStats").caption = hitpoints;
|
||||
getGUIObjectByName("healthSection").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("healthSection").hidden = true;
|
||||
}
|
||||
|
||||
// TODO: Stamina
|
||||
var player = Engine.GetPlayerID();
|
||||
if (entState.stamina && (entState.player == player || g_DevSettings.controlAll))
|
||||
{
|
||||
getGUIObjectByName("staminaSection").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("staminaSection").hidden = true;
|
||||
}
|
||||
|
||||
// Experience
|
||||
if (entState.promotion)
|
||||
{
|
||||
var experienceBar = 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;
|
||||
|
||||
var experience = "[font=\"serif-bold-13\"]Experience: [/font]" + Math.floor(entState.promotion.curr);
|
||||
if (entState.promotion.curr < entState.promotion.req)
|
||||
experience += " / " + entState.promotion.req;
|
||||
getGUIObjectByName("experience").tooltip = experience;
|
||||
getGUIObjectByName("experience").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("experience").hidden = true;
|
||||
}
|
||||
|
||||
// Resource stats
|
||||
if (entState.resourceSupply)
|
||||
{
|
||||
var resources = entState.resourceSupply.isInfinite ? "\u221E" : // Infinity symbol
|
||||
Math.ceil(+entState.resourceSupply.amount) + " / " + entState.resourceSupply.max;
|
||||
var resourceType = entState.resourceSupply.type["generic"];
|
||||
if (resourceType == "treasure")
|
||||
resourceType = entState.resourceSupply.type["specific"];
|
||||
|
||||
var unitResourceBar = 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;
|
||||
getGUIObjectByName("resourceLabel").caption = toTitleCase(resourceType) + ":";
|
||||
getGUIObjectByName("resourceStats").caption = resources;
|
||||
|
||||
if (entState.hitpoints)
|
||||
getGUIObjectByName("resourceSection").size = getGUIObjectByName("staminaSection").size;
|
||||
else
|
||||
getGUIObjectByName("resourceSection").size = getGUIObjectByName("healthSection").size;
|
||||
|
||||
getGUIObjectByName("resourceSection").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("resourceSection").hidden = true;
|
||||
}
|
||||
|
||||
var activeResource = entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints)
|
||||
getGUIObjectByName("resourceThroughputIcon").hidden = !activeResource;
|
||||
getGUIObjectByName("resourceThroughputText").hidden = !activeResource;
|
||||
|
||||
// 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];
|
||||
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+carried.type+".png";
|
||||
getGUIObjectByName("resourceCarryingText").caption = carried.amount + " / " + carried.max;
|
||||
getGUIObjectByName("resourceCarryingIcon").tooltip = "";
|
||||
}
|
||||
// Use the same indicators for traders
|
||||
else if (entState.trader && entState.trader.goods.amount)
|
||||
{
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = false;
|
||||
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;
|
||||
getGUIObjectByName("resourceCarryingText").caption = totalGain;
|
||||
getGUIObjectByName("resourceCarryingIcon").tooltip = "Gain: " + getTradingTooltip(entState.trader.goods.amount);
|
||||
}
|
||||
else if (entState.foundation)
|
||||
{
|
||||
// Number of builders
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
|
||||
getGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + " ";
|
||||
getGUIObjectByName("resourceCarryingIcon").tooltip = "Number of builders";
|
||||
}
|
||||
else if (activeResource)
|
||||
{
|
||||
// Number of gatherers
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = false;
|
||||
getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
|
||||
getGUIObjectByName("resourceCarryingText").caption = entState.resourceSupply.gatherers.length + " / " + entState.resourceSupply.maxGatherers + " ";
|
||||
getGUIObjectByName("resourceCarryingIcon").tooltip = "Current/max gatherers";
|
||||
|
||||
// Current total resource output in resources/second
|
||||
getGUIObjectByName("resourceThroughputIcon").sprite = "stretched:session/icons/production.png";
|
||||
getGUIObjectByName("resourceThroughputText").caption = entState.resourceSupply.throughput.toFixed(2);
|
||||
getGUIObjectByName("resourceThroughputIcon").tooltip = "Currently producing " + entState.resourceSupply.throughput.toFixed(2) + " resources/second.";
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("resourceCarryingIcon").hidden = true;
|
||||
getGUIObjectByName("resourceCarryingText").hidden = true;
|
||||
}
|
||||
|
||||
// Set Player details
|
||||
getGUIObjectByName("specific").caption = specificName;
|
||||
getGUIObjectByName("player").caption = playerName;
|
||||
getGUIObjectByName("playerColorBackground").sprite = "colour: " + playerColor;
|
||||
|
||||
if (genericName)
|
||||
{
|
||||
getGUIObjectByName("generic").caption = "(" + genericName + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("generic").caption = "";
|
||||
|
||||
}
|
||||
|
||||
if ("Gaia" != civName)
|
||||
{
|
||||
getGUIObjectByName("playerCivIcon").sprite = "stretched:grayscale:" + civEmblem;
|
||||
getGUIObjectByName("player").tooltip = civName;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("playerCivIcon").sprite = "";
|
||||
getGUIObjectByName("player").tooltip = "";
|
||||
}
|
||||
|
||||
// Icon image
|
||||
if (template.icon)
|
||||
{
|
||||
getGUIObjectByName("icon").sprite = "stretched:session/portraits/" + template.icon;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: we should require all entities to have icons, so this case never occurs
|
||||
getGUIObjectByName("icon").sprite = "bkFillBlack";
|
||||
}
|
||||
|
||||
@ -221,11 +231,11 @@ function displaySingle(entState, template)
|
||||
{
|
||||
var realRange = entState.attack.elevationAdaptedRange;
|
||||
var range = entState.attack.maxRange;
|
||||
attack += ", [font=\"serif-bold-13\"]Range:[/font] " +
|
||||
attack += ", [font=\"serif-bold-13\"]Range:[/font] " +
|
||||
Math.round(range/4);
|
||||
|
||||
if (Math.round((realRange - range)/4) > 0)
|
||||
{
|
||||
{
|
||||
attack += " (+" + Math.round((realRange - range)/4) + ")";
|
||||
}
|
||||
else if (Math.round((realRange - range)/4) < 0)
|
||||
@ -235,113 +245,113 @@ function displaySingle(entState, template)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n[font=\"serif-bold-13\"]Armor:[/font] " + armorTypeDetails(entState.armour);
|
||||
|
||||
// Icon Tooltip
|
||||
var iconTooltip = "";
|
||||
|
||||
if (genericName)
|
||||
iconTooltip = "[font=\"serif-bold-16\"]" + genericName + "[/font]";
|
||||
|
||||
if (template.tooltip)
|
||||
iconTooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
|
||||
|
||||
getGUIObjectByName("iconBorder").tooltip = iconTooltip;
|
||||
|
||||
// Unhide Details Area
|
||||
getGUIObjectByName("detailsAreaSingle").hidden = false;
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = true;
|
||||
}
|
||||
|
||||
// Fills out information for multiple entities
|
||||
function displayMultiple(selection, template)
|
||||
{
|
||||
var averageHealth = 0;
|
||||
var maxHealth = 0;
|
||||
|
||||
for (var i = 0; i < selection.length; i++)
|
||||
{
|
||||
var entState = GetEntityState(selection[i])
|
||||
if (entState)
|
||||
{
|
||||
if (entState.hitpoints)
|
||||
{
|
||||
averageHealth += entState.hitpoints;
|
||||
maxHealth += entState.maxHitpoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (averageHealth > 0)
|
||||
{
|
||||
var unitHealthBar = getGUIObjectByName("healthBarMultiple");
|
||||
var healthSize = unitHealthBar.size;
|
||||
healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
|
||||
unitHealthBar.size = healthSize;
|
||||
|
||||
var hitpoints = "[font=\"serif-bold-13\"]Hitpoints [/font]" + averageHealth + " / " + maxHealth;
|
||||
var healthMultiple = getGUIObjectByName("healthMultiple");
|
||||
healthMultiple.tooltip = hitpoints;
|
||||
healthMultiple.hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("healthMultiple").hidden = true;
|
||||
}
|
||||
|
||||
// TODO: Stamina
|
||||
// getGUIObjectByName("staminaBarMultiple");
|
||||
|
||||
getGUIObjectByName("numberOfUnits").caption = selection.length;
|
||||
|
||||
// Unhide Details Area
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = false;
|
||||
getGUIObjectByName("detailsAreaSingle").hidden = true;
|
||||
}
|
||||
|
||||
// Updates middle entity Selection Details Panel
|
||||
function updateSelectionDetails()
|
||||
{
|
||||
var supplementalDetailsPanel = getGUIObjectByName("supplementalSelectionDetails");
|
||||
var detailsPanel = getGUIObjectByName("selectionDetails");
|
||||
var commandsPanel = getGUIObjectByName("unitCommands");
|
||||
|
||||
g_Selection.update();
|
||||
var selection = g_Selection.toList();
|
||||
|
||||
if (selection.length == 0)
|
||||
{
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = true;
|
||||
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 = GetEntityState(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 Panels
|
||||
supplementalDetailsPanel.hidden = false;
|
||||
detailsPanel.hidden = false;
|
||||
commandsPanel.hidden = false;
|
||||
|
||||
// Fill out commands panel for specific unit selected (or first unit of primary group)
|
||||
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
|
||||
}
|
||||
iconTooltip = "[font=\"serif-bold-16\"]" + genericName + "[/font]";
|
||||
|
||||
if (template.tooltip)
|
||||
iconTooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
|
||||
|
||||
getGUIObjectByName("iconBorder").tooltip = iconTooltip;
|
||||
|
||||
// Unhide Details Area
|
||||
getGUIObjectByName("detailsAreaSingle").hidden = false;
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = true;
|
||||
}
|
||||
|
||||
// Fills out information for multiple entities
|
||||
function displayMultiple(selection, template)
|
||||
{
|
||||
var averageHealth = 0;
|
||||
var maxHealth = 0;
|
||||
|
||||
for (var i = 0; i < selection.length; i++)
|
||||
{
|
||||
var entState = GetEntityState(selection[i])
|
||||
if (entState)
|
||||
{
|
||||
if (entState.hitpoints)
|
||||
{
|
||||
averageHealth += entState.hitpoints;
|
||||
maxHealth += entState.maxHitpoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (averageHealth > 0)
|
||||
{
|
||||
var unitHealthBar = getGUIObjectByName("healthBarMultiple");
|
||||
var healthSize = unitHealthBar.size;
|
||||
healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
|
||||
unitHealthBar.size = healthSize;
|
||||
|
||||
var hitpoints = "[font=\"serif-bold-13\"]Hitpoints [/font]" + averageHealth + " / " + maxHealth;
|
||||
var healthMultiple = getGUIObjectByName("healthMultiple");
|
||||
healthMultiple.tooltip = hitpoints;
|
||||
healthMultiple.hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
getGUIObjectByName("healthMultiple").hidden = true;
|
||||
}
|
||||
|
||||
// TODO: Stamina
|
||||
// getGUIObjectByName("staminaBarMultiple");
|
||||
|
||||
getGUIObjectByName("numberOfUnits").caption = selection.length;
|
||||
|
||||
// Unhide Details Area
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = false;
|
||||
getGUIObjectByName("detailsAreaSingle").hidden = true;
|
||||
}
|
||||
|
||||
// Updates middle entity Selection Details Panel
|
||||
function updateSelectionDetails()
|
||||
{
|
||||
var supplementalDetailsPanel = getGUIObjectByName("supplementalSelectionDetails");
|
||||
var detailsPanel = getGUIObjectByName("selectionDetails");
|
||||
var commandsPanel = getGUIObjectByName("unitCommands");
|
||||
|
||||
g_Selection.update();
|
||||
var selection = g_Selection.toList();
|
||||
|
||||
if (selection.length == 0)
|
||||
{
|
||||
getGUIObjectByName("detailsAreaMultiple").hidden = true;
|
||||
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 = GetEntityState(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 Panels
|
||||
supplementalDetailsPanel.hidden = false;
|
||||
detailsPanel.hidden = false;
|
||||
commandsPanel.hidden = false;
|
||||
|
||||
// Fill out commands panel for specific unit selected (or first unit of primary group)
|
||||
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
|
||||
}
|
||||
|
@ -177,12 +177,12 @@
|
||||
<object hotkey="camera.jump.set.10">
|
||||
<action on="Press">setJumpCamera(10);</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Stop the selected units -->
|
||||
<object hotkey="session.stop">
|
||||
<action on="Press">stopUnits(g_Selection.toList());</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- queue first unit in the training queue -->
|
||||
<object hotkey="session.queueunit.1">
|
||||
<action on="Press">addTrainingByPosition(0);</action>
|
||||
@ -202,7 +202,7 @@
|
||||
<object hotkey="session.queueunit.4">
|
||||
<action on="Press">addTrainingByPosition(3);</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- queue 5th unit in the training queue -->
|
||||
<object hotkey="session.queueunit.5">
|
||||
<action on="Press">addTrainingByPosition(4);</action>
|
||||
@ -315,7 +315,7 @@
|
||||
<!-- ================================ ================================ -->
|
||||
<!-- Time elapsed counter -->
|
||||
<!-- ================================ ================================ -->
|
||||
|
||||
|
||||
<object size="100%-120 45 100%-10 65" type="text" name="timeElapsedCounter" style="SettingsText" hotkey="timeelapsedcounter.toggle" hidden="true">
|
||||
<action on="Press">this.hidden = !this.hidden;</action>
|
||||
</object>
|
||||
@ -442,7 +442,7 @@
|
||||
z="200"
|
||||
>
|
||||
<object type="text" style="TitleText" size="50%-96 -16 50%+96 16">Settings</object>
|
||||
|
||||
|
||||
<object style="TranslucentPanelThinBorder"
|
||||
type="image"
|
||||
size="32 32 100%-32 100%-70"
|
||||
@ -453,21 +453,21 @@
|
||||
<action on="Load">if (renderer.shadows) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.shadows = this.checked;</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Shadow PCF -->
|
||||
<object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">Enable Shadow Filtering</object>
|
||||
<object name="shadowPCFCheckbox" size="100%-56 40 100%-30 65" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
<action on="Load">if (renderer.shadowPCF) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.shadowPCF = this.checked;</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Water Normals -->
|
||||
<object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Water - HQ Waviness</object>
|
||||
<object name="waterNormalCheckox" size="100%-56 65 100%-30 90" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
<action on="Load">if (renderer.waternormal) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.waternormal = this.checked;</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Real Depth -->
|
||||
<object size="0 85 100%-80 110" type="text" style="RightLabelText" ghost="true">Water - Use Actual Depth</object>
|
||||
<object name="waterRealDepthCheckbox" size="100%-56 90 100%-30 115" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
@ -481,28 +481,28 @@
|
||||
<action on="Load">if (renderer.waterreflection) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.waterreflection = this.checked; </action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Refraction -->
|
||||
<object size="0 135 100%-80 160" type="text" style="RightLabelText" ghost="true">Water - Enable Refraction</object>
|
||||
<object name="waterRefractionCheckbox" size="100%-56 140 100%-30 165" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
<action on="Load">if (renderer.waterrefraction) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.waterrefraction = this.checked; </action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Foam -->
|
||||
<object size="0 160 100%-80 185" type="text" style="RightLabelText" ghost="true">Water - Enable Shore Foam</object>
|
||||
<object name="waterFoamCheckbox" size="100%-56 165 100%-30 190" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
<action on="Load">if (renderer.waterfoam) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.waterfoam = this.checked; </action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Waves -->
|
||||
<object size="0 185 100%-80 210" type="text" style="RightLabelText" ghost="true">Water - Enable Shore Waves</object>
|
||||
<object name="waterCoastalWavesCheckbox" size="100%-56 190 100%-30 215" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
<action on="Load">if (renderer.watercoastalwaves) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.watercoastalwaves = this.checked; </action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Shadows -->
|
||||
<object size="0 210 100%-80 235" type="text" style="RightLabelText" ghost="true">Water - Use Surface Shadows</object>
|
||||
<object name="waterShadowsCheckbox" size="100%-56 215 100%-30 240" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
@ -516,27 +516,27 @@
|
||||
<action on="Load">if (renderer.particles) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.particles = this.checked;</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Unit Silhouettes -->
|
||||
<object size="0 260 100%-80 285" type="text" style="RightLabelText" ghost="true">Enable Unit Silhouettes</object>
|
||||
<object name="silhouettesCheckbox" size="100%-56 265 100%-30 290" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
<action on="Load">if (renderer.silhouettes) this.checked = true; else this.checked = false;</action>
|
||||
<action on="Press">renderer.silhouettes = this.checked;</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Music-->
|
||||
<object size="0 285 100%-80 310" type="text" style="RightLabelText" ghost="true">Enable Music</object>
|
||||
<object name="musicCheckbox" size="100%-56 290 100%-30 315" type="checkbox" style="StoneCrossBox" checked="true">
|
||||
<action on="Press">if (this.checked) global.music.start(); else global.music.stop();</action>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Settings / Dev Overlay -->
|
||||
<object size="0 310 100%-80 335" type="text" style="RightLabelText" ghost="true">Developer Overlay</object>
|
||||
<object name="developerOverlayCheckbox" size="100%-56 315 100%-30 340" type="checkbox" style="StoneCrossBox" checked="false">
|
||||
<action on="Press">toggleDeveloperOverlay();</action>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Close button -->
|
||||
<object type="button"
|
||||
style="StoneButton"
|
||||
@ -640,7 +640,7 @@
|
||||
</object>
|
||||
|
||||
<object size="100%-348 40 100%-198 65" name="gameSpeed" type="dropdown" buffer_zone="5" style="StoneDropDown" hidden="true" tooltip="Choose game speed" tooltip_style="sessionToolTip"/>
|
||||
|
||||
|
||||
<!-- ================================ ================================ -->
|
||||
<!-- Diplomacy Button -->
|
||||
<!-- ================================ ================================ -->
|
||||
@ -790,12 +790,12 @@
|
||||
name="unitHeroPanel"
|
||||
size="0 36 50 86"
|
||||
>
|
||||
<object name="unitHeroButton" size="0 0 50 50" type="button" hidden="false" style="iconButton"
|
||||
<object name="unitHeroButton" size="0 0 50 50" type="button" hidden="false" style="iconButton"
|
||||
tooltip_style="sessionToolTip" tooltip="Attack and Armor">
|
||||
<object name="unitHeroImage" size="5 5 100%-5 100%-5" type="image" ghost="true"/>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- ================================ ================================ -->
|
||||
<!-- Unit Selection Groups -->
|
||||
<!-- ================================ ================================ -->
|
||||
@ -896,7 +896,7 @@
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object name="unitBarterPanel"
|
||||
<object name="unitBarterPanel"
|
||||
size="6 36 100% 100%"
|
||||
hidden="true"
|
||||
>
|
||||
@ -950,10 +950,10 @@
|
||||
>
|
||||
<!-- Unit details for Single Unit -->
|
||||
<object size="50%-112 0 50%+112 100%" name="detailsAreaSingle">
|
||||
|
||||
|
||||
<!-- Stats Bars -->
|
||||
<object size= "2 0 100%-2 98" type="image" tooltip_style="sessionToolTip">
|
||||
|
||||
|
||||
<object size="0 8 100% 60" type="image" sprite="edgedPanelShader">
|
||||
<!-- Health bar -->
|
||||
<object size="88 0 100% 24" name="healthSection">
|
||||
@ -991,21 +991,25 @@
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
||||
<object size="0 60 100% 96" type="image" sprite="edgedPanelShader">
|
||||
<!-- Attack and Armor -->
|
||||
<object size="90 -2 126 34" name="attackAndArmorStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip="Attack and Armor" tooltip_style="sessionToolTip"/>
|
||||
|
||||
<!-- Resource carrying icon/counter -->
|
||||
<!-- Used also for number of gatherers/builders -->
|
||||
<object size="100%-98 -2 100%-28 34" type="text" name="resourceCarryingText" style="CarryingTextRight"/>
|
||||
<object size="100%-36 -2 100% 34" type="image" name="resourceCarryingIcon" tooltip_style="sessionToolTip"/>
|
||||
<object size="100%-78 -2 100%-84 34" type="text" name="resourceCarryingText" style="CarryingTextRight"/>
|
||||
<object size="100%-36 -2 100%-56 34" type="image" name="resourceCarryingIcon" tooltip_style="sessionToolTip"/>
|
||||
|
||||
<!-- Throughput of this resource in resources/second -->
|
||||
<object size="100%-10 -2 100%-28 34" type="text" name="resourceThroughputText" style="CarryingTextRight"/>
|
||||
<object size="100%-5 -2 100% 34" type="text" name="resourceThroughputIcon" tooltip_style="sessionToolTip"/>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Big unit icon -->
|
||||
<object size="-8 -8 88 88" type="image" name="iconBorder" sprite="iconBorder" tooltip_style="sessionToolTip">
|
||||
<object size="1 1 100%-1 100%-1" type="image" name="icon" ghost="true"/>
|
||||
|
||||
|
||||
<!-- Experience bar -->
|
||||
<object size="2 2 6 100%-2" type="image" name="experience" tooltip="Experience" tooltip_style="sessionToolTip">
|
||||
<object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
|
||||
@ -1013,18 +1017,18 @@
|
||||
<object type="image" sprite="experienceForeground" ghost="true" name="experienceBar"/>
|
||||
<object type="image" sprite="statsBarShaderVertical" ghost="true"/>
|
||||
</object>
|
||||
|
||||
|
||||
<object z="20" size="4 4 20 20" name="rankIcon" type="image" tooltip="Rank" tooltip_style="sessionToolTip"/>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Names (this must come before the attack and armor icon to avoid clipping issues) -->
|
||||
<object size="2 96 100%-2 100%-36" name="statsArea" type="image" sprite="edgedPanelShader">
|
||||
|
||||
|
||||
<!-- These images are used to clip off the top and bottom of the civ icon -->
|
||||
<object z="30" size="0 -5 100% 40" ghost="true" type="image" sprite="remove"/>
|
||||
<object z="30" size="0 100%-5 100% 100%+40" ghost="true" type="image" sprite="remove"/>
|
||||
|
||||
|
||||
<object z="30" size="0 2 100% 45" ghost="true">
|
||||
<!-- Specific Name -->
|
||||
<object size="0 0 100% 20" name="specific" ghost="true" type="text" style="SpecificNameCentered"/>
|
||||
@ -1032,13 +1036,13 @@
|
||||
<!-- Generic Name -->
|
||||
<object size="0 15 100% 36" name="generic" ghost="true" type="text" style="GenericNameCentered"/>
|
||||
</object>
|
||||
|
||||
|
||||
<!-- Player Name and Civ -->
|
||||
<object size="0 40 100% 100%">
|
||||
<object size="50%-64 50%-64 50%+64 50%+64" name="playerCivIcon" type="image" ghost="true"/>
|
||||
<object size="0 0 100% 100%" name="playerColorBackground" type="image" sprite="playerColorBackground" ghost="true"/>
|
||||
<object size="0 0 100% 100%" type="image" sprite="bottomEdgedPanelShader" ghost="true"/>
|
||||
|
||||
|
||||
<!-- Why is this being automatically ghosted? In the mean time, set ghost to false -->
|
||||
<object ghost="false" size="0 0 100% 100%-5" name="player" type="text" style="largeCenteredOutlinedText" tooltip_style="sessionToolTip"/>
|
||||
</object>
|
||||
@ -1212,7 +1216,7 @@
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
||||
<object name="unitPackPanel"
|
||||
size="10 12 100% 100%"
|
||||
>
|
||||
|
@ -28,9 +28,9 @@ GuiInterface.prototype.Init = function()
|
||||
/*
|
||||
* All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
|
||||
* from GUI scripts, and executed here with arguments (player, arg).
|
||||
*
|
||||
* CAUTION: The input to the functions in this module is not network-synchronised, so it
|
||||
* mustn't affect the simulation state (i.e. the data that is serialised and can affect
|
||||
*
|
||||
* CAUTION: The input to the functions in this module is not network-synchronised, so it
|
||||
* mustn't affect the simulation state (i.e. the data that is serialised and can affect
|
||||
* the behaviour of the rest of the simulation) else it'll cause out-of-sync errors.
|
||||
*/
|
||||
|
||||
@ -43,7 +43,7 @@ GuiInterface.prototype.GetSimulationState = function(player)
|
||||
var ret = {
|
||||
"players": []
|
||||
};
|
||||
|
||||
|
||||
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
||||
var n = cmpPlayerMan.GetNumPlayers();
|
||||
for (var i = 0; i < n; ++i)
|
||||
@ -51,7 +51,7 @@ GuiInterface.prototype.GetSimulationState = function(player)
|
||||
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
|
||||
var cmpPlayerEntityLimits = Engine.QueryInterface(playerEnt, IID_EntityLimits);
|
||||
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
|
||||
|
||||
|
||||
// Work out what phase we are in
|
||||
var cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
|
||||
var phase = "";
|
||||
@ -61,7 +61,7 @@ GuiInterface.prototype.GetSimulationState = function(player)
|
||||
phase = "town";
|
||||
else if (cmpTechnologyManager.IsTechnologyResearched("phase_village"))
|
||||
phase = "village";
|
||||
|
||||
|
||||
// store player ally/neutral/enemy data as arrays
|
||||
var allies = [];
|
||||
var mutualAllies = [];
|
||||
@ -110,7 +110,7 @@ GuiInterface.prototype.GetSimulationState = function(player)
|
||||
{
|
||||
ret.circularMap = cmpRangeManager.GetLosCircular();
|
||||
}
|
||||
|
||||
|
||||
// Add timeElapsed
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
ret.timeElapsed = cmpTimer.GetTime();
|
||||
@ -169,7 +169,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
"selectionGroupName": cmpIdentity.GetSelectionGroupName()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
if (cmpPosition && cmpPosition.IsInWorld())
|
||||
{
|
||||
@ -216,7 +216,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
// not in world, set a default?
|
||||
ret.attack.elevationAdaptedRange = ret.attack.maxRange;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -291,16 +291,19 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
}
|
||||
|
||||
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
|
||||
if (cmpResourceSupply)
|
||||
{
|
||||
ret.resourceSupply = {
|
||||
"isInfinite": cmpResourceSupply.IsInfinite(),
|
||||
"max": cmpResourceSupply.GetMaxAmount(),
|
||||
"amount": cmpResourceSupply.GetCurrentAmount(),
|
||||
"type": cmpResourceSupply.GetType(),
|
||||
if (cmpResourceSupply)
|
||||
{
|
||||
ret.resourceSupply = {
|
||||
"isInfinite": cmpResourceSupply.IsInfinite(),
|
||||
"max": cmpResourceSupply.GetMaxAmount(),
|
||||
"amount": cmpResourceSupply.GetCurrentAmount(),
|
||||
"type": cmpResourceSupply.GetType(),
|
||||
"killBeforeGather": cmpResourceSupply.GetKillBeforeGather(),
|
||||
"maxGatherers": cmpResourceSupply.GetMaxGatherers(),
|
||||
"gatherers": cmpResourceSupply.GetGatherers()
|
||||
"gatherers": cmpResourceSupply.GetGatherers(),
|
||||
"throughput": cmpResourceSupply.GetGatherers().reduce(function (total, e)
|
||||
total + Engine.QueryInterface(e, IID_ResourceGatherer).GetTargetGatherRate(ent)
|
||||
, 0)
|
||||
};
|
||||
}
|
||||
|
||||
@ -334,7 +337,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
"capacity": cmpGarrisonHolder.GetCapacity()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
|
||||
if (cmpPromotion)
|
||||
{
|
||||
@ -361,7 +364,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
ret.gate = {
|
||||
"locked": cmpGate.IsLocked(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("BarterMarket"))
|
||||
{
|
||||
@ -372,7 +375,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
var cmpHeal = Engine.QueryInterface(ent, IID_Heal);
|
||||
if (cmpHeal)
|
||||
{
|
||||
ret.Healer = {
|
||||
ret.Healer = {
|
||||
"unhealableClasses": cmpHeal.GetUnhealableClasses(),
|
||||
"healableClasses": cmpHeal.GetHealableClasses(),
|
||||
};
|
||||
@ -424,7 +427,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
||||
"crush": GetTechModifiedProperty(techMods, template, "Armour/Crush", +template.Armour.Crush),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (template.Attack)
|
||||
{
|
||||
ret.attack = {};
|
||||
@ -440,7 +443,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (template.BuildRestrictions)
|
||||
{
|
||||
// required properties
|
||||
@ -449,7 +452,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
||||
"territory": template.BuildRestrictions.Territory,
|
||||
"category": template.BuildRestrictions.Category,
|
||||
};
|
||||
|
||||
|
||||
// optional properties
|
||||
if (template.BuildRestrictions.Distance)
|
||||
{
|
||||
@ -479,11 +482,11 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
||||
if (template.Cost.PopulationBonus) ret.cost.populationBonus = GetTechModifiedProperty(techMods, template, "Cost/PopulationBonus", +template.Cost.PopulationBonus);
|
||||
if (template.Cost.BuildTime) ret.cost.time = GetTechModifiedProperty(techMods, template, "Cost/BuildTime", +template.Cost.BuildTime);
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
@ -491,7 +494,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
||||
else
|
||||
warn("[GetTemplateData] Unrecognized Footprint type");
|
||||
}
|
||||
|
||||
|
||||
if (template.Obstruction)
|
||||
{
|
||||
ret.obstruction = {
|
||||
@ -504,7 +507,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
||||
"disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
|
||||
"shape": {}
|
||||
};
|
||||
|
||||
|
||||
if (template.Obstruction.Static)
|
||||
{
|
||||
ret.obstruction.shape.type = "static";
|
||||
@ -573,7 +576,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
||||
"minTowerOverlap": +template.WallSet.MinTowerOverlap,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (template.WallPiece)
|
||||
{
|
||||
ret.wallPiece = {"length": +template.WallPiece.Length};
|
||||
@ -586,16 +589,16 @@ GuiInterface.prototype.GetTechnologyData = function(player, name)
|
||||
{
|
||||
var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager);
|
||||
var template = cmpTechTempMan.GetTemplate(name);
|
||||
|
||||
|
||||
if (!template)
|
||||
{
|
||||
warn("Tried to get data for invalid technology: " + name);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var ret = {};
|
||||
|
||||
// Get specific name for this civ or else the generic specific name
|
||||
|
||||
// Get specific name for this civ or else the generic specific name
|
||||
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
|
||||
var specific = undefined;
|
||||
if (template.specificName)
|
||||
@ -605,7 +608,7 @@ GuiInterface.prototype.GetTechnologyData = function(player, name)
|
||||
else
|
||||
specific = template.specificName['generic'];
|
||||
}
|
||||
|
||||
|
||||
ret.name = {
|
||||
"specific": specific,
|
||||
"generic": template.genericName,
|
||||
@ -619,24 +622,24 @@ GuiInterface.prototype.GetTechnologyData = function(player, name)
|
||||
"time": template.researchTime ? (+template.researchTime) : 0,
|
||||
}
|
||||
ret.tooltip = template.tooltip;
|
||||
|
||||
|
||||
if (template.requirementsTooltip)
|
||||
ret.requirementsTooltip = template.requirementsTooltip;
|
||||
else
|
||||
ret.requirementsTooltip = "";
|
||||
|
||||
|
||||
ret.description = template.description;
|
||||
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
GuiInterface.prototype.IsTechnologyResearched = function(player, tech)
|
||||
{
|
||||
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
|
||||
|
||||
|
||||
if (!cmpTechnologyManager)
|
||||
return false;
|
||||
|
||||
|
||||
return cmpTechnologyManager.IsTechnologyResearched(tech);
|
||||
};
|
||||
|
||||
@ -644,10 +647,10 @@ GuiInterface.prototype.IsTechnologyResearched = function(player, tech)
|
||||
GuiInterface.prototype.CheckTechnologyRequirements = function(player, tech)
|
||||
{
|
||||
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
|
||||
|
||||
|
||||
if (!cmpTechnologyManager)
|
||||
return false;
|
||||
|
||||
|
||||
return cmpTechnologyManager.CanResearch(tech);
|
||||
};
|
||||
|
||||
@ -751,7 +754,7 @@ GuiInterface.prototype.SetSelectionHighlight = function(player, cmd, selected)
|
||||
{
|
||||
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
||||
var playerColours = {}; // cache of owner -> colour map
|
||||
|
||||
|
||||
for each (var ent in cmd.entities)
|
||||
{
|
||||
var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
|
||||
@ -796,11 +799,11 @@ GuiInterface.prototype.GetPlayerEntities = function(player)
|
||||
|
||||
/**
|
||||
* Displays the rally points of a given list of entities (carried in cmd.entities).
|
||||
*
|
||||
* The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
|
||||
* be rendered, in order to support instantaneously rendering a rally point marker at a specified location
|
||||
*
|
||||
* The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
|
||||
* be rendered, in order to support instantaneously rendering a rally point marker at a specified location
|
||||
* instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
|
||||
* If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
|
||||
* If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
|
||||
* RallyPoint component.
|
||||
*/
|
||||
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
|
||||
@ -815,16 +818,16 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
|
||||
if (cmpRallyPointRenderer)
|
||||
cmpRallyPointRenderer.SetDisplayed(false);
|
||||
}
|
||||
|
||||
|
||||
this.entsRallyPointsDisplayed = [];
|
||||
|
||||
|
||||
// Show the rally points for the passed entities
|
||||
for each (var ent in cmd.entities)
|
||||
{
|
||||
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
|
||||
if (!cmpRallyPointRenderer)
|
||||
continue;
|
||||
|
||||
|
||||
// entity must have a rally point component to display a rally point marker
|
||||
// (regardless of whether cmd specifies a custom location)
|
||||
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
|
||||
@ -841,7 +844,7 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
|
||||
// override the real rally point position; otherwise use the real position
|
||||
var pos;
|
||||
if (cmd.x && cmd.z)
|
||||
pos = cmd;
|
||||
pos = cmd;
|
||||
else
|
||||
pos = cmpRallyPoint.GetPositions()[0]; // may return undefined if no rally point is set
|
||||
|
||||
@ -853,7 +856,7 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
|
||||
else if (cmd.queued == false)
|
||||
cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
|
||||
cmpRallyPointRenderer.SetDisplayed(true);
|
||||
|
||||
|
||||
// remember which entities have their rally points displayed so we can hide them again
|
||||
this.entsRallyPointsDisplayed.push(ent);
|
||||
}
|
||||
@ -864,7 +867,7 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
|
||||
* Display the building placement preview.
|
||||
* cmd.template is the name of the entity template, or "" to disable the preview.
|
||||
* cmd.x, cmd.z, cmd.angle give the location.
|
||||
*
|
||||
*
|
||||
* Returns result object from CheckPlacement:
|
||||
* {
|
||||
* "success": true iff the placement is valid, else false
|
||||
@ -936,34 +939,34 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
||||
};
|
||||
|
||||
/**
|
||||
* Previews the placement of a wall between cmd.start and cmd.end, or just the starting piece of a wall if cmd.end is not
|
||||
* Previews the placement of a wall between cmd.start and cmd.end, or just the starting piece of a wall if cmd.end is not
|
||||
* specified. Returns an object with information about the list of entities that need to be newly constructed to complete
|
||||
* at least a part of the wall, or false if there are entities required to build at least part of the wall but none of
|
||||
* them can be validly constructed.
|
||||
*
|
||||
*
|
||||
* It's important to distinguish between three lists of entities that are at play here, because they may be subsets of one
|
||||
* another depending on things like snapping and whether some of the entities inside them can be validly positioned.
|
||||
* We have:
|
||||
* - The list of entities that previews the wall. This list is usually equal to the entities required to construct the
|
||||
* entire wall. However, if there is snapping to an incomplete tower (i.e. a foundation), it includes extra entities
|
||||
* to preview the completed tower on top of its foundation.
|
||||
*
|
||||
*
|
||||
* - The list of entities that need to be newly constructed to build the entire wall. This list is regardless of whether
|
||||
* any of them can be validly positioned. The emphasishere here is on 'newly'; this list does not include any existing
|
||||
* towers at either side of the wall that we snapped to. Or, more generally; it does not include any _entities_ that we
|
||||
* snapped to; we might still snap to e.g. terrain, in which case the towers on either end will still need to be newly
|
||||
* constructed.
|
||||
*
|
||||
*
|
||||
* - The list of entities that need to be newly constructed to build at least a part of the wall. This list is the same
|
||||
* as the one above, except that it is truncated at the first entity that cannot be validly positioned. This happens
|
||||
* e.g. if the player tries to build a wall straight through an obstruction. Note that any entities that can be validly
|
||||
* constructed but come after said first invalid entity are also truncated away.
|
||||
*
|
||||
*
|
||||
* With this in mind, this method will return false if the second list is not empty, but the third one is. That is, if there
|
||||
* were entities that are needed to build the wall, but none of them can be validly constructed. False is also returned in
|
||||
* case of unexpected errors (typically missing components), and when clearing the preview by passing an empty wallset
|
||||
* argument (see below). Otherwise, it will return an object with the following information:
|
||||
*
|
||||
*
|
||||
* result: {
|
||||
* 'startSnappedEnt': ID of the entity that we snapped to at the starting side of the wall. Currently only supports towers.
|
||||
* 'endSnappedEnt': ID of the entity that we snapped to at the (possibly truncated) ending side of the wall. Note that this
|
||||
@ -972,7 +975,7 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
||||
* supports towers.
|
||||
* 'pieces': Array with the following data for each of the entities in the third list:
|
||||
* [{
|
||||
* 'template': Template name of the entity.
|
||||
* 'template': Template name of the entity.
|
||||
* 'x': X coordinate of the entity's position.
|
||||
* 'z': Z coordinate of the entity's position.
|
||||
* 'angle': Rotation around the Y axis of the entity (in radians).
|
||||
@ -987,7 +990,7 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
||||
* 'populationBonus': ...,
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @param cmd.wallSet Object holding the set of wall piece template names. Set to an empty value to clear the preview.
|
||||
* @param cmd.start Starting point of the wall segment being created.
|
||||
* @param cmd.end (Optional) Ending point of the wall segment being created. If not defined, it is understood that only
|
||||
@ -999,27 +1002,27 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
||||
GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
{
|
||||
var wallSet = cmd.wallSet;
|
||||
|
||||
|
||||
var start = {
|
||||
"pos": cmd.start,
|
||||
"angle": 0,
|
||||
"snapped": false, // did the start position snap to anything?
|
||||
"snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID
|
||||
};
|
||||
|
||||
|
||||
var end = {
|
||||
"pos": cmd.end,
|
||||
"angle": 0,
|
||||
"snapped": false, // did the start position snap to anything?
|
||||
"snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID
|
||||
};
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// do some entity cache management and check for snapping
|
||||
|
||||
|
||||
if (!this.placementWallEntities)
|
||||
this.placementWallEntities = {};
|
||||
|
||||
|
||||
if (!wallSet)
|
||||
{
|
||||
// we're clearing the preview, clear the entity cache and bail
|
||||
@ -1028,12 +1031,12 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
{
|
||||
for each (var ent in this.placementWallEntities[tpl].entities)
|
||||
Engine.DestroyEntity(ent);
|
||||
|
||||
|
||||
this.placementWallEntities[tpl].numUsed = 0;
|
||||
this.placementWallEntities[tpl].entities = [];
|
||||
// keep template data around
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@ -1047,10 +1050,10 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
if (pos)
|
||||
pos.MoveOutOfWorld();
|
||||
}
|
||||
|
||||
|
||||
this.placementWallEntities[tpl].numUsed = 0;
|
||||
}
|
||||
|
||||
|
||||
// Create cache entries for templates we haven't seen before
|
||||
for each (var tpl in wallSet.templates)
|
||||
{
|
||||
@ -1061,7 +1064,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
"entities": [],
|
||||
"templateData": this.GetTemplateData(player, tpl),
|
||||
};
|
||||
|
||||
|
||||
// ensure that the loaded template data contains a wallPiece component
|
||||
if (!this.placementWallEntities[tpl].templateData.wallPiece)
|
||||
{
|
||||
@ -1071,11 +1074,11 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// prevent division by zero errors further on if the start and end positions are the same
|
||||
if (end.pos && (start.pos.x === end.pos.x && start.pos.z === end.pos.z))
|
||||
end.pos = undefined;
|
||||
|
||||
|
||||
// See if we need to snap the start and/or end coordinates to any of our list of snap entities. Note that, despite the list
|
||||
// of snapping candidate entities, it might still snap to e.g. terrain features. Use the "ent" key in the returned snapping
|
||||
// data to determine whether it snapped to an entity (if any), and to which one (see GetFoundationSnapData).
|
||||
@ -1089,18 +1092,18 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
"snapEntities": cmd.snapEntities,
|
||||
"snapRadius": snapRadius,
|
||||
});
|
||||
|
||||
|
||||
if (startSnapData)
|
||||
{
|
||||
start.pos.x = startSnapData.x;
|
||||
start.pos.z = startSnapData.z;
|
||||
start.angle = startSnapData.angle;
|
||||
start.snapped = true;
|
||||
|
||||
|
||||
if (startSnapData.ent)
|
||||
start.snappedEnt = startSnapData.ent;
|
||||
start.snappedEnt = startSnapData.ent;
|
||||
}
|
||||
|
||||
|
||||
if (end.pos)
|
||||
{
|
||||
var endSnapData = this.GetFoundationSnapData(player, {
|
||||
@ -1110,41 +1113,41 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
"snapEntities": cmd.snapEntities,
|
||||
"snapRadius": snapRadius,
|
||||
});
|
||||
|
||||
|
||||
if (endSnapData)
|
||||
{
|
||||
end.pos.x = endSnapData.x;
|
||||
end.pos.z = endSnapData.z;
|
||||
end.angle = endSnapData.angle;
|
||||
end.snapped = true;
|
||||
|
||||
|
||||
if (endSnapData.ent)
|
||||
end.snappedEnt = endSnapData.ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// clear the single-building preview entity (we'll be rolling our own)
|
||||
this.SetBuildingPlacementPreview(player, {"template": ""});
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// calculate wall placement and position preview entities
|
||||
|
||||
|
||||
var result = {
|
||||
"pieces": [],
|
||||
"cost": {"food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0},
|
||||
};
|
||||
|
||||
|
||||
var previewEntities = [];
|
||||
if (end.pos)
|
||||
previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js
|
||||
|
||||
// For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would
|
||||
|
||||
// For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would
|
||||
// otherwise be allowed by their obstruction shapes. However, during this preview phase, this is not so much of
|
||||
// an issue, because all preview entities have their obstruction components deactivated, meaning that their
|
||||
// an issue, because all preview entities have their obstruction components deactivated, meaning that their
|
||||
// obstruction shapes do not register in the simulation and hence cannot affect it. This implies that the preview
|
||||
// entities cannot be found to obstruct each other, which largely solves the issue of overlap between wall pieces.
|
||||
|
||||
|
||||
// Note that they will still be obstructed by existing shapes in the simulation (that have the BLOCK_FOUNDATION
|
||||
// flag set), which is what we want. The only exception to this is when snapping to existing towers (or
|
||||
// foundations thereof); the wall segments that connect up to these will be found to be obstructed by the
|
||||
@ -1152,17 +1155,17 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
// we manually set the control group of the outermost wall pieces equal to those of the snapped-to towers, so
|
||||
// that they are free from mutual obstruction (per definition of obstruction control groups). This is done by
|
||||
// assigning them an extra "controlGroup" field, which we'll then set during the placement loop below.
|
||||
|
||||
|
||||
// Additionally, in the situation that we're snapping to merely a foundation of a tower instead of a fully
|
||||
// constructed one, we'll need an extra preview entity for the starting tower, which also must not be obstructed
|
||||
// by the foundation it snaps to.
|
||||
|
||||
|
||||
if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
|
||||
{
|
||||
var startEntObstruction = Engine.QueryInterface(start.snappedEnt, IID_Obstruction);
|
||||
if (previewEntities.length > 0 && startEntObstruction)
|
||||
previewEntities[0].controlGroups = [startEntObstruction.GetControlGroup()];
|
||||
|
||||
|
||||
// if we're snapping to merely a foundation, add an extra preview tower and also set it to the same control group
|
||||
var startEntState = this.GetEntityState(player, start.snappedEnt);
|
||||
if (startEntState.foundation)
|
||||
@ -1182,19 +1185,19 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Didn't snap to an existing entity, add the starting tower manually. To prevent odd-looking rotation jumps
|
||||
// when shift-clicking to build a wall, reuse the placement angle that was last seen on a validly positioned
|
||||
// Didn't snap to an existing entity, add the starting tower manually. To prevent odd-looking rotation jumps
|
||||
// when shift-clicking to build a wall, reuse the placement angle that was last seen on a validly positioned
|
||||
// wall piece.
|
||||
|
||||
|
||||
// To illustrate the last point, consider what happens if we used some constant instead, say, 0. Issuing the
|
||||
// build command for a wall is asynchronous, so when the preview updates after shift-clicking, the wall piece
|
||||
// foundations are not registered yet in the simulation. This means they cannot possibly be picked in the list
|
||||
// of candidate entities for snapping. In the next preview update, we therefore hit this case, and would rotate
|
||||
// the preview to 0 radians. Then, after one or two simulation updates or so, the foundations register and
|
||||
// the preview to 0 radians. Then, after one or two simulation updates or so, the foundations register and
|
||||
// onSimulationUpdate in session.js updates the preview again. It first grabs a new list of snapping candidates,
|
||||
// which this time does include the new foundations; so we snap to the entity, and rotate the preview back to
|
||||
// the foundation's angle.
|
||||
|
||||
|
||||
// The result is a noticeable rotation to 0 and back, which is undesirable. So, for a split second there until
|
||||
// the simulation updates, we fake it by reusing the last angle and hope the player doesn't notice.
|
||||
previewEntities.unshift({
|
||||
@ -1203,14 +1206,14 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
"angle": (previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (end.pos)
|
||||
{
|
||||
// Analogous to the starting side case above
|
||||
if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY)
|
||||
{
|
||||
var endEntObstruction = Engine.QueryInterface(end.snappedEnt, IID_Obstruction);
|
||||
|
||||
|
||||
// Note that it's possible for the last entity in previewEntities to be the same as the first, i.e. the
|
||||
// same wall piece snapping to both a starting and an ending tower. And it might be more common than you would
|
||||
// expect; the allowed overlap between wall segments and towers facilitates this to some degree. To deal with
|
||||
@ -1221,7 +1224,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
previewEntities[previewEntities.length-1].controlGroups = (previewEntities[previewEntities.length-1].controlGroups || []);
|
||||
previewEntities[previewEntities.length-1].controlGroups.push(endEntObstruction.GetControlGroup());
|
||||
}
|
||||
|
||||
|
||||
// if we're snapping to a foundation, add an extra preview tower and also set it to the same control group
|
||||
var endEntState = this.GetEntityState(player, end.snappedEnt);
|
||||
if (endEntState.foundation)
|
||||
@ -1248,37 +1251,37 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
|
||||
if (!cmpTerrain)
|
||||
{
|
||||
error("[SetWallPlacementPreview] System Terrain component not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
if (!cmpRangeManager)
|
||||
{
|
||||
error("[SetWallPlacementPreview] System RangeManager component not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Loop through the preview entities, and construct the subset of them that need to be, and can be, validly constructed
|
||||
// to build at least a part of the wall (meaning that the subset is truncated after the first entity that needs to be,
|
||||
// but cannot validly be, constructed). See method-level documentation for more details.
|
||||
|
||||
|
||||
var allPiecesValid = true;
|
||||
var numRequiredPieces = 0; // number of entities that are required to build the entire wall, regardless of validity
|
||||
|
||||
|
||||
for (var i = 0; i < previewEntities.length; ++i)
|
||||
{
|
||||
var entInfo = previewEntities[i];
|
||||
|
||||
|
||||
var ent = null;
|
||||
var tpl = entInfo.template;
|
||||
var tplData = this.placementWallEntities[tpl].templateData;
|
||||
var entPool = this.placementWallEntities[tpl];
|
||||
|
||||
|
||||
if (entPool.numUsed >= entPool.entities.length)
|
||||
{
|
||||
// allocate new entity
|
||||
@ -1290,13 +1293,13 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
// reuse an existing one
|
||||
ent = entPool.entities[entPool.numUsed];
|
||||
}
|
||||
|
||||
|
||||
if (!ent)
|
||||
{
|
||||
error("[SetWallPlacementPreview] Failed to allocate or reuse preview entity of template '" + tpl + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// move piece to right location
|
||||
// TODO: consider reusing SetBuildingPlacementReview for this, enhanced to be able to deal with multiple entities
|
||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
@ -1304,18 +1307,18 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
{
|
||||
cmpPosition.JumpTo(entInfo.pos.x, entInfo.pos.z);
|
||||
cmpPosition.SetYRotation(entInfo.angle);
|
||||
|
||||
|
||||
// if this piece is a tower, then it should have a Y position that is at least as high as its surrounding pieces
|
||||
if (tpl === wallSet.templates.tower)
|
||||
{
|
||||
var terrainGroundPrev = null;
|
||||
var terrainGroundNext = null;
|
||||
|
||||
|
||||
if (i > 0)
|
||||
terrainGroundPrev = cmpTerrain.GetGroundLevel(previewEntities[i-1].pos.x, previewEntities[i-1].pos.z);
|
||||
if (i < previewEntities.length - 1)
|
||||
terrainGroundNext = cmpTerrain.GetGroundLevel(previewEntities[i+1].pos.x, previewEntities[i+1].pos.z);
|
||||
|
||||
|
||||
if (terrainGroundPrev != null || terrainGroundNext != null)
|
||||
{
|
||||
var targetY = Math.max(terrainGroundPrev, terrainGroundNext);
|
||||
@ -1323,27 +1326,27 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
|
||||
if (!cmpObstruction)
|
||||
{
|
||||
error("[SetWallPlacementPreview] Preview entity of template '" + tpl + "' does not have an Obstruction component");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Assign any predefined control groups. Note that there can only be 0, 1 or 2 predefined control groups; if there are
|
||||
// more, we've made a programming error. The control groups are assigned from the entInfo.controlGroups array on a
|
||||
// first-come first-served basis; the first value in the array is always assigned as the primary control group, and
|
||||
// any second value as the secondary control group.
|
||||
|
||||
|
||||
// By default, we reset the control groups to their standard values. Remember that we're reusing entities; if we don't
|
||||
// reset them, then an ending wall segment that was e.g. at one point snapped to an existing tower, and is subsequently
|
||||
// reused as a non-snapped ending wall segment, would no longer be capable of being obstructed by the same tower it was
|
||||
// once snapped to.
|
||||
|
||||
|
||||
var primaryControlGroup = ent;
|
||||
var secondaryControlGroup = INVALID_ENTITY;
|
||||
|
||||
|
||||
if (entInfo.controlGroups && entInfo.controlGroups.length > 0)
|
||||
{
|
||||
if (entInfo.controlGroups.length > 2)
|
||||
@ -1351,21 +1354,21 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
error("[SetWallPlacementPreview] Encountered preview entity of template '" + tpl + "' with more than 2 initial control groups");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
primaryControlGroup = entInfo.controlGroups[0];
|
||||
if (entInfo.controlGroups.length > 1)
|
||||
secondaryControlGroup = entInfo.controlGroups[1];
|
||||
}
|
||||
|
||||
|
||||
cmpObstruction.SetControlGroup(primaryControlGroup);
|
||||
cmpObstruction.SetControlGroup2(secondaryControlGroup);
|
||||
|
||||
|
||||
// check whether this wall piece can be validly positioned here
|
||||
var validPlacement = false;
|
||||
|
||||
|
||||
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
||||
cmpOwnership.SetOwner(player);
|
||||
|
||||
|
||||
// Check whether it's in a visible or fogged region
|
||||
// tell GetLosVisibility to force RetainInFog because preview entities set this to false,
|
||||
// which would show them as hidden instead of fogged
|
||||
@ -1379,7 +1382,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for preview entity of template '" + tpl + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Handle results of CheckPlacement
|
||||
validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success);
|
||||
|
||||
@ -1391,18 +1394,18 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
}
|
||||
|
||||
allPiecesValid = allPiecesValid && validPlacement;
|
||||
|
||||
|
||||
// The requirement below that all pieces so far have to have valid positions, rather than only this single one,
|
||||
// ensures that no more foundations will be placed after a first invalidly-positioned piece. (It is possible
|
||||
// for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall
|
||||
// for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall
|
||||
// through and past an existing building).
|
||||
|
||||
|
||||
// Additionally, the excludeFromResult flag is set for preview entities that were manually added to be placed
|
||||
// on top of foundations of incompleted towers that we snapped to; they must not be part of the result.
|
||||
|
||||
|
||||
if (!entInfo.excludeFromResult)
|
||||
numRequiredPieces++;
|
||||
|
||||
|
||||
if (allPiecesValid && !entInfo.excludeFromResult)
|
||||
{
|
||||
result.pieces.push({
|
||||
@ -1412,7 +1415,7 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
"angle": entInfo.angle,
|
||||
});
|
||||
this.placementWallLastAngle = entInfo.angle;
|
||||
|
||||
|
||||
// grab the cost of this wall piece and add it up (note; preview entities don't have their Cost components
|
||||
// copied over, so we need to fetch it from the template instead).
|
||||
// TODO: we should really use a Cost object or at least some utility functions for this, this is mindless
|
||||
@ -1442,27 +1445,27 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
|
||||
|
||||
entPool.numUsed++;
|
||||
}
|
||||
|
||||
|
||||
// If any were entities required to build the wall, but none of them could be validly positioned, return failure
|
||||
// (see method-level documentation).
|
||||
if (numRequiredPieces > 0 && result.pieces.length == 0)
|
||||
return false;
|
||||
|
||||
|
||||
if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
|
||||
result.startSnappedEnt = start.snappedEnt;
|
||||
|
||||
|
||||
// We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed,
|
||||
// i.e. are included in result.pieces (see docs for the result object).
|
||||
if (end.pos && end.snappedEnt && end.snappedEnt != INVALID_ENTITY && allPiecesValid)
|
||||
result.endSnappedEnt = end.snappedEnt;
|
||||
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given the current position {data.x, data.z} of an foundation of template data.template, returns the position and angle to snap
|
||||
* it to (if necessary/useful).
|
||||
*
|
||||
*
|
||||
* @param data.x The X position of the foundation to snap.
|
||||
* @param data.z The Z position of the foundation to snap.
|
||||
* @param data.template The template to get the foundation snapping data for.
|
||||
@ -1484,38 +1487,38 @@ GuiInterface.prototype.GetFoundationSnapData = function(player, data)
|
||||
warn("[GetFoundationSnapData] Failed to load template '" + data.template + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (data.snapEntities && data.snapRadius && data.snapRadius > 0)
|
||||
{
|
||||
// see if {data.x, data.z} is inside the snap radius of any of the snap entities; and if so, to which it is closest
|
||||
// (TODO: break unlikely ties by choosing the lowest entity ID)
|
||||
|
||||
|
||||
var minDist2 = -1;
|
||||
var minDistEntitySnapData = null;
|
||||
var radius2 = data.snapRadius * data.snapRadius;
|
||||
|
||||
|
||||
for each (var ent in data.snapEntities)
|
||||
{
|
||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
if (!cmpPosition || !cmpPosition.IsInWorld())
|
||||
continue;
|
||||
|
||||
|
||||
var pos = cmpPosition.GetPosition();
|
||||
var dist2 = (data.x - pos.x) * (data.x - pos.x) + (data.z - pos.z) * (data.z - pos.z);
|
||||
if (dist2 > radius2)
|
||||
continue;
|
||||
|
||||
|
||||
if (minDist2 < 0 || dist2 < minDist2)
|
||||
{
|
||||
minDist2 = dist2;
|
||||
minDistEntitySnapData = {"x": pos.x, "z": pos.z, "angle": cmpPosition.GetRotation().y, "ent": ent};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (minDistEntitySnapData != null)
|
||||
return minDistEntitySnapData;
|
||||
}
|
||||
|
||||
|
||||
if (template.BuildRestrictions.Category == "Dock")
|
||||
{
|
||||
// warning: copied almost identically in helpers/command.js , "GetDockAngle".
|
||||
@ -1525,7 +1528,7 @@ GuiInterface.prototype.GetFoundationSnapData = function(player, data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Get footprint size
|
||||
var halfSize = 0;
|
||||
if (template.Footprint.Square)
|
||||
@ -1536,7 +1539,7 @@ GuiInterface.prototype.GetFoundationSnapData = function(player, data)
|
||||
{
|
||||
halfSize = template.Footprint.Circle["@radius"];
|
||||
}
|
||||
|
||||
|
||||
/* Find direction of most open water, algorithm:
|
||||
* 1. Pick points in a circle around dock
|
||||
* 2. If point is in water, add to array
|
||||
@ -1556,7 +1559,7 @@ GuiInterface.prototype.GetFoundationSnapData = function(player, data)
|
||||
var d = halfSize*(dist+1);
|
||||
var nx = data.x - d*Math.sin(angle);
|
||||
var nz = data.z + d*Math.cos(angle);
|
||||
|
||||
|
||||
if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz))
|
||||
{
|
||||
waterPoints.push(i);
|
||||
@ -1590,7 +1593,7 @@ GuiInterface.prototype.GetFoundationSnapData = function(player, data)
|
||||
count = consec[c];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we've found a shoreline, stop searching
|
||||
if (count != numPoints-1)
|
||||
{
|
||||
@ -1615,7 +1618,7 @@ function isIdleUnit(ent, idleClass)
|
||||
{
|
||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
||||
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
||||
|
||||
|
||||
// TODO: Do something with garrisoned idle units
|
||||
return (cmpUnitAI && cmpIdentity && cmpUnitAI.IsIdle() && !cmpUnitAI.IsGarrisoned() && idleClass && cmpIdentity.HasClass(idleClass));
|
||||
}
|
||||
@ -1767,7 +1770,7 @@ GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
|
||||
// trusted and indicates the player associated with the current client; no data should
|
||||
// be returned unless this player is meant to be able to see it.)
|
||||
var exposedFunctions = {
|
||||
|
||||
|
||||
"GetSimulationState": 1,
|
||||
"GetExtendedSimulationState": 1,
|
||||
"GetRenamedEntities": 1,
|
||||
|
@ -7,13 +7,13 @@ ResourceSupply.prototype.Schema =
|
||||
"<Type>food.meat</Type>" +
|
||||
"</a:example>" +
|
||||
"<element name='KillBeforeGather' a:help='Whether this entity must be killed (health reduced to 0) before its resources can be gathered'>" +
|
||||
"<data type='boolean'/>" +
|
||||
"</element>" +
|
||||
"<element name='Amount' a:help='Amount of resources available from this entity'>" +
|
||||
"<choice><data type='nonNegativeInteger'/><value>Infinity</value></choice>" +
|
||||
"</element>" +
|
||||
"<element name='Type' a:help='Type of resources'>" +
|
||||
"<choice>" +
|
||||
"<data type='boolean'/>" +
|
||||
"</element>" +
|
||||
"<element name='Amount' a:help='Amount of resources available from this entity'>" +
|
||||
"<choice><data type='nonNegativeInteger'/><value>Infinity</value></choice>" +
|
||||
"</element>" +
|
||||
"<element name='Type' a:help='Type of resources'>" +
|
||||
"<choice>" +
|
||||
"<value>wood.tree</value>" +
|
||||
"<value>wood.ruins</value>" +
|
||||
"<value>stone.rock</value>" +
|
||||
@ -29,32 +29,63 @@ ResourceSupply.prototype.Schema =
|
||||
"<value>treasure.metal</value>" +
|
||||
"<value>treasure.food</value>" +
|
||||
"</choice>" +
|
||||
"</element>" +
|
||||
"<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"<optional>" +
|
||||
"<element name='DiminishingReturns' a:help='The rate at which adding more gatherers decreases overall efficiency. Lower numbers = faster dropoff. Leave the element out for no diminishing returns.'>" +
|
||||
"<ref name='positiveDecimal'/>" +
|
||||
"</element>" +
|
||||
"</optional>";
|
||||
|
||||
ResourceSupply.prototype.Init = function()
|
||||
{
|
||||
// Current resource amount (non-negative)
|
||||
this.amount = this.GetMaxAmount();
|
||||
this.gatherers = []; // list of IDs
|
||||
this.infinite = !isFinite(+this.template.Amount);
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.IsInfinite = function()
|
||||
{
|
||||
return this.infinite;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetKillBeforeGather = function()
|
||||
{
|
||||
return (this.template.KillBeforeGather == "true");
|
||||
"</element>" +
|
||||
"<optional>" +
|
||||
"<element name='Regeneration' a:help='Controls whether this resource can regenerate its remaining amount.'>" +
|
||||
"<interleave>" +
|
||||
"<element name='Rate' a:help='Optional regeneration rate. Resources/second if the growth is linear or the rate of quadratic growth if the growth is quadratic.'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<optional>" +
|
||||
"<element name='Acceleration' a:help='Controls the curve the regeneration rate will follow for quadratic growth; does nothing for linear growth.'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<element name='Delay' a:help='Seconds between when the number of gatherers hit 0 and the resource starts regenerating.'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"<element name='Growth' a:help='Growth formula for regeneration. Either linear or quadratic. Linear continues at a steady rate, while quadratic gets faster over time.'>" +
|
||||
"<choice>" +
|
||||
"<value>linear</value>" +
|
||||
"<value>quadratic</value>" +
|
||||
"</choice>" +
|
||||
"</element>" +
|
||||
"</interleave>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"<optional>" +
|
||||
"<element name='DiminishingReturns' a:help='The rate at which adding more gatherers decreases overall efficiency. Lower numbers = faster dropoff. Leave the element out for no diminishing returns.'>" +
|
||||
"<ref name='positiveDecimal'/>" +
|
||||
"</element>" +
|
||||
"</optional>";
|
||||
|
||||
ResourceSupply.prototype.Init = function()
|
||||
{
|
||||
// Current resource amount (non-negative)
|
||||
this.amount = this.GetMaxAmount();
|
||||
this.gatherers = []; // list of IDs
|
||||
this.infinite = !isFinite(+this.template.Amount);
|
||||
if (this.template.Regeneration) {
|
||||
this.regenRate = +this.template.Regeneration.Rate;
|
||||
if (this.template.Regeneration.Acceleration)
|
||||
this.regenAccel = +this.template.Regeneration.Acceleration;
|
||||
this.regenDelay = +this.template.Regeneration.Delay;
|
||||
}
|
||||
if (this.IsRegenerative())
|
||||
this.RegenerateResources();
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.IsInfinite = function()
|
||||
{
|
||||
return this.infinite;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetKillBeforeGather = function()
|
||||
{
|
||||
return (this.template.KillBeforeGather == "true");
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetMaxAmount = function()
|
||||
@ -74,34 +105,71 @@ ResourceSupply.prototype.GetMaxGatherers = function()
|
||||
|
||||
ResourceSupply.prototype.GetGatherers = function()
|
||||
{
|
||||
return this.gatherers;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetDiminishingReturns = function()
|
||||
{
|
||||
if ("DiminishingReturns" in this.template)
|
||||
return ApplyTechModificationsToEntity("ResourceSupply/DiminishingReturns", +this.template.DiminishingReturns, this.entity);
|
||||
return null;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.TakeResources = function(rate)
|
||||
{
|
||||
if (this.infinite)
|
||||
return { "amount": rate, "exhausted": false };
|
||||
|
||||
// 'rate' should be a non-negative integer
|
||||
|
||||
var old = this.amount;
|
||||
return this.gatherers;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetDiminishingReturns = function()
|
||||
{
|
||||
if ("DiminishingReturns" in this.template)
|
||||
return ApplyTechModificationsToEntity("ResourceSupply/DiminishingReturns", +this.template.DiminishingReturns, this.entity);
|
||||
return null;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.TakeResources = function(rate)
|
||||
{
|
||||
if (this.infinite)
|
||||
return { "amount": rate, "exhausted": false };
|
||||
|
||||
// 'rate' should be a non-negative integer
|
||||
|
||||
var old = this.amount;
|
||||
this.amount = Math.max(0, old - rate);
|
||||
var change = old - this.amount;
|
||||
|
||||
// Remove entities that have been exhausted
|
||||
if (this.amount == 0)
|
||||
if (this.amount == 0 && !this.IsRegenerative())
|
||||
Engine.DestroyEntity(this.entity);
|
||||
|
||||
Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, { "from": old, "to": this.amount });
|
||||
|
||||
return { "amount": change, "exhausted": (this.amount == 0) };
|
||||
return { "amount": change, "exhausted": this.amount == 0 };
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.RegenerateResources = function(data, lateness)
|
||||
{
|
||||
var max = this.GetMaxAmount();
|
||||
if (this.gatherers.length == 0 && !this.regenDelayTimer && this.amount < max)
|
||||
{
|
||||
var old = this.amount;
|
||||
if (this.regenGrowth == "linear")
|
||||
this.amount = Math.min(max, this.amount + data.rate);
|
||||
else
|
||||
this.amount = Math.min(max, this.amount + Math.max(1, data.rate * max * (data.acceleration * this.amount / max -Math.pow(this.amount / max, 2)) / 100));
|
||||
Engine.PostMessage(this.entity, MT_ResourceSupplyChanged, { "from": old, "to": this.amount });
|
||||
}
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
var regenRate = this.GetRegenerationRate();
|
||||
var absRegen = Math.abs(regenRate);
|
||||
if (Math.floor(regenRate) == regenRate || this.regenGrowth == "quadratic")
|
||||
cmpTimer.SetTimeout(this.entity, IID_ResourceSupply, "RegenerateResources", 1000, { "rate": regenRate, "acceleration": this.GetRegenerationAcceleration() });
|
||||
else
|
||||
cmpTimer.SetTimeout(this.entity, IID_ResourceSupply, "RegenerateResources", 1000 / absRegen,
|
||||
{ "rate": absRegen == regenRate ? 1 : -1 });
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.StartRegenerationDelayTimer = function()
|
||||
{
|
||||
if (!this.regenDelayTimer) {
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.regenDelayTimer = cmpTimer.SetTimeout(this.entity, IID_ResourceSupply, "CancelRegenerationDelayTimer", this.GetRegenerationDelay() * 1000, null);
|
||||
}
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.CancelRegenerationDelayTimer = function()
|
||||
{
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
cmpTimer.CancelTimer(this.regenDelayTimer);
|
||||
this.regenDelayTimer = null;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetType = function()
|
||||
@ -119,18 +187,50 @@ ResourceSupply.prototype.IsAvailable = function(gathererID)
|
||||
return false;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.IsRegenerative = function()
|
||||
{
|
||||
return this.GetRegenerationRate() != 0;
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetTerritoryOwner = function ()
|
||||
{
|
||||
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
||||
var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
if (!(cmpPosition && cmpPosition.IsInWorld()))
|
||||
return 0; // Something's wrong, just say we're in neutral territory.
|
||||
var pos = cmpPosition.GetPosition2D();
|
||||
return cmpPlayerManager.GetPlayerByID(cmpTerritoryManager.GetOwner(pos.x, pos.y));
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetRegenerationRate = function()
|
||||
{
|
||||
return ApplyTechModificationsToPlayer("ResourceSupply/Regeneration/Rate", this.regenRate, this.GetTerritoryOwner());
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetRegenerationAcceleration = function()
|
||||
{
|
||||
return ApplyTechModificationsToPlayer("ResourceSupply/Regeneration/Acceleration", this.regenAccel, this.GetTerritoryOwner());
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.GetRegenerationDelay = function()
|
||||
{
|
||||
return ApplyTechModificationsToPlayer("ResourcesSupply/Regeneration/Delay", this.regenDelay, this.GetTerritoryOwner());
|
||||
};
|
||||
|
||||
ResourceSupply.prototype.AddGatherer = function(gathererID)
|
||||
{
|
||||
if (!this.IsAvailable(gathererID))
|
||||
return false;
|
||||
|
||||
|
||||
if (this.gatherers.indexOf(gathererID) === -1)
|
||||
{
|
||||
this.gatherers.push(gathererID);
|
||||
this.CancelRegenerationDelayTimer();
|
||||
// broadcast message, mainly useful for the AIs.
|
||||
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -143,6 +243,8 @@ ResourceSupply.prototype.RemoveGatherer = function(gathererID)
|
||||
// broadcast message, mainly useful for the AIs.
|
||||
Engine.PostMessage(this.entity, MT_ResourceSupplyGatherersChanged, { "to": this.gatherers });
|
||||
}
|
||||
if (this.gatherers.length == 0)
|
||||
this.StartRegenerationDelayTimer();
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply);
|
||||
|
@ -98,7 +98,10 @@ StatisticsTracker.prototype.KilledEntity = function(targetEntity)
|
||||
{
|
||||
var cmpTargetEntityIdentity = Engine.QueryInterface(targetEntity, IID_Identity);
|
||||
var cmpCost = Engine.QueryInterface(targetEntity, IID_Cost);
|
||||
var costs = cmpCost.GetResourceCosts();
|
||||
if (cmpCost)
|
||||
var costs = cmpCost.GetResourceCosts();
|
||||
else
|
||||
var costs = { "food": 0, "wood": 0, "stone": 0, "metal": 0 };
|
||||
if (cmpTargetEntityIdentity)
|
||||
{
|
||||
var cmpFoundation = Engine.QueryInterface(targetEntity, IID_Foundation);
|
||||
@ -110,7 +113,7 @@ StatisticsTracker.prototype.KilledEntity = function(targetEntity)
|
||||
var targetIsCivCentre = cmpTargetEntityIdentity.HasClass("CivCentre");
|
||||
|
||||
var cmpTargetOwnership = Engine.QueryInterface(targetEntity, IID_Ownership);
|
||||
|
||||
|
||||
// Don't increase counters if target player is gaia (player 0)
|
||||
if (cmpTargetOwnership.GetOwner() != 0)
|
||||
{
|
||||
@ -121,7 +124,7 @@ StatisticsTracker.prototype.KilledEntity = function(targetEntity)
|
||||
{
|
||||
this.enemyUnitsKilledValue += costs[r];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetIsStructure)
|
||||
{
|
||||
this.enemyBuildingsDestroyed++;
|
||||
@ -140,7 +143,10 @@ StatisticsTracker.prototype.LostEntity = function(lostEntity)
|
||||
{
|
||||
var cmpLostEntityIdentity = Engine.QueryInterface(lostEntity, IID_Identity);
|
||||
var cmpCost = Engine.QueryInterface(lostEntity, IID_Cost);
|
||||
var costs = cmpCost.GetResourceCosts();
|
||||
if (cmpCost)
|
||||
var costs = cmpCost.GetResourceCosts();
|
||||
else
|
||||
var costs = { "food": 0, "wood": 0, "stone": 0, "metal": 0 };
|
||||
if (cmpLostEntityIdentity)
|
||||
{
|
||||
var cmpFoundation = Engine.QueryInterface(lostEntity, IID_Foundation);
|
||||
@ -156,8 +162,8 @@ StatisticsTracker.prototype.LostEntity = function(lostEntity)
|
||||
for (var r in costs)
|
||||
{
|
||||
this.unitsLostValue += costs[r];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lostEntityIsStructure)
|
||||
{
|
||||
this.buildingsLost++;
|
||||
@ -177,7 +183,7 @@ StatisticsTracker.prototype.LostEntity = function(lostEntity)
|
||||
StatisticsTracker.prototype.IncreaseResourceGatheredCounter = function(type, amount, specificType)
|
||||
{
|
||||
this.resourcesGathered[type] += amount;
|
||||
|
||||
|
||||
if (type == "food" && (specificType == "fruit" || specificType == "grain"))
|
||||
this.resourcesGathered["vegetarianFood"] += amount;
|
||||
};
|
||||
|
@ -12,6 +12,9 @@
|
||||
<Obstruction>
|
||||
<Unit radius="0.9"/>
|
||||
</Obstruction>
|
||||
<Health>
|
||||
<Max>150</Max>
|
||||
</Health>
|
||||
<ResourceSupply>
|
||||
<Amount>400</Amount>
|
||||
<Type>food.fruit</Type>
|
||||
|
@ -15,11 +15,28 @@
|
||||
<Type>food</Type>
|
||||
<Colour r="205" g="115" b="16"/>
|
||||
</Minimap>
|
||||
<Health>
|
||||
<Max>100</Max>
|
||||
<RegenRate>0</RegenRate>
|
||||
<DeathType>vanish</DeathType>
|
||||
<Unhealable>true</Unhealable>
|
||||
<Repairable>false</Repairable>
|
||||
</Health>
|
||||
<Armour>
|
||||
<Hack>1.0</Hack>
|
||||
<Pierce>1.0</Pierce>
|
||||
<Crush>1.0</Crush>
|
||||
</Armour>
|
||||
<ResourceSupply>
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>200</Amount>
|
||||
<Type>food.fruit</Type>
|
||||
<MaxGatherers>8</MaxGatherers>
|
||||
<MaxGatherers>8</MaxGatherers>
|
||||
<Regeneration>
|
||||
<Rate>3</Rate>
|
||||
<Delay>10</Delay>
|
||||
<Growth>linear</Growth>
|
||||
</Regeneration>
|
||||
</ResourceSupply>
|
||||
<Selectable>
|
||||
<EditorOnly disable=""/>
|
||||
|
@ -22,7 +22,13 @@
|
||||
<KillBeforeGather>false</KillBeforeGather>
|
||||
<Amount>1000</Amount>
|
||||
<Type>food.fish</Type>
|
||||
<MaxGatherers>4</MaxGatherers>
|
||||
<MaxGatherers>4</MaxGatherers>
|
||||
<Regeneration>
|
||||
<Rate>1</Rate>
|
||||
<Acceleration>1.03</Acceleration>
|
||||
<Delay>5</Delay>
|
||||
<Growth>quadratic</Growth>
|
||||
</Regeneration>
|
||||
</ResourceSupply>
|
||||
<Selectable>
|
||||
<Overlay>
|
||||
|
@ -1775,6 +1775,15 @@ void CGUI::Xeromyces_ReadScrollBarStyle(XMBElement Element, CXeromyces* pFile)
|
||||
scrollbar.m_MinimumBarSize = f;
|
||||
}
|
||||
else
|
||||
if (attr_name == "maximum_bar_size")
|
||||
{
|
||||
float f;
|
||||
if (!GUI<float>::ParseString(attr_value.FromUTF8(), f))
|
||||
LOGERROR(L"GUI: Error parsing '%hs' (\"%hs\")", attr_name.c_str(), attr_value.c_str());
|
||||
else
|
||||
scrollbar.m_MaximumBarSize = f;
|
||||
}
|
||||
else
|
||||
if (attr_name == "sprite_button_top")
|
||||
scrollbar.m_SpriteButtonTop = attr_value;
|
||||
else
|
||||
|
@ -22,7 +22,6 @@ IGUIScrollBar
|
||||
#include "precompiled.h"
|
||||
#include "GUI.h"
|
||||
#include "CGUIScrollBarVertical.h"
|
||||
|
||||
#include "ps/CLogger.h"
|
||||
|
||||
|
||||
@ -39,18 +38,22 @@ void CGUIScrollBarVertical::SetPosFromMousePos(const CPos &mouse)
|
||||
if (!GetStyle())
|
||||
return;
|
||||
|
||||
m_Pos = (m_PosWhenPressed + m_ScrollRange*(mouse.y-m_BarPressedAtPos.y)/(m_Length-GetStyle()->m_Width*2));
|
||||
/**
|
||||
* Calculate the position for the top of the item being scrolled
|
||||
*/
|
||||
m_Pos = m_PosWhenPressed + GetMaxPos() * (mouse.y - m_BarPressedAtPos.y) / (m_Length - GetStyle()->m_Width * 2 - m_BarSize);
|
||||
}
|
||||
|
||||
void CGUIScrollBarVertical::Draw()
|
||||
{
|
||||
if (!GetStyle())
|
||||
{
|
||||
// TODO Gee: Report in error log
|
||||
LOGWARNING(L"Attempt to draw scrollbar without a style.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetGUI())
|
||||
// Only draw the scrollbar if the GUI exists and there is something to scroll.
|
||||
if (GetGUI() && GetMaxPos() != 1)
|
||||
{
|
||||
CRect outline = GetOuterRect();
|
||||
|
||||
@ -79,7 +82,7 @@ void CGUIScrollBarVertical::Draw()
|
||||
}
|
||||
else button_top = &GetStyle()->m_SpriteButtonTop;
|
||||
|
||||
// figure out what sprite to use for top button
|
||||
// figure out what sprite to use for bottom button
|
||||
if (m_ButtonPlusHovered)
|
||||
{
|
||||
if (m_ButtonPlusPressed)
|
||||
@ -111,22 +114,10 @@ void CGUIScrollBarVertical::Draw()
|
||||
}
|
||||
|
||||
// Draw bar
|
||||
/*if (m_BarPressed)
|
||||
GetGUI()->DrawSprite(GUI<>::FallBackSprite(GetStyle()->m_SpriteBarVerticalPressed, GetStyle()->m_SpriteBarVertical),
|
||||
0,
|
||||
m_Z+0.2f,
|
||||
GetBarRect());
|
||||
else
|
||||
if (m_BarHovered)
|
||||
GetGUI()->DrawSprite(GUI<>::FallBackSprite(GetStyle()->m_SpriteBarVerticalOver, GetStyle()->m_SpriteBarVertical),
|
||||
0,
|
||||
m_Z+0.2f,
|
||||
GetBarRect());
|
||||
else*/
|
||||
GetGUI()->DrawSprite(GetStyle()->m_SpriteBarVertical,
|
||||
0,
|
||||
m_Z+0.2f,
|
||||
GetBarRect());
|
||||
GetGUI()->DrawSprite(GetStyle()->m_SpriteBarVertical,
|
||||
0,
|
||||
m_Z + 0.2f,
|
||||
GetBarRect());
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,32 +132,19 @@ CRect CGUIScrollBarVertical::GetBarRect() const
|
||||
if (!GetStyle())
|
||||
return ret;
|
||||
|
||||
float size;
|
||||
float from, to;
|
||||
// Get from where the scroll area begins to where it ends
|
||||
float from = m_Y;
|
||||
float to = m_Y + m_Length - m_BarSize;
|
||||
|
||||
// is edge buttons used?
|
||||
if (m_UseEdgeButtons)
|
||||
{
|
||||
size = (m_Length-GetStyle()->m_Width*2.f)*m_BarSize;
|
||||
if (size < GetStyle()->m_MinimumBarSize)
|
||||
size = GetStyle()->m_MinimumBarSize;
|
||||
|
||||
from = m_Y+GetStyle()->m_Width;
|
||||
to = m_Y+m_Length-GetStyle()->m_Width-size;
|
||||
}
|
||||
else
|
||||
{
|
||||
size = m_Length*m_BarSize;
|
||||
if (size < GetStyle()->m_MinimumBarSize)
|
||||
size = GetStyle()->m_MinimumBarSize;
|
||||
|
||||
from = m_Y;
|
||||
to = m_Y+m_Length-size;
|
||||
from += GetStyle()->m_Width;
|
||||
to -= GetStyle()->m_Width;
|
||||
}
|
||||
|
||||
// Setup rectangle
|
||||
ret.top = (from + (to-from)*(m_Pos/(std::max(1.f, m_ScrollRange - m_ScrollSpace))));
|
||||
ret.bottom = ret.top+size;
|
||||
ret.top = from + (to - from) * (m_Pos / GetMaxPos());
|
||||
ret.bottom = ret.top + m_BarSize;
|
||||
ret.right = m_X + ((m_RightAligned)?(0.f):(GetStyle()->m_Width));
|
||||
ret.left = ret.right - GetStyle()->m_Width;
|
||||
|
||||
|
@ -74,7 +74,7 @@ public:
|
||||
virtual void HandleMessage(SGUIMessage &Message);
|
||||
|
||||
/**
|
||||
* Set m_Pos with g_mouse_x/y input, i.e. when draggin.
|
||||
* Set m_Pos with g_mouse_x/y input, i.e. when dragging.
|
||||
*/
|
||||
virtual void SetPosFromMousePos(const CPos &mouse);
|
||||
|
||||
|
@ -21,7 +21,8 @@ IGUIScrollBar
|
||||
|
||||
#include "precompiled.h"
|
||||
#include "GUI.h"
|
||||
|
||||
#include "maths/MathUtil.h"
|
||||
#include "ps/CLogger.h"
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// IGUIScrollBar
|
||||
@ -30,7 +31,7 @@ IGUIScrollBar::IGUIScrollBar() : m_pStyle(NULL), m_pGUI(NULL),
|
||||
m_X(300.f), m_Y(300.f),
|
||||
m_ScrollRange(1.f), m_ScrollSpace(0.f), // MaxPos: not 0, due to division.
|
||||
m_Length(200.f), m_Width(20.f),
|
||||
m_BarSize(0.5f), m_Pos(0.f),
|
||||
m_BarSize(0.f), m_Pos(0.f),
|
||||
m_UseEdgeButtons(true),
|
||||
m_ButtonPlusPressed(false),
|
||||
m_ButtonMinusPressed(false),
|
||||
@ -47,7 +48,25 @@ IGUIScrollBar::~IGUIScrollBar()
|
||||
|
||||
void IGUIScrollBar::SetupBarSize()
|
||||
{
|
||||
m_BarSize = std::min((float)m_ScrollSpace/(float)m_ScrollRange, 1.f);
|
||||
float min = GetStyle()->m_MinimumBarSize;
|
||||
float max = GetStyle()->m_MaximumBarSize;
|
||||
float length = m_Length;
|
||||
|
||||
// Check for edge buttons
|
||||
if (m_UseEdgeButtons)
|
||||
length -= GetStyle()->m_Width * 2.f;
|
||||
|
||||
// Check min and max are valid
|
||||
if (min > length)
|
||||
{
|
||||
LOGWARNING(L"Minimum scrollbar size of %g is larger than the total scrollbar size of %g", min, length);
|
||||
min = 0.f;
|
||||
}
|
||||
if (max < min)
|
||||
max = length;
|
||||
|
||||
// Clamp size to not exceed a minimum or maximum.
|
||||
m_BarSize = clamp(length * std::min((float)m_ScrollSpace / (float)m_ScrollRange, 1.f), min, max);
|
||||
}
|
||||
|
||||
const SGUIScrollBarStyle *IGUIScrollBar::GetStyle() const
|
||||
@ -72,8 +91,8 @@ void IGUIScrollBar::UpdatePosBoundaries()
|
||||
m_ScrollRange < m_ScrollSpace) // <= scrolling not applicable
|
||||
m_Pos = 0.f;
|
||||
else
|
||||
if (m_Pos > m_ScrollRange - m_ScrollSpace)
|
||||
m_Pos = m_ScrollRange - m_ScrollSpace;
|
||||
if (m_Pos > GetMaxPos())
|
||||
m_Pos = GetMaxPos();
|
||||
}
|
||||
|
||||
void IGUIScrollBar::HandleMessage(SGUIMessage &Message)
|
||||
@ -93,12 +112,10 @@ void IGUIScrollBar::HandleMessage(SGUIMessage &Message)
|
||||
UpdatePosBoundaries();
|
||||
}
|
||||
|
||||
CRect bar_rect = GetBarRect();
|
||||
// check if components are being hovered
|
||||
m_BarHovered = bar_rect.PointInside(mouse);
|
||||
|
||||
m_ButtonMinusHovered = HoveringButtonMinus(m_pHostObject->GetMousePos());
|
||||
m_ButtonPlusHovered = HoveringButtonPlus(m_pHostObject->GetMousePos());
|
||||
m_BarHovered = GetBarRect().PointInside(mouse);
|
||||
m_ButtonMinusHovered = HoveringButtonMinus(mouse);
|
||||
m_ButtonPlusHovered = HoveringButtonPlus(mouse);
|
||||
|
||||
if (!m_ButtonMinusHovered)
|
||||
m_ButtonMinusPressed = false;
|
||||
|
@ -90,6 +90,13 @@ struct SGUIScrollBarStyle
|
||||
*/
|
||||
float m_MinimumBarSize;
|
||||
|
||||
/**
|
||||
* Sometimes you would like your scroll bar to have a fixed maximum size
|
||||
* so that the texture does not get too stretched, you can set a maximum
|
||||
* in pixels.
|
||||
*/
|
||||
float m_MaximumBarSize;
|
||||
|
||||
//@}
|
||||
//--------------------------------------------------------
|
||||
/** @name Horizontal Sprites */
|
||||
@ -199,27 +206,27 @@ public:
|
||||
virtual void SetPos(float f) { m_Pos = f; UpdatePosBoundaries(); }
|
||||
|
||||
/**
|
||||
* Get the value of Pos that corresponds to the bottom of the scrollable region
|
||||
* Get the value of m_Pos that corresponds to the bottom of the scrollable region
|
||||
*/
|
||||
float GetMaxPos() const { return m_ScrollRange - m_ScrollSpace; }
|
||||
float GetMaxPos() const { return std::max(1.f, m_ScrollRange - m_ScrollSpace); }
|
||||
|
||||
/**
|
||||
* Scroll towards 1.0 one step
|
||||
* Increase scroll one step
|
||||
*/
|
||||
virtual void ScrollPlus() { m_Pos += 30.f; UpdatePosBoundaries(); }
|
||||
|
||||
/**
|
||||
* Scroll towards 0.0 one step
|
||||
* Decrease scroll one step
|
||||
*/
|
||||
virtual void ScrollMinus() { m_Pos -= 30.f; UpdatePosBoundaries(); }
|
||||
|
||||
/**
|
||||
* Scroll towards 1.0 one step
|
||||
* Increase scroll three steps
|
||||
*/
|
||||
virtual void ScrollPlusPlenty() { m_Pos += 90.f; UpdatePosBoundaries(); }
|
||||
|
||||
/**
|
||||
* Scroll towards 0.0 one step
|
||||
* Decrease scroll three steps
|
||||
*/
|
||||
virtual void ScrollMinusPlenty() { m_Pos -= 90.f; UpdatePosBoundaries(); }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user