Ticket #30: trading_2012_03_08.diff

File trading_2012_03_08.diff, 35.5 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

     
    3636    AddSetting(GUIST_float,                 "maxwidth");
    3737    AddSetting(GUIST_CPos,                  "offset");
    3838    AddSetting(GUIST_EVAlign,               "anchor");
     39    AddSetting(GUIST_bool,                  "independent");
    3940
    4041    // If the tooltip is just a reference to another object:
    4142    AddSetting(GUIST_CStr,                  "use_object");
     
    8485
    8586    CPos mousepos, offset;
    8687    EVAlign anchor;
    87     GUI<CPos>::GetSetting(this, "_mousepos", mousepos);
     88    bool independent;
     89    GUI<bool>::GetSetting(this, "independent", independent);
     90    if (independent)
     91        mousepos = GetMousePos();
     92    else
     93        GUI<CPos>::GetSetting(this, "_mousepos", mousepos);
    8894    GUI<CPos>::GetSetting(this, "offset", offset);
    8995    GUI<EVAlign>::GetSetting(this, "anchor", anchor);
    9096
  • binaries/data/mods/public/gui/session/session.xml

     
    458458        </object>
    459459
    460460        <!-- ================================  ================================ -->
     461        <!-- Information tooltip -->
     462        <!-- ================================  ================================ -->
     463        <object name="informationTooltip" type="tooltip" independent="true" 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};
     
    9631008                updateBuildingPlacementPreview();
    9641009                break;
    9651010            }
    966 
    9671011            break;
    9681012
    9691013        }
     
    10091053        Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
    10101054        return true;
    10111055
     1056    case "setup-trade-route":
     1057        Engine.PostNetworkCommand({"type": "setup-trade-route", "entities": selection, "target": action.target});
     1058        return true;
     1059
    10121060    case "garrison":
    10131061        Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued});
    10141062        // TODO: Play a sound?
     
    11011149    inputState = INPUT_BUILDING_PLACEMENT;
    11021150}
    11031151
     1152// Called by GUI when user changes preferred trading goods
     1153function selectTradingPreferredGoods(data)
     1154{
     1155    Engine.PostNetworkCommand({"type": "select-trading-goods", "trader": data.trader, "preferredGoods": data.preferredGoods });
     1156}
     1157
    11041158// Called by GUI when user clicks exchange resources button
    11051159function exchangeResources(command)
    11061160{
    11071161    Engine.PostNetworkCommand({"type": "barter", "sell": command.sell, "buy": command.buy, "amount": command.amount});
    11081162}
    11091163
    1110 
    11111164// Batch training:
    11121165// When the user shift-clicks, we set these variables and switch to INPUT_BATCHTRAINING
    11131166// 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    if (!cmpEntityTrader || !cmpEntityTrader.CanTrade(data.target))
     696        return null;
     697    var firstMarket = cmpEntityTrader.GetFirstMarket();
     698    var secondMarket = cmpEntityTrader.GetSecondMarket();
     699    var result = null;
     700    if (data.target == firstMarket)
     701    {
     702        result = {
     703            "type": "is first",
     704            "goods": cmpEntityTrader.GetPreferredGoods(),
     705            "hasBothMarkets": cmpEntityTrader.HasBothMarkets()
     706        };
     707        if (cmpEntityTrader.HasBothMarkets())
     708            result.gain = cmpEntityTrader.GetGain();
     709    }
     710    else if (data.target == secondMarket)
     711    {
     712        result = {
     713            "type": "is second",
     714            "gain": cmpEntityTrader.GetGain(),
     715            "goods": cmpEntityTrader.GetPreferredGoods()
     716        };
     717    }
     718    else if (firstMarket == null)
     719    {
     720        result = {"type": "set first"};
     721    }
     722    else if (secondMarket == null)
     723    {
     724        result = {
     725            "type": "set second",
     726            "gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
     727            "goods": cmpEntityTrader.GetPreferredGoods()
     728        };
     729    }
     730    else
     731    {
     732        // Else both markets are not null and target is different from them
     733        result = {"type": "set first"};
     734    }
     735    return result;
     736};
     737
    683738GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
    684739{
    685740    var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
     
    738793    "GetFoundationSnapData": 1,
    739794    "PlaySound": 1,
    740795    "FindIdleUnit": 1,
     796    "GetTradingDetails": 1,
    741797
    742798    "SetPathfinderDebugOverlay": 1,
    743799    "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))
     
    10621070            },
    10631071        },
    10641072
     1073        "TRADE": {
     1074            "Attacked": function(msg) {
     1075                // Ignore attack
     1076                // TODO: Inform player
     1077            },
     1078
     1079            "APPROACHINGFIRSTMARKET": {
     1080                "enter": function () {
     1081                    this.SelectAnimation("move");
     1082                },
     1083
     1084                "MoveCompleted": function() {
     1085                    this.PerformTradeAndMoveToNextMarket(this.order.data.firstMarket, this.order.data.secondMarket, "INDIVIDUAL.TRADE.APPROACHINGSECONDMARKET");
     1086                },
     1087            },
     1088
     1089            "APPROACHINGSECONDMARKET": {
     1090                "enter": function () {
     1091                    this.SelectAnimation("move");
     1092                },
     1093
     1094                "MoveCompleted": function() {
     1095                    this.order.data.firstPass = false;
     1096                    this.PerformTradeAndMoveToNextMarket(this.order.data.secondMarket, this.order.data.firstMarket, "INDIVIDUAL.TRADE.APPROACHINGFIRSTMARKET");
     1097                },
     1098            },
     1099        },
     1100
    10651101        "REPAIR": {
    10661102            "APPROACHING": {
    10671103                "enter": function () {
     
    23692405    this.AddOrder("ReturnResource", { "target": target }, queued);
    23702406};
    23712407
     2408UnitAI.prototype.SetupTradeRoute = function(target, queued)
     2409{
     2410    if (!this.CanTrade(target))
     2411    {
     2412        this.WalkToTarget(target, queued);
     2413        return;
     2414    }
     2415
     2416    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2417    var marketsChanged = cmpTrader.SetTargetMarket(target);
     2418    if (marketsChanged)
     2419    {
     2420        if (cmpTrader.HasBothMarkets())
     2421            this.AddOrder("Trade", { "firstMarket": cmpTrader.GetFirstMarket(), "secondMarket": cmpTrader.GetSecondMarket() }, queued);
     2422        else
     2423            this.WalkToTarget(cmpTrader.GetFirstMarket(), queued);
     2424    }
     2425}
     2426
     2427UnitAI.prototype.MoveToMarket = function(targetMarket)
     2428{
     2429    if (this.MoveToTarget(targetMarket))
     2430    {
     2431        // We've started walking to the market
     2432        return true;
     2433    }
     2434    else
     2435    {
     2436        // We can't reach the market.
     2437        // Give up.
     2438        this.StopMoving();
     2439        this.StopTrading();
     2440        return false;
     2441    }
     2442}
     2443
     2444UnitAI.prototype.PerformTradeAndMoveToNextMarket = function(currentMarket, nextMarket, nextFsmStateName)
     2445{
     2446    if (this.CanTrade(currentMarket) && this.CheckTargetRange(currentMarket, IID_Trader))
     2447    {
     2448        this.PerformTrade();
     2449        if (this.MoveToMarket(nextMarket))
     2450        {
     2451            // We've started walking to the next market
     2452            this.SetNextState(nextFsmStateName);
     2453        }
     2454    }
     2455    else
     2456    {
     2457        // Trading impossible or market wasn't reached, give up
     2458        this.StopTrading();
     2459    }
     2460}
     2461
     2462UnitAI.prototype.PerformTrade = function()
     2463{
     2464    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2465    cmpTrader.PerformTrade();
     2466}
     2467
     2468UnitAI.prototype.StopTrading = function()
     2469{
     2470    this.FinishOrder();
     2471    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2472    cmpTrader.StopTrading();
     2473}
     2474
    23722475UnitAI.prototype.Repair = function(target, autocontinue, queued)
    23732476{
    23742477    if (!this.CanRepair(target))
     
    25882691    return true;
    25892692};
    25902693
     2694UnitAI.prototype.CanTrade = function(target)
     2695{
     2696    // Formation controllers should always respond to commands
     2697    // (then the individual units can make up their own minds)
     2698    if (this.IsFormationController())
     2699        return true;
     2700
     2701    // Verify that we're able to respond to Trade commands
     2702    var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     2703    if (!cmpTrader || !cmpTrader.CanTrade(target))
     2704        return false;
     2705
     2706    return true;
     2707}
     2708
    25912709UnitAI.prototype.CanRepair = function(target)
    25922710{
    25932711    // 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    {
     126        // Drop carried goods
     127        this.goods.amount = 0;
     128    }
     129    return marketsChanged;
     130}
     131
     132Trader.prototype.GetFirstMarket = function()
     133{
     134    return this.firstMarket;
     135}
     136
     137Trader.prototype.GetSecondMarket = function()
     138{
     139    return this.secondMarket;
     140}
     141
     142Trader.prototype.HasBothMarkets = function()
     143{
     144    return (this.firstMarket != null) && (this.secondMarket != null);
     145}
     146
     147Trader.prototype.GetPreferredGoods = function()
     148{
     149    return this.preferredGoods;
     150}
     151
     152Trader.prototype.SetPreferredGoods = function(preferredGoods)
     153{
     154    // Check that argument is a correct resource name
     155    if (RESOURCES.indexOf(preferredGoods) == -1)
     156        return;
     157    this.preferredGoods = preferredGoods;
     158}
     159
     160Trader.prototype.CanTrade = function(target)
     161{
     162    var cmpTraderIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     163    var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity);
     164    // Check that the target exists
     165    if (!cmpTargetIdentity)
     166        return false;
     167    var landTradingPossible = cmpTraderIdentity.HasClass("Organic") && cmpTargetIdentity.HasClass("Market");
     168    var seaTradingPossible = cmpTraderIdentity.HasClass("Ship") && cmpTargetIdentity.HasClass("SeaMarket");
     169    if (!landTradingPossible && !seaTradingPossible)
     170        return false;
     171
     172    var cmpTraderPlayer = QueryOwnerInterface(this.entity, IID_Player);
     173    var traderPlayerId = cmpTraderPlayer.GetPlayerID();
     174    var cmpTargetPlayer = QueryOwnerInterface(target, IID_Player);
     175    var targetPlayerId = cmpTargetPlayer.GetPlayerID();
     176    var ownershipSuitableForTrading = (traderPlayerId == targetPlayerId) || cmpTraderPlayer.IsAlly(targetPlayerId);
     177    if (!ownershipSuitableForTrading)
     178        return false;
     179    return true;
     180}
     181
     182Trader.prototype.PerformTrade = function()
     183{
     184    if (this.goods.amount > 0)
     185    {
     186        var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     187        cmpPlayer.AddResource(this.goods.type, this.goods.amount);
     188    }
     189    this.goods.type = this.preferredGoods;
     190    this.goods.amount = this.gain;
     191}
     192
     193Trader.prototype.GetGoods = function()
     194{
     195    return this.goods;
     196}
     197
     198Trader.prototype.StopTrading = function()
     199{
     200    // Drop carried goods
     201    this.goods.amount = 0;
     202    // Reset markets
     203    this.firstMarket = null;
     204    this.secondMarket = null;
     205}
     206
     207// Get range in which deals with market are available,
     208// i.e. trader should be in no more than MaxDistance from market
     209// to be able to trade with it.
     210Trader.prototype.GetRange = function()
     211{
     212    return { "min": 0, "max": +this.template.MaxDistance };
     213}
     214
     215Engine.RegisterComponentType(IID_Trader, "Trader", Trader);
     216
  • 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

     
    3939    <BarHeight>0.5</BarHeight>
    4040    <HeightOffset>6.0</HeightOffset>
    4141  </StatusBars>
     42  <Trader>
     43    <MaxDistance>10.0</MaxDistance>
     44  </Trader>
    4245  <UnitMotion>
    4346    <WalkSpeed>10.5</WalkSpeed>
    4447  </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