diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 2e39b9ef36..108bcb2e51 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -210,6 +210,7 @@ function getActionInfo(action, target) var gaiaOwned = (targetState.player == 0); var cursor = ""; + var tooltip; // default to walking there var data = {command: "walk"}; @@ -254,8 +255,29 @@ function getActionInfo(action, target) data.target = target; cursor = "action-repair"; } + else if (hasClass(entState, "Market") && hasClass(targetState, "Market") && entState.id != targetState.id && + (!hasClass(entState, "NavalMarket") || hasClass(targetState, "NavalMarket"))) + { + // Find a trader (if any) that this building can produce. + var trader; + if (entState.production && entState.production.entities.length) + for (var i = 0; i < entState.production.entities.length; ++i) + if ((trader = GetTemplateData(entState.production.entities[i]).trader)) + break; + + var traderData = { "firstMarket": entState.id, "secondMarket": targetState.id, "template": trader }; + var gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData); + if (gain !== null) + { + data.command = "trade"; + data.target = traderData.secondMarket; + data.source = traderData.firstMarket; + cursor = "action-setup-trade-route"; + tooltip = "Click to establish a default route for new traders. Gain: " + gain + " metal."; + } + } - return {"possible": true, "data": data, "position": targetState.position, "cursor": cursor}; + return {"possible": true, "data": data, "position": targetState.position, "cursor": cursor, "tooltip": tooltip}; } for each (var entityID in selection) @@ -463,7 +485,7 @@ function determineAction(x, y, fromMinimap) else if (getActionInfo("repair", target).possible) return {"type": "build", "cursor": "action-repair", "target": target}; else if ((actionInfo = getActionInfo("set-rallypoint", target)).possible) - return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "position": actionInfo.position}; + return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "tooltip": actionInfo.tooltip, "position": actionInfo.position}; else if (getActionInfo("heal", target).possible) return {"type": "heal", "cursor": "action-heal", "target": target}; else if (getActionInfo("attack", target).possible) diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index e59ce4c8ae..659b5067c2 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -452,6 +452,9 @@ GuiInterface.prototype.GetTemplateData = function(player, name) if (template.UnitMotion.Run) ret.speed.run = +template.UnitMotion.Run.Speed; } + if (template.Trader) + ret.trader = template.Trader; + if (template.WallSet) { ret.wallSet = { @@ -1474,6 +1477,14 @@ GuiInterface.prototype.FindIdleUnit = function(player, data) return 0; }; +GuiInterface.prototype.GetTradingRouteGain = function(player, data) +{ + if (!data.firstMarket || !data.secondMarket) + return null; + + return CalculateTraderGain(data.firstMarket, data.secondMarket, data.template); +} + GuiInterface.prototype.GetTradingDetails = function(player, data) { var cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader); @@ -1595,6 +1606,7 @@ var exposedFunctions = { "GetFoundationSnapData": 1, "PlaySound": 1, "FindIdleUnit": 1, + "GetTradingRouteGain": 1, "GetTradingDetails": 1, "CanAttack": 1, diff --git a/binaries/data/mods/public/simulation/components/Trader.js b/binaries/data/mods/public/simulation/components/Trader.js index 03c6456904..d548963996 100644 --- a/binaries/data/mods/public/simulation/components/Trader.js +++ b/binaries/data/mods/public/simulation/components/Trader.js @@ -1,8 +1,6 @@ -// This constant used to adjust gain value depending on distance -const DISTANCE_FACTOR = 1 / 110; +// See helpers/TraderGain.js for the CalculateTaderGain() function which works out how many +// resources a trader gets -// 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; @@ -36,30 +34,13 @@ Trader.prototype.Init = function() this.goods = { "type": null, "amount": 0 }; } -Trader.prototype.CalculateGain = function(firstMarket, secondMarket) +Trader.prototype.CalculateGain = function(firstMarket, secondMarket, template) { - 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 + // Calculate this here to save passing unnecessary stuff into the CalculatetraderGain function + var garrisonMultiplier = 1; var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); - if (cmpIdentity.HasClass("Ship")) + if (cmpIdentity && cmpIdentity.HasClass("Ship")) { var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); if (cmpGarrisonHolder) @@ -71,14 +52,11 @@ Trader.prototype.CalculateGain = function(firstMarket, secondMarket) if (cmpGarrisonedUnitTrader) garrisonedTradersCount++; } - gain *= 1 + GARRISONED_TRADER_ADDITION * garrisonedTradersCount / 100; + garrisonMultiplier *= 1 + GARRISONED_TRADER_ADDITION * garrisonedTradersCount / 100; } } - - if (this.template.GainMultiplier) - gain *= this.template.GainMultiplier; - gain = Math.round(gain); - return gain; + + return garrisonMultiplier * CalculateTraderGain(firstMarket, secondMarket, this.template); } Trader.prototype.GetGain = function() @@ -88,7 +66,7 @@ Trader.prototype.GetGain = function() // Set target as target market. // Return true if at least one of markets was changed. -Trader.prototype.SetTargetMarket = function(target) +Trader.prototype.SetTargetMarket = function(target, source) { // Check that target is a market var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity); @@ -96,24 +74,37 @@ Trader.prototype.SetTargetMarket = function(target) return false; if (!cmpTargetIdentity.HasClass("Market") && !cmpTargetIdentity.HasClass("NavalMarket")) return false; - var marketsChanged = false; + var marketsChanged = true; + if (source) + { + // Establish a trade route with both markets in one go. + cmpTargetIdentity = Engine.QueryInterface(source, IID_Identity); + if (!cmpTargetIdentity) + return false; + if (!cmpTargetIdentity.HasClass("Market") && !cmpTargetIdentity.HasClass("NavalMarket")) + return false; + + this.firstMarket = source; + this.secondMarket = INVALID_ENTITY; + } + 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) + if (target == this.firstMarket) + marketsChanged = false; + else { this.secondMarket = target; this.gain = this.CalculateGain(this.firstMarket, this.secondMarket); - marketsChanged = true; } } else @@ -121,7 +112,6 @@ Trader.prototype.SetTargetMarket = function(target) // Else we don't have target markets at all, // set the target as first market this.firstMarket = target; - marketsChanged = true; } if (marketsChanged) { diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index bba5f332cd..45d79a7cd1 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -2919,7 +2919,7 @@ UnitAI.prototype.ReturnResource = function(target, queued) * Adds trade order to the queue. Either walk to the first market, or * start a new route. Not forced, so it can be interrupted by attacks. */ -UnitAI.prototype.SetupTradeRoute = function(target, queued) +UnitAI.prototype.SetupTradeRoute = function(target, source, queued) { if (!this.CanTrade(target)) { @@ -2928,7 +2928,7 @@ UnitAI.prototype.SetupTradeRoute = function(target, queued) } var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); - var marketsChanged = cmpTrader.SetTargetMarket(target); + var marketsChanged = cmpTrader.SetTargetMarket(target, source); if (marketsChanged) { if (cmpTrader.HasBothMarkets()) diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index b4f7437396..b9f3daf2b1 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -379,7 +379,7 @@ function ProcessCommand(player, cmd) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (cmpUnitAI) - cmpUnitAI.SetupTradeRoute(cmd.target); + cmpUnitAI.SetupTradeRoute(cmd.target, cmd.source); } break; diff --git a/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js b/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js index daf0d8f588..ac5a49e484 100644 --- a/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js +++ b/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js @@ -60,6 +60,15 @@ function GetRallyPointCommands(cmpRallyPoint, spawnedEnts) "queued": true }); break; + case "trade": + ret.push( { + "type": "setup-trade-route", + "entities": spawnedEnts, + "source": data[i].source, + "target": data[i].target, + "queued": true + }); + break; default: ret.push( { "type": "walk", diff --git a/binaries/data/mods/public/simulation/helpers/TraderGain.js b/binaries/data/mods/public/simulation/helpers/TraderGain.js new file mode 100644 index 0000000000..dcb6f614a1 --- /dev/null +++ b/binaries/data/mods/public/simulation/helpers/TraderGain.js @@ -0,0 +1,34 @@ +// This constant used to adjust gain value depending on distance +const DISTANCE_FACTOR = 1 / 110; + +// Additional gain for trading performed between markets of different players, in percents +const INTERNATIONAL_TRADING_ADDITION = 50; + +function CalculateTraderGain(firstMarket, secondMarket, template) +{ + 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; + + if (template.GainMultiplier) + gain *= template.GainMultiplier; + gain = Math.round(gain); + return gain; +} + +Engine.RegisterGlobal("CalculateTraderGain", CalculateTraderGain);