1
0
forked from 0ad/0ad

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:
alpha123 2013-08-26 03:06:08 +00:00
parent 2dc0c40ff0
commit f5ab6255d0
14 changed files with 776 additions and 613 deletions

View File

@ -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
>
<!--

View File

@ -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);
}

View File

@ -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%"
>

View File

@ -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,

View File

@ -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);

View File

@ -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;
};

View File

@ -12,6 +12,9 @@
<Obstruction>
<Unit radius="0.9"/>
</Obstruction>
<Health>
<Max>150</Max>
</Health>
<ResourceSupply>
<Amount>400</Amount>
<Type>food.fruit</Type>

View File

@ -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=""/>

View File

@ -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>

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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(); }