Ticket #3697: Trade.patch

File Trade.patch, 42.0 KB (added by wraitii, 8 years ago)

First version (probably not RC)

  • binaries/data/mods/public/globalscripts/Templates.js

     
    239239        if (template.UnitMotion.Run)
    240240            ret.speed.run = func("UnitMotion/Run/Speed", +template.UnitMotion.Run.Speed, player, template);
    241241    }
    242 
    243     if (template.Trader)
    244     {
    245         ret.trader = {
    246             "GainMultiplier": func("Trader/GainMultiplier", +template.Trader.GainMultiplier, player, template)
    247         };
    248     }
    249242       
    250243    if (template.WallSet)
    251244    {
  • binaries/data/mods/public/gui/session/utility_functions.js

     
    113113/**
    114114 * Returns a message with the details of the trade gain.
    115115 */
    116 function getTradingTooltip(gain)
     116function getTradeRouteTooltip(gain)
    117117{
    118 
    119     var playerID = Engine.GetPlayerID();
    120     var simState = GetSimState();
    121118   
    122     var gainString = gain.traderGain;
    123     if (gain.market1Gain && gain.market1Owner == gain.traderOwner)
    124         gainString += translate("+") + gain.market1Gain;
    125     if (gain.market2Gain && gain.market2Owner == gain.traderOwner)
    126         gainString += translate("+") + gain.market2Gain;
     119    return translate("This trader is currently trading between two markets, creating wealth for his empire.");
     120}
    127121
    128     var tooltip = sprintf(translate("%(gain)s (%(player)s)"), {
    129         gain: gainString,
    130         player: (!g_IsNetworked && gain.traderOwner == playerID) ? translate("You") : simState.players[gain.traderOwner].name
    131     });
    132    
    133     if (gain.market1Gain && gain.market1Owner != gain.traderOwner)
    134         tooltip += translateWithContext("Separation mark in an enumeration", ", ") + sprintf(translate("%(gain)s (%(player)s)"), {
    135             gain: gain.market1Gain,
    136             player: (!g_IsNetworked && gain.market1Owner == playerID) ? translate("You") : simState.players[gain.market1Owner].name
    137         });
    138     if (gain.market2Gain && gain.market2Owner != gain.traderOwner)
    139         tooltip += translateWithContext("Separation mark in an enumeration", ", ") + sprintf(translate("%(gain)s (%(player)s)"), {
    140             gain: gain.market2Gain,
    141             player: (!g_IsNetworked && gain.market2Owner == playerID) ? translate("You") : simState.players[gain.market2Owner].name
    142         });
    143 
     122/**
     123 * Returns a message with the details of the trade income.
     124 */
     125function getTradeIncomeTooltip(details)
     126{
     127    if (details.connections.length === 0)
     128        return translate("This market has no trade routes to other markets. Use traders to connect markets together and create wealth.");
     129    var avgStrength = 0.0
     130    for (let connection in details.connections)
     131        avgStrength += Math.min(100, details.connections[connection][1]);
     132    avgStrength /= details.connections.length;
     133    var tooltip = sprintf(translate("[font=\"sans-bold-13\"]Trade income:[/font] %(rate).1f [font=\"sans-10\"]resources/minute[/font]"
     134                                    + "\n[font=\"sans-bold-13\"]Efficiency:[/font] %(eff)u%%\n[font=\"sans-bold-13\"]"
     135                                    + "Nb of trade routes:[/font] %(connections)s\n[font=\"sans-bold-13\"]"
     136                                    + "Average trade route effiency:[/font] %(strength)s%%"),
     137                         { "rate" : details.rate*60.0, "eff": details.rawRate*100,  "connections": details.connections.length, "strength": avgStrength });
     138    tooltip += translate("[font=\"sans-10\"]\n\nTo increase your income, garrison workers until the efficiency is 100% and trade with more markets, or reinforce your current trade lines' strengths[/font]");
    144139    return tooltip;
    145140}
    146141
  • binaries/data/mods/public/gui/session/unit_actions.js

     
    320320            case "is first":
    321321                tooltip = translate("Origin trade market.");
    322322                if (tradingDetails.hasBothMarkets)
    323                     tooltip += "\n" + sprintf(translate("Gain: %(gain)s"), {
    324                         gain: getTradingTooltip(tradingDetails.gain)
    325                     });
     323                    tooltip += "\n" +  + getTradeRouteTooltip(tradingDetails.gain);
    326324                else
    327325                    tooltip += "\n" + translate("Right-click on another market to set it as a destination trade market.");
    328326                break;
    329327            case "is second":
    330                 tooltip = translate("Destination trade market.") + "\n" + sprintf(translate("Gain: %(gain)s"), {
    331                     gain: getTradingTooltip(tradingDetails.gain)
    332                 });
     328                tooltip = translate("Destination trade market.") + "\n" + getTradeRouteTooltip(tradingDetails.gain);
    333329                break;
    334330            case "set first":
    335331                tooltip = translate("Right-click to set as origin trade market");
    336332                break;
    337333            case "set second":
    338                 if (tradingDetails.gain.traderGain == 0)   // markets too close
    339                     return false;
    340                 tooltip = translate("Right-click to set as destination trade market.") + "\n" + sprintf(translate("Gain: %(gain)s"), {
    341                     gain: getTradingTooltip(tradingDetails.gain)
    342                 });
     334                tooltip = translate("Right-click to set as destination trade market.") + "\n" + getTradeRouteTooltip(tradingDetails.gain);
    343335                break;
    344336            }
    345337            return {"possible": true, "tooltip": tooltip};
     
    348340        "actionCheck": function(target)
    349341        {
    350342            var actionInfo = getActionInfo("setup-trade-route", target);
    351             if (!actionInfo.possible)
     343
     344            if (actionInfo.possible === false && actionInfo.tooltip != undefined)
     345                return {"type": "none", "cursor": "cursor-no", "tooltip": actionInfo.tooltip, "target": null};
     346            else if (actionInfo.possible === false)
    352347                return false;
    353348            return {"type": "setup-trade-route", "cursor": "action-setup-trade-route", "tooltip": actionInfo.tooltip, "target": target};
    354349        },
     
    500495                cursor = "action-attack-move";
    501496            }
    502497
    503             if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "Ally"]))
     498            if (hasClass(entState, "Market") && hasClass(targetState, "Market") && entState.id != targetState.id &&
     499                    (!hasClass(entState, "NavalMarket") || hasClass(targetState, "NavalMarket")) && !playerCheck(entState, targetState, ["Enemy"]))
    504500            {
     501                // Find a trader (if any) that this building can produce.
     502                var trader;
     503                if (entState.production && entState.production.entities.length)
     504                    for (var i = 0; i < entState.production.entities.length; ++i)
     505                        if ((trader = GetTemplateData(entState.production.entities[i]).trader))
     506                            break;
     507                var traderData = { "firstMarket": entState.id, "secondMarket": targetState.id, "tradeOwner": entState.player };
     508                var gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData);
     509                if (gain)
     510                {
     511                    data.command = "trade";
     512                    data.target = traderData.secondMarket;
     513                    data.source = traderData.firstMarket;
     514                    cursor = "action-setup-trade-route";
     515                    tooltip = translate("Right-click to establish a default route for new traders.");
     516                }
     517            } else if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "Ally"]))
     518            {
    505519                data.command = "garrison";
    506520                data.target = targetState.id;
    507521                cursor = "action-garrison";
     
    523537                data.resourceType = resourceType;
    524538                data.resourceTemplate = targetState.template;
    525539            }
    526             else if (hasClass(entState, "Market") && hasClass(targetState, "Market") && entState.id != targetState.id &&
    527                     (!hasClass(entState, "NavalMarket") || hasClass(targetState, "NavalMarket")) && !playerCheck(entState, targetState, ["Enemy"]))
    528             {
    529                 // Find a trader (if any) that this building can produce.
    530                 var trader;
    531                 if (entState.production && entState.production.entities.length)
    532                     for (var i = 0; i < entState.production.entities.length; ++i)
    533                         if ((trader = GetTemplateData(entState.production.entities[i]).trader))
    534                             break;
    535 
    536                 var traderData = { "firstMarket": entState.id, "secondMarket": targetState.id, "template": trader };
    537                 var gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData);
    538                 if (gain && gain.traderGain)
    539                 {
    540                     data.command = "trade";
    541                     data.target = traderData.secondMarket;
    542                     data.source = traderData.firstMarket;
    543                     cursor = "action-setup-trade-route";
    544                     tooltip = translate("Right-click to establish a default route for new traders.");
    545                     if (trader)
    546                         tooltip += "\n" + sprintf(translate("Gain: %(gain)s"), { gain: getTradingTooltip(gain) });
    547                     else // Foundation or cannot produce traders
    548                         tooltip += "\n" + sprintf(translate("Expected gain: %(gain)s"), { gain: getTradingTooltip(gain) });
    549                 }
    550             }
    551540            else if (targetState.foundation && playerCheck(entState, targetState, ["Ally"]))
    552541            {
    553542                data.command = "build";
  • binaries/data/mods/public/gui/session/selection_panels.js

     
    926926                else
    927927                    data.carried[carrying.type] = carrying.amount;
    928928            }
    929 
    930             if (state.trader && state.trader.goods && state.trader.goods.amount)
    931             {
    932                 if (!data.carried)
    933                     data.carried = {};
    934                 var amount = state.trader.goods.amount;
    935                 var type = state.trader.goods.type;
    936                 var totalGain = amount.traderGain;
    937                 if (amount.market1Gain)
    938                     totalGain += amount.market1Gain;
    939                 if (amount.market2Gain)
    940                     totalGain += amount.market2Gain;
    941                 if (data.carried[type])
    942                     data.carried[type] += totalGain;
    943                 else
    944                     data.carried[type] = totalGain;
    945             }
    946929        }
    947930        return true;
    948931    },
  • binaries/data/mods/public/gui/session/selection_details.js

     
    172172        Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "";
    173173    }
    174174    // Use the same indicators for traders
    175     else if (entState.trader && entState.trader.goods.amount)
     175    else if (entState.trader && entState.trader.trading)
    176176    {
     177        var nextMarket = GetEntityState(entState.trader.nextMarket);
     178        // find the connection if it exists.
     179        if (nextMarket && nextMarket.tradeIncome)
     180        {
     181            for each (let conn in nextMarket.tradeIncome.connections)
     182                if (conn[0] === entState.trader.originMarket)
     183                {
     184                    var connection = conn;
     185                    break;
     186                }
     187        }
    177188        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
    178189        Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
    179         Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+entState.trader.goods.type+".png";
    180         var totalGain = entState.trader.goods.amount.traderGain;
    181         if (entState.trader.goods.amount.market1Gain)
    182             totalGain += entState.trader.goods.amount.market1Gain;
    183         if (entState.trader.goods.amount.market2Gain)
    184             totalGain += entState.trader.goods.amount.market2Gain;
    185         Engine.GetGUIObjectByName("resourceCarryingText").caption = totalGain;
    186         Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(gain)s"), { "gain": getTradingTooltip(entState.trader.goods.amount) });
     190        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/trade.png";
     191        Engine.GetGUIObjectByName("resourceCarryingText").caption = "";
     192        if (connection !== undefined)
     193            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("This trader is part of a trade route between two markets.\nThis route's efficiency is %(strength)u%%."), { "strength": Math.min(connection[1], 100) });
     194        else
     195            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "This trader is trying to establish a trade route, but the number of traders isn't high enough for this trade route to be viable yet.";
    187196    }
    188197    // And for number of workers
    189198    else if (entState.foundation && entState.visibility == "visible")
     
    202211        else
    203212            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
    204213    }
     214    // And for the trade income icon
     215    else if (entState.tradeIncome)
     216    {
     217        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
     218        Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
     219        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/trade.png";
     220        let caption = sprintf(translate("%(eff)u%%  x%(nb)u"),
     221                                       { "eff": +entState.tradeIncome.rawRate*100,
     222                                         "nb": entState.tradeIncome.connections.length });
     223        Engine.GetGUIObjectByName("resourceCarryingText").caption = caption;
     224        Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = getTradeIncomeTooltip(entState.tradeIncome);
     225    }
    205226    else if (entState.repairable && entState.repairable.numBuilders > 0 && entState.visibility == "visible")
    206227    {
    207228        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    51945194    {
    51955195        var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
    51965196        cmpTrader.PerformTrade(currentMarket);
    5197         if (!cmpTrader.GetGain().traderGain)
    5198         {
    5199             this.StopTrading();
    5200             return;
    5201         }
     5197//      if (!cmpTrader.GetGain().traderGain)
     5198//      {
     5199//          this.StopTrading();
     5200//          return;
     5201//      }
    52025202
    52035203        if (this.order.data.route && this.order.data.route.length)
    52045204        {
  • binaries/data/mods/public/simulation/components/Trader.js

     
    1 // See helpers/TraderGain.js for the CalculateTaderGain() function which works out how many
    2 // resources a trader gets
    3 
    4 // Additional gain for ships for each garrisoned trader, in percents
    5 const GARRISONED_TRADER_ADDITION = 20;
    6 
    71// Array of resource names
    82const RESOURCES = ["food", "wood", "stone", "metal"];
    93
     
    126Trader.prototype.Schema =
    137    "<a:help>Lets the unit generate resouces while moving between markets (or docks in case of water trading).</a:help>" +
    148    "<a:example>" +
    15         "<MaxDistance>2.0</MaxDistance>" +
    16         "<GainMultiplier>1.0</GainMultiplier>" +
     9        "<ConnectionStrength>5.0</ConnectionStrength>" +
    1710    "</a:example>" +
    18     "<element name='GainMultiplier' a:help='Additional gain multiplier'>" +
     11    "<element name='ConnectionStrength' a:help='How much more connection strength a trader gives.'>" +
    1912        "<ref name='positiveDecimal'/>" +
    2013    "</element>";
    2114
     
    2316{
    2417    this.firstMarket = INVALID_ENTITY;
    2518    this.secondMarket = INVALID_ENTITY;
    26     // Gain from one pass between markets
    27     this.gain = null;
    28     // Selected resource for trading
    29     this.requiredGoods = undefined;
     19
    3020    // Currently carried goods
    31     this.goods = { "type": null, "amount": null, "origin": null };
     21    this.goodsOrigin = null;
    3222};
    3323
    34 Trader.prototype.CalculateGain = function(firstMarket, secondMarket)
    35 {
    36     var gain = CalculateTraderGain(firstMarket, secondMarket, this.template, this.entity);
    37     if (!gain)  // One of our markets must have been destroyed
    38         return null;
    39 
    40     // For ship increase gain for each garrisoned trader
    41     // Calculate this here to save passing unnecessary stuff into the CalculateTraderGain function
    42     var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
    43     if (cmpIdentity && cmpIdentity.HasClass("Ship"))
    44     {
    45         var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
    46         if (cmpGarrisonHolder)
    47         {
    48             var garrisonMultiplier = 1;
    49             var garrisonedTradersCount = 0;
    50             for each (var entity in cmpGarrisonHolder.GetEntities())
    51             {
    52                 var cmpGarrisonedUnitTrader = Engine.QueryInterface(entity, IID_Trader);
    53                 if (cmpGarrisonedUnitTrader)
    54                     garrisonedTradersCount++;
    55             }
    56             garrisonMultiplier *= 1 + GARRISONED_TRADER_ADDITION * garrisonedTradersCount / 100;
    57 
    58             if (gain.traderGain)
    59                 gain.traderGain = Math.round(garrisonMultiplier * gain.traderGain);
    60             if (gain.market1Gain)
    61                 gain.market1Gain = Math.round(garrisonMultiplier * gain.market1Gain);
    62             if (gain.market2Gain)
    63                 gain.market2Gain = Math.round(garrisonMultiplier * gain.market2Gain);
    64         }
    65     }
    66    
    67     return gain;
    68 };
    69 
    70 Trader.prototype.GetGain = function()
    71 {
    72     return this.gain;
    73 };
    74 
    7524// Set target as target market.
    7625// Return true if at least one of markets was changed.
    7726Trader.prototype.SetTargetMarket = function(target, source)
     
    11059        if (target == this.firstMarket)
    11160            marketsChanged = false;
    11261        else
    113         {
    11462            this.secondMarket = target;
    115             this.gain = this.CalculateGain(this.firstMarket, this.secondMarket);
    116         }
    11763    }
    11864    else
    11965    {
     
    12470    if (marketsChanged)
    12571    {
    12672        // Drop carried goods
    127         this.goods.amount = null;
     73        this.goodsOrigin = null;
    12874    }
    12975    return marketsChanged;
    13076};
     
    14490    return this.firstMarket && this.secondMarket;
    14591};
    14692
    147 Trader.prototype.GetRequiredGoods = function()
    148 {
    149     return this.requiredGoods;
    150 };
    151 
    152 Trader.prototype.SetRequiredGoods = function(requiredGoods)
    153 {
    154     // Check that argument is a correct resource name
    155     if (!requiredGoods || RESOURCES.indexOf(requiredGoods) == -1)
    156         this.requiredGoods = undefined;
    157     else
    158         this.requiredGoods = requiredGoods;
    159 };
    160 
    16193Trader.prototype.CanTrade = function(target)
    16294{
    16395    var cmpTraderIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     
    186118
    187119Trader.prototype.PerformTrade = function(currentMarket)
    188120{
    189     if (this.goods.amount && this.goods.amount.traderGain)
     121    if (this.goodsOrigin && this.goodsOrigin !== currentMarket)
    190122    {
    191         var cmpPlayer = QueryOwnerInterface(this.entity);
    192         if (cmpPlayer)
    193             cmpPlayer.AddResource(this.goods.type, this.goods.amount.traderGain);
    194 
    195         var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
    196         if (cmpStatisticsTracker)
    197             cmpStatisticsTracker.IncreaseTradeIncomeCounter(this.goods.amount.traderGain);
    198 
    199         if (this.goods.amount.market1Gain)
    200         {
    201             var cmpPlayer = QueryOwnerInterface(this.firstMarket);
    202             if (cmpPlayer)
    203                 cmpPlayer.AddResource(this.goods.type, this.goods.amount.market1Gain);
    204 
    205             var cmpStatisticsTracker = QueryOwnerInterface(this.firstMarket, IID_StatisticsTracker);
    206             if (cmpStatisticsTracker)
    207                 cmpStatisticsTracker.IncreaseTradeIncomeCounter(this.goods.amount.market1Gain);
    208         }
    209 
    210         if (this.goods.amount.market2Gain)
    211         {
    212             var cmpPlayer = QueryOwnerInterface(this.secondMarket);
    213             if (cmpPlayer)
    214                 cmpPlayer.AddResource(this.goods.type, this.goods.amount.market2Gain);
    215 
    216             var cmpStatisticsTracker = QueryOwnerInterface(this.secondMarket, IID_StatisticsTracker);
    217             if (cmpStatisticsTracker)
    218                 cmpStatisticsTracker.IncreaseTradeIncomeCounter(this.goods.amount.market2Gain);
    219         }
     123        var cmpTradeIncome = Engine.QueryInterface(currentMarket, IID_TradeIncome);
     124        if (cmpTradeIncome)
     125            cmpTradeIncome.AddConnectionWithMarket(this.goodsOrigin, this.template.ConnectionStrength);
    220126    }
    221 
    222     // First take the preferred goods of the trader if any,
    223     // otherwise choose one according to the player's trading priorities
    224     // if still nothing (but should never happen), choose metal
    225     // and recomputes the gain in case it has changed (for example by technology)
    226     var nextGoods = this.GetRequiredGoods();
    227     if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
    228     {
    229         var cmpPlayer = QueryOwnerInterface(this.entity);
    230         if (cmpPlayer)
    231             nextGoods = cmpPlayer.GetNextTradingGoods();
    232 
    233         if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
    234             nextGoods = "metal";
    235     }
    236     this.goods.type = nextGoods;
    237     this.goods.amount = this.CalculateGain(this.firstMarket, this.secondMarket);
    238     this.goods.origin = currentMarket;
     127    this.goodsOrigin = +currentMarket;
    239128};
    240129
    241 Trader.prototype.GetGoods = function()
    242 {
    243     return this.goods;
    244 };
    245 
    246130Trader.prototype.GetNextMarket = function()
    247131{
    248     if (this.goods.amount && this.goods.origin == this.firstMarket)
     132    if (this.goodsOrigin == this.firstMarket)
    249133        return this.secondMarket;
    250134
    251     if (this.goods.amount && this.goods.origin != this.secondMarket)
    252         this.goods.amount = null;   // leftover from previous trading
     135    if (this.goodsOrigin != this.secondMarket)
     136        this.goodsOrigin = null;   // leftover from previous trading
    253137    return this.firstMarket;
    254138};
    255139
     140Trader.prototype.GetOriginMarket = function()
     141{
     142    return (this.GetNextMarket() == this.firstMarket) ? this.secondMarket : this.firstMarket;
     143};
     144
    256145Trader.prototype.StopTrading = function()
    257146{
    258147    // Drop carried goods
    259     this.goods.amount = null;
     148    this.goodsOrigin = null;
    260149    // Reset markets
    261150    this.firstMarket = INVALID_ENTITY;
    262151    this.secondMarket = INVALID_ENTITY;
     
    274163    return { "min": 0, "max": max};
    275164};
    276165
    277 Trader.prototype.OnGarrisonedUnitsChanged = function()
    278 {
    279     if (this.HasBothMarkets())
    280         this.gain = this.CalculateGain(this.firstMarket, this.secondMarket);
    281 };
    282 
    283166Engine.RegisterComponentType(IID_Trader, "Trader", Trader);
  • binaries/data/mods/public/simulation/components/TradeIncome.js

     
     1// Array of resource names
     2const RESOURCES = ["food", "wood", "stone", "metal"];
     3
     4function TradeIncome() {}
     5
     6TradeIncome.prototype.Schema =
     7    "<a:help>Lets the unit generate resources that traders could pick up and trade with.</a:help>" +
     8    "<a:example>" +
     9        "<Efficiency>2.0</Efficiency>" +
     10    "</a:example>" +
     11    "<element name='Efficiency' a:help='Gives the basic trade income of the building at full efficiency.'>" +
     12        "<ref name='positiveDecimal'/>" +
     13    "</element>" +
     14    "<optional>" +
     15        "<element name='GarrisonEfficiency' a:help='Improve the efficiency of trading by garrisoning units inside'>" +
     16            "<optional>" +
     17                "<element name='Classes' a:help='only units having one of the following classes will be considered. Defaults to all.'>" +
     18                    "<text/>" +
     19                "</element>" +
     20            "</optional>" +
     21            "<element name='EmptyEfficiency' a:help='Basic efficiency when empty. Expressed as a % of total efficiency.'>" +
     22                "<ref name='nonNegativeDecimal'/>" +
     23            "</element>" +
     24            "<optional>" +
     25                "<element name='MaxGarrison' a:help='Number of unit required for efficiency to be 100%. Defaults to \"garrison capacity\"'>" +
     26                    "<data type='nonNegativeInteger'/>" +
     27                "</element>" +
     28            "</optional>" +
     29        "</element>" +
     30    "</optional>";
     31
     32TradeIncome.prototype.Init = function()
     33{
     34    this.connections = new Map();
     35
     36    // Call the timer
     37    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     38    cmpTimer.SetInterval(this.entity, IID_TradeIncome, "GenerateResources", 5000, 5000, undefined);
     39
     40    // quick sanity check
     41    var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
     42    if (this.template.GarrisonEfficiency && !cmpGarrisonHolder)
     43        warn("Entity " + this.entity + " has a TradeIncome component that makes use of garrisoned entity but does not support garrisoning.");
     44};
     45
     46// When a trader arrives, it either establishes or reinforces a connection.
     47TradeIncome.prototype.AddConnectionWithMarket = function(market, amount)
     48{
     49    if (!this.connections.get(market))
     50        this.connections.set(market, { "strength" : 0, "distance" : 0, "international" : false });
     51
     52    this.connections.get(market)["strength"] += +amount;
     53    // set up a maximal amount so that you can't just get enough connection strength then delete your traders.
     54    if (this.connections.get(market)["strength"] > 1000)
     55        this.connections.get(market)["strength"] = 1000;
     56
     57    // reset distance. Ought not change.
     58    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     59    var cmpConnectionPosition = Engine.QueryInterface(market, IID_Position);
     60    if (!cmpPosition || !cmpPosition.IsInWorld() || !cmpConnectionPosition || !cmpConnectionPosition.IsInWorld())
     61        return;
     62
     63    var Position = cmpPosition.GetPosition2D();
     64    var ConnectionPosition = cmpConnectionPosition.GetPosition2D();
     65    var distance = Math.sqrt(Math.pow(Position.x - ConnectionPosition.x, 2) + Math.pow(Position.y - ConnectionPosition.y, 2));
     66    this.connections.get(market)["distance"] = distance;
     67
     68    // reset internationality, in case the owner changes: from our POV until a trader brings news, we are selling items from a faraway land.
     69    var owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     70    var ownerConnection = Engine.QueryInterface(market, IID_Ownership).GetOwner();
     71
     72    this.connections.get(market)["international"] = (owner != ownerConnection);
     73}
     74
     75TradeIncome.prototype.GetConnections = function()
     76{
     77    var ret = [];
     78    for (let conn of this.connections.keys())
     79        ret.push([conn, this.connections.get(conn).strength, this.connections.get(conn).distance, this.connections.get(conn).international]);
     80    return ret;
     81}
     82
     83TradeIncome.prototype.GetTradeIncomeRate = function()
     84{
     85    if (this.connections.size === 0)
     86        return 0;
     87
     88    // Calculate our connection bonuses.
     89    var connectionEffect = 0;
     90
     91    var owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     92
     93    for (let i of this.connections.keys())
     94    {
     95        let bonus = CalculateMarketConnectionBonusDirect(this.connections.get(i)["distance"], owner, this.connections.get(i)["international"]);
     96        connectionEffect += bonus * Math.min(1.0, this.connections.get(i)["strength"]/100.0);
     97    }
     98    connectionEffect *= +ApplyValueModificationsToEntity("TradeIncome/ConnectionMulitplier", +this.template.ConnectionMultiplier, this.entity);
     99    connectionEffect = Math.sqrt(connectionEffect);
     100
     101    connectionEffect *= this.GetRawEfficiency();
     102   
     103    return connectionEffect;
     104}
     105
     106TradeIncome.prototype.GetRawEfficiency = function()
     107{
     108    if (!this.template.GarrisonEfficiency)
     109        return 1.0;
     110
     111    var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
     112    if (!cmpGarrisonHolder)
     113        return 1.0; // probably should not happen
     114
     115
     116    // Depending on how many units are garrisoned, modulate efficiency
     117    var capacity = (this.template.GarrisonEfficiency.MaxGarrison === undefined) ? cmpGarrisonHolder.GetCapacity() : +this.template.GarrisonEfficiency.MaxGarrison;
     118    var currentCount = (this.template.GarrisonEfficiency.Classes === undefined) ? cmpGarrisonHolder.GetEntities().length
     119                                                                                  : cmpGarrisonHolder.GetGarrisonedArcherCount(this.template.GarrisonEfficiency.Classes);
     120    return 0.2 + (currentCount / capacity) * 0.8;
     121}
     122
     123TradeIncome.prototype.GenerateResources = function()
     124{
     125    if (this.connections.size === 0)
     126        return;
     127
     128    var cmpPlayer = QueryOwnerInterface(this.entity);
     129    if (!cmpPlayer)
     130        return;
     131
     132    var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
     133
     134    var rate = this.GetTradeIncomeRate() * 5.0;
     135    var goods = cmpPlayer.GetTradingGoods();
     136
     137    for (let good in goods)
     138    {
     139        if (RESOURCES.indexOf(good) === -1)
     140            continue;
     141
     142        cmpPlayer.AddResource(good, rate * goods[good]/100.0);  // should add up to 100%
     143
     144        if (cmpStatisticsTracker)
     145            cmpStatisticsTracker.IncreaseTradeIncomeCounter(rate * goods[good]);
     146    }
     147
     148    for (let i of this.connections.keys())
     149    {
     150        this.connections.get(i)["strength"] -= 10;
     151        if (this.connections.get(i)["strength"] <= 0)
     152            this.connections.delete(i);
     153    }
     154};
     155
     156Engine.RegisterComponentType(IID_TradeIncome, "TradeIncome", TradeIncome);
  • binaries/data/mods/public/simulation/components/Looter.js

    Property changes on: binaries/data/mods/public/simulation/components/TradeIncome.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    3636        for (let resource of cmpResourceGatherer.GetCarryingStatus())
    3737            resources[resource.type] += resource.amount;
    3838
    39     // Loot resources traders carry
    40     var cmpTrader = Engine.QueryInterface(targetEntity, IID_Trader);
    41     if (cmpTrader)
    42     {
    43         let carriedGoods = cmpTrader.GetGoods();
    44         if (carriedGoods.amount)
    45         {
    46             resources[carriedGoods.type] +=
    47                 + (carriedGoods.amount.traderGain || 0)
    48                 + (carriedGoods.amount.market1Gain || 0)
    49                 + (carriedGoods.amount.market2Gain || 0);
    50         }
    51     }
    52 
    5339    // Transfer resources
    5440    var cmpPlayer = QueryOwnerInterface(this.entity);
    5541    cmpPlayer.AddResources(resources);
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    239239        "resourceCarrying": null,
    240240        "rotation": null,
    241241        "trader": null,
     242        "tradeIncome": null,
    242243        "unitAI": null,
    243244        "visibility": null,
    244245    };
     
    309310    if (cmpTrader)
    310311    {
    311312        ret.trader = {
    312             "goods": cmpTrader.GetGoods(),
    313             "requiredGoods": cmpTrader.GetRequiredGoods()
     313            "trading": cmpTrader.HasBothMarkets(),
     314            "nextMarket": cmpTrader.GetNextMarket(),
     315            "originMarket": cmpTrader.GetOriginMarket()
    314316        };
    315317    }
    316318
     319    var cmpTradeIncome = Engine.QueryInterface(ent, IID_TradeIncome);
     320    if (cmpTradeIncome)
     321    {
     322        ret.tradeIncome = {
     323            "connections": cmpTradeIncome.GetConnections(),
     324            "rate": cmpTradeIncome.GetTradeIncomeRate(),
     325            "rawRate": cmpTradeIncome.GetRawEfficiency()
     326        };
     327    }
     328
    317329    var cmpFogging = Engine.QueryInterface(ent, IID_Fogging);
    318330    if (cmpFogging)
    319331    {
     
    16791691    if (!data.firstMarket || !data.secondMarket)
    16801692        return null;
    16811693
    1682     return CalculateTraderGain(data.firstMarket, data.secondMarket, data.template);
     1694    return CalculateMarketConnectionBonus(data.firstMarket, data.secondMarket, data.tradeOwner);
    16831695};
    16841696
    16851697GuiInterface.prototype.GetTradingDetails = function(player, data)
     
    16971709            "hasBothMarkets": cmpEntityTrader.HasBothMarkets()
    16981710        };
    16991711        if (cmpEntityTrader.HasBothMarkets())
    1700             result.gain = cmpEntityTrader.GetGain();
     1712            result.gain = CalculateMarketConnectionBonus(firstMarket, secondMarket, player)
    17011713    }
    17021714    else if (data.target === secondMarket)
    17031715    {
    17041716        result = {
    17051717            "type": "is second",
    1706             "gain": cmpEntityTrader.GetGain(),
     1718            "gain": CalculateMarketConnectionBonus(firstMarket, data.target, player)
    17071719        };
    17081720    }
    17091721    else if (!firstMarket)
     
    17141726    {
    17151727        result = {
    17161728            "type": "set second",
    1717             "gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
     1729            "gain": CalculateMarketConnectionBonus(firstMarket, data.target, player)
    17181730        };
    17191731    }
    17201732    else
  • binaries/data/mods/public/simulation/components/interfaces/TradeIncome.js

     
     1Engine.RegisterInterface("TradeIncome");
  • binaries/data/mods/public/simulation/helpers/TraderGain.js

    Property changes on: binaries/data/mods/public/simulation/components/interfaces/TradeIncome.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    11// This constant used to adjust gain value depending on distance
    2 const DISTANCE_FACTOR = 1 / 115;
     2const DISTANCE_FACTOR = 1 / 100.0;
    33
    4 // Additional gain (applying to each market) for trading performed between markets of different players, in percents
    5 const INTERNATIONAL_TRADING_ADDITION = 25;
     4// When performing trade between markets owned by two different players, multiply trader gain by this value.
     5// When trading between your markets, you gain the resources at both markets.
     6// So this must be at least above 2 to make it interesting to trade with your ally.
     7const INTERNATIONAL_TRADING_MULTIPLICATION = 1.4;
    68
    7 // If trader undefined, the trader owner is supposed to be the same as the first market
    8 function CalculateTraderGain(firstMarket, secondMarket, template, trader)
     9function CalculateMarketConnectionBonus(firstMarket, secondMarket, tradeOwner)
    910{
    10     var gain = {};
     11    var bonus = 1;
    1112
    1213    var cmpFirstMarketPosition = Engine.QueryInterface(firstMarket, IID_Position);
    1314    var cmpSecondMarketPosition = Engine.QueryInterface(secondMarket, IID_Position);
     
    1920    // Calculate ordinary Euclidean distance between markets.
    2021    // We don't use pathfinder, because ordinary distance looks more fair.
    2122    var distance = Math.sqrt(Math.pow(firstMarketPosition.x - secondMarketPosition.x, 2) + Math.pow(firstMarketPosition.y - secondMarketPosition.y, 2));
    22     // We calculate gain as square of distance to encourage trading between remote markets
    23     gain.traderGain = Math.pow(distance * DISTANCE_FACTOR, 2);
    24     if (template && template.GainMultiplier)
    25     {
    26         if (trader)
    27             gain.traderGain *= ApplyValueModificationsToEntity("Trader/GainMultiplier", +template.GainMultiplier, trader);
    28         else    // called from the gui with modifications already applied
    29             gain.traderGain *= template.GainMultiplier;
    30     }
    31     gain.traderGain = Math.round(gain.traderGain);
    32     // If trader undefined, the trader owner is supposed to be the same as the first market
    33     if (trader)
    34         var cmpOwnership = Engine.QueryInterface(trader, IID_Ownership);
    35     else
    36         var cmpOwnership = Engine.QueryInterface(firstMarket, IID_Ownership);
    37     gain.traderOwner = cmpOwnership.GetOwner();
    3823
    3924    // If markets belong to different players, add gain from international trading
    4025    var ownerFirstMarket = Engine.QueryInterface(firstMarket, IID_Ownership).GetOwner();
    4126    var ownerSecondMarket = Engine.QueryInterface(secondMarket, IID_Ownership).GetOwner();
    4227    if (ownerFirstMarket != ownerSecondMarket)
    43     {
    44         var internationalGain1 = ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_ADDITION, firstMarket);
    45         gain.market1Gain = Math.round(gain.traderGain * internationalGain1 / 100);
    46         gain.market1Owner = ownerFirstMarket;
    47         var internationalGain2 = ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_ADDITION, secondMarket);
    48         gain.market2Gain = Math.round(gain.traderGain * internationalGain2 / 100);
    49         gain.market2Owner = ownerSecondMarket;
     28        return CalculateMarketConnectionBonusDirect(distance, tradeOwner, true);
     29   
     30    return CalculateMarketConnectionBonusDirect(distance, tradeOwner);
     31}
    5032
    51     }
    52 
    53     return gain;
     33function CalculateMarketConnectionBonusDirect(distance, tradeOwner, international)
     34{
     35    var bonus = Math.log(distance*DISTANCE_FACTOR);
     36    if (international)
     37        bonus *= +ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_MULTIPLICATION, tradeOwner);
     38    return Math.max(0, bonus);
    5439}
    5540
    56 Engine.RegisterGlobal("CalculateTraderGain", CalculateTraderGain);
     41Engine.RegisterGlobal("CalculateMarketConnectionBonusDirect", CalculateMarketConnectionBonusDirect);
     42Engine.RegisterGlobal("CalculateMarketConnectionBonus", CalculateMarketConnectionBonus);
  • binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml

     
    3939    </SoundGroups>
    4040  </Sound>
    4141  <Trader>
    42     <GainMultiplier>1.0</GainMultiplier>
     42    <ConnectionStrength>50</ConnectionStrength>
    4343  </Trader>
    4444  <UnitAI>
    4545    <CanGuard>false</CanGuard>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml

     
    4545    <HeightOffset>6.0</HeightOffset>
    4646  </StatusBars>
    4747  <Trader>
    48     <GainMultiplier>1.0</GainMultiplier>
     48    <ConnectionStrength>100</ConnectionStrength>
    4949  </Trader>
    5050  <UnitAI>
    5151    <DefaultStance>passive</DefaultStance>
  • binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml

     
    7171      armor_ship_hullsheathing
    7272    </Technologies>
    7373  </ProductionQueue>
     74  <TradeIncome>
     75    <Efficiency>0.5</Efficiency>
     76  </TradeIncome>
    7477  <Vision>
    7578    <Range>40</Range>
    7679  </Vision>
  • binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml

     
    22<Entity parent="template_structure_economic">
    33  <BuildRestrictions>
    44    <Category>Market</Category>
     5    <Distance>
     6      <FromClass>CivilCentre</FromClass>
     7      <MaxDistance>140</MaxDistance>
     8    </Distance>
    59  </BuildRestrictions>
    610  <Cost>
    711    <BuildTime>150</BuildTime>
     
    1317    <Square width="33.0" depth="29.0"/>
    1418    <Height>8.0</Height>
    1519  </Footprint>
     20  <GarrisonHolder>
     21    <Max>10</Max>
     22    <EjectHealth>0.2</EjectHealth>
     23    <EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
     24    <List datatype="tokens">Worker</List>
     25    <BuffHeal>0</BuffHeal>
     26    <LoadingRange>2</LoadingRange>
     27  </GarrisonHolder>
    1628  <Health>
    1729    <Max>1500</Max>
    1830    <SpawnEntityOnDeath>rubble/rubble_stone_5x5</SpawnEntityOnDeath>
     
    4759    <Radius>40</Radius>
    4860    <Weight>30000</Weight>
    4961  </TerritoryInfluence>
     62  <TradeIncome>
     63    <Efficiency>1.0</Efficiency>
     64    <GarrisonEfficiency>
     65      <EmptyEfficiency>0.2</EmptyEfficiency>
     66    </GarrisonEfficiency>
     67  </TradeIncome>
    5068  <ProductionQueue>
    5169    <BatchTimeModifier>0.7</BatchTimeModifier>
    5270    <Technologies datatype="tokens">
  • binaries/data/mods/public/simulation/templates/template_formation.xml

     
    4040    <WalkSpeed>1.0</WalkSpeed>
    4141    <PassabilityClass>large</PassabilityClass>
    4242  </UnitMotion>
    43   <Trader>
    44     <GainMultiplier>1.0</GainMultiplier>
    45   </Trader>
    4643</Entity>
  • binaries/data/mods/public/simulation/templates/special/player.xml

     
    3030      <Library>1</Library>
    3131      <Lighthouse>1</Lighthouse>
    3232      <Juggernaut>1</Juggernaut>
     33      <Market>0</Market>
    3334    </Limits>
    3435    <LimitChangers>
     36      <Market>
     37        <CivilCentre>1</CivilCentre>
     38      </Market>
    3539      <WarDog>
    3640        <Kennel>10</Kennel>
    3741      </WarDog>
  • binaries/data/mods/public/simulation/templates/structures/pers_market.xml

     
    1515  <Obstruction>
    1616    <Static width="24.0" depth="24.0"/>
    1717  </Obstruction>
     18  <TradeIncome>
     19    <Efficiency>1.25</Efficiency>
     20  </TradeIncome>
    1821  <VisualActor>
    1922    <Actor>structures/persians/market.xml</Actor>
    2023    <FoundationActor>structures/fndn_5x5.xml</FoundationActor>
  • binaries/data/mods/public/simulation/templates/structures/cart_dock.xml

     
    2727      -training_naval_architects
    2828    </Technologies>
    2929  </ProductionQueue>
     30  <TradeIncome>
     31    <Efficiency>0.8</Efficiency>
     32  </TradeIncome>
    3033  <VisualActor>
    3134    <Actor>structures/carthaginians/dock.xml</Actor>
    3235    <FoundationActor>structures/fndn_6x4_dock.xml</FoundationActor>
  • binaries/data/mods/public/simulation/templates/units/pers_support_trader.xml

     
    2020      <death>actor/fauna/death/death_camel.xml</death>
    2121    </SoundGroups>
    2222  </Sound>
    23   <Trader>
    24     <GainMultiplier>1.25</GainMultiplier>
    25   </Trader>
    2623  <VisualActor>
    2724    <Actor>units/persians/trader.xml</Actor>
    2825  </VisualActor>
  • binaries/data/mods/public/simulation/templates/units/cart_ship_merchant.xml

     
    1212    <Icon>units/cart_ship_merchant.png</Icon>
    1313    <RequiredTechnology>phase_village</RequiredTechnology>
    1414  </Identity>
    15   <Trader>
    16     <GainMultiplier>1.25</GainMultiplier>
    17   </Trader>
    1815  <VisualActor>
    1916    <Actor>structures/carthaginians/merchant_ship.xml</Actor>
    2017  </VisualActor>