Trade. Closes #30.

This was SVN commit r11279.
This commit is contained in:
fcxSanya 2012-03-08 20:42:28 +00:00
parent 84f1914434
commit 894dc30c69
24 changed files with 599 additions and 16 deletions

Binary file not shown.

View File

@ -70,6 +70,14 @@
/>
</sprite>
<sprite name="BackgroundInformationTooltip">
<image
backcolor="0 0 0 191"
size="0 0 100% 100%"
border="false"
/>
</sprite>
<sprite name="BackgroundIndentFillDark">
<!-- Starting with top left corner continuing in a clockwise manner -->
<!-- Top border -->

View File

@ -52,8 +52,11 @@ const doublePressTime = 500;
var doublePressTimer = 0;
var prevHotkey = 0;
function updateCursor()
function updateCursorAndTooltip()
{
var cursorSet = false;
var tooltipSet = false;
var informationTooltip = getGUIObjectByName("informationTooltip");
if (!mouseIsOverObject)
{
var action = determineAction(mouseX, mouseY);
@ -64,13 +67,22 @@ function updateCursor()
if (action.cursor)
{
Engine.SetCursor(action.cursor);
return;
cursorSet = true;
}
if (action.tooltip)
{
tooltipSet = true;
informationTooltip.caption = action.tooltip;
informationTooltip.hidden = false;
}
}
}
}
if (!cursorSet)
Engine.SetCursor("arrow-default");
if (!tooltipSet)
informationTooltip.hidden = true;
}
function updateBuildingPlacementPreview()
@ -255,6 +267,36 @@ function getActionInfo(action, target)
}
}
break;
case "setup-trade-route":
// If ground or sea trade possible
if ((entState.trader && hasClass(entState, "Organic") && (playerOwned || allyOwned) && hasClass(targetState, "Market")) ||
(entState.trader && hasClass(entState, "Ship") && (playerOwned || allyOwned) && hasClass(targetState, "NavalMarket")))
{
var tradingData = {"trader": entState.id, "target": target};
var tradingDetails = Engine.GuiInterfaceCall("GetTradingDetails", tradingData);
var tooltip;
switch (tradingDetails.type)
{
case "is first":
tooltip = "First trade market.";
if (tradingDetails.hasBothMarkets)
tooltip += " Gain: " + tradingDetails.gain + " " + tradingDetails.goods + ". Click to establish another route."
else
tooltip += " Click on another market to establish a trade route."
break;
case "is second":
tooltip = "Second trade market. Gain: " + tradingDetails.gain + " " + tradingDetails.goods + "." + " Click to establish another route.";
break;
case "set first":
tooltip = "Set as first trade market";
break;
case "set second":
tooltip = "Set as second trade market. Gain: " + tradingDetails.gain + " " + tradingDetails.goods + ".";
break;
}
return {"possible": true, "tooltip": tooltip};
}
break;
case "gather":
if (targetState.resourceSupply && (playerOwned || gaiaOwned))
{
@ -278,6 +320,7 @@ function getActionInfo(action, target)
case "attack":
if (entState.attack && targetState.hitpoints && enemyOwned)
return {"possible": true};
break;
}
}
if (action == "move")
@ -362,7 +405,9 @@ function determineAction(x, y, fromMinimap)
else
{
var actionInfo = undefined;
if ((actionInfo = getActionInfo("gather", target)).possible)
if ((actionInfo = getActionInfo("setup-trade-route", target)).possible)
return {"type": "setup-trade-route", "cursor": "action-setup-trade-route", "tooltip": actionInfo.tooltip, "target": target};
else if ((actionInfo = getActionInfo("gather", target)).possible)
return {"type": "gather", "cursor": actionInfo.cursor, "target": target};
else if ((actionInfo = getActionInfo("returnresource", target)).possible)
return {"type": "returnresource", "cursor": actionInfo.cursor, "target": target};
@ -963,7 +1008,6 @@ function handleInputAfterGui(ev)
updateBuildingPlacementPreview();
break;
}
break;
}
@ -1009,6 +1053,10 @@ function doAction(action, ev)
Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
return true;
case "setup-trade-route":
Engine.PostNetworkCommand({"type": "setup-trade-route", "entities": selection, "target": action.target});
return true;
case "garrison":
Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued});
// TODO: Play a sound?
@ -1101,13 +1149,18 @@ function startBuildingPlacement(buildEntType)
inputState = INPUT_BUILDING_PLACEMENT;
}
// Called by GUI when user changes preferred trading goods
function selectTradingPreferredGoods(data)
{
Engine.PostNetworkCommand({"type": "select-trading-goods", "trader": data.trader, "preferredGoods": data.preferredGoods});
}
// Called by GUI when user clicks exchange resources button
function exchangeResources(command)
{
Engine.PostNetworkCommand({"type": "barter", "sell": command.sell, "buy": command.buy, "amount": command.amount});
}
// Batch training:
// When the user shift-clicks, we set these variables and switch to INPUT_BATCHTRAINING
// When the user releases shift, or clicks on a different training button, we create the batched units

