1
0
forked from 0ad/0ad

change the way to manage trade, closes #2311

This was SVN commit r14417.
This commit is contained in:
mimo 2013-12-28 10:44:59 +00:00
parent 6accb6faf6
commit 749c4d5270
11 changed files with 374 additions and 37 deletions

View File

@ -8,6 +8,10 @@
<include>common/common_sprites.xml</include>
<include>common/common_styles.xml</include>
<include>common/modern/styles.xml</include>
<include>common/modern/sprites.xml</include>
<include>common/modern/setup.xml</include>
<include>session/sprites.xml</include>
<include>session/setup.xml</include>
<include>session/styles.xml</include>

View File

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

View File

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

View File

@ -54,9 +54,7 @@ var g_AdditionalHighlight = [];
function GetSimState()
{
if (!g_SimState)
{
g_SimState = Engine.GuiInterfaceCall("GetSimulationState");
}
return g_SimState;
}

View File

@ -434,6 +434,56 @@
</object>
</object>
<!-- ================================ ================================ -->
<!-- Trade Window -->
<!-- ================================ ================================ -->
<object name="tradeDialogPanel"
size="50%-250 50%-130 50%+250 50%+100"
type="image"
hidden="true"
sprite="ModernDialog"
>
<object type="text" style="TitleText" size="50%-96 -16 50%+96 16">Trade</object>
<!-- Trading goods -->
<object name="tradeGoods" size="20 50 100%-20 82">
<object name="tradeHeader" size="0 0 180 100%" type="text" style="ModernLeftLabelText" ghost="true" caption="Trading goods selection:"/>
<object size="180 0 100% 100%">
<repeat count="4">
<object name="tradeResource[n]" size="0 0 58 32">
<object name="tradeResourceButton[n]" size="4 0 36 100%" type="button" style="StoneButton">
<object name="tradeResourceIcon[n]" type="image" ghost="true"/>
<object name="tradeResourceSelection[n]" type="image" sprite="stretched:session/icons/corners.png" ghost="true"/>
<object name="tradeResourceText[n]" type="text" style="ModernCenteredLabelText" ghost="true"/>
</object>
<object name="tradeArrowUp[n]" size="36 0 52 50%" type="button" style="iconButton">
<object type="image" ghost="true" sprite="wheatArrowUp"/>
</object>
<object name="tradeArrowDn[n]" size="36 50% 52 100%" type="button" style="iconButton">
<object type="image" ghost="true" sprite="wheatArrowDn"/>
</object>
</object>
</repeat>
<object name="tradeHelp" size="100%-24 4 100% 28" enabled="false" type="button" style="StoneButton" tooltip_style="sessionToolTipBold" tooltip="Select one goods as origin of the changes, \nthen use the arrows of the target goods to make the changes.">
<object size="20% 15% 80% 75%" type="image" ghost="true" sprite="iconInfoWhite"/>
</object>
</object>
</object>
<object name="tradeStatistics" size="20 120 100%-20 168">
<object size="0 0 130 50%" type="text" style="ModernLeftLabelText" ghost="true" caption="Traders:"/>
<object name="landTraders" size="130 0 100% 50%" type="text" style="ModernLeftLabelText" ghost="true" />
<object size="0 50% 130 100%" type="text" style="ModernLeftLabelText" ghost="true" caption = "Merchant ships:"/>
<object name="shipTraders" size="130 50% 100% 100%" type="text" style="ModernLeftLabelText" ghost="true" />
</object>
<object size="50%-64 100%-50 50%+64 100%-22" type="button" style="StoneButton">
Close
<action on="Press">closeTrade();</action>
</object>
</object>
<!-- ================================ ================================ -->
<!-- Settings Window -->
<!-- ================================ ================================ -->
@ -635,7 +685,7 @@
<!-- ================================ ================================ -->
<object type="button"
name="gameSpeedButton"
size="100%-226 4 100%-198 32"
size="100%-258 4 100%-230 32"
style="iconButton"
tooltip_style="sessionToolTip"
tooltip="Game speed"
@ -646,14 +696,14 @@
</action>
</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"/>
<object size="100%-380 40 100%-230 65" name="gameSpeed" type="dropdown" buffer_zone="5" style="StoneDropDown" hidden="true" tooltip="Choose game speed" tooltip_style="sessionToolTip"/>
<!-- ================================ ================================ -->
<!-- Diplomacy Button -->
<!-- ================================ ================================ -->
<object type="button"
name="diplomacyButton1"
size="100%-194 4 100%-166 32"
size="100%-226 4 100%-198 32"
style="iconButton"
tooltip_style="sessionToolTip"
tooltip="Diplomacy"
@ -665,6 +715,23 @@
</action>
</object>
<!-- ================================ ================================ -->
<!-- Trade Button -->
<!-- ================================ ================================ -->
<object type="button"
name="tradeButton1"
size="100%-194 4 100%-166 32"
style="iconButton"
tooltip_style="sessionToolTip"
tooltip="Trade"
>
<!-- TODO make the button less ugly -->
<object size="0 0 100% 100%" name="tradeButtonImage" type="image" sprite="stretched:session/icons/economics.png" ghost="true"/>
<action on="Press">
toggleTrade();
</action>
</object>
<!-- ================================ ================================ -->
<!-- Menu Button -->
<!-- ================================ ================================ -->

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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