parent
84f1914434
commit
894dc30c69
BIN
binaries/data/mods/public/art/textures/cursors/action-setup-trade-route.png
(Stored with Git LFS)
Normal file
BIN
binaries/data/mods/public/art/textures/cursors/action-setup-trade-route.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
||||
1 1
|
@ -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 -->
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Engine.SetCursor("arrow-default");
|
||||
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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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 -->
|
||||
<!-- ================================ ================================ -->
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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>" +
|
||||
|
@ -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);
|
||||
|
218
binaries/data/mods/public/simulation/components/Trader.js
Normal file
218
binaries/data/mods/public/simulation/components/Trader.js
Normal 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);
|
||||
|
@ -341,7 +341,15 @@ var UnitFsmSpec = {
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"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
|
||||
|
@ -0,0 +1 @@
|
||||
Engine.RegisterInterface("Trader");
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
@ -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,7 +86,12 @@ void CTooltip::SetupText()
|
||||
|
||||
CPos mousepos, offset;
|
||||
EVAlign anchor;
|
||||
GUI<CPos>::GetSetting(this, "_mousepos", mousepos);
|
||||
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user