diff --git a/binaries/data/mods/public/gui/page_session.xml b/binaries/data/mods/public/gui/page_session.xml
index be0c721f78..34c0bef7d5 100644
--- a/binaries/data/mods/public/gui/page_session.xml
+++ b/binaries/data/mods/public/gui/page_session.xml
@@ -8,6 +8,10 @@
common/common_sprites.xml
common/common_styles.xml
+ common/modern/styles.xml
+ common/modern/sprites.xml
+ common/modern/setup.xml
+
session/sprites.xml
session/setup.xml
session/styles.xml
diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
index b31c2bc6ca..ccd984e448 100644
--- a/binaries/data/mods/public/gui/session/input.js
+++ b/binaries/data/mods/public/gui/session/input.js
@@ -290,9 +290,9 @@ function getActionInfo(action, target)
cursor = "action-setup-trade-route";
tooltip = "Right-click to establish a default route for new traders.";
if (trader)
- tooltip += "\nGain (metal): " + getTradingTooltip(gain);
+ tooltip += "\nGain: " + getTradingTooltip(gain);
else // Foundation or cannot produce traders
- tooltip += "\nExpected gain (metal): " + getTradingTooltip(gain);
+ tooltip += "\nExpected gain: " + getTradingTooltip(gain);
}
}
@@ -363,18 +363,18 @@ function getActionInfo(action, target)
case "is first":
tooltip = "Origin trade market.";
if (tradingDetails.hasBothMarkets)
- tooltip += "\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
+ tooltip += "\nGain: " + getTradingTooltip(tradingDetails.gain);
else
tooltip += "\nRight-click on another market to set it as a destination trade market."
break;
case "is second":
- tooltip = "Destination trade market.\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
+ tooltip = "Destination trade market.\nGain: " + getTradingTooltip(tradingDetails.gain);
break;
case "set first":
tooltip = "Right-click to set as origin trade market";
break;
case "set second":
- tooltip = "Right-click to set as destination trade market.\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
+ tooltip = "Right-click to set as destination trade market.\nGain: " + getTradingTooltip(tradingDetails.gain);
break;
}
return {"possible": true, "tooltip": tooltip};
@@ -1572,10 +1572,10 @@ function startBuildingPlacement(buildTemplate, playerState)
}
}
-// Called by GUI when user changes preferred trading goods
-function selectTradingPreferredGoods(data)
+// Called by GUI when user changes required trading goods
+function selectRequiredGoods(data)
{
- Engine.PostNetworkCommand({"type": "select-trading-goods", "entities": data.entities, "preferredGoods": data.preferredGoods});
+ Engine.PostNetworkCommand({"type": "select-required-goods", "entities": data.entities, "requiredGoods": data.requiredGoods});
}
// Called by GUI when user clicks exchange resources button
@@ -1914,6 +1914,9 @@ function performCommand(entity, commandName)
case "alert-end":
endOfAlert();
break;
+ case "select-trading-goods":
+ toggleTrade();
+ break;
default:
break;
}
diff --git a/binaries/data/mods/public/gui/session/menu.js b/binaries/data/mods/public/gui/session/menu.js
index 7b745b1f72..113e5a3310 100644
--- a/binaries/data/mods/public/gui/session/menu.js
+++ b/binaries/data/mods/public/gui/session/menu.js
@@ -29,10 +29,15 @@ const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
// Number of pixels per millisecond to move
const MENU_SPEED = 1.2;
+// Trade menu: available resources and step for probability changes
+const RESOURCES = ["food", "wood", "stone", "metal"];
+const STEP = 5;
+
var isMenuOpen = false;
var menu;
var isDiplomacyOpen = false;
+var isTradeOpen = false;
// Redefined every time someone makes a tribute (so we can save some data in a closure). Called in input.js handleInputBeforeGui.
var flushTributing = function() {};
@@ -238,6 +243,8 @@ function tributeResource(data)
function openDiplomacy()
{
+ if (isTradeOpen)
+ closeTrade();
isDiplomacyOpen = true;
var we = Engine.GetPlayerID();
@@ -369,6 +376,145 @@ function toggleDiplomacy()
openDiplomacy();
};
+function openTrade()
+{
+ if (isDiplomacyOpen)
+ closeDiplomacy();
+ isTradeOpen = true;
+
+ var updateButtons = function()
+ {
+ for (var res in button)
+ {
+ button[res].label.caption = proba[res] + "%";
+ if (res == selec)
+ {
+ button[res].res.enabled = false;
+ button[res].sel.hidden = false;
+ button[res].up.hidden = true;
+ button[res].dn.hidden = true;
+ }
+ else
+ {
+ button[res].res.enabled = true;
+ button[res].sel.hidden = true;
+ button[res].up.hidden = (proba[res] == 100 || proba[selec] == 0);
+ button[res].dn.hidden = (proba[res] == 0 || proba[selec] == 100);
+ }
+ }
+ }
+
+ var proba = Engine.GuiInterfaceCall("GetTradingGoods");
+ var button = {};
+ var selec = RESOURCES[0];
+ for (var i = 0; i < RESOURCES.length; ++i)
+ {
+ var buttonResource = getGUIObjectByName("tradeResource["+i+"]");
+ if (i > 0)
+ {
+ var size = getGUIObjectByName("tradeResource["+(i-1)+"]").size;
+ var width = size.right - size.left;
+ size.left += width;
+ size.right += width;
+ getGUIObjectByName("tradeResource["+i+"]").size = size;
+ }
+ var resource = RESOURCES[i];
+ proba[resource] = (proba[resource] ? proba[resource] : 0);
+ var buttonResource = getGUIObjectByName("tradeResourceButton["+i+"]");
+ var icon = getGUIObjectByName("tradeResourceIcon["+i+"]");
+ icon.sprite = "stretched:session/icons/resources/" + resource + ".png";
+ var label = getGUIObjectByName("tradeResourceText["+i+"]");
+ var buttonUp = getGUIObjectByName("tradeArrowUp["+i+"]");
+ var buttonDn = getGUIObjectByName("tradeArrowDn["+i+"]");
+ var iconSel = getGUIObjectByName("tradeResourceSelection["+i+"]");
+ button[resource] = { "res": buttonResource, "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
+
+ buttonResource.onpress = (function(resource){
+ return function() {
+ if (selec == resource)
+ return;
+ selec = resource;
+ updateButtons();
+ }
+ })(resource);
+
+ buttonUp.onpress = (function(resource){
+ return function() {
+ proba[resource] += Math.min(STEP, proba[selec]);
+ proba[selec] -= Math.min(STEP, proba[selec]);
+ Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
+ updateButtons();
+ }
+ })(resource);
+
+ buttonDn.onpress = (function(resource){
+ return function() {
+ proba[selec] += Math.min(STEP, proba[resource]);
+ proba[resource] -= Math.min(STEP, proba[resource]);
+ Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
+ updateButtons();
+ }
+ })(resource);
+ }
+ updateButtons();
+
+ var traderNumber = Engine.GuiInterfaceCall("GetTraderNumber");
+ var caption = "";
+ var comma = "";
+ if (traderNumber.landTrader.total == 0)
+ caption += "0";
+ else
+ {
+ if (traderNumber.landTrader.trading > 0)
+ {
+ caption += traderNumber.landTrader.trading + " trading"
+ comma = ", ";
+ }
+ if (traderNumber.landTrader.garrisoned > 0)
+ {
+ caption += comma + traderNumber.landTrader.garrisoned + " garrisoned inside ships";
+ comma = ", ";
+ }
+ var inactive = traderNumber.landTrader.total - traderNumber.landTrader.trading - traderNumber.landTrader.garrisoned;
+ if (inactive > 0)
+ caption += comma + "[color=\"orange\"]" + inactive + " inactive[/color]";
+ }
+ getGUIObjectByName("landTraders").caption = caption;
+
+ caption = "";
+ comma = "";
+ if (traderNumber.shipTrader.total == 0)
+ caption += "0";
+ else
+ {
+ if (traderNumber.shipTrader.trading > 0)
+ {
+ caption += traderNumber.shipTrader.trading + " trading"
+ comma = ", ";
+ }
+ var inactive = traderNumber.shipTrader.total - traderNumber.shipTrader.trading;
+ if (inactive > 0)
+ caption += comma + "[color=\"orange\"]" + inactive + " inactive[/color]";
+ }
+ getGUIObjectByName("shipTraders").caption = caption;
+
+ getGUIObjectByName("tradeDialogPanel").hidden = false;
+}
+
+function closeTrade()
+{
+ isTradeOpen = false;
+ getGUIObjectByName("tradeDialogPanel").hidden = true;
+}
+
+function toggleTrade()
+{
+ if (isTradeOpen)
+ closeTrade();
+ else
+ openTrade();
+};
+
function toggleGameSpeed()
{
var gameSpeed = getGUIObjectByName("gameSpeed");
@@ -436,6 +582,7 @@ function closeOpenDialogs()
closeMenu();
closeChat();
closeDiplomacy();
+ closeTrade();
closeSettings(false);
}
diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js
index d504fc1a1c..3047067080 100644
--- a/binaries/data/mods/public/gui/session/session.js
+++ b/binaries/data/mods/public/gui/session/session.js
@@ -54,9 +54,7 @@ var g_AdditionalHighlight = [];
function GetSimState()
{
if (!g_SimState)
- {
g_SimState = Engine.GuiInterfaceCall("GetSimulationState");
- }
return g_SimState;
}
diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
index b3c6939aa1..4487acf0b1 100644
--- a/binaries/data/mods/public/gui/session/session.xml
+++ b/binaries/data/mods/public/gui/session/session.xml
@@ -434,6 +434,56 @@
+
+
+
+
+
@@ -635,7 +685,7 @@
-
+
+
+
+
+
+
+
+
+ toggleTrade();
+
+
+
diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js
index 3893364091..590b0e4a1d 100644
--- a/binaries/data/mods/public/gui/session/unit_commands.js
+++ b/binaries/data/mods/public/gui/session/unit_commands.js
@@ -906,20 +906,23 @@ function setupUnitTradingPanel(usedPanels, unitEntState, selection)
{
usedPanels[TRADING] = 1;
+ var requiredGoods = unitEntState.trader.requiredGoods;
for (var i = 0; i < TRADING_RESOURCES.length; i++)
{
var resource = TRADING_RESOURCES[i];
var button = getGUIObjectByName("unitTradingButton["+i+"]");
button.size = (i * 46) + " 0 " + ((i + 1) * 46) + " 46";
- var selectTradingPreferredGoodsData = { "entities": selection, "preferredGoods": resource };
- button.onpress = (function(e){ return function() { selectTradingPreferredGoods(e); } })(selectTradingPreferredGoodsData);
+ if (resource == requiredGoods)
+ var selectRequiredGoodsData = { "entities": selection, "requiredGoods": undefined };
+ else
+ var selectRequiredGoodsData = { "entities": selection, "requiredGoods": resource };
+ button.onpress = (function(e){ return function() { selectRequiredGoods(e); } })(selectRequiredGoodsData);
button.enabled = true;
- button.tooltip = "Set " + resource + " as trading goods";
+ button.tooltip = "Set/unset " + resource + " as forced trading goods.";
var icon = getGUIObjectByName("unitTradingIcon["+i+"]");
- var preferredGoods = unitEntState.trader.preferredGoods;
var selected = getGUIObjectByName("unitTradingSelection["+i+"]");
- selected.hidden = !(resource == preferredGoods);
- var grayscale = (resource != preferredGoods) ? "grayscale:" : "";
+ selected.hidden = !(resource == requiredGoods);
+ var grayscale = (resource != requiredGoods) ? "grayscale:" : "";
icon.sprite = "stretched:"+grayscale+"session/icons/resources/" + resource + ".png";
}
}
@@ -1085,8 +1088,8 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
else if (entState.production && entState.production.entities)
setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
- else if (entState.trader)
- setupUnitTradingPanel(usedPanels, entState, selection);
+// else if (entState.trader)
+// setupUnitTradingPanel(usedPanels, entState, selection);
else if (!entState.foundation && entState.gate || hasClass(entState, "LongWall"))
{
// Allow long wall pieces to be converted to gates
diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js
index 23d0a2227c..84fa378f42 100644
--- a/binaries/data/mods/public/gui/session/utility_functions.js
+++ b/binaries/data/mods/public/gui/session/utility_functions.js
@@ -320,7 +320,16 @@ function getEntityCommandsList(entState)
"icon": "remove-guard.png"
});
}
-
+
+ if (hasClass(entState, "Market"))
+ {
+ commands.push({
+ "name": "select-trading-goods",
+ "tooltip": "Select trading goods",
+ "icon": "economics.png"
+ });
+ }
+
if(entState.alertRaiser)
{
if(entState.alertRaiser.canIncreaseLevel)
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 90f5df4c69..a252b5c237 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -219,7 +219,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
{
ret.trader = {
"goods": cmpTrader.GetGoods(),
- "preferredGoods": cmpTrader.GetPreferredGoods()
+ "requiredGoods": cmpTrader.GetRequiredGoods()
};
}
@@ -1720,7 +1720,6 @@ GuiInterface.prototype.GetTradingDetails = function(player, data)
{
result = {
"type": "is first",
- "goods": cmpEntityTrader.GetPreferredGoods(),
"hasBothMarkets": cmpEntityTrader.HasBothMarkets()
};
if (cmpEntityTrader.HasBothMarkets())
@@ -1731,7 +1730,6 @@ GuiInterface.prototype.GetTradingDetails = function(player, data)
result = {
"type": "is second",
"gain": cmpEntityTrader.GetGain(),
- "goods": cmpEntityTrader.GetPreferredGoods()
};
}
else if (!firstMarket)
@@ -1743,7 +1741,6 @@ GuiInterface.prototype.GetTradingDetails = function(player, data)
result = {
"type": "set second",
"gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
- "goods": cmpEntityTrader.GetPreferredGoods()
};
}
else
@@ -1809,6 +1806,51 @@ GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
cmpRangeManager.SetDebugOverlay(enabled);
};
+GuiInterface.prototype.GetTraderNumber = function(player)
+{
+ var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ var traders = cmpRangeManager.GetEntitiesByPlayer(player).filter( function(e) {
+ return Engine.QueryInterface(e, IID_Trader);
+ });
+
+ var landTrader = { "total": 0, "trading": 0, "garrisoned": 0 };
+ var shipTrader = { "total": 0, "trading": 0 };
+ for each (var ent in traders)
+ {
+ var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
+ var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ if (!cmpIdentity || !cmpUnitAI)
+ continue;
+ if (cmpIdentity.HasClass("Ship"))
+ {
+ ++shipTrader.total;
+ if (cmpUnitAI.order && cmpUnitAI.order.type == "Trade")
+ ++shipTrader.trading;
+ }
+ else
+ {
+ ++landTrader.total;
+ if (cmpUnitAI.order && cmpUnitAI.order.type == "Trade")
+ ++landTrader.trading;
+ if (cmpUnitAI.order && cmpUnitAI.order.type == "Garrison")
+ {
+ var holder = cmpUnitAI.order.data.target;
+ var cmpHolderUnitAI = Engine.QueryInterface(holder, IID_UnitAI);
+ if (cmpHolderUnitAI && cmpHolderUnitAI.order && cmpHolderUnitAI.order.type == "Trade")
+ ++landTrader.garrisoned;
+ }
+ }
+ }
+
+ return { "landTrader": landTrader, "shipTrader": shipTrader };
+};
+
+GuiInterface.prototype.GetTradingGoods = function(player, tradingGoods)
+{
+ var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
+ return cmpPlayer.GetTradingGoods();
+};
+
GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
{
this.renamedEntities.push(msg);
@@ -1863,6 +1905,9 @@ var exposedFunctions = {
"SetObstructionDebugOverlay": 1,
"SetMotionDebugOverlay": 1,
"SetRangeDebugOverlay": 1,
+
+ "GetTraderNumber": 1,
+ "GetTradingGoods": 1,
};
GuiInterface.prototype.ScriptCall = function(player, name, args)
diff --git a/binaries/data/mods/public/simulation/components/Player.js b/binaries/data/mods/public/simulation/components/Player.js
index 195cfdb266..c6d41b5d07 100644
--- a/binaries/data/mods/public/simulation/components/Player.js
+++ b/binaries/data/mods/public/simulation/components/Player.js
@@ -19,7 +19,10 @@ Player.prototype.Init = function()
"metal": 300,
"stone": 300
};
-
+ this.tradingGoods = [ // goods for next trade-route and its proba in % (the sum of probas must be 100)
+ { "goods": "wood", "proba": 30 },
+ { "goods": "stone", "proba": 35 },
+ { "goods": "metal", "proba": 35 } ];
this.team = -1; // team number of the player, players on the same team will always have ally diplomatic status - also this is useful for team emblems, scoring, etc.
this.teamsLocked = false;
this.state = "active"; // game state - one of "active", "defeated", "won"
@@ -234,6 +237,45 @@ Player.prototype.TrySubtractResources = function(amounts)
return true;
};
+Player.prototype.GetNextTradingGoods = function()
+{
+ var value = 100*Math.random();
+ var last = this.tradingGoods.length - 1;
+ var sumProba = 0;
+ for (var i = 0; i < last; ++i)
+ {
+ sumProba += this.tradingGoods[i].proba;
+ if (value < sumProba)
+ return this.tradingGoods[i].goods;
+ }
+ return this.tradingGoods[last].goods;
+};
+
+Player.prototype.GetTradingGoods = function()
+{
+ var tradingGoods = {};
+ for each (var resource in this.tradingGoods)
+ tradingGoods[resource.goods] = resource.proba;
+
+ return tradingGoods;
+};
+
+Player.prototype.SetTradingGoods = function(tradingGoods)
+{
+ var sumProba = 0;
+ for (var resource in tradingGoods)
+ sumProba += tradingGoods[resource];
+ if (sumProba != 100) // consistency check
+ {
+ error("Player.js SetTradingGoods: " + uneval(tradingGoods));
+ tradingGoods = { "food": 20, "wood":20, "stone":30, "metal":30 };
+ }
+
+ this.tradingGoods = [];
+ for (var resource in tradingGoods)
+ this.tradingGoods.push( {"goods": resource, "proba": tradingGoods[resource]} );
+};
+
Player.prototype.GetState = function()
{
return this.state;
diff --git a/binaries/data/mods/public/simulation/components/Trader.js b/binaries/data/mods/public/simulation/components/Trader.js
index 180c8cc8e8..068b57b046 100644
--- a/binaries/data/mods/public/simulation/components/Trader.js
+++ b/binaries/data/mods/public/simulation/components/Trader.js
@@ -29,7 +29,7 @@ Trader.prototype.Init = function()
// Gain from one pass between markets
this.gain = null;
// Selected resource for trading
- this.preferredGoods = "metal";
+ this.requiredGoods = undefined;
// Currently carried goods
this.goods = { "type": null, "amount": null, "origin": null };
};
@@ -145,17 +145,18 @@ Trader.prototype.HasBothMarkets = function()
return this.firstMarket && this.secondMarket;
};
-Trader.prototype.GetPreferredGoods = function()
+Trader.prototype.GetRequiredGoods = function()
{
- return this.preferredGoods;
+ return this.requiredGoods;
};
-Trader.prototype.SetPreferredGoods = function(preferredGoods)
+Trader.prototype.SetRequiredGoods = function(requiredGoods)
{
// Check that argument is a correct resource name
- if (RESOURCES.indexOf(preferredGoods) == -1)
- return;
- this.preferredGoods = preferredGoods;
+ if (!requiredGoods || RESOURCES.indexOf(requiredGoods) == -1)
+ this.requiredGoods = undefined;
+ else
+ this.requiredGoods = requiredGoods;
};
Trader.prototype.CanTrade = function(target)
@@ -218,7 +219,21 @@ Trader.prototype.PerformTrade = function(currentMarket)
cmpStatisticsTracker.IncreaseTradeIncomeCounter(this.goods.amount.market2Gain);
}
}
- this.goods.type = this.preferredGoods;
+
+ // First take the preferred goods of the trader if any,
+ // otherwise choose one according to the player's trading priorities
+ // if still nothing (but should never happen), choose metal
+ var nextGoods = this.GetRequiredGoods();
+ if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
+ {
+ var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
+ if (cmpPlayer)
+ nextGoods = cmpPlayer.GetNextTradingGoods();
+
+ if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
+ nextGoods = "metal";
+ }
+ this.goods.type = nextGoods;
this.goods.amount = this.gain;
this.goods.origin = currentMarket;
};
diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js
index 523567c355..48d3872998 100644
--- a/binaries/data/mods/public/simulation/helpers/Commands.js
+++ b/binaries/data/mods/public/simulation/helpers/Commands.js
@@ -499,15 +499,19 @@ function ProcessCommand(player, cmd)
}
break;
- case "select-trading-goods":
+ case "select-required-goods":
for each (var ent in entities)
{
var cmpTrader = Engine.QueryInterface(ent, IID_Trader);
if (cmpTrader)
- cmpTrader.SetPreferredGoods(cmd.preferredGoods);
+ cmpTrader.SetRequiredGoods(cmd.requiredGoods);
}
break;
+ case "set-trading-goods":
+ cmpPlayer.SetTradingGoods(cmd.tradingGoods);
+ break;
+
case "barter":
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
cmpBarter.ExchangeResources(playerEnt, cmd.sell, cmd.buy, cmd.amount);