View File

@ -135,6 +135,14 @@ function displaySingle(entState, template)
// getGUIObjectByName("resourceCarryingText").hidden = true;
// }
}
// Use the same indicators for traders
else if (entState.trader && entState.trader.goods.amount > 0)
{
getGUIObjectByName("resourceCarryingIcon").hidden = false;
getGUIObjectByName("resourceCarryingText").hidden = false;
getGUIObjectByName("resourceCarryingIcon").cell_id = RESOURCE_ICON_CELL_IDS[entState.trader.goods.type];
getGUIObjectByName("resourceCarryingText").caption = entState.trader.goods.amount;
}
else
{
getGUIObjectByName("resourceCarryingIcon").hidden = true;

View File

@ -185,7 +185,7 @@ function onTick()
handleNetMessage(message);
}
updateCursor();
updateCursorAndTooltip();
// If the selection changed, we need to regenerate the sim display
if (g_Selection.dirty)

View File

@ -457,6 +457,11 @@
</repeat>
</object>
<!-- ================================ ================================ -->
<!-- Information tooltip -->
<!-- ================================ ================================ -->
<object name="informationTooltip" type="tooltip" independent="true" style="informationTooltip"/>
<!-- ================================ ================================ -->
<!-- START of BOTTOM PANEL -->
<!-- ================================ ================================ -->
@ -596,7 +601,6 @@
<!-- Resource carrying icon/counter -->
<object size="0 40 48 88" type="image" name="resourceCarryingIcon" style="resourceIcon"/>
<object size="0 80 48 100" type="text" name="resourceCarryingText" style="statsText"/>
</object>
<!-- Big unit icon -->
@ -751,6 +755,18 @@
</object>
</object>
<object name="unitTradingPanel"
size="14 12 100% 100%"
>
<object size="0 0 100% 100%">
<repeat count="4">
<object name="unitTradingButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
<object name="unitTradingIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
</object>
</repeat>
</object>
</object>
<object name="unitQueuePanel"
size="4 -56 100% 0"
type="image"

View File

@ -161,6 +161,16 @@
tooltip_style="snToolTip"
/>
<style name="informationTooltip"
anchor="top"
buffer_zone="4"
font="serif-bold-14"
maxwidth="300"
offset="16 32"
sprite="BackgroundInformationTooltip"
textcolor="255 255 255"
/>
<!-- ================================ ================================ -->
<!-- Misc Styles -->
<!-- ================================ ================================ -->

View File

