Ticket #30: trading_2012_02_24.diff

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

     
    445445    AddObjectType("input",          &CInput::ConstructObject);
    446446    AddObjectType("list",           &CList::ConstructObject);
    447447    AddObjectType("dropdown",       &CDropDown::ConstructObject);
     448    AddObjectType("tooltip",        &CTooltip::ConstructObject);
    448449}
    449450
    450451void 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 += " Gain: " + tradingDetails.gain + " " + tradingDetails.goods + ". 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. Gain: " + tradingDetails.gain + " " + tradingDetails.goods + "." + " 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    {
     712        result = {
     713            "type": "is first",
     714            "goods": cmpEntityTrader.GetPreferredGoods(),
     715            "hasBothMarkets": cmpEntityTrader.HasBothMarkets()
     716        };
     717        if (cmpEntityTrader.HasBothMarkets())
     718            result.gain = cmpEntityTrader.GetGain();
     719    }
     720    else if (data.target == secondMarket)
     721    {
     722        result = {
     723            "type": "is second",
     724            "gain": cmpEntityTrader.GetGain(),
     725            "goods": cmpEntityTrader.GetPreferredGoods()
     726        };
     727    }
     728    else if (firstMarket == null)
     729    {
     730        result = {"type": "set first"};
     731    }
     732    else if (secondMarket == null)
     733    {
     734        result = {
     735            "type": "set second",
     736            "gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
     737            "goods": cmpEntityTrader.GetPreferredGoods()
     738        };
     739    }
     740    else
     741    {
     742        // Else both markets are not null and target is different from them
     743        result = {"type": "set first"};
     744    }
     745    return result;
     746};
     747
    683748GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
    684749{
    685750    var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
     
    738803    "GetFoundationSnapData": 1,
    739804    "PlaySound": 1,
    740805    "FindIdleUnit": 1,
     806    "GetTradingDetails": 1,
    741807
    742808    "SetPathfinderDebugOverlay": 1,
    743809    "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/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))
     
    10631071            },
    10641072        },
    10651073
     1074        "TRADE": {
     1075            "Attacked": function(msg) {
     1076                // Ignore attack
     1077                // TODO: Inform player
     1078            },
     1079
     1080            "APPROACHINGFIRSTMARKET": {
     1081                "enter": function () {
     1082                    this.SelectAnimation("move");
     1083                },
     1084
     1085                "MoveCompleted": function() {
     1086                    this.PerformTradeAndMoveToNextMarket(this.order.data.firstMarket, this.order.data.secondMarket, "INDIVIDUAL.TRADE.APPROACHINGSECONDMARKET");
     1087                },
     1088            },
     1089
     1090            "APPROACHINGSECONDMARKET": {
     1091                "enter": function () {
     1092                    this.SelectAnimation("move");
     1093                },
     1094
     1095                "MoveCompleted": function() {
     1096                    this.order.data.firstPass = false;
     1097                    this.PerformTradeAndMoveToNextMarket(this.order.data.secondMarket, this.order.data.firstMarket, "INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
     1098                },
     1099            },
     1100        },
     1101
    10661102        "REPAIR": {
    10671103            "APPROACHING": {
    10681104                "enter": function () {
     
    23702406    this.AddOrder("ReturnResource", { "target": target }, queued);
    23712407};
    23722408
     2409UnitAI.prototype.SetupTradeRoute = function(target, queued)
     2410{
     2411    if (!this.CanTrade(target))
     2412    {
     2413        this.WalkToTarget(target, queued);
     2414        return;
     2415    }
     2416
     2417    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2418    var marketsChanged = cmpTrader.SetTargetMarket(target);
     2419    if (marketsChanged)
     2420    {
     2421        if (cmpTrader.HasBothMarkets())
     2422            this.AddOrder("Trade", { "firstMarket": cmpTrader.GetFirstMarket(), "secondMarket": cmpTrader.GetSecondMarket() }, queued);
     2423        else
     2424            this.WalkToTarget(cmpTrader.GetFirstMarket(), queued);
     2425    }
     2426}
     2427
     2428UnitAI.prototype.MoveToMarket = function(targetMarket)
     2429{
     2430    if (this.MoveToTarget(targetMarket))
     2431    {
     2432        // We've started walking to the market
     2433        return true;
     2434    }
     2435    else
     2436    {
     2437        // We can't reach the market.
     2438        // Give up.
     2439        this.StopMoving();
     2440        this.FinishOrder();
     2441        var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2442        cmpTrader.DropGoods();
     2443        return false;
     2444    }
     2445}
     2446
     2447UnitAI.prototype.PerformTradeAndMoveToNextMarket = function(currentMarket, nextMarket, nextFsmStateName)
     2448{
     2449    if (this.CheckTargetRange(currentMarket, IID_Trader))
     2450    {
     2451        this.PerformTrade();
     2452        if (this.MoveToMarket(nextMarket))
     2453        {
     2454            // We've started walking to the next market
     2455            this.SetNextState(nextFsmStateName);
     2456        }
     2457    }
     2458    else
     2459    {
     2460        // Market wasn't reached, give up
     2461        this.FinishOrder();
     2462        var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2463        cmpTrader.DropGoods();
     2464    }
     2465}
     2466
     2467UnitAI.prototype.PerformTrade = function()
     2468{
     2469    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2470    cmpTrader.PerformTrade();
     2471}
     2472
    23732473UnitAI.prototype.Repair = function(target, autocontinue, queued)
    23742474{
    23752475    if (!this.CanRepair(target))
     
    25892689    return true;
    25902690};
    25912691
     2692UnitAI.prototype.CanTrade = function(target)
     2693{
     2694    // Formation controllers should always respond to commands
     2695    // (then the individual units can make up their own minds)
     2696    if (this.IsFormationController())
     2697        return true;
     2698
     2699    // Verify that we're able to respond to Trade commands
     2700    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2701    if (!cmpTrader)
     2702        return false;
     2703
     2704    // Check that target is a market
     2705    var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity);
     2706    if (!cmpTargetIdentity)
     2707        return false;
     2708    if (!cmpTargetIdentity.HasClass("Market") && !cmpTargetIdentity.HasClass("SeaMarket"))
     2709        return false;
     2710
     2711    return true;
     2712}
     2713
    25922714UnitAI.prototype.CanRepair = function(target)
    25932715{
    25942716    // Formation controllers should always respond to commands
  • binaries/data/mods/public/simulation/components/Identity.js

     
    4949        "</element>" +
    5050    "</optional>" +
    5151    "<optional>" +
    52         "<element name='Classes' a:help='Optional list of space-separated classes applying to this entity. Choices include: Unit, Infantry, Melee, Cavalry, Ranged, Mechanical, Ship, Siege, Champion, Hero, Elephant, Chariot, Mercenary, Spear, Sword, Bow, Javelin, Sling, Support, Animal, Organic, Structure, Civic, CivCentre, Economic, Defensive, Gates, Wall, BarterMarket, Village, Town, City, ConquestCritical, Worker, Female, Healer, Slave, CitizenSoldier, Trade, Warship, SeaCreature, ForestPlant, DropsiteFood, DropsiteWood, DropsiteStone, DropsiteMetal'>" +
     52        "<element name='Classes' a:help='Optional list of space-separated classes applying to this entity. Choices include: Unit, Infantry, Melee, Cavalry, Ranged, Mechanical, Ship, Siege, Champion, Hero, Elephant, Chariot, Mercenary, Spear, Sword, Bow, Javelin, Sling, Support, Animal, Organic, Structure, Civic, CivCentre, Economic, Defensive, Gates, Wall, BarterMarket, Village, Town, City, ConquestCritical, Worker, Female, Healer, Slave, CitizenSoldier, Trade, Market, SeaMarket, Warship, SeaCreature, ForestPlant, DropsiteFood, DropsiteWood, DropsiteStone, DropsiteMetal'>" +
    5353            "<attribute name='datatype'>" +
    5454                "<value>tokens</value>" +
    5555            "</attribute>" +
  • 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
     9// Array of resource names
     10const RESOURCES = ["food", "wood", "stone", "metal"];
     11
     12function Trader() {}
     13
     14Trader.prototype.Schema =
     15    "<a:help>Lets the unit generate resouces while moving between markets (or docks in case of water trading).</a:help>" +
     16    "<a:example>" +
     17        "<MaxDistance>2.0</MaxDistance>" +
     18        "<GainMultiplier>1.0</GainMultiplier>" +
     19    "</a:example>" +
     20    "<element name='MaxDistance' a:help='Max distance from market when performing deal'>" +
     21        "<ref name='positiveDecimal'/>" +
     22    "</element>" +
     23    "<optional>" +
     24        "<element name='GainMultiplier' a:help='Additional gain multiplier'>" +
     25            "<ref name='positiveDecimal'/>" +
     26        "</element>" +
     27    "</optional>";
     28
     29Trader.prototype.Init = function()
     30{
     31    this.firstMarket = null;
     32    this.secondMarket = null;
     33    // Gain from one pass between markets
     34    this.gain = null;
     35    // Selected resource for trading
     36    this.preferredGoods = "metal";
     37    // Currently carried goods
     38    this.goods = { "type": null, "amount": 0 };
     39}
     40
     41Trader.prototype.CalculateGain = function(firstMarket, secondMarket)
     42{
     43    var cmpFirstMarketPosition = Engine.QueryInterface(firstMarket, IID_Position);
     44    var cmpSecondMarketPosition = Engine.QueryInterface(secondMarket, IID_Position);
     45    if (!cmpFirstMarketPosition || !cmpFirstMarketPosition.IsInWorld() || !cmpSecondMarketPosition || !cmpSecondMarketPosition.IsInWorld())
     46        return null;
     47    var firstMarketPosition = cmpFirstMarketPosition.GetPosition2D();
     48    var secondMarketPosition = cmpSecondMarketPosition.GetPosition2D();
     49    // Calculate ordinary Euclidean distance between markets.
     50    // We don't use pathfinder, because ordinary distance looks more fair.
     51    var distance = Math.sqrt(Math.pow(firstMarketPosition.x - secondMarketPosition.x, 2) + Math.pow(firstMarketPosition.y - secondMarketPosition.y, 2));
     52    // We calculate gain as square of distance to encourage trading between remote markets
     53    var gain = Math.round(Math.pow(distance * DISTANCE_FACTOR, 2));
     54    // If markets belongs to different players, multiple gain to INTERNATIONAL_TRADING_MULTIPLIER
     55    var cmpFirstMarketOwnership = Engine.QueryInterface(firstMarket, IID_Ownership);
     56    var cmpSecondMarketOwnership = Engine.QueryInterface(secondMarket, IID_Ownership);
     57    if (cmpFirstMarketOwnership.GetOwner() != cmpSecondMarketOwnership.GetOwner())
     58        gain *= 1 + INTERNATIONAL_TRADING_ADDITION / 100;
     59    // For ship increase gain for each garrisoned trader
     60    var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     61    if (cmpIdentity.HasClass("Ship"))
     62    {
     63        var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
     64        if (cmpGarrisonHolder)
     65        {
     66            var garrisonedTradersCount = 0;
     67            for each (var entity in cmpGarrisonHolder.GetEntities())
     68            {
     69                var cmpGarrisonedUnitTrader = Engine.QueryInterface(entity, IID_Trader);
     70                if (cmpGarrisonedUnitTrader)
     71                    garrisonedTradersCount++;
     72            }
     73            gain *= 1 + GARRISONED_TRADER_ADDITION * garrisonedTradersCount / 100;
     74        }
     75    }
     76    if (this.template.GainMultiplier)
     77        gain *= this.template.GainMultiplier;
     78    gain = Math.round(gain);
     79    return gain;
     80}
     81
     82Trader.prototype.GetGain = function()
     83{
     84    return this.gain;
     85}
     86
     87// Set target as target market.
     88// Return true if at least one of markets was changed.
     89Trader.prototype.SetTargetMarket = function(target)
     90{
     91    // Check that target is a market
     92    var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity);
     93    if (!cmpTargetIdentity)
     94        return false;
     95    if (!cmpTargetIdentity.HasClass("Market") && !cmpTargetIdentity.HasClass("SeaMarket"))
     96        return false;
     97    var marketsChanged = false;
     98    if (this.secondMarket)
     99    {
     100        // If we already have both markets - drop them
     101        // and use the target as first market
     102        this.firstMarket = target;
     103        this.secondMarket = null;
     104        marketsChanged = true;
     105    }
     106    else if (this.firstMarket)
     107    {
     108        // If we have only one market and target is different from it,
     109        // set the target as second one
     110        if (target != this.firstMarket)
     111        {
     112            this.secondMarket = target;
     113            this.gain = this.CalculateGain(this.firstMarket, this.secondMarket);
     114            marketsChanged = true;
     115        }
     116    }
     117    else
     118    {
     119        // Else we don't have target markets at all,
     120        // set the target as first market
     121        this.firstMarket = target;
     122        marketsChanged = true;
     123    }
     124    if (marketsChanged)
     125        this.DropGoods();
     126    return marketsChanged;
     127}
     128
     129Trader.prototype.GetFirstMarket = function()
     130{
     131    return this.firstMarket;
     132}
     133
     134Trader.prototype.GetSecondMarket = function()
     135{
     136    return this.secondMarket;
     137}
     138
     139Trader.prototype.HasBothMarkets = function()
     140{
     141    return (this.firstMarket != null) && (this.secondMarket != null);
     142}
     143
     144Trader.prototype.GetPreferredGoods = function()
     145{
     146    return this.preferredGoods;
     147}
     148
     149Trader.prototype.SetPreferredGoods = function(preferredGoods)
     150{
     151    // Check that argument is a correct resource name
     152    if (RESOURCES.indexOf(preferredGoods) == -1)
     153        return;
     154    this.preferredGoods = preferredGoods;
     155}
     156
     157Trader.prototype.PerformTrade = function()
     158{
     159    if (this.goods.amount > 0)
     160    {
     161        var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     162        cmpPlayer.AddResource(this.goods.type, this.goods.amount);
     163    }
     164    this.goods.type = this.preferredGoods;
     165    this.goods.amount = this.gain;
     166}
     167
     168Trader.prototype.GetGoods = function()
     169{
     170    return this.goods;
     171}
     172
     173// Called when the trader stops trading
     174Trader.prototype.DropGoods = function()
     175{
     176    this.goods.amount = 0;
     177}
     178
     179// Get range in which deals with market are available,
     180// i.e. trader should be in no more than MaxDistance from market
     181// to be able to trade with it.
     182Trader.prototype.GetRange = function()
     183{
     184    return { "min": 0, "max": +this.template.MaxDistance };
     185}
     186
     187Engine.RegisterComponentType(IID_Trader, "Trader", Trader);
     188
  • 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