Ticket #30: trading_2012_02_06.diff

File trading_2012_02_06.diff, 32.7 KB (added by fcxSanya, 12 years ago)
  • source/gui/CGUI.cpp

     
    438438    AddObjectType("input",          &CInput::ConstructObject);
    439439    AddObjectType("list",           &CList::ConstructObject);
    440440    AddObjectType("dropdown",       &CDropDown::ConstructObject);
     441    AddObjectType("tooltip",        &CTooltip::ConstructObject);
    441442}
    442443
    443444void CGUI::Draw()
  • source/gui/CTooltip.cpp

     
    135135
    136136void CTooltip::HandleMessage(SGUIMessage &Message)
    137137{
     138    if (Message.type == GUIM_MOUSE_MOTION)
     139        if (GUI<CPos>::SetSetting(this, "_mousepos", GetMousePos()) != PSRETURN_OK)
     140            debug_warn(L"Failed to set tooltip mouse position");
    138141    IGUITextOwner::HandleMessage(Message);
    139142}
    140143
  • binaries/data/mods/public/gui/session/session.xml

     
    458458        </object>
    459459
    460460        <!-- ================================  ================================ -->
     461        <!-- Information tooltip -->
     462        <!-- ================================  ================================ -->
     463        <object name="informationTooltip" type="tooltip" style="informationTooltip" />
     464
     465        <!-- ================================  ================================ -->
    461466        <!-- START of BOTTOM PANEL -->
    462467        <!-- ================================  ================================ -->
    463468
     
    596601                        <!-- Resource carrying icon/counter -->
    597602                        <object size="0 40 48 88" type="image" name="resourceCarryingIcon" style="resourceIcon"/>
    598603                        <object size="0 80 48 100" type="text" name="resourceCarryingText" style="statsText"/>
    599 
    600604                    </object>
    601605
    602606                    <!-- Big unit icon -->
     
    751755                    </object>
    752756                </object>
    753757
     758                <object name="unitTradingPanel"
     759                    size="14 12 100% 100%"
     760                >
     761                    <object size="0 0 100% 100%">
     762                        <repeat count="4">
     763                            <object name="unitTradingButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
     764                                <object name="unitTradingIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     765                            </object>
     766                        </repeat>
     767                    </object>
     768                </object>
     769
    754770                <object name="unitQueuePanel"
    755771                    size="4 -56 100% 0"
    756772                    type="image"
  • binaries/data/mods/public/gui/session/styles.xml

     
    161161        tooltip_style="snToolTip"
    162162    />
    163163
     164    <style name="informationTooltip"
     165        anchor="top"
     166        buffer_zone="4"
     167        font="serif-bold-14"
     168        maxwidth="300"
     169        offset="16 32"
     170        sprite="BackgroundInformationTooltip"
     171        textcolor="255 255 255"
     172    />
     173
    164174    <!-- ================================  ================================ -->
    165175    <!-- Misc Styles -->
    166176    <!-- ================================  ================================ -->
  • binaries/data/mods/public/gui/session/selection_details.js

     
    135135    //      getGUIObjectByName("resourceCarryingText").hidden = true;
    136136    //  }
    137137    }
     138    // Use the same indicators for traders
     139    else if (entState.trader && entState.trader.goods.amount > 0)
     140    {
     141        getGUIObjectByName("resourceCarryingIcon").hidden = false;
     142        getGUIObjectByName("resourceCarryingText").hidden = false;
     143        getGUIObjectByName("resourceCarryingIcon").cell_id = RESOURCE_ICON_CELL_IDS[entState.trader.goods.type];
     144        getGUIObjectByName("resourceCarryingText").caption = entState.trader.goods.amount;
     145    }
    138146    else
    139147    {
    140148        getGUIObjectByName("resourceCarryingIcon").hidden = true;
  • binaries/data/mods/public/gui/session/utility_functions.js

     
    8383            player.offline = true;
    8484}
    8585
     86function hasClass(entState, className)
     87{
     88    if (entState.identity)
     89    {
     90        var classes = entState.identity.classes;
     91        if (classes && classes.length)
     92            return (classes.indexOf(className) != -1);
     93    }
     94    return false;
     95}
     96
    8697function isUnit(entState)
    8798{
    8899    if (entState.identity)
  • binaries/data/mods/public/gui/session/input.js

     
    5252var doublePressTimer = 0;
    5353var prevHotkey = 0;
    5454
    55 function updateCursor()
     55function updateCursorAndTooltip()
    5656{
     57    var cursorSet = false;
     58    var tooltipSet = false;
     59    var informationTooltip = getGUIObjectByName("informationTooltip");
    5760    if (!mouseIsOverObject)
    5861    {
    5962        var action = determineAction(mouseX, mouseY);
     
    6467                if (action.cursor)
    6568                {
    6669                    Engine.SetCursor(action.cursor);
    67                     return;
     70                    cursorSet = true;
    6871                }
     72                if (action.tooltip)
     73                {
     74                    tooltipSet = true;
     75                    informationTooltip.caption = action.tooltip;
     76                    informationTooltip.hidden = false;
     77                }
    6978            }
    7079        }
    7180    }
    7281
    73     Engine.SetCursor("arrow-default");
     82    if (!cursorSet)
     83        Engine.SetCursor("arrow-default");
     84    if (!tooltipSet)
     85        informationTooltip.hidden = true;
    7486}
    7587
    7688function updateBuildingPlacementPreview()
     
    255267                }
    256268            }
    257269            break;
     270        case "setup-trade-route":
     271            // If ground or sea trade possible
     272            if ((entState.trader && hasClass(entState, "Organic") && (playerOwned || allyOwned) && hasClass(targetState, "Market")) ||
     273                (entState.trader && hasClass(entState, "Ship") && (playerOwned || allyOwned) && hasClass(targetState, "SeaMarket")))
     274            {
     275                var tradingData = {"trader": entState.id, "target": target};
     276                var tradingDetails = Engine.GuiInterfaceCall("GetTradingDetails", tradingData);
     277                var tooltip;
     278                switch (tradingDetails.type)
     279                {
     280                case "is first":
     281                    tooltip = "First trade market.";
     282                    if (tradingDetails.hasBothMarkets)
     283                        tooltip += " Click to establish another route."
     284                    else
     285                        tooltip += " Click on another market to establish a trade route."
     286                    break;
     287                case "is second":
     288                    tooltip = "Second trade market. Click to establish another route.";
     289                    break;
     290                case "set first":
     291                    tooltip = "Set as first trade market";
     292                    break;
     293                case "set second":
     294                    tooltip = "Set as second trade market. Gain: " + tradingDetails.gain + " " + tradingDetails.goods + ".";
     295                    break;
     296                }
     297                return {"possible": true, "tooltip": tooltip};
     298            }
     299            break;
    258300        case "gather":
    259301            if (targetState.resourceSupply && (playerOwned || gaiaOwned))
    260302            {
     
    278320        case "attack":
    279321            if (entState.attack && targetState.hitpoints && enemyOwned)
    280322                return {"possible": true};
     323            break;
    281324        }
    282325    }
    283326    if (action == "move")
     
    362405    else
    363406    {
    364407        var actionInfo = undefined;
    365         if ((actionInfo = getActionInfo("gather", target)).possible)
     408        if ((actionInfo = getActionInfo("setup-trade-route", target)).possible)
     409            return {"type": "setup-trade-route", "cursor": "action-setup-trade-route", "tooltip": actionInfo.tooltip, "target": target};
     410        else if ((actionInfo = getActionInfo("gather", target)).possible)
    366411            return {"type": "gather", "cursor": actionInfo.cursor, "target": target};
    367412        else if ((actionInfo = getActionInfo("returnresource", target)).possible)
    368413            return {"type": "returnresource", "cursor": actionInfo.cursor, "target": target};
     
    9621007                updateBuildingPlacementPreview();
    9631008                break;
    9641009            }
    965 
    9661010            break;
    9671011
    9681012        }
     
    10081052        Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
    10091053        return true;
    10101054
     1055    case "setup-trade-route":
     1056        Engine.PostNetworkCommand({"type": "setup-trade-route", "entities": selection, "target": action.target});
     1057        return true;
     1058
    10111059    case "garrison":
    10121060        Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued});
    10131061        // TODO: Play a sound?
     
    11001148    inputState = INPUT_BUILDING_PLACEMENT;
    11011149}
    11021150
     1151// Called by GUI when user changes preferred trading goods
     1152function selectTradingPreferredGoods(data)
     1153{
     1154    Engine.PostNetworkCommand({"type": "select-trading-goods", "trader": data.trader, "preferredGoods": data.preferredGoods });
     1155}
     1156
    11031157// Called by GUI when user clicks exchange resources button
    11041158function exchangeResources(command)
    11051159{
    11061160    Engine.PostNetworkCommand({"type": "barter", "sell": command.sell, "buy": command.buy, "amount": command.amount});
    11071161}
    11081162
    1109 
    11101163// Batch training:
    11111164// When the user shift-clicks, we set these variables and switch to INPUT_BATCHTRAINING
    11121165// When the user releases shift, or clicks on a different training button, we create the batched units
  • binaries/data/mods/public/gui/session/unit_commands.js

     
    1313const UNIT_PANEL_BASE = -52; // QUEUE: The offset above the main panel (will often be negative)
    1414const UNIT_PANEL_HEIGHT = 44; // QUEUE: The height needed for a row of buttons
    1515
     16// Trading constants
     17const TRADING_RESOURCES = ["food", "wood", "stone", "metal"];
     18
    1619// Barter constants
    1720const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
    1821const BARTER_BUNCH_MULTIPLIER = 5;
     
    2023const BARTER_ACTIONS = ["Sell", "Buy"];
    2124
    2225// The number of currently visible buttons (used to optimise showing/hiding)
    23 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, "Command": 0, "Stance": 0};
     26var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Command": 0, "Stance": 0};
    2427
    2528// Unit panels are panels with row(s) of buttons
    26 var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", "Research", "Stance", "Command"];
     29var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Barter", "Trading", "Construction", "Research", "Stance", "Command"];
    2730
    2831// Indexes of resources to sell and buy on barter panel
    2932var g_barterSell = 0;
     
    132135function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
    133136{
    134137    usedPanels[guiName] = 1;
     138
    135139    var numberOfItems = items.length;
    136140    var selection = g_Selection.toList();
    137141    var garrisonGroups = new EntityGroups();
     
    389393    g_unitPanelButtons[guiName] = numButtons;
    390394}
    391395
     396// Sets up "unit trading panel" - special case for setupUnitPanel
     397function setupUnitTradingPanel(unitEntState)
     398{
     399    for (var i = 0; i < TRADING_RESOURCES.length; i++)
     400    {
     401        var resource = TRADING_RESOURCES[i];
     402        var button = getGUIObjectByName("unitTradingButton["+i+"]");
     403        button.size = (i * 46) + " 0 " + ((i + 1) * 46) + " 46";
     404        var selectTradingPreferredGoodsData = { "trader": unitEntState.id, "preferredGoods": resource };
     405        button.onpress = (function(e){ return function() { selectTradingPreferredGoods(e); } })(selectTradingPreferredGoodsData);
     406        button.enabled = true;
     407        button.tooltip = "Set " + resource + " as trading goods";
     408        var icon = getGUIObjectByName("unitTradingIcon["+i+"]");
     409        var preferredGoods = unitEntState.trader.preferredGoods;
     410        var imageNameSuffix = (resource == preferredGoods) ? "selected" : "inactive";
     411        icon.sprite = "stretched:session/resources/" + resource + "_" + imageNameSuffix + ".png";
     412    }
     413}
     414
    392415// Sets up "unit barter panel" - special case for setupUnitPanel
    393416function setupUnitBarterPanel(unitEntState)
    394417{
     
    528551            setupUnitPanel("Queue", usedPanels, entState, entState.training.queue,
    529552                function (item) { removeFromTrainingQueue(entState.id, item.id); } );
    530553
     554        if (entState.trader)
     555        {
     556            usedPanels["Trading"] = 1;
     557            setupUnitTradingPanel(entState);
     558        }
     559
    531560//      supplementalDetailsPanel.hidden = false;
    532561//      commandsPanel.hidden = isInvisible;
    533562    }
  • binaries/data/mods/public/gui/session/session.js

     
    185185        handleNetMessage(message);
    186186    }
    187187
    188     updateCursor();
     188    updateCursorAndTooltip();
    189189
    190190    // If the selection changed, we need to regenerate the sim display
    191191    if (g_Selection.dirty)
  • binaries/data/mods/public/gui/common/common_sprites.xml

     
    7070        />
    7171    </sprite>
    7272
     73    <sprite name="BackgroundInformationTooltip">
     74        <image
     75            backcolor="0 0 0 191"
     76            size="0 0 100% 100%"
     77            border="false"
     78        />
     79    </sprite>
     80
    7381    <sprite name="BackgroundIndentFillDark">
    7482        <!-- Starting with top left corner continuing in a clockwise manner -->
    7583        <!-- Top border -->
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    404404        }
    405405        break;
    406406
     407    case "setup-trade-route":
     408        for each (var ent in cmd.entities)
     409        {
     410            var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     411            if (cmpUnitAI)
     412                cmpUnitAI.SetupTradeRoute(cmd.target);
     413        }
     414        break;
     415
     416    case "select-trading-goods":
     417        var cmpTrader = Engine.QueryInterface(cmd.trader, IID_Trader);
     418        cmpTrader.SetPreferredGoods(cmd.preferredGoods);
     419        break;
     420
    407421    case "barter":
    408422        var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
    409423        cmpBarter.ExchangeResources(playerEnt, cmd.sell, cmd.buy, cmd.amount);
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    185185        };
    186186    }
    187187
     188    var cmpTrader = Engine.QueryInterface(ent, IID_Trader);
     189    if (cmpTrader)
     190    {
     191        ret.trader = {
     192            "goods": cmpTrader.GetGoods(),
     193            "preferredGoods": cmpTrader.GetPreferredGoods()
     194        };
     195    }
     196
    188197    var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
    189198    if (cmpFoundation)
    190199    {
     
    680689    return 0;
    681690};
    682691
     692GuiInterface.prototype.GetTradingDetails = function(player, data)
     693{
     694    var cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader);
     695    var cmpEntityIdentity = Engine.QueryInterface(data.trader, IID_Identity);
     696    var cmpEntityPlayer = QueryOwnerInterface(data.trader, IID_Player);
     697    var entityPlayerId = cmpEntityPlayer.GetPlayerID();
     698    var cmpTargetIdentity = Engine.QueryInterface(data.target, IID_Identity);
     699    var cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
     700    var targetPlayerId = cmpTargetPlayer.GetPlayerID();
     701
     702    var ownershipSuitableForTrading = (entityPlayerId == targetPlayerId) || cmpEntityPlayer.IsAlly(targetPlayerId);
     703    var landTradingPossible = cmpEntityIdentity.HasClass("Organic") && cmpTargetIdentity.HasClass("Market");
     704    var seaTradingPossible = cmpEntityIdentity.HasClass("Ship") && cmpTargetIdentity.HasClass("SeaMarket");
     705    if (!(ownershipSuitableForTrading && cmpEntityTrader && (landTradingPossible || seaTradingPossible)))
     706        return null;
     707    var firstMarket = cmpEntityTrader.GetFirstMarket();
     708    var secondMarket = cmpEntityTrader.GetSecondMarket();
     709    var result = null;
     710    if (data.target == firstMarket)
     711        result = {"type": "is first", "hasBothMarkets": cmpEntityTrader.HasBothMarkets() };
     712    else if (data.target == secondMarket)
     713        result = {"type": "is second"};
     714    else if (firstMarket == null)
     715        result = {"type": "set first"};
     716    else if (secondMarket == null)
     717        result = {
     718            "type": "set second",
     719            "gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
     720            "goods": cmpEntityTrader.GetPreferredGoods()
     721        };
     722    // Else both markets are not null and target is different from them
     723    else
     724        result = {"type": "set first"};
     725    return result;
     726};
     727
    683728GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
    684729{
    685730    var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
     
    738783    "GetFoundationSnapData": 1,
    739784    "PlaySound": 1,
    740785    "FindIdleUnit": 1,
     786    "GetTradingDetails": 1,
    741787
    742788    "SetPathfinderDebugOverlay": 1,
    743789    "SetObstructionDebugOverlay": 1,
  • binaries/data/mods/public/simulation/components/interfaces/Trader.js

     
     1Engine.RegisterInterface("Trader");
  • binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js

     
    1212Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
    1313Engine.LoadComponentScript("interfaces/ResourceSupply.js");
    1414Engine.LoadComponentScript("interfaces/TrainingQueue.js");
     15Engine.LoadComponentScript("interfaces/Trader.js")
    1516Engine.LoadComponentScript("interfaces/Timer.js");
    1617Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
    1718Engine.LoadComponentScript("interfaces/UnitAI.js");
  • binaries/data/mods/public/simulation/components/Identity.js

     
    103103                        "<value>Slave</value>" +
    104104                        "<value>CitizenSoldier</value>" +
    105105                        "<value>Trade</value>" +
     106                        "<value>Market</value>" +
     107                        "<value>SeaMarket</value>" +
    106108                        "<value>Warship</value>" +
    107109                        "<value>SeaCreature</value>" +
    108110                        "<value>ForestPlant</value>" +
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    341341            return;
    342342        }
    343343    },
    344    
     344
     345    "Order.Trade": function(msg) {
     346        if (this.MoveToMarket(this.order.data.firstMarket))
     347        {
     348            // We've started walking to the first market
     349            this.SetNextState("INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
     350        }
     351    },
     352
    345353    "Order.Repair": function(msg) {
    346354        // Try to move within range
    347355        if (this.MoveToTargetRange(this.order.data.target, IID_Builder))
     
    10481056            },
    10491057        },
    10501058
     1059        "TRADE": {
     1060            "Attacked": function(msg) {
     1061                // Ignore attack
     1062                // TODO: Inform player
     1063            },
     1064
     1065            "APPROACHINGFIRSTMARKET": {
     1066                "enter": function () {
     1067                    this.SelectAnimation("move");
     1068                },
     1069
     1070                "MoveCompleted": function() {
     1071                    this.PerformTradeAndMoveToNextMarket(this.order.data.firstMarket, this.order.data.secondMarket, "INDIVIDUAL.TRADE.APPROACHINGSECONDMARKET");
     1072                },
     1073            },
     1074
     1075            "APPROACHINGSECONDMARKET": {
     1076                "enter": function () {
     1077                    this.SelectAnimation("move");
     1078                },
     1079
     1080                "MoveCompleted": function() {
     1081                    this.order.data.firstPass = false;
     1082                    this.PerformTradeAndMoveToNextMarket(this.order.data.secondMarket, this.order.data.firstMarket, "INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
     1083                },
     1084            },
     1085        },
     1086
    10511087        "REPAIR": {
    10521088            "APPROACHING": {
    10531089                "enter": function () {
     
    23642400    this.AddOrder("ReturnResource", { "target": target }, queued);
    23652401};
    23662402
     2403UnitAI.prototype.SetupTradeRoute = function(target, queued)
     2404{
     2405    if (!this.CanTrade(target))
     2406    {
     2407        this.WalkToTarget(target, queued);
     2408        return;
     2409    }
     2410
     2411    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2412    var marketsChanged = cmpTrader.SetTargetMarket(target);
     2413    if (marketsChanged)
     2414    {
     2415        if (cmpTrader.HasBothMarkets())
     2416            this.AddOrder("Trade", { "firstMarket": cmpTrader.GetFirstMarket(), "secondMarket": cmpTrader.GetSecondMarket() }, queued);
     2417        else
     2418            this.WalkToTarget(cmpTrader.GetFirstMarket(), queued);
     2419    }
     2420}
     2421
     2422UnitAI.prototype.MoveToMarket = function(targetMarket)
     2423{
     2424    if (this.MoveToTarget(targetMarket))
     2425    {
     2426        // We've started walking to the market
     2427        return true;
     2428    }
     2429    else
     2430    {
     2431        // We can't reach the market.
     2432        // Give up.
     2433        this.StopMoving();
     2434        this.FinishOrder();
     2435        var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2436        cmpTrader.DropGoods();
     2437        return false;
     2438    }
     2439}
     2440
     2441UnitAI.prototype.PerformTradeAndMoveToNextMarket = function(currentMarket, nextMarket, nextFsmStateName)
     2442{
     2443    if (this.CheckTargetRange(currentMarket, IID_Trader))
     2444    {
     2445        this.PerformTrade();
     2446        if (this.MoveToMarket(nextMarket))
     2447        {
     2448            // We've started walking to the next market
     2449            this.SetNextState(nextFsmStateName);
     2450        }
     2451    }
     2452    else
     2453    {
     2454        // Market don't reached, give up
     2455        this.FinishOrder();
     2456        var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2457        cmpTrader.DropGoods();
     2458    }
     2459}
     2460
     2461UnitAI.prototype.PerformTrade = function()
     2462{
     2463    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2464    cmpTrader.PerformTrade();
     2465}
     2466
    23672467UnitAI.prototype.Repair = function(target, autocontinue, queued)
    23682468{
    23692469    if (!this.CanRepair(target))
     
    25832683    return true;
    25842684};
    25852685
     2686UnitAI.prototype.CanTrade = function(target)
     2687{
     2688    // Formation controllers should always respond to commands
     2689    // (then the individual units can make up their own minds)
     2690    if (this.IsFormationController())
     2691        return true;
     2692
     2693    // Verify that we're able to respond to Trade commands
     2694    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2695    if (!cmpTrader)
     2696        return false;
     2697
     2698    return true;
     2699}
     2700
    25862701UnitAI.prototype.CanRepair = function(target)
    25872702{
    25882703    // Formation controllers should always respond to commands
  • binaries/data/mods/public/simulation/components/Looter.js

     
    2121    }
    2222    var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
    2323    cmpPlayer.AddResources(cmpLoot.GetResources());
     24
     25    // If target entity has trader component, add carried goods to loot too
     26    var cmpTrader = Engine.QueryInterface(targetEntity, IID_Trader);
     27    if (cmpTrader)
     28    {
     29        var carriedGoods = cmpTrader.GetGoods();
     30        if (carriedGoods.amount > 0)
     31        {
     32            // Convert from {type:<type>,amount:<amount>} to {<type>:<amount>}
     33            var resourcesToAdd = {};
     34            resourcesToAdd[carriedGoods.type] = carriedGoods.amount;
     35            cmpPlayer.AddResources(resourcesToAdd);
     36        }
     37    }
    2438}
    2539
    2640Engine.RegisterComponentType(IID_Looter, "Looter", Looter);
  • binaries/data/mods/public/simulation/components/Trader.js

     
     1// This constant used to adjust gain value depending on distance
     2const DISTANCE_FACTOR = 1 / 50;
     3
     4// Additional gain for trading perfromed between markets of different players, in percents
     5const INTERNATIONAL_TRADING_ADDITION = 50;
     6// Additional gain for ships for each garrisoned trader, in percents
     7const GARRISONED_TRADER_ADDITION = 20;
     8
     9function Trader() {}
     10
     11Trader.prototype.Schema =
     12    "<a:help>Lets the unit generate resouces while moving between markets (or docks in case of water trading).</a:help>" +
     13    "<a:example>" +
     14        "<MaxDistance>2.0</MaxDistance>" +
     15        "<GainMultiplier>1.0</GainMultiplier>" +
     16    "</a:example>" +
     17    "<element name='MaxDistance' a:help='Max distance from market when performing deal'>" +
     18        "<ref name='positiveDecimal'/>" +
     19    "</element>" +
     20    "<optional>" +
     21        "<element name='GainMultiplier' a:help='Additional gain multiplier'>" +
     22            "<ref name='positiveDecimal'/>" +
     23        "</element>" +
     24    "</optional>";
     25
     26Trader.prototype.Init = function()
     27{
     28    this.firstMarket = null;
     29    this.secondMarket = null;
     30    // Gain from one pass between markets
     31    this.gain = null;
     32    // Selected resource for trading
     33    this.preferredGoods = "metal";
     34    // Currently carried goods
     35    this.goods = { "type": null, "amount": 0 };
     36}
     37
     38Trader.prototype.CalculateGain = function(firstMarket, secondMarket)
     39{
     40    var cmpFirstMarketPosition = Engine.QueryInterface(firstMarket, IID_Position);
     41    var cmpSecondMarketPosition = Engine.QueryInterface(secondMarket, IID_Position);
     42    var firstMarketPosition = cmpFirstMarketPosition.GetPosition2D();
     43    var secondMarketPosition = cmpSecondMarketPosition.GetPosition2D();
     44    // Calculate ordinary Euclidean distance between markets.
     45    // We don't use pathfinder, because ordinary distance looks more fair.
     46    var distance = Math.sqrt(Math.pow(firstMarketPosition.x - secondMarketPosition.x, 2) + Math.pow(firstMarketPosition.y - secondMarketPosition.y, 2));
     47    // We calculate gain as square of distance to encourage trading between remote markets
     48    var gain = Math.round(Math.pow(distance * DISTANCE_FACTOR, 2));
     49    // If markets belongs to different players, multiple gain to INTERNATIONAL_TRADING_MULTIPLIER
     50    var cmpFirstMarketOwnership = Engine.QueryInterface(firstMarket, IID_Ownership);
     51    var cmpSecondMarketOwnership = Engine.QueryInterface(secondMarket, IID_Ownership);
     52    if (cmpFirstMarketOwnership.GetOwner() != cmpSecondMarketOwnership.GetOwner())
     53        gain *= 1 + INTERNATIONAL_TRADING_ADDITION / 100;
     54    // For ship increase gain for each garrisoned trader
     55    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     56    if (cmpIdentity.HasClass("Ship"))
     57    {
     58        var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
     59        if (cmpGarrisonHolder)
     60        {
     61            var garrisonedTradersCount = 0;
     62            for each (var entity in cmpGarrisonHolder.GetEntities())
     63            {
     64                var cmpGarrisonedUnitTrader = Engine.QueryInterface(entity, IID_Trader);
     65                if (cmpGarrisonedUnitTrader)
     66                    garrisonedTradersCount++;
     67            }
     68            gain *= 1 + GARRISONED_TRADER_ADDITION * garrisonedTradersCount / 100;
     69        }
     70    }
     71    if (this.template.GainMultiplier)
     72        gain *= this.template.GainMultiplier;
     73    gain = Math.round(gain);
     74    return gain;
     75}
     76
     77// Set target as target market.
     78// Return true if at least one of markets was changed.
     79Trader.prototype.SetTargetMarket = function(target)
     80{
     81    var marketsChanged = false;
     82    // If we already have both markets - drop them
     83    // and use the target as first market
     84    if (this.secondMarket)
     85    {
     86        this.firstMarket = target;
     87        this.secondMarket = null;
     88        marketsChanged = true;
     89    }
     90    // If we have only one market and target is different from it,
     91    // set the target as second one
     92    else if (this.firstMarket)
     93    {
     94        if (target != this.firstMarket)
     95        {
     96            this.secondMarket = target;
     97            this.gain = this.CalculateGain(this.firstMarket, this.secondMarket);
     98            marketsChanged = true;
     99        }
     100    }
     101    // Else we don't have target markets at all,
     102    // set the target as first market
     103    else
     104    {
     105        this.firstMarket = target;
     106        marketsChanged = true;
     107    }
     108    if (marketsChanged)
     109        this.DropGoods();
     110    return marketsChanged;
     111}
     112
     113Trader.prototype.GetFirstMarket = function()
     114{
     115    return this.firstMarket;
     116}
     117
     118Trader.prototype.GetSecondMarket = function()
     119{
     120    return this.secondMarket;
     121}
     122
     123Trader.prototype.HasBothMarkets = function()
     124{
     125    return (this.firstMarket != null) && (this.secondMarket != null);
     126}
     127
     128Trader.prototype.GetPreferredGoods = function()
     129{
     130    return this.preferredGoods;
     131}
     132
     133Trader.prototype.SetPreferredGoods = function(preferredGoods)
     134{
     135    this.preferredGoods = preferredGoods;
     136}
     137
     138Trader.prototype.PerformTrade = function()
     139{
     140    if (this.goods.amount > 0)
     141    {
     142        var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     143        cmpPlayer.AddResource(this.goods.type, this.goods.amount);
     144    }
     145    this.goods.type = this.preferredGoods;
     146    this.goods.amount = this.gain;
     147}
     148
     149Trader.prototype.GetGoods = function()
     150{
     151    return this.goods;
     152}
     153
     154// Called when the trader stops trading
     155Trader.prototype.DropGoods = function()
     156{
     157    this.goods.amount = 0;
     158}
     159
     160// Get range in which deals with market are available,
     161// i.e. trader should be in no more than MaxDistance from market
     162// to be able to trade with it.
     163Trader.prototype.GetRange = function()
     164{
     165    return { "min": 0, "max": +this.template.MaxDistance };
     166}
     167
     168Engine.RegisterComponentType(IID_Trader, "Trader", Trader);
     169
  • binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml

     
    1818    <GenericName>Trader</GenericName>
    1919    <Rollover>Trade was a very important part of ancient civilisation - 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.</Rollover>
    2020    <Tooltip>Trades resources between allied Markets.</Tooltip>
    21     <Classes datatype="tokens">Trade</Classes>
    2221  </Identity>
     22  <Trader>
     23    <MaxDistance>2.0</MaxDistance>
     24  </Trader>
    2325  <UnitMotion>
    2426    <WalkSpeed>7.0</WalkSpeed>
    2527  </UnitMotion>
  • binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml

     
    2424  <Identity>
    2525    <GenericName>Market</GenericName>
    2626    <Tooltip>Create Trade units and Barter resources.</Tooltip>
    27     <Classes datatype="tokens">Town BarterMarket</Classes>
     27    <Classes datatype="tokens">Town Market BarterMarket</Classes>
    2828    <Icon>structures/market.png</Icon>
    2929  </Identity>
    3030  <Obstruction>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml

     
    3838    <BarHeight>0.5</BarHeight>
    3939    <HeightOffset>6.0</HeightOffset>
    4040  </StatusBars>
     41  <Trader>
     42    <MaxDistance>10.0</MaxDistance>
     43  </Trader>
    4144  <UnitMotion>
    4245    <WalkSpeed>10.5</WalkSpeed>
    4346  </UnitMotion>
  • binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml

     
    2727  <Identity>
    2828    <GenericName>Dock</GenericName>
    2929    <Tooltip>Build upon a shoreline to construct naval vessels and to open sea trade.</Tooltip>
    30     <Classes datatype="tokens">Town</Classes>
     30    <Classes datatype="tokens">Town Market SeaMarket</Classes>
    3131    <Icon>structures/dock.png</Icon>
    3232  </Identity>
    3333  <Obstruction>
  • binaries/data/mods/public/art/textures/cursors/action-setup-trade-route.txt

     
     11 1
     21 1