Ticket #1207: 2+-way-trade.patch

File 2+-way-trade.patch, 14.3 KB (added by leper, 11 years ago)

Trade with more than two markets (WIP missing code to display proper stuff in the gui)

  • binaries/data/mods/public/gui/session/input.js

     
    13681368        return true;
    13691369
    13701370    case "setup-trade-route":
    1371         Engine.PostNetworkCommand({"type": "setup-trade-route", "entities": selection, "target": action.target});
     1371        Engine.PostNetworkCommand({"type": "setup-trade-route", "entities": selection, "target": action.target, "queued": queued});
    13721372        Engine.GuiInterfaceCall("PlaySound", { "name": "order_trade", "entity": selection[0] });
    13731373        return true;
    13741374
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    16081608
    16091609GuiInterface.prototype.GetTradingRouteGain = function(player, data)
    16101610{
     1611    // TODO
    16111612    if (!data.firstMarket || !data.secondMarket)
    16121613        return null;
    16131614
     
    16161617
    16171618GuiInterface.prototype.GetTradingDetails = function(player, data)
    16181619{
     1620    // TODO
     1621   
    16191622    var cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader);
    16201623    if (!cmpEntityTrader || !cmpEntityTrader.CanTrade(data.target))
    16211624        return null;
    1622     var firstMarket = cmpEntityTrader.GetFirstMarket();
     1625    /*var firstMarket = cmpEntityTrader.GetFirstMarket();
    16231626    var secondMarket = cmpEntityTrader.GetSecondMarket();
    1624     var result = null;
     1627    */var result = null;/*
    16251628    if (data.target === firstMarket)
    16261629    {
    16271630        result = {
     
    16521655            "goods": cmpEntityTrader.GetPreferredGoods()
    16531656        };
    16541657    }
    1655     else
     1658    else*/
    16561659    {
    16571660        // Else both markets are not null and target is different from them
    16581661        result = {"type": "set first"};
  • binaries/data/mods/public/simulation/components/Trader.js

     
    2424
    2525Trader.prototype.Init = function()
    2626{
    27     this.firstMarket = INVALID_ENTITY;
    28     this.secondMarket = INVALID_ENTITY;
     27    this.markets = [];
     28
    2929    // Gain from one pass between markets
    30     this.gain = null;
     30    this.gain = []; // from the current to the next
     31   
     32    this.pos = 0; // Current market
     33   
    3134    // Selected resource for trading
    3235    this.preferredGoods = "metal";
    3336    // Currently carried goods
    3437    this.goods = { "type": null, "amount": 0 };
    35 }
     38};
    3639
    3740Trader.prototype.CalculateGain = function(firstMarket, secondMarket, template)
    3841{
     
    5760    }
    5861   
    5962    return garrisonMultiplier * CalculateTraderGain(firstMarket, secondMarket, this.template);
    60 }
     63};
    6164
    6265Trader.prototype.GetGain = function()
    6366{
    64     return this.gain;
    65 }
     67    return this.gain[pos];
     68};
    6669
    67 // Set target as target market.
    68 // Return true if at least one of markets was changed.
    69 Trader.prototype.SetTargetMarket = function(target, source)
     70// Add a market.
     71// Returns true if adding succeeded
     72Trader.prototype.AddMarket = function(target)
    7073{
    7174    // Check that target is a market
    72     var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity);
    73     if (!cmpTargetIdentity)
     75    var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
     76    if (!cmpIdentity)
    7477        return false;
    75     if (!cmpTargetIdentity.HasClass("Market") && !cmpTargetIdentity.HasClass("NavalMarket"))
     78    if (!cmpIdentity.HasClass("Market") && !cmpIdentity.HasClass("NavalMarket"))
    7679        return false;
    77     var marketsChanged = true;
    78     if (source)
    79     {
    80         // Establish a trade route with both markets in one go.
    81         cmpTargetIdentity = Engine.QueryInterface(source, IID_Identity);
    82         if (!cmpTargetIdentity)
    83             return false;
    84         if (!cmpTargetIdentity.HasClass("Market") && !cmpTargetIdentity.HasClass("NavalMarket"))
    85             return false;
    86 
    87         this.firstMarket = source;
    88         this.secondMarket = INVALID_ENTITY;
    89     }
    9080
    91     if (this.secondMarket)
    92     {
    93         // If we already have both markets - drop them
    94         // and use the target as first market
    95         this.firstMarket = target;
    96         this.secondMarket = INVALID_ENTITY;
    97     }
    98     else if (this.firstMarket)
     81    // Insert the market at the end of our current tour (right before
     82    this.markets.splice(this.pos-1, 0, target);
     83
     84    if (this.markets.length <=1)
    9985    {
    100         // If we have only one market and target is different from it,
    101         // set the target as second one
    102         if (target == this.firstMarket)
    103             marketsChanged = false;
    104         else
    105         {
    106             this.secondMarket = target;
    107             this.gain = this.CalculateGain(this.firstMarket, this.secondMarket);
    108         }
     86        this.gain.push(0);
    10987    }
    110     else
     88    else if (this.markets.length <= 2)
    11189    {
    112         // Else we don't have target markets at all,
    113         // set the target as first market
    114         this.firstMarket = target;
     90        this.gain[0] = this.CalculateGain(this.markets[0], this.markets[1]);
     91        this.gain.push(this.gain[0]);
    11592    }
    116     if (marketsChanged)
     93    else // this.markets.length > 2
    11794    {
    118         // Drop carried goods
    119         this.goods.amount = 0;
     95        // We are approaching a[this.pos]
     96        // So we need to update a[pos-2] and insert a[pos-1]
     97
     98        this.gain.splice(this.pos-1, 0, 0);
     99
     100        var p = this.pos;
     101        var p1 = p - 1;
     102        var p2 = p - 2;
     103
     104        if (p2 < 0)
     105            p2 += this.markets.length;
     106        if (p1 < 0)
     107            p1 += this.markets.length;
     108
     109        this.gain[p2] = this.CalculateGain(this.markets[p2], this.markets[p1]);
     110        this.gain[p1] = this.CalculateGain(this.markets[p1], this.markets[p]);
    120111    }
    121     return marketsChanged;
    122 }
    123112
    124 Trader.prototype.GetFirstMarket = function()
    125 {
    126     return this.firstMarket;
    127 }
     113    return true;
     114};
    128115
    129 Trader.prototype.GetSecondMarket = function()
     116Trader.prototype.AdvanceMarket = function()
    130117{
    131     return this.secondMarket;
     118    this.pos = (this.pos + 1) % this.markets.length;
    132119}
    133120
    134 Trader.prototype.HasBothMarkets = function()
     121Trader.prototype.HasEnoughMarkets = function()
    135122{
    136     return this.firstMarket && this.secondMarket;
    137 }
     123    return (this.markets.length >= 2);
     124};
    138125
    139126Trader.prototype.GetPreferredGoods = function()
    140127{
    141128    return this.preferredGoods;
    142 }
     129};
    143130
    144131Trader.prototype.SetPreferredGoods = function(preferredGoods)
    145132{
     
    147134    if (RESOURCES.indexOf(preferredGoods) == -1)
    148135        return;
    149136    this.preferredGoods = preferredGoods;
    150 }
     137};
    151138
    152139Trader.prototype.CanTrade = function(target)
    153140{
     
    173160    if (!ownershipSuitableForTrading)
    174161        return false;
    175162    return true;
    176 }
     163};
    177164
    178165Trader.prototype.PerformTrade = function()
    179166{
     
    187174            cmpStatisticsTracker.IncreaseTradeIncomeCounter(this.goods.amount);
    188175    }
    189176    this.goods.type = this.preferredGoods;
    190     this.goods.amount = this.gain;
    191 }
     177    this.goods.amount = this.gain[this.pos];
     178};
    192179
    193180Trader.prototype.GetGoods = function()
    194181{
    195182    return this.goods;
    196 }
     183};
    197184
    198185Trader.prototype.StopTrading = function()
    199186{
    200187    // Drop carried goods
    201188    this.goods.amount = 0;
    202189    // Reset markets
    203     this.firstMarket = INVALID_ENTITY;
    204     this.secondMarket = INVALID_ENTITY;
    205 }
     190    this.markets = [];
     191    this.gain = [];
     192    this.pos = 0;
     193};
    206194
    207195// Get range in which deals with market are available,
    208196// i.e. trader should be in no more than MaxDistance from market
     
    210198Trader.prototype.GetRange = function()
    211199{
    212200    return { "min": 0, "max": +this.template.MaxDistance };
    213 }
     201};
    214202
    215203Engine.RegisterComponentType(IID_Trader, "Trader", Trader);
    216 
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    230230
    231231        // Stop moving immediately.
    232232        this.StopMoving();
     233        this.StopTrading();
    233234        this.FinishOrder();
    234235
    235236        // No orders left, we're an individual now
     
    562563    },
    563564
    564565    "Order.Trade": function(msg) {
    565         if (this.MoveToMarket(this.order.data.firstMarket))
     566        if (this.MoveToMarket(this.order.data.target))
    566567        {
     568            this.cycle = true;
    567569            // We've started walking to the first market
    568             this.SetNextState("INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
     570            this.SetNextState("INDIVIDUAL.TRADE.APPROACHINGMARKET");
    569571        }
    570572    },
    571573
     
    20282030                // TODO: Inform player
    20292031            },
    20302032
    2031             "APPROACHINGFIRSTMARKET": {
     2033            "APPROACHINGMARKET": {
    20322034                "enter": function () {
    20332035                    this.SelectAnimation("move");
    20342036                },
    20352037
    20362038                "MoveCompleted": function() {
    2037                     this.PerformTradeAndMoveToNextMarket(this.order.data.firstMarket, this.order.data.secondMarket, "INDIVIDUAL.TRADE.APPROACHINGSECONDMARKET");
    2038                 },
    2039             },
     2039                    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2040                    if (!cmpTrader || !this.CanTrade(this.order.data.target))
     2041                    {
     2042                        // The target market probably stopped existing
     2043                        this.StopTrading();
     2044                        this.FinishOrder();
     2045                        return;
     2046                    }
    20402047
    2041             "APPROACHINGSECONDMARKET": {
    2042                 "enter": function () {
    2043                     this.SelectAnimation("move");
    2044                 },
     2048                    if (!this.CheckTargetRange(this.order.data.target, IID_Trader))
     2049                    {
     2050                        this.MoveToMarket(this.order.data.target);
     2051                        return;
     2052                    }
    20452053
    2046                 "MoveCompleted": function() {
    2047                     this.order.data.firstPass = false;
    2048                     this.PerformTradeAndMoveToNextMarket(this.order.data.secondMarket, this.order.data.firstMarket, "INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
     2054                    if (!cmpTrader.HasEnoughMarkets())
     2055                        warn("we should wait and not move on");
     2056                        // TODO: Check if we have more than one market, else wait
     2057
     2058                    this.PerformTrade();
     2059                    // Get next market
     2060                    cmpTrader.AdvanceMarket();
     2061                    this.FinishOrder();
    20492062                },
    20502063            },
    20512064        },
     
    24432456    this.isIdle = false;
    24442457    this.lastFormationName = "";
    24452458    this.finishedOrder = false; // used to find if all formation members finished the order
     2459    this.cycle = false;
    24462460
    24472461    // For preventing increased action rate due to Stop orders or target death.
    24482462    this.lastAttacked = undefined;
     
    27432757        error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack);
    27442758    }
    27452759
    2746     this.orderQueue.shift();
     2760    var oldOrder = this.orderQueue.shift();
    27472761    this.order = this.orderQueue[0];
    27482762
     2763    // TODO: Ensure Stop orders work (they should, but double checking doesn't hurt)
     2764    if (this.cycle)
     2765        this.orderQueue.push(oldOrder);
     2766
    27492767    if (this.orderQueue.length)
    27502768    {
    27512769        var ret = UnitFsm.ProcessMessage(this,
     
    27682786    {
    27692787        this.SetNextState("IDLE");
    27702788
     2789        this.StopTrading();
     2790
    27712791        // Check if there are queued formation orders
    27722792        if (this.IsFormationMember())
    27732793        {
     
    38763896 * Adds trade order to the queue. Either walk to the first market, or
    38773897 * start a new route. Not forced, so it can be interrupted by attacks.
    38783898 */
    3879 UnitAI.prototype.SetupTradeRoute = function(target, source, queued)
     3899UnitAI.prototype.SetupTradeRoute = function(target, queued)
    38803900{
    38813901    if (!this.CanTrade(target))
    38823902    {
     
    38853905    }
    38863906
    38873907    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
    3888     var marketsChanged = cmpTrader.SetTargetMarket(target, source);
    3889     if (marketsChanged)
     3908    if (cmpTrader.AddMarket(target))
    38903909    {
    3891         if (cmpTrader.HasBothMarkets())
    3892             this.AddOrder("Trade", { "firstMarket": cmpTrader.GetFirstMarket(), "secondMarket": cmpTrader.GetSecondMarket(), "force": false }, queued);
     3910        if (cmpTrader.HasEnoughMarkets())
     3911        {
     3912            // We assume (for now) that both markets get set separately
     3913            // We'll just assume this always.
     3914           
     3915            // TODO: Handle queued == false properly and not just ignore it
     3916            this.AddOrder("Trade", { "target": target, "force": false }, true); // We already have one market, so we need to start cycling
     3917        }
    38933918        else
    3894             this.WalkToTarget(cmpTrader.GetFirstMarket(), queued);
     3919            this.AddOrder("Trade", { "target": target, "force": false }, queued);
    38953920    }
    38963921};
    38973922
     
    39083933        // Give up.
    39093934        this.StopMoving();
    39103935        this.StopTrading();
     3936        this.FinishOrder();
    39113937        return false;
    39123938    }
    39133939};
    39143940
    3915 UnitAI.prototype.PerformTradeAndMoveToNextMarket = function(currentMarket, nextMarket, nextFsmStateName)
    3916 {
    3917     if (!this.CanTrade(currentMarket))
    3918     {
    3919         this.StopTrading();
    3920         return;
    3921     }
    3922 
    3923     if (this.CheckTargetRange(currentMarket, IID_Trader))
    3924     {
    3925         this.PerformTrade();
    3926         if (this.MoveToMarket(nextMarket))
    3927         {
    3928             // We've started walking to the next market
    3929             this.SetNextState(nextFsmStateName);
    3930         }
    3931     }
    3932     else
    3933     {
    3934         // If the current market is not reached try again
    3935         this.MoveToMarket(currentMarket);
    3936     }
    3937 };
    3938 
    39393941UnitAI.prototype.PerformTrade = function()
    39403942{
    39413943    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     
    39443946
    39453947UnitAI.prototype.StopTrading = function()
    39463948{
    3947     this.FinishOrder();
    39483949    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
    3949     cmpTrader.StopTrading();
     3950    if (cmpTrader)
     3951    {
     3952        this.cycle = false;
     3953        cmpTrader.StopTrading();
     3954    }
    39503955};
    39513956
    39523957/**
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    441441        break;
    442442
    443443    case "setup-trade-route":
    444         for each (var ent in entities)
    445         {
    446             var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
    447             if (cmpUnitAI)
    448                 cmpUnitAI.SetupTradeRoute(cmd.target, cmd.source);
    449         }
     444        GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) {
     445            cmpUnitAI.SetupTradeRoute(cmd.target, cmd.queued);
     446        });
    450447        break;
    451448
    452449    case "select-trading-goods":
  • binaries/data/mods/public/simulation/helpers/RallyPointCommands.js

     
    55    var data = cmpRallyPoint.GetData();
    66    var rallyPos = cmpRallyPoint.GetPositions();
    77    var ret = [];
     8    var trade = 0;
    89    for(var i = 0; i < rallyPos.length; ++i)
    910    {
    1011        // Look and see if there is a command in the rally point data, otherwise just walk there.
     
    6465            ret.push( {
    6566                "type": "setup-trade-route",
    6667                "entities": spawnedEnts,
    67                 "source": data[i].source,
    6868                "target": data[i].target,
    6969                "queued": true
    7070            });
     71            trade++;
    7172            break;
    7273        default:
    7374            ret.push( {
     
    8081            break;
    8182        }
    8283    }
     84    if (trade == 1)
     85        ; // TODO: Figure out if we have a trader, if yes add the current building (if market) in front with a trade order
     86
    8387    return ret;
    8488}
    8589