Ticket #3697: trade-v2.2.patch

File trade-v2.2.patch, 44.5 KB (added by wraitii, 8 years ago)
  • binaries/data/mods/public/globalscripts/Templates.js

     
    245245        if (template.UnitMotion.Run)
    246246            ret.speed.run = func("UnitMotion/Run/Speed", +template.UnitMotion.Run.Speed, player, template);
    247247    }
    248 
    249     if (template.Trader)
    250     {
    251         ret.trader = {
    252             "GainMultiplier": func("Trader/GainMultiplier", +template.Trader.GainMultiplier, player, template)
    253         };
    254     }
    255248       
    256249    if (template.WallSet)
    257250    {
  • 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            }
    345             return {"possible": true, "tooltip": tooltip};
    346 
     337            if (tradingDetails.gain !== undefined && tradingDetails.gain == 0)
     338                return { "possible": false, "tooltip": translate("This market is too close to trade with.") };
     339            else
     340                return { "possible": true, "tooltip": tooltip };
    347341        },
    348342        "actionCheck": function(target)
    349343        {
    350344            var actionInfo = getActionInfo("setup-trade-route", target);
    351             if (!actionInfo.possible)
     345
     346            if (!actionInfo.possible && actionInfo.tooltip != undefined)
     347                return { "type": "none", "cursor": "cursor-no", "tooltip": actionInfo.tooltip, "target": null };
     348            else if (!actionInfo.possible)
    352349                return false;
    353             return {"type": "setup-trade-route", "cursor": "action-setup-trade-route", "tooltip": actionInfo.tooltip, "target": target};
     350            return { "type": "setup-trade-route", "cursor": "action-setup-trade-route", "tooltip": actionInfo.tooltip, "target": target };
    354351        },
    355352        "specificness": 0,
    356353    },
     
    500497                cursor = "action-attack-move";
    501498            }
    502499
    503             if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "Ally"]))
     500            if (hasClass(entState, "Market") && hasClass(targetState, "Market") && entState.id != targetState.id &&
     501                    (!hasClass(entState, "NavalMarket") || hasClass(targetState, "NavalMarket")) && !playerCheck(entState, targetState, ["Enemy"]))
    504502            {
     503                // Find a trader (if any) that this building can produce.
     504                let trader;
     505                if (entState.production && entState.production.entities.length)
     506                    for (let i = 0; i < entState.production.entities.length; ++i)
     507                        if ((trader = GetTemplateData(entState.production.entities[i]).trader))
     508                            break;
     509                let traderData = { "firstMarket": entState.id, "secondMarket": targetState.id, "tradeOwner": entState.player };
     510                let gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData);
     511                if (gain)
     512                {
     513                    data.command = "trade";
     514                    data.target = traderData.secondMarket;
     515                    data.source = traderData.firstMarket;
     516                    cursor = "action-setup-trade-route";
     517                    tooltip = translate("Right-click to establish a default route for new traders.");
     518                }
     519            }
     520            else if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "Ally"]))
     521            {
    505522                data.command = "garrison";
    506523                data.target = targetState.id;
    507524                cursor = "action-garrison";
     
    523540                data.resourceType = resourceType;
    524541                data.resourceTemplate = targetState.template;
    525542            }
    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             }
    551543            else if (targetState.foundation && playerCheck(entState, targetState, ["Ally"]))
    552544            {
    553545                data.command = "build";
  • binaries/data/mods/public/gui/session/utility_functions.js

     
    8282/**
    8383 * Returns a message with the details of the trade gain.
    8484 */
    85 function getTradingTooltip(gain)
     85function getTradeRouteTooltip(gain)
    8686{
     87    return translate("This trader is currently trading between two markets, creating wealth for his empire.") + "\n" +
     88           sprintf(translate("The basic value of this trade route is %(value).1f."), { value:gain });
     89}
    8790
    88     var playerID = Engine.GetPlayerID();
    89     var simState = GetSimState();
    90    
    91     var gainString = gain.traderGain;
    92     if (gain.market1Gain && gain.market1Owner == gain.traderOwner)
    93         gainString += translate("+") + gain.market1Gain;
    94     if (gain.market2Gain && gain.market2Owner == gain.traderOwner)
    95         gainString += translate("+") + gain.market2Gain;
    96 
    97     var tooltip = sprintf(translate("%(gain)s (%(player)s)"), {
    98         gain: gainString,
    99         player: (!g_IsNetworked && gain.traderOwner == playerID) ? translate("You") : simState.players[gain.traderOwner].name
    100     });
    101    
    102     if (gain.market1Gain && gain.market1Owner != gain.traderOwner)
    103         tooltip += translateWithContext("Separation mark in an enumeration", ", ") + sprintf(translate("%(gain)s (%(player)s)"), {
    104             gain: gain.market1Gain,
    105             player: (!g_IsNetworked && gain.market1Owner == playerID) ? translate("You") : simState.players[gain.market1Owner].name
    106         });
    107     if (gain.market2Gain && gain.market2Owner != gain.traderOwner)
    108         tooltip += translateWithContext("Separation mark in an enumeration", ", ") + sprintf(translate("%(gain)s (%(player)s)"), {
    109             gain: gain.market2Gain,
    110             player: (!g_IsNetworked && gain.market2Owner == playerID) ? translate("You") : simState.players[gain.market2Owner].name
    111         });
    112 
     91/**
     92 * Returns a message with the details of the trade income.
     93 */
     94function getTradeIncomeTooltip(details)
     95{
     96    if (details.connections.length === 0)
     97        return translate("This market has no trade routes to other markets. Use traders to connect markets together and create wealth.");
     98    let avgStrength = 0.0;
     99    for (let connection in details.connections)
     100        avgStrength += Math.min(100, details.connections[connection][1]);
     101    avgStrength /= details.connections.length;
     102    let tooltip = sprintf("[font=\"sans-bold-13\"]" + translate("Trade income") + ":[/font] " + translate("%(tradeRate).1f") + " [font=\"sans-10\"]" + translate("resources/minute") + "[/font]" +
     103                        "\n[font=\"sans-bold-13\"]" + translate("Efficiency")   + ":[/font] " + translate("%(tradeEfficiency)u%%") +
     104                        "\n[font=\"sans-bold-13\"]" + translate("Number of trade routes") + ":[/font] " + translate("%(MarketConnection)s") +
     105                        "\n[font=\"sans-bold-13\"]" + translate("Average trade route effiency") + ":[/font] " + translate("%(ConnectionStrenght)s%%"),
     106                  { "tradeRate" : details.rate*60.0, "tradeEfficiency": details.rawRate*100,  "MarketConnection": details.connections.length, "ConnectionStrenght": avgStrength });
     107    tooltip += "[font=\"sans-10\"]\n\n" + translate("To increase your income, garrison workers until the efficiency is 100% and trade with more markets, or reinforce your current trade lines' strengths.") + "[/font]";
    113108    return tooltip;
    114109}
    115110
  • binaries/data/mods/public/gui/session/selection_panels.js

     
    947947                else
    948948                    data.carried[carrying.type] = carrying.amount;
    949949            }
    950 
    951             if (state.trader && state.trader.goods && state.trader.goods.amount)
    952             {
    953                 if (!data.carried)
    954                     data.carried = {};
    955                 var amount = state.trader.goods.amount;
    956                 var type = state.trader.goods.type;
    957                 var totalGain = amount.traderGain;
    958                 if (amount.market1Gain)
    959                     totalGain += amount.market1Gain;
    960                 if (amount.market2Gain)
    961                     totalGain += amount.market2Gain;
    962                 if (data.carried[type])
    963                     data.carried[type] += totalGain;
    964                 else
    965                     data.carried[type] = totalGain;
    966             }
    967950        }
    968951        return true;
    969952    },
  • binaries/data/mods/public/gui/session/selection_details.js

     
    175175        Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { "amount": carried.amount, "max": carried.max });
    176176        Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "";
    177177    }
    178     // Use the same indicators for traders
    179     else if (entState.trader && entState.trader.goods.amount)
    180     {
    181         Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
    182         Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
    183         Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+entState.trader.goods.type+".png";
    184         let totalGain = entState.trader.goods.amount.traderGain;
    185         if (entState.trader.goods.amount.market1Gain)
    186             totalGain += entState.trader.goods.amount.market1Gain;
    187         if (entState.trader.goods.amount.market2Gain)
    188             totalGain += entState.trader.goods.amount.market2Gain;
    189         Engine.GetGUIObjectByName("resourceCarryingText").caption = totalGain;
    190         Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(gain)s"), { "gain": getTradingTooltip(entState.trader.goods.amount) });
    191     }
     178    // Use the same indicators for traders
     179    else if (entState.trader && entState.trader.trading)
     180    {
     181        var nextMarket = GetEntityState(entState.trader.nextMarket);
     182        // find the connection if it exists.
     183        let connection = null;
     184        if (nextMarket && nextMarket.tradeIncome)
     185            connection = nextMarket.tradeIncome.connections.find(conn => conn[0] === entState.trader.originMarket);
     186
     187        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
     188        Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
     189        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/trade.png";
     190        Engine.GetGUIObjectByName("resourceCarryingText").caption = "";
     191        if (connection)
     192            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) });
     193        else
     194            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("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.");
     195    }
    192196    // And for number of workers
    193197    else if (entState.foundation && entState.visibility == "visible")
    194198    {
     
    206210        else
    207211            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
    208212    }
     213    // And for the trade income icon
     214    else if (entState.tradeIncome)
     215    {
     216        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
     217        Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
     218        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/trade.png";
     219        Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(eff)u%%  x%(nb)u"),
     220                                                                            { "eff": +entState.tradeIncome.rawRate*100,
     221                                                                              "nb": entState.tradeIncome.connections.length });
     222        Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = getTradeIncomeTooltip(entState.tradeIncome);
     223    }
    209224    else if (entState.repairable && entState.repairable.numBuilders > 0 && entState.visibility == "visible")
    210225    {
    211226        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
  • 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)
     66                           + Math.pow(Position.y - ConnectionPosition.y, 2));
     67    this.connections.get(market)["distance"] = distance;
     68
     69    // reset internationality, in case the owner changes: from our POV until a trader brings news, we are selling items from a faraway land.
     70    var owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     71    var ownerConnection = Engine.QueryInterface(market, IID_Ownership).GetOwner();
     72
     73    this.connections.get(market)["international"] = (owner != ownerConnection);
     74}
     75
     76TradeIncome.prototype.GetConnections = function()
     77{
     78    var ret = [];
     79    for (let conn of this.connections.keys())
     80        ret.push([conn, this.connections.get(conn).strength, this.connections.get(conn).distance, this.connections.get(conn).international]);
     81    return ret;
     82}
     83
     84TradeIncome.prototype.GetTradeIncomeRate = function()
     85{
     86    if (this.connections.size === 0)
     87        return 0;
     88
     89    // Calculate our connection bonuses.
     90    var connectionEffect = 0;
     91
     92    var owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     93
     94    for (let i of this.connections.keys())
     95    {
     96        let bonus = CalculateMarketConnectionBonusDirect(this.connections.get(i)["distance"], owner, this.connections.get(i)["international"]);
     97        connectionEffect += bonus * Math.min(1.0, this.connections.get(i)["strength"]/100.0);
     98    }
     99    return Math.sqrt(connectionEffect) * this.GetRawEfficiency();
     100}
     101
     102TradeIncome.prototype.GetRawEfficiency = function()
     103{
     104    if (!this.template.GarrisonEfficiency)
     105        return 1.0;
     106
     107    var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
     108    if (!cmpGarrisonHolder)
     109        return 1.0; // probably should not happen
     110
     111
     112    // Depending on how many units are garrisoned, modulate efficiency
     113    var capacity = (this.template.GarrisonEfficiency.MaxGarrison === undefined) ? cmpGarrisonHolder.GetCapacity() : +this.template.GarrisonEfficiency.MaxGarrison;
     114    var currentCount = (this.template.GarrisonEfficiency.Classes === undefined) ?
     115                          cmpGarrisonHolder.GetEntities().length : cmpGarrisonHolder.GetGarrisonedArcherCount(this.template.GarrisonEfficiency.Classes);
     116    return 0.2 + (currentCount / capacity) * 0.8;
     117}
     118
     119TradeIncome.prototype.GenerateResources = function()
     120{
     121    if (this.connections.size === 0)
     122        return;
     123
     124    var cmpPlayer = QueryOwnerInterface(this.entity);
     125    if (!cmpPlayer)
     126        return;
     127
     128    var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
     129
     130    var rate = this.GetTradeIncomeRate() * 5.0;
     131    var goods = cmpPlayer.GetTradingGoods();
     132
     133    for (let good in goods)
     134    {
     135        if (RESOURCES.indexOf(good) === -1)
     136            continue;
     137
     138        cmpPlayer.AddResource(good, rate * goods[good]/100.0);  // should add up to 100%
     139
     140        if (cmpStatisticsTracker)
     141            cmpStatisticsTracker.IncreaseTradeIncomeCounter(rate * goods[good]);
     142    }
     143
     144    for (let i of this.connections.keys())
     145    {
     146        this.connections.get(i)["strength"] -= 10;
     147        if (this.connections.get(i)["strength"] <= 0)
     148            this.connections.delete(i);
     149    }
     150};
     151
     152Engine.RegisterComponentType(IID_TradeIncome, "TradeIncome", TradeIncome);
  • binaries/data/mods/public/simulation/components/UnitAI.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
     
    52215221
    52225222    if (this.CheckTargetRange(currentMarket, IID_Trader))
    52235223    {
    5224         var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     5224        let cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
    52255225        cmpTrader.PerformTrade(currentMarket);
    5226         if (!cmpTrader.GetGain().traderGain)
    5227         {
    5228             this.StopTrading();
    5229             return;
    5230         }
    52315226
    52325227        if (this.order.data.route && this.order.data.route.length)
    52335228        {
  • 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        let 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/GuiInterface.js

     
    240240        "resourceCarrying": null,
    241241        "rotation": null,
    242242        "trader": null,
     243        "tradeIncome": null,
    243244        "unitAI": null,
    244245        "visibility": null,
    245246    };
     
    303304    let cmpTrader = Engine.QueryInterface(ent, IID_Trader);
    304305    if (cmpTrader)
    305306        ret.trader = {
    306             "goods": cmpTrader.GetGoods(),
    307             "requiredGoods": cmpTrader.GetRequiredGoods()
     307            "trading": cmpTrader.HasBothMarkets(),
     308            "nextMarket": cmpTrader.GetNextMarket(),
     309            "originMarket": cmpTrader.GetOriginMarket()
    308310        };
    309311
     312    var cmpTradeIncome = Engine.QueryInterface(ent, IID_TradeIncome);
     313    if (cmpTradeIncome)
     314        ret.tradeIncome = {
     315            "connections": cmpTradeIncome.GetConnections(),
     316            "rate": cmpTradeIncome.GetTradeIncomeRate(),
     317            "rawRate": cmpTradeIncome.GetRawEfficiency()
     318        };
     319   
    310320    let cmpFogging = Engine.QueryInterface(ent, IID_Fogging);
    311321    if (cmpFogging)
    312322    {
     
    16461656    if (!data.firstMarket || !data.secondMarket)
    16471657        return null;
    16481658
    1649     return CalculateTraderGain(data.firstMarket, data.secondMarket, data.template);
     1659    return CalculateMarketConnectionBonus(data.firstMarket, data.secondMarket, data.tradeOwner);
    16501660};
    16511661
    16521662GuiInterface.prototype.GetTradingDetails = function(player, data)
     
    16651675            "hasBothMarkets": cmpEntityTrader.HasBothMarkets()
    16661676        };
    16671677        if (cmpEntityTrader.HasBothMarkets())
    1668             result.gain = cmpEntityTrader.GetGain();
     1678            result.gain = CalculateMarketConnectionBonus(firstMarket, secondMarket, player)
    16691679    }
    16701680    else if (data.target === secondMarket)
    16711681    {
    16721682        result = {
    16731683            "type": "is second",
    1674             "gain": cmpEntityTrader.GetGain(),
     1684            "gain": CalculateMarketConnectionBonus(firstMarket, data.target, player)
    16751685        };
    16761686    }
    16771687    else if (!firstMarket)
     
    16821692    {
    16831693        result = {
    16841694            "type": "set second",
    1685             "gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
     1695            "gain": CalculateMarketConnectionBonus(firstMarket, data.target, player)
    16861696        };
    16871697    }
    16881698    else
  • binaries/data/mods/public/simulation/components/Looter.js

     
    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/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);
     
    1718    var firstMarketPosition = cmpFirstMarketPosition.GetPosition2D();
    1819    var secondMarketPosition = cmpSecondMarketPosition.GetPosition2D();
    1920
    20     // Calculate ordinary Euclidean distance between markets.
    21     // We don't use pathfinder, because ordinary distance looks more fair.
    22     var distance = firstMarketPosition.distanceTo(secondMarketPosition);
    23     // We calculate gain as square of distance to encourage trading between remote markets
    24     gain.traderGain = Math.pow(distance * DISTANCE_FACTOR, 2);
    25     if (template && template.GainMultiplier)
    26     {
    27         if (trader)
    28             gain.traderGain *= ApplyValueModificationsToEntity("Trader/GainMultiplier", +template.GainMultiplier, trader);
    29         else    // called from the gui with modifications already applied
    30             gain.traderGain *= template.GainMultiplier;
    31     }
    32     // If trader undefined, the trader owner is supposed to be the same as the first market
    33     var cmpOwnership = trader ? Engine.QueryInterface(trader, IID_Ownership) : Engine.QueryInterface(firstMarket, IID_Ownership);
    34     if (!cmpOwnership)
    35         return null;
    36     gain.traderOwner = cmpOwnership.GetOwner();
    37 
    38     // If markets belong to different players, add gain from international trading
    39     var ownerFirstMarket = Engine.QueryInterface(firstMarket, IID_Ownership).GetOwner();
    40     var ownerSecondMarket = Engine.QueryInterface(secondMarket, IID_Ownership).GetOwner();
    41     if (ownerFirstMarket != ownerSecondMarket)
    42     {
    43         gain.market1Gain = gain.traderGain * ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_ADDITION, firstMarket) / 100;
    44         gain.market1Owner = ownerFirstMarket;
    45         gain.market2Gain = gain.traderGain * ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_ADDITION, secondMarket) / 100;
    46         gain.market2Owner = ownerSecondMarket;
    47     }
    48 
    49     // Add potential trade multipliers and roundings
    50     var cmpPlayer = trader ? QueryOwnerInterface(trader) : QueryOwnerInterface(firstMarket);
     21   
     22      // Calculate ordinary Euclidean distance between markets.
     23    // We don't use pathfinder, because ordinary distance looks more fair.
     24    var distance = Math.sqrt(Math.pow(firstMarketPosition.x - secondMarketPosition.x, 2) + Math.pow(firstMarketPosition.y - secondMarketPosition.y, 2));
     25 
     26    // If markets belong to different players, add gain from international trading
     27    var ownerFirstMarket = Engine.QueryInterface(firstMarket, IID_Ownership).GetOwner();
     28    var ownerSecondMarket = Engine.QueryInterface(secondMarket, IID_Ownership).GetOwner();
     29    if (ownerFirstMarket != ownerSecondMarket)
     30        return CalculateMarketConnectionBonusDirect(distance, tradeOwner, true);
     31   
     32    return CalculateMarketConnectionBonusDirect(distance, tradeOwner);
     33}
     34 
     35function CalculateMarketConnectionBonusDirect(distance, tradeOwner, international)
     36{
     37    var bonus = Math.log(distance*DISTANCE_FACTOR);
     38    var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     39    var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(tradeOwner), IID_Player);
    5140    if (cmpPlayer)
    52         gain.traderGain *= cmpPlayer.GetTradeRateMultiplier();
    53     gain.traderGain = Math.round(gain.traderGain);
     41        bonus *= cmpPlayer.GetTradeRateMultiplier();
    5442
    55     if (ownerFirstMarket != ownerSecondMarket)
    56     {
    57         if ((cmpPlayer = QueryOwnerInterface(firstMarket)))
    58             gain.market1Gain *= cmpPlayer.GetTradeRateMultiplier();
    59         gain.market1Gain = Math.round(gain.market1Gain);
    60 
    61         if ((cmpPlayer = QueryOwnerInterface(secondMarket)))
    62             gain.market2Gain *= cmpPlayer.GetTradeRateMultiplier();
    63         gain.market2Gain = Math.round(gain.market2Gain);
    64     }
    65 
    66     return gain;
     43    if (international)
     44        bonus *= +ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_MULTIPLICATION, tradeOwner);
     45    return Math.max(0, bonus);
    6746}
    68 
    69 Engine.RegisterGlobal("CalculateTraderGain", CalculateTraderGain);
     47 
     48Engine.RegisterGlobal("CalculateMarketConnectionBonusDirect", CalculateMarketConnectionBonusDirect);
     49Engine.RegisterGlobal("CalculateMarketConnectionBonus", CalculateMarketConnectionBonus);
  • 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/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_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_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_unit_support_trader.xml

     
    1818    <Tooltip>Trade resources between your own markets and those of your allies.</Tooltip>
    1919    <Formations disable=""/>
    2020  </Identity>
     21  <Loot>
     22    <xp>10</xp>
     23    <food>25</food>
     24    <wood>25</wood>
     25    <stone>15</stone>
     26    <metal>15</metal>
     27  </Loot>
    2128  <Sound>
    2229    <SoundGroups>
    2330      <select>voice/{lang}/civ/civ_{gender}_select.xml</select>
     
    3946    </SoundGroups>
    4047  </Sound>
    4148  <Trader>
    42     <GainMultiplier>1.0</GainMultiplier>
     49    <ConnectionStrength>50</ConnectionStrength>
    4350  </Trader>
    4451  <UnitAI>
    4552    <CanGuard>false</CanGuard>
  • 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/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/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/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>
  • 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>