Ticket #3697: trade-v2.4.patch

File trade-v2.4.patch, 49.5 KB (added by elexis, 8 years ago)

Rebased, style fixes, Ally -> MutualAlly, string changes, conn[0] and similar error fixed, whitespace fixes, remove unused bonus, missing semicolons, removed water-patch, let instead of var, typo "Strenght"

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

    function GetTemplateDataHelper(template,  
    243243            "walk": func("UnitMotion/WalkSpeed", +template.UnitMotion.WalkSpeed, player, template),
    244244        };
    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    {
    258251        ret.wallSet = {
    259252            "templates": {
  • binaries/data/mods/public/gui/session/selection_details.js

    function displaySingle(entState)  
    174174        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+carried.type+".png";
    175175        Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { "amount": carried.amount, "max": carried.max });
    176176        Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "";
    177177    }
    178178    // Use the same indicators for traders
    179     else if (entState.trader && entState.trader.goods.amount)
     179    else if (entState.trader && entState.trader.trading)
    180180    {
     181        var nextMarket = GetEntityState(entState.trader.nextMarket);
     182        // Find the connection if it exists.
     183        let connection = null;
     184
     185        if (nextMarket && nextMarket.tradeIncome)
     186            connection = nextMarket.tradeIncome.connections.find(conn => conn.marketID === entState.trader.originMarket);
     187
    181188        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
    182189        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) });
     190        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/trade.png";
     191        Engine.GetGUIObjectByName("resourceCarryingText").caption = "";
     192        if (connection)
     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.strength, 100) });
     194        else
     195            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.");
    191196    }
    192197    // And for number of workers
    193198    else if (entState.foundation && entState.visibility == "visible")
    194199    {
    195200        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
    function displaySingle(entState)  
    204209            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translatePlural("Number of builders.\nTasking another to this foundation would speed construction up by %(speedup)s second.", "Number of builders.\nTasking another to this foundation would speed construction up by %(speedup)s seconds.", timeSpeedup), { "speedup": timeSpeedup });
    205210        }
    206211        else
    207212            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
    208213    }
     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        Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(MarketEfficiency)u%%  x%(NumberOfConnections)u"),
     221                                                                            { "MarketEfficiency": +entState.tradeIncome.rawRate*100,
     222                                                                              "NumberOfConnections": entState.tradeIncome.connections.length });
     223        Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = getTradeIncomeTooltip(entState.tradeIncome);
     224    }
    209225    else if (entState.repairable && entState.repairable.numBuilders > 0 && entState.visibility == "visible")
    210226    {
    211227        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
    212228        Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
    213229        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
  • binaries/data/mods/public/gui/session/selection_panels.js

    g_SelectionPanels.Selection = {  
    949949                if (data.carried[carrying.type])
    950950                    data.carried[carrying.type] += carrying.amount;
    951951                else
    952952                    data.carried[carrying.type] = carrying.amount;
    953953            }
    954 
    955             if (state.trader && state.trader.goods && state.trader.goods.amount)
    956             {
    957                 if (!data.carried)
    958                     data.carried = {};
    959                 var amount = state.trader.goods.amount;
    960                 var type = state.trader.goods.type;
    961                 var totalGain = amount.traderGain;
    962                 if (amount.market1Gain)
    963                     totalGain += amount.market1Gain;
    964                 if (amount.market2Gain)
    965                     totalGain += amount.market2Gain;
    966                 if (data.carried[type])
    967                     data.carried[type] += totalGain;
    968                 else
    969                     data.carried[type] = totalGain;
    970             }
    971954        }
    972955        return true;
    973956    },
    974957    "setTooltip": function(data)
    975958    {
  • binaries/data/mods/public/gui/session/unit_actions.js

    var unitActions =  
    318318            switch (tradingDetails.type)
    319319            {
    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    },
    357354
    358355    "garrison":
    var unitActions =  
    498495                data.command = "attack-walk";
    499496                data.targetClasses = targetClasses;
    500497                cursor = "action-attack-move";
    501498            }
    502499
    503             if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "MutualAlly"]))
     500            if (hasClass(entState, "Market") && hasClass(targetState, "Market") && entState.id != targetState.id &&
     501                    (!hasClass(entState, "NavalMarket") || hasClass(targetState, "NavalMarket")) && playerCheck(entState, targetState, ["MutualAlly"]))
     502            {
     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", "MutualAlly"]))
    504521            {
    505522                data.command = "garrison";
    506523                data.target = targetState.id;
    507524                cursor = "action-garrison";
    508525                tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
    var unitActions =  
    521538                    cursor = "action-gather-" + resourceType.specific;
    522539                data.command = "gather";
    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";
    554546                data.target = targetState.id;
    555547                cursor = "action-build";
  • binaries/data/mods/public/gui/session/utility_functions.js

    function getRankIconSprite(entState)  
    7070}
    7171
    7272/**
    7373 * Returns a message with the details of the trade gain.
    7474 */
    75 function getTradingTooltip(gain)
     75function getTradeRouteTooltip(gain)
    7676{
     77    return translate("This trader is currently trading between two markets, creating wealth for his empire.") + "\n" +
     78           sprintf(translate("The basic value of this trade route is %(value).1f."), { "value": gain });
     79}
     80
     81/**
     82 * Returns a message with the details of the trade income.
     83 */
     84function getTradeIncomeTooltip(details)
     85{
     86    if (details.connections.length === 0)
     87        return translate("This market has no trade routes. Use traders to connect markets and create wealth.");
     88
     89    let avgStrength = 0.0;
     90    for (let connection in details.connections)
     91        avgStrength += Math.min(100, details.connections[connection].strength);
     92    avgStrength /= details.connections.length;
     93
     94    let tooltip = "[font=\"sans-bold-13\"]" + translate("Trade income") + ":[/font] " +
     95        sprintf(translate("%(tradeRate).1f"), { "tradeRate": details.rate * 60.0 }) +
     96        " [font=\"sans-10\"]" + translate("resources/minute") + "[/font]\n";
     97
     98    tooltip += "[font=\"sans-bold-13\"]" + translate("Efficiency") + ":[/font] " +
     99        sprintf(translate("%(tradeEfficiency)u%%"), { "tradeEfficiency": details.rawRate * 100 }) + "\n";
     100
     101    tooltip += "[font=\"sans-bold-13\"]" + translate("Number of trade routes") + ":[/font] " +
     102        sprintf(translateWithContext("trade", "%(connections)s"), { "connections": details.connections.length }) + "\n";
     103
     104    tooltip += "[font=\"sans-bold-13\"]" + translate("Average trade route effiency") + ":[/font] " +
     105        sprintf(translateWithContext("trade", "%(connectionStrength)s%%"), { "connectionStrength": avgStrength });
    77106
    78     var playerID = Engine.GetPlayerID();
    79     var simState = GetSimState();
    80    
    81     var gainString = gain.traderGain;
    82     if (gain.market1Gain && gain.market1Owner == gain.traderOwner)
    83         gainString += translate("+") + gain.market1Gain;
    84     if (gain.market2Gain && gain.market2Owner == gain.traderOwner)
    85         gainString += translate("+") + gain.market2Gain;
    86 
    87     var tooltip = sprintf(translate("%(gain)s (%(player)s)"), {
    88         gain: gainString,
    89         player: (!g_IsNetworked && gain.traderOwner == playerID) ? translate("You") : simState.players[gain.traderOwner].name
    90     });
    91    
    92     if (gain.market1Gain && gain.market1Owner != gain.traderOwner)
    93         tooltip += translateWithContext("Separation mark in an enumeration", ", ") + sprintf(translate("%(gain)s (%(player)s)"), {
    94             gain: gain.market1Gain,
    95             player: (!g_IsNetworked && gain.market1Owner == playerID) ? translate("You") : simState.players[gain.market1Owner].name
    96         });
    97     if (gain.market2Gain && gain.market2Owner != gain.traderOwner)
    98         tooltip += translateWithContext("Separation mark in an enumeration", ", ") + sprintf(translate("%(gain)s (%(player)s)"), {
    99             gain: gain.market2Gain,
    100             player: (!g_IsNetworked && gain.market2Owner == playerID) ? translate("You") : simState.players[gain.market2Owner].name
    101         });
     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]";
    102108
    103109    return tooltip;
    104110}
    105111
    106112/**
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    GuiInterface.prototype.GetEntityState =  
    240240        "production": null,
    241241        "rallyPoint": null,
    242242        "resourceCarrying": null,
    243243        "rotation": null,
    244244        "trader": null,
     245        "tradeIncome": null,
    245246        "unitAI": null,
    246247        "visibility": null,
    247248    };
    248249
    249250    let cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
    GuiInterface.prototype.GetEntityState =  
    303304        };
    304305
    305306    let cmpTrader = Engine.QueryInterface(ent, IID_Trader);
    306307    if (cmpTrader)
    307308        ret.trader = {
    308             "goods": cmpTrader.GetGoods(),
    309             "requiredGoods": cmpTrader.GetRequiredGoods()
     309            "trading": cmpTrader.HasBothMarkets(),
     310            "nextMarket": cmpTrader.GetNextMarket(),
     311            "originMarket": cmpTrader.GetOriginMarket()
     312        };
     313
     314    let cmpTradeIncome = Engine.QueryInterface(ent, IID_TradeIncome);
     315    if (cmpTradeIncome)
     316        ret.tradeIncome = {
     317            "connections": cmpTradeIncome.GetConnections(),
     318            "rate": cmpTradeIncome.GetTradeIncomeRate(),
     319            "rawRate": cmpTradeIncome.GetRawEfficiency()
    310320        };
    311321
    312322    let cmpFogging = Engine.QueryInterface(ent, IID_Fogging);
    313323    if (cmpFogging)
    314324    {
    GuiInterface.prototype.FindIdleUnits = f  
    16461656GuiInterface.prototype.GetTradingRouteGain = function(player, data)
    16471657{
    16481658    if (!data.firstMarket || !data.secondMarket)
    16491659        return null;
    16501660
    1651     return CalculateTraderGain(data.firstMarket, data.secondMarket, data.template);
     1661    return CalculateMarketConnectionBonus(data.firstMarket, data.secondMarket, data.tradeOwner);
    16521662};
    16531663
    16541664GuiInterface.prototype.GetTradingDetails = function(player, data)
    16551665{
    16561666    let cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader);
    GuiInterface.prototype.GetTradingDetails  
    16651675        result = {
    16661676            "type": "is first",
    16671677            "hasBothMarkets": cmpEntityTrader.HasBothMarkets()
    16681678        };
    16691679        if (cmpEntityTrader.HasBothMarkets())
    1670             result.gain = cmpEntityTrader.GetGain();
     1680            result.gain = CalculateMarketConnectionBonus(firstMarket, secondMarket, player)
    16711681    }
    16721682    else if (data.target === secondMarket)
    16731683    {
    16741684        result = {
    16751685            "type": "is second",
    1676             "gain": cmpEntityTrader.GetGain(),
     1686            "gain": CalculateMarketConnectionBonus(firstMarket, data.target, player)
    16771687        };
    16781688    }
    16791689    else if (!firstMarket)
    16801690    {
    16811691        result = { "type": "set first" };
    16821692    }
    16831693    else if (!secondMarket)
    16841694    {
    16851695        result = {
    16861696            "type": "set second",
    1687             "gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
     1697            "gain": CalculateMarketConnectionBonus(firstMarket, data.target, player)
    16881698        };
    16891699    }
    16901700    else
    16911701    {
    16921702        // Else both markets are not null and target is different from them
  • binaries/data/mods/public/simulation/components/Looter.js

    Looter.prototype.Collect = function(targ  
    3434    var cmpResourceGatherer = Engine.QueryInterface(targetEntity, IID_ResourceGatherer);
    3535    if (cmpResourceGatherer)
    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);
    5642
    5743    // Update statistics
  • 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    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     38    cmpTimer.SetInterval(this.entity, IID_TradeIncome, "GenerateResources", 5000, 5000, undefined);
     39
     40    // Quick sanity check
     41    let 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    let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     59    let cmpConnectionPosition = Engine.QueryInterface(market, IID_Position);
     60    if (!cmpPosition || !cmpPosition.IsInWorld() || !cmpConnectionPosition || !cmpConnectionPosition.IsInWorld())
     61        return;
     62
     63    let Position = cmpPosition.GetPosition2D();
     64    let ConnectionPosition = cmpConnectionPosition.GetPosition2D();
     65    let 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    let owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     71    let ownerConnection = Engine.QueryInterface(market, IID_Ownership).GetOwner();
     72
     73    this.connections.get(market).international = owner != ownerConnection;
     74};
     75
     76TradeIncome.prototype.GetConnections = function()
     77{
     78    let ret = [];
     79    for (let conn of this.connections.keys())
     80        ret.push(
     81            {
     82                "marketID": conn,
     83                "strength": this.connections.get(conn).strength,
     84                "distance": this.connections.get(conn).distance,
     85                "international": this.connections.get(conn).international
     86            });
     87    return ret;
     88};
     89
     90TradeIncome.prototype.GetTradeIncomeRate = function()
     91{
     92    if (this.connections.size === 0)
     93        return 0;
     94
     95    // Calculate our connection bonuses
     96    let connectionEffect = 0;
     97
     98    let owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     99
     100    for (let i of this.connections.keys())
     101    {
     102        let bonus = CalculateMarketConnectionBonusDirect(this.connections.get(i).distance, owner, this.connections.get(i).international);
     103        connectionEffect += bonus * Math.min(1.0, this.connections.get(i).strength/100.0);
     104    }
     105    return Math.sqrt(connectionEffect) * this.GetRawEfficiency();
     106};
     107
     108TradeIncome.prototype.GetRawEfficiency = function()
     109{
     110    if (!this.template.GarrisonEfficiency)
     111        return 1.0;
     112
     113    let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
     114    if (!cmpGarrisonHolder)
     115        return 1.0;
     116
     117    // Depending on how many units are garrisoned, modulate efficiency
     118    let capacity = this.template.GarrisonEfficiency.MaxGarrison === undefined ? cmpGarrisonHolder.GetCapacity() : +this.template.GarrisonEfficiency.MaxGarrison;
     119    let currentCount = this.template.GarrisonEfficiency.Classes === undefined ?
     120                          cmpGarrisonHolder.GetEntities().length : cmpGarrisonHolder.GetGarrisonedArcherCount(this.template.GarrisonEfficiency.Classes);
     121    return 0.2 + (currentCount / capacity) * 0.8;
     122};
     123
     124TradeIncome.prototype.GenerateResources = function()
     125{
     126    if (this.connections.size === 0)
     127        return;
     128
     129    let cmpPlayer = QueryOwnerInterface(this.entity);
     130    if (!cmpPlayer)
     131        return;
     132
     133    let cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
     134
     135    let rate = this.GetTradeIncomeRate() * 5;
     136    let goods = cmpPlayer.GetTradingGoods();
     137
     138    for (let good in goods)
     139    {
     140        if (RESOURCES.indexOf(good) === -1)
     141            continue;
     142
     143        cmpPlayer.AddResource(good, rate * goods[good]/100.0);  // Should add up to 100%
     144
     145        if (cmpStatisticsTracker)
     146            cmpStatisticsTracker.IncreaseTradeIncomeCounter(rate * goods[good]);
     147    }
     148
     149    for (let i of this.connections.keys())
     150    {
     151        this.connections.get(i).strength -= 10;
     152        if (this.connections.get(i).strength <= 0)
     153            this.connections.delete(i);
     154    }
     155};
     156
     157Engine.RegisterComponentType(IID_TradeIncome, "TradeIncome", TradeIncome);
  • 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
    104function Trader() {}
    115
    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
    2215Trader.prototype.Init = function()
    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;
    30     // Currently carried goods
    31     this.goods = { "type": null, "amount": null, "origin": null };
    32 };
    33 
    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 };
    6919
    70 Trader.prototype.GetGain = function()
    71 {
    72     return this.gain;
     20    // Currently carried goods
     21    this.goodsOrigin = null;
    7322};
    7423
    7524// Set target as target market.
    7625// Return true if at least one of markets was changed.
    7726Trader.prototype.SetTargetMarket = function(target, source)
    Trader.prototype.SetTargetMarket = funct  
    10857        // If we have only one market and target is different from it,
    10958        // set the target as second one
    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    {
    12066        // Else we don't have target markets at all,
    12167        // set the target as first market
    12268        this.firstMarket = target;
    12369    }
    12470    if (marketsChanged)
    12571    {
    12672        // Drop carried goods
    127         this.goods.amount = null;
     73        this.goodsOrigin = null;
    12874    }
    12975    return marketsChanged;
    13076};
    13177
    13278Trader.prototype.GetFirstMarket = function()
    Trader.prototype.GetSecondMarket = funct  
    14288Trader.prototype.HasBothMarkets = function()
    14389{
    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);
    16496    var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity);
    16597    // Check that the target exists
    Trader.prototype.CanTrade = function(tar  
    184116    return true;
    185117};
    186118
    187119Trader.prototype.PerformTrade = function(currentMarket)
    188120{
    189     if (this.goods.amount && this.goods.amount.traderGain)
    190     {
    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         }
    220     }
    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)
     121    if (this.goodsOrigin && this.goodsOrigin !== currentMarket)
    228122    {
    229         var cmpPlayer = QueryOwnerInterface(this.entity);
    230         if (cmpPlayer)
    231             nextGoods = cmpPlayer.GetNextTradingGoods();
    232 
    233         if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
    234             nextGoods = "metal";
     123        let cmpTradeIncome = Engine.QueryInterface(currentMarket, IID_TradeIncome);
     124        if (cmpTradeIncome)
     125            cmpTradeIncome.AddConnectionWithMarket(this.goodsOrigin, this.template.ConnectionStrength);
    235126    }
    236     this.goods.type = nextGoods;
    237     this.goods.amount = this.CalculateGain(this.firstMarket, this.secondMarket);
    238     this.goods.origin = currentMarket;
    239 };
    240 
    241 Trader.prototype.GetGoods = function()
    242 {
    243     return this.goods;
     127    this.goodsOrigin = +currentMarket;
    244128};
    245129
    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;
    263152};
    264153
    Trader.prototype.GetRange = function()  
    272161    if (cmpObstruction)
    273162        max += cmpObstruction.GetUnitRadius()*1.5;
    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/UnitAI.js

    UnitAI.prototype.PerformTradeAndMoveToNe  
    52385238        return;
    52395239    }
    52405240
    52415241    if (this.CheckTargetRange(currentMarket, IID_Trader))
    52425242    {
    5243         var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     5243        let cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
    52445244        cmpTrader.PerformTrade(currentMarket);
    5245         if (!cmpTrader.GetGain().traderGain)
    5246         {
    5247             this.StopTrading();
    5248             return;
    5249         }
    52505245
    52515246        if (this.order.data.route && this.order.data.route.length)
    52525247        {
    52535248            this.waypoints = this.order.data.route.slice();
    52545249            if (nextFsmStateName == "APPROACHINGSECONDMARKET")
  • binaries/data/mods/public/simulation/components/interfaces/TradeIncome.js

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

     
    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 
    12     var cmpFirstMarketPosition = Engine.QueryInterface(firstMarket, IID_Position);
    13     var cmpSecondMarketPosition = Engine.QueryInterface(secondMarket, IID_Position);
     11    let cmpFirstMarketPosition = Engine.QueryInterface(firstMarket, IID_Position);
     12    let cmpSecondMarketPosition = Engine.QueryInterface(secondMarket, IID_Position);
    1413    if (!cmpFirstMarketPosition || !cmpFirstMarketPosition.IsInWorld() ||
    15         !cmpSecondMarketPosition || !cmpSecondMarketPosition.IsInWorld())
     14        !cmpSecondMarketPosition || !cmpSecondMarketPosition.IsInWorld())
    1615        return null;
    17     var firstMarketPosition = cmpFirstMarketPosition.GetPosition2D();
    18     var secondMarketPosition = cmpSecondMarketPosition.GetPosition2D();
     16    let firstMarketPosition = cmpFirstMarketPosition.GetPosition2D();
     17    let secondMarketPosition = cmpSecondMarketPosition.GetPosition2D();
    1918
    2019    // Calculate ordinary Euclidean distance between markets.
    2120    // 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();
     21    let distance = Math.sqrt(Math.pow(firstMarketPosition.x - secondMarketPosition.x, 2) +
     22                             Math.pow(firstMarketPosition.y - secondMarketPosition.y, 2));
    3723
    3824    // 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     }
     25    let ownerFirstMarket = Engine.QueryInterface(firstMarket, IID_Ownership).GetOwner();
     26    let ownerSecondMarket = Engine.QueryInterface(secondMarket, IID_Ownership).GetOwner();
    4827
    49     // Add potential trade multipliers and roundings
    50     var cmpPlayer = trader ? QueryOwnerInterface(trader) : QueryOwnerInterface(firstMarket);
    51     if (cmpPlayer)
    52         gain.traderGain *= cmpPlayer.GetTradeRateMultiplier();
    53     gain.traderGain = Math.round(gain.traderGain);
     28    return CalculateMarketConnectionBonusDirect(distance, tradeOwner, ownerFirstMarket != ownerSecondMarket);
     29}
    5430
    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     }
     31function CalculateMarketConnectionBonusDirect(distance, tradeOwner, international)
     32{
     33    let bonus = Math.log(distance * DISTANCE_FACTOR);
     34    let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     35    let cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(tradeOwner), IID_Player);
     36    if (cmpPlayer)
     37        bonus *= cmpPlayer.GetTradeRateMultiplier();
    6538
    66     return gain;
     39    if (international)
     40        bonus *= +ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_MULTIPLICATION, tradeOwner);
     41    return Math.max(0, bonus);
    6742}
    6843
    69 Engine.RegisterGlobal("CalculateTraderGain", CalculateTraderGain);
     44Engine.RegisterGlobal("CalculateMarketConnectionBonusDirect", CalculateMarketConnectionBonusDirect);
     45Engine.RegisterGlobal("CalculateMarketConnectionBonus", CalculateMarketConnectionBonus);
  • binaries/data/mods/public/simulation/templates/special/player.xml

     
    2828      <Theater>1</Theater>
    2929      <Council>1</Council>
    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>
    3842      <Pillar>
    3943        <Ashoka>5</Ashoka>
  • binaries/data/mods/public/simulation/templates/structures/cart_dock.xml

     
    2525  <ProductionQueue>
    2626    <Technologies datatype="tokens">
    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>
    3336  </VisualActor>
    3437</Entity>
  • binaries/data/mods/public/simulation/templates/structures/pers_market.xml

     
    1313    <History>Traders from all distant parts of the huge empire met, exchanged and sold goods in the huge bazaars present in almost every big city. Babylon and Susa were the largest and most frequented trade centers.</History>
    1414  </Identity>
    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>
    2124  </VisualActor>
    2225</Entity>
  • binaries/data/mods/public/simulation/templates/template_formation.xml

     
    3838  <UnitMotion>
    3939    <FormationController>true</FormationController>
    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_economic_market.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    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>
    812    <Resources>
    913      <wood>300</wood>
     
    1115  </Cost>
    1216  <Footprint>
    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>
    1931  </Health>
    2032  <Identity>
     
    4557  <TerritoryInfluence>
    4658    <Root>false</Root>
    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">
    5371      unlock_shared_los
    5472      trade_convoys_speed
  • binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml

     
    6969      armor_ship_reinforcedhull
    7070      armor_ship_hypozomata
    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>
    7780  <VisualActor>
    7881    <FoundationActor>structures/fndn_4x4_dock.xml</FoundationActor>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml

     
    4343    <BarWidth>6.0</BarWidth>
    4444    <BarHeight>0.5</BarHeight>
    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>
    5252    <CanGuard>false</CanGuard>
    5353  </UnitAI>
  • binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml

     
    1616    <GenericName>Trader</GenericName>
    1717    <History>Trade was a very important part of ancient civilization - effective trading and control of trade routes equaled wealth. Trade took place by many forms from foot to caravans to merchant ships. One of the most notorious examples of the power of trade was the Silk Road.</History>
    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>
    2431      <order_trade>voice/{lang}/civ/civ_{gender}_trade.xml</order_trade>
    2532      <order_walk>voice/{lang}/civ/civ_{gender}_walk.xml</order_walk>
     
    3744      <gather_rock>resource/mining/pickaxe.xml</gather_rock>
    3845      <gather_ore>resource/mining/mining.xml</gather_ore>
    3946    </SoundGroups>
    4047  </Sound>
    4148  <Trader>
    42     <GainMultiplier>1.0</GainMultiplier>
     49    <ConnectionStrength>50</ConnectionStrength>
    4350  </Trader>
    4451  <UnitAI>
    4552    <CanGuard>false</CanGuard>
    4653  </UnitAI>
    4754  <UnitMotion>
  • binaries/data/mods/public/simulation/templates/units/cart_ship_merchant.xml

     
    1010    <History>The entire purpose of the Phoenicians/Carthaginians was to conduct trade and commerce principally upon the sea (though not limited to that as far as Carthage was concerned). </History>
    1111    <Tooltip>Trade between docks. Garrison a Trader aboard for additional profit (+20% for each garrisoned). Gather profitable aquatic treasures. Carthaginians have +25% sea trading bonus.</Tooltip>
    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>
    2118</Entity>
  • binaries/data/mods/public/simulation/templates/units/pers_support_trader.xml

     
    1818      <select>actor/fauna/animal/camel.xml</select>
    1919      <order_walk>actor/fauna/movement/camel.xml</order_walk>
    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>
    2926</Entity>