@ -13,6 +13,9 @@ const COMMANDS_PANEL_WIDTH = 228;
const UNIT_PANEL_BASE = -52; // QUEUE: The offset above the main panel (will often be negative)
const UNIT_PANEL_HEIGHT = 44; // QUEUE: The height needed for a row of buttons
// Trading constants
const TRADING_RESOURCES = ["food", "wood", "stone", "metal"];
// Barter constants
const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
const BARTER_BUNCH_MULTIPLIER = 5;
@ -20,10 +23,10 @@ const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
const BARTER_ACTIONS = ["Sell", "Buy"];
// The number of currently visible buttons (used to optimise showing/hiding)
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, "Command": 0, "Stance": 0};
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Command": 0, "Stance": 0};
// Unit panels are panels with row(s) of buttons
var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", "Research", "Stance", "Command"];
var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Barter", "Trading", "Construction", "Research", "Stance", "Command"];
// Indexes of resources to sell and buy on barter panel
var g_barterSell = 0;
@ -132,6 +135,7 @@ function selectBarterResourceToSell(resourceIndex)
function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
{
usedPanels[guiName] = 1;
var numberOfItems = items.length;
var selection = g_Selection.toList();
var garrisonGroups = new EntityGroups();
@ -389,6 +393,25 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
g_unitPanelButtons[guiName] = numButtons;
}
// Sets up "unit trading panel" - special case for setupUnitPanel
function setupUnitTradingPanel(unitEntState)
{
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 = { "trader": unitEntState.id, "preferredGoods": resource };
button.onpress = (function(e){ return function() { selectTradingPreferredGoods(e); } })(selectTradingPreferredGoodsData);
button.enabled = true;
button.tooltip = "Set " + resource + " as trading goods";
var icon = getGUIObjectByName("unitTradingIcon["+i+"]");
var preferredGoods = unitEntState.trader.preferredGoods;
var imageNameSuffix = (resource == preferredGoods) ? "selected" : "inactive";
icon.sprite = "stretched:session/resources/" + resource + "_" + imageNameSuffix + ".png";
}
}
// Sets up "unit barter panel" - special case for setupUnitPanel
function setupUnitBarterPanel(unitEntState)
{
@ -528,6 +551,12 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
setupUnitPanel("Queue", usedPanels, entState, entState.training.queue,
function (item) { removeFromTrainingQueue(entState.id, item.id); } );
if (entState.trader)
{
usedPanels["Trading"] = 1;
setupUnitTradingPanel(entState);
}
// supplementalDetailsPanel.hidden = false;
// commandsPanel.hidden = isInvisible;
}

View File

@ -83,6 +83,17 @@ function updatePlayerDataRemove(players, hostGuid)
player.offline = true;
}
function hasClass(entState, className)
{
if (entState.identity)
{
var classes = entState.identity.classes;
if (classes && classes.length)
return (classes.indexOf(className) != -1);
}
return false;
}
function isUnit(entState)
{
if (entState.identity)

View File

@ -185,6 +185,15 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
};
}
var cmpTrader = Engine.QueryInterface(ent, IID_Trader);
if (cmpTrader)
{
ret.trader = {
"goods": cmpTrader.GetGoods(),
"preferredGoods": cmpTrader.GetPreferredGoods()
};
}
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (cmpFoundation)
{
@ -680,6 +689,52 @@ GuiInterface.prototype.FindIdleUnit = function(player, data)
return 0;
};
GuiInterface.prototype.GetTradingDetails = function(player, data)
{
var cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader);
if (!cmpEntityTrader || !cmpEntityTrader.CanTrade(data.target))
return null;
var firstMarket = cmpEntityTrader.GetFirstMarket();
var secondMarket = cmpEntityTrader.GetSecondMarket();
var result = null;
if (data.target === firstMarket)
{
result = {
"type": "is first",
"goods": cmpEntityTrader.GetPreferredGoods(),
"hasBothMarkets": cmpEntityTrader.HasBothMarkets()
};
if (cmpEntityTrader.HasBothMarkets())
result.gain = cmpEntityTrader.GetGain();
}
else if (data.target === secondMarket)
{
result = {
"type": "is second",
"gain": cmpEntityTrader.GetGain(),
"goods": cmpEntityTrader.GetPreferredGoods()
};
}
else if (firstMarket)
{
result = {"type": "set first"};
}
else if (secondMarket)
{
result = {
"type": "set second",
"gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
"goods": cmpEntityTrader.GetPreferredGoods()
};
}
else
{
// Else both markets are not null and target is different from them
result = {"type": "set first"};
}
return result;
};
GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
{
var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
@ -738,6 +793,7 @@ var exposedFunctions = {
"GetFoundationSnapData": 1,
"PlaySound": 1,
"FindIdleUnit": 1,
"GetTradingDetails": 1,
"SetPathfinderDebugOverlay": 1,
"SetObstructionDebugOverlay": 1,

View File

@ -49,7 +49,7 @@ Identity.prototype.Schema =
"</element>" +
"</optional>" +
"<optional>" +
"<element name='Classes' a:help='Optional list of space-separated classes applying to this entity. Choices include: Unit, Infantry, Melee, Cavalry, Ranged, Mechanical, Ship, Siege, Champion, Hero, Elephant, Chariot, Mercenary, Spear, Sword, Bow, Javelin, Sling, Support, Animal, Organic, Structure, Civic, CivCentre, Economic, Defensive, Gates, Wall, BarterMarket, Village, Town, City, ConquestCritical, Worker, Female, Healer, Slave, CitizenSoldier, Trade, Warship, SeaCreature, ForestPlant, DropsiteFood, DropsiteWood, DropsiteStone, DropsiteMetal'>" +
"<element name='Classes' a:help='Optional list of space-separated classes applying to this entity. Choices include: Unit, Infantry, Melee, Cavalry, Ranged, Mechanical, Ship, Siege, Champion, Hero, Elephant, Chariot, Mercenary, Spear, Sword, Bow, Javelin, Sling, Support, Animal, Organic, Structure, Civic, CivCentre, Economic, Defensive, Gates, Wall, BarterMarket, Village, Town, City, ConquestCritical, Worker, Female, Healer, Slave, CitizenSoldier, Trade, Market, NavalMarket, Warship, SeaCreature, ForestPlant, DropsiteFood, DropsiteWood, DropsiteStone, DropsiteMetal'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +

View File

@ -21,6 +21,20 @@ Looter.prototype.Collect = function(targetEntity)
}
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
cmpPlayer.AddResources(cmpLoot.GetResources());
// If target entity has trader component, add carried goods to loot too
var cmpTrader = Engine.QueryInterface(targetEntity, IID_Trader);
if (cmpTrader)
{
var carriedGoods = cmpTrader.GetGoods();
if (carriedGoods.amount > 0)
{
// Convert from {type:<type>,amount:<amount>} to {<type>:<amount>}
var resourcesToAdd = {};
resourcesToAdd[carriedGoods.type] = carriedGoods.amount;
cmpPlayer.AddResources(resourcesToAdd);
}
}
}
Engine.RegisterComponentType(IID_Looter, "Looter", Looter);

View File

@ -0,0 +1,218 @@
// This constant used to adjust gain value depending on distance
const DISTANCE_FACTOR = 1 / 50;
// Additional gain for trading performed between markets of different players, in percents
const INTERNATIONAL_TRADING_ADDITION = 50;
// Additional gain for ships for each garrisoned trader, in percents
const GARRISONED_TRADER_ADDITION = 20;
// Array of resource names
const RESOURCES = ["food", "wood", "stone", "metal"];
function Trader() {}
Trader.prototype.Schema =
"<a:help>Lets the unit generate resouces while moving between markets (or docks in case of water trading).</a:help>" +
"<a:example>" +
"<MaxDistance>2.0</MaxDistance>" +
"<GainMultiplier>1.0</GainMultiplier>" +
"</a:example>" +
"<element name='MaxDistance' a:help='Max distance from market when performing deal'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='GainMultiplier' a:help='Additional gain multiplier'>" +
"<ref name='positiveDecimal'/>" +
"</element>";
Trader.prototype.Init = function()
{
this.firstMarket = INVALID_ENTITY;
this.secondMarket = INVALID_ENTITY;
// Gain from one pass between markets
this.gain = null;
// Selected resource for trading
this.preferredGoods = "metal";
// Currently carried goods
this.goods = { "type": null, "amount": 0 };
}
Trader.prototype.CalculateGain = function(firstMarket, secondMarket)
{
var cmpFirstMarketPosition = Engine.QueryInterface(firstMarket, IID_Position);
var cmpSecondMarketPosition = Engine.QueryInterface(secondMarket, IID_Position);
if (!cmpFirstMarketPosition || !cmpFirstMarketPosition.IsInWorld() || !cmpSecondMarketPosition || !cmpSecondMarketPosition.IsInWorld())
return null;
var firstMarketPosition = cmpFirstMarketPosition.GetPosition2D();
var secondMarketPosition = cmpSecondMarketPosition.GetPosition2D();
// Calculate ordinary Euclidean distance between markets.
// We don't use pathfinder, because ordinary distance looks more fair.
var distance = Math.sqrt(Math.pow(firstMarketPosition.x - secondMarketPosition.x, 2) + Math.pow(firstMarketPosition.y - secondMarketPosition.y, 2));
// We calculate gain as square of distance to encourage trading between remote markets
var gain = Math.pow(distance * DISTANCE_FACTOR, 2);
// If markets belongs to different players, multiple gain to INTERNATIONAL_TRADING_MULTIPLIER
var cmpFirstMarketOwnership = Engine.QueryInterface(firstMarket, IID_Ownership);
var cmpSecondMarketOwnership = Engine.QueryInterface(secondMarket, IID_Ownership);
if (cmpFirstMarketOwnership.GetOwner() != cmpSecondMarketOwnership.GetOwner())
gain *= 1 + INTERNATIONAL_TRADING_ADDITION / 100;
// For ship increase gain for each garrisoned trader
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity.HasClass("Ship"))
{
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (cmpGarrisonHolder)
{
var garrisonedTradersCount = 0;
for each (var entity in cmpGarrisonHolder.GetEntities())
{
var cmpGarrisonedUnitTrader = Engine.QueryInterface(entity, IID_Trader);
if (cmpGarrisonedUnitTrader)
garrisonedTradersCount++;
}
gain *= 1 + GARRISONED_TRADER_ADDITION * garrisonedTradersCount / 100;
}
}
if (this.template.GainMultiplier)
gain *= this.template.GainMultiplier;
gain = Math.round(gain);
return gain;
}
Trader.prototype.GetGain = function()
{
return this.gain;
}
// Set target as target market.
// Return true if at least one of markets was changed.
Trader.prototype.SetTargetMarket = function(target)
{
// Check that target is a market
var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpTargetIdentity)
return false;
if (!cmpTargetIdentity.HasClass("Market") && !cmpTargetIdentity.HasClass("NavalMarket"))
return false;
var marketsChanged = false;
if (this.secondMarket)
{
// If we already have both markets - drop them
// and use the target as first market
this.firstMarket = target;
this.secondMarket = INVALID_ENTITY;
marketsChanged = true;
}
else if (this.firstMarket)
{
// If we have only one market and target is different from it,
// set the target as second one
if (target != this.firstMarket)
{
this.secondMarket = target;
this.gain = this.CalculateGain(this.firstMarket, this.secondMarket);
marketsChanged = true;
}
}
else
{
// Else we don't have target markets at all,
// set the target as first market
this.firstMarket = target;
marketsChanged = true;
}
if (marketsChanged)
{
// Drop carried goods
this.goods.amount = 0;
}
return marketsChanged;
}
Trader.prototype.GetFirstMarket = function()
{
return this.firstMarket;
}
Trader.prototype.GetSecondMarket = function()
{
return this.secondMarket;
}
Trader.prototype.HasBothMarkets = function()
{
return this.firstMarket && this.secondMarket;
}
Trader.prototype.GetPreferredGoods = function()
{
return this.preferredGoods;
}
Trader.prototype.SetPreferredGoods = function(preferredGoods)
{
// Check that argument is a correct resource name
if (RESOURCES.indexOf(preferredGoods) == -1)
return;
this.preferredGoods = preferredGoods;
}
Trader.prototype.CanTrade = function(target)
{
var cmpTraderIdentity = Engine.QueryInterface(this.entity, IID_Identity);
var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity);
// Check that the target exists
if (!cmpTargetIdentity)
return false;
var landTradingPossible = cmpTraderIdentity.HasClass("Organic") && cmpTargetIdentity.HasClass("Market");
var seaTradingPossible = cmpTraderIdentity.HasClass("Ship") && cmpTargetIdentity.HasClass("NavalMarket");
if (!landTradingPossible && !seaTradingPossible)
return false;
var cmpTraderPlayer = QueryOwnerInterface(this.entity, IID_Player);
var traderPlayerId = cmpTraderPlayer.GetPlayerID();
var cmpTargetPlayer = QueryOwnerInterface(target, IID_Player);
var targetPlayerId = cmpTargetPlayer.GetPlayerID();
var ownershipSuitableForTrading = (traderPlayerId == targetPlayerId) || cmpTraderPlayer.IsAlly(targetPlayerId);
if (!ownershipSuitableForTrading)
return false;
return true;
}
Trader.prototype.PerformTrade = function()
{
if (this.goods.amount > 0)
{
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
cmpPlayer.AddResource(this.goods.type, this.goods.amount);
}
this.goods.type = this.preferredGoods;
this.goods.amount = this.gain;
}
Trader.prototype.GetGoods = function()
{
return this.goods;
}
Trader.prototype.StopTrading = function()
{
// Drop carried goods
this.goods.amount = 0;
// Reset markets
this.firstMarket = INVALID_ENTITY;
this.secondMarket = INVALID_ENTITY;
}
// Get range in which deals with market are available,
// i.e. trader should be in no more than MaxDistance from market
// to be able to trade with it.
Trader.prototype.GetRange = function()
{
return { "min": 0, "max": +this.template.MaxDistance };
}
Engine.RegisterComponentType(IID_Trader, "Trader", Trader);

View File

@ -342,6 +342,14 @@ var UnitFsmSpec = {
}
},
"Order.Trade": function(msg) {
if (this.MoveToMarket(this.order.data.firstMarket))
{
// We've started walking to the first market
this.SetNextState("INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
}
},
"Order.Repair": function(msg) {
// Try to move within range
if (this.MoveToTargetRange(this.order.data.target, IID_Builder))
@ -1062,6 +1070,34 @@ var UnitFsmSpec = {
},
},
"TRADE": {
"Attacked": function(msg) {
// Ignore attack
// TODO: Inform player
},
"APPROACHINGFIRSTMARKET": {
"enter": function () {
this.SelectAnimation("move");
},
"MoveCompleted": function() {
this.PerformTradeAndMoveToNextMarket(this.order.data.firstMarket, this.order.data.secondMarket, "INDIVIDUAL.TRADE.APPROACHINGSECONDMARKET");
},
},
"APPROACHINGSECONDMARKET": {
"enter": function () {
this.SelectAnimation("move");
},
"MoveCompleted": function() {
this.order.data.firstPass = false;
this.PerformTradeAndMoveToNextMarket(this.order.data.secondMarket, this.order.data.firstMarket, "INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
},
},
},
"REPAIR": {
"APPROACHING": {
"enter": function () {
@ -2369,6 +2405,79 @@ UnitAI.prototype.ReturnResource = function(target, queued)
this.AddOrder("ReturnResource", { "target": target }, queued);
};
UnitAI.prototype.SetupTradeRoute = function(target, queued)
{
if (!this.CanTrade(target))
{
this.WalkToTarget(target, queued);
return;
}
var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
var marketsChanged = cmpTrader.SetTargetMarket(target);
if (marketsChanged)
{
if (cmpTrader.HasBothMarkets())
this.AddOrder("Trade", { "firstMarket": cmpTrader.GetFirstMarket(), "secondMarket": cmpTrader.GetSecondMarket() }, queued);
else
this.WalkToTarget(cmpTrader.GetFirstMarket(), queued);
}
}
UnitAI.prototype.MoveToMarket = function(targetMarket)
{
if (this.MoveToTarget(targetMarket))
{
// We've started walking to the market
return true;
}
else
{
// We can't reach the market.
// Give up.
this.StopMoving();
this.StopTrading();
return false;
}
}
UnitAI.prototype.PerformTradeAndMoveToNextMarket = function(currentMarket, nextMarket, nextFsmStateName)
{
if (!this.CanTrade(currentMarket))
{
this.StopTrading();
return;
}
if (this.CheckTargetRange(currentMarket, IID_Trader))
{
this.PerformTrade();
if (this.MoveToMarket(nextMarket))
{
// We've started walking to the next market
this.SetNextState(nextFsmStateName);
}
}
else
{
// If the current market is not reached try again
this.MoveToMarket(currentMarket);
}
}
UnitAI.prototype.PerformTrade = function()
{
var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
cmpTrader.PerformTrade();
}
UnitAI.prototype.StopTrading = function()
{
this.FinishOrder();
var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
cmpTrader.StopTrading();
}
UnitAI.prototype.Repair = function(target, autocontinue, queued)
{
if (!this.CanRepair(target))
@ -2588,6 +2697,21 @@ UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource)
return true;
};
UnitAI.prototype.CanTrade = function(target)
{
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())
return true;
// Verify that we're able to respond to Trade commands
var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
if (!cmpTrader || !cmpTrader.CanTrade(target))
return false;
return true;
}
UnitAI.prototype.CanRepair = function(target)
{
// Formation controllers should always respond to commands

View File

@ -0,0 +1 @@
Engine.RegisterInterface("Trader");

View File

@ -12,6 +12,7 @@ Engine.LoadComponentScript("interfaces/ResourceDropsite.js");
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/TrainingQueue.js");
Engine.LoadComponentScript("interfaces/Trader.js")
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");

View File

@ -404,6 +404,21 @@ function ProcessCommand(player, cmd)
}
break;
case "setup-trade-route":
for each (var ent in cmd.entities)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.SetupTradeRoute(cmd.target);
}
break;
case "select-trading-goods":
var cmpTrader = Engine.QueryInterface(cmd.trader, IID_Trader);
if (cmpTrader)
cmpTrader.SetPreferredGoods(cmd.preferredGoods);
break;
case "barter":
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
cmpBarter.ExchangeResources(playerEnt, cmd.sell, cmd.buy, cmd.amount);

View File

@ -24,7 +24,7 @@
<Identity>
<GenericName>Market</GenericName>
<Tooltip>Create trade units and barter resources.</Tooltip>
<Classes datatype="tokens">Town BarterMarket</Classes>
<Classes datatype="tokens">Town Market BarterMarket</Classes>
<Icon>structures/market.png</Icon>
</Identity>
<Obstruction>

View File

@ -27,7 +27,7 @@
<Identity>
<GenericName>Dock</GenericName>
<Tooltip>Build upon a shoreline to construct naval vessels and to open sea trade.</Tooltip>
<Classes datatype="tokens">Town</Classes>
<Classes datatype="tokens">Town Market NavalMarket</Classes>
<Icon>structures/dock.png</Icon>
</Identity>
<Obstruction>

View File

@ -39,6 +39,10 @@
<BarHeight>0.5</BarHeight>
<HeightOffset>6.0</HeightOffset>
</StatusBars>
<Trader>
<MaxDistance>10.0</MaxDistance>
<GainMultiplier>1.0</GainMultiplier>
</Trader>
<UnitMotion>
<WalkSpeed>10.5</WalkSpeed>
</UnitMotion>

View File

@ -18,8 +18,11 @@
<GenericName>Trader</GenericName>
<Rollover>Trade was a very important part of ancient civilisation - effective trading and control of trade routes equaled wealth. Trade took place by many forms from foot to caravans to merchant ships. One of the most notorious examples of the power of trade was the Silk Road.</Rollover>
<Tooltip>Trades resources between allied Markets.</Tooltip>
<Classes datatype="tokens">Trade</Classes>
</Identity>
<Trader>
<MaxDistance>2.0</MaxDistance>
<GainMultiplier>1.0</GainMultiplier>
</Trader>
<UnitMotion>
<WalkSpeed>7.0</WalkSpeed>
</UnitMotion>

View File

@ -443,6 +443,7 @@ void CGUI::Initialize()
AddObjectType("input", &CInput::ConstructObject);
AddObjectType("list", &CList::ConstructObject);
AddObjectType("dropdown", &CDropDown::ConstructObject);
AddObjectType("tooltip", &CTooltip::ConstructObject);
}
void CGUI::Draw()

View File

@ -36,6 +36,8 @@ CTooltip::CTooltip()
AddSetting(GUIST_float, "maxwidth");
AddSetting(GUIST_CPos, "offset");
AddSetting(GUIST_EVAlign, "anchor");
// This is used for tooltips that are hidden/revealed manually by scripts, rather than through the standard tooltip display mechanism
AddSetting(GUIST_bool, "independent");
// If the tooltip is just a reference to another object:
AddSetting(GUIST_CStr, "use_object");
@ -84,6 +86,11 @@ void CTooltip::SetupText()
CPos mousepos, offset;
EVAlign anchor;
bool independent;
GUI<bool>::GetSetting(this, "independent", independent);
if (independent)
mousepos = GetMousePos();
else
GUI<CPos>::GetSetting(this, "_mousepos", mousepos);
GUI<CPos>::GetSetting(this, "offset", offset);
GUI<EVAlign>::GetSetting(this, "anchor", anchor);