Index: source/simulation2/Simulation2.cpp
===================================================================
--- source/simulation2/Simulation2.cpp	(revision 10574)
+++ source/simulation2/Simulation2.cpp	(working copy)
@@ -120,6 +120,7 @@
 			componentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
 
 			LOAD_SCRIPTED_COMPONENT("AIInterface");
+			LOAD_SCRIPTED_COMPONENT("Barter");
 			LOAD_SCRIPTED_COMPONENT("EndGameManager");
 			LOAD_SCRIPTED_COMPONENT("GuiInterface");
 			LOAD_SCRIPTED_COMPONENT("PlayerManager");
Index: binaries/data/mods/public/gui/session/session.xml
===================================================================
--- binaries/data/mods/public/gui/session/session.xml	(revision 10574)
+++ binaries/data/mods/public/gui/session/session.xml	(working copy)
@@ -524,6 +524,31 @@
 					</object>
 				</object>
 
+				<object name="unitBarterPanel" hidden="true"
+					size="5 5 100% 100%"
+				>
+					<object ghost="true" style="resourceText" type="text" size="0 0 100% 18">Exchange resources:</object>
+					<object size="0 18 100% 64">
+						<repeat count="4">
+							<object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
+								<object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
+								<object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
+							</object>
+						</repeat>
+					</object>
+					<object size="0 64 100% 110">
+						<repeat count="4">
+							<object name="unitBarterBuyButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
+								<object name="unitBarterBuyIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
+								<object name="unitBarterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
+							</object>
+						</repeat>
+					</object>
+					<object name="PerformDealButton" type="button" style="StoneButton" size="2 112 100%-7 142">
+						<object ghost="true" style="statsText" type="text" size="0 0 100% 100%">Barter</object>
+					</object>
+				</object>
+
 				<!-- Stance Selection -->
 				<object name="unitStancePanel"
 					style="TranslucentPanel"
Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js	(revision 10574)
+++ binaries/data/mods/public/gui/session/input.js	(working copy)
@@ -1008,6 +1008,13 @@
 	inputState = INPUT_BUILDING_PLACEMENT;
 }
 
+// Called by GUI when user clicks exchange resources button
+function exchangeResources(command)
+{
+	Engine.PostNetworkCommand({"type": "barter", "sell": command.sell, "buy": command.buy, "amount": command.amount});
+}
+
+
 // Batch training:
 // When the user shift-clicks, we set these variables and switch to INPUT_BATCHTRAINING
 // When the user releases shift, or clicks on a different training button, we create the batched units
Index: binaries/data/mods/public/gui/session/unit_commands.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_commands.js	(revision 10574)
+++ binaries/data/mods/public/gui/session/unit_commands.js	(working copy)
@@ -13,12 +13,22 @@
 const UNIT_PANEL_BASE = -52; // QUEUE: The offset above the main panel (will often be negative)
 const UNIT_PANEL_HEIGHT = 44; // QUEUE: The height needed for a row of buttons
 
+// Barter constants
+const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
+const BARTER_BUNCH_MULTIPLIER = 5;
+const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
+const BARTER_ACTIONS = ["Sell", "Buy"];
+
 // The number of currently visible buttons (used to optimise showing/hiding)
-var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Construction": 0, "Command": 0, "Stance": 0};
+var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, "Command": 0, "Stance": 0};
 
 // Unit panels are panels with row(s) of buttons
-var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Construction", "Research", "Stance", "Command"];
+var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", "Research", "Stance", "Command"];
 
+// Indexes of resources to sell and buy on barter panel
+var g_barterSell = 0;
+var g_barterBuy = 1;
+
 // Lay out a row of centered buttons (does not work inside a loop like the other function)
 function layoutButtonRowCentered(rowNumber, guiName, startIndex, endIndex, width)
 {
@@ -108,6 +118,16 @@
 	}
 }
 
+function selectBarterResourceToSell(resourceIndex)
+{
+	g_barterSell = resourceIndex;
+	// g_barterBuy should be set to different value in case if it is the same as g_barterSell
+	// (it is no make sense to exchange resource to the same one).
+	// We change it cyclic to next value.
+	if (g_barterBuy == g_barterSell)
+		g_barterBuy = (g_barterBuy + 1) % BARTER_RESOURCES.length;
+}
+
 // Sets up "unit panels" - the panels with rows of icons (Helper function for updateUnitDisplay)
 function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
 {
@@ -368,6 +388,65 @@
 	g_unitPanelButtons[guiName] = numButtons;
 }
 
+// Sets up "unit barter panel" - special case for setupUnitPanel
+function setupUnitBarterPanel(unitEntState)
+{
+	// Amount of player's resource to exchange
+	var amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
+	if (Engine.HotkeyIsPressed("session.massbarter"))
+		amountToSell *= BARTER_BUNCH_MULTIPLIER;
+	// One pass for each resource
+	for (var i = 0; i < BARTER_RESOURCES.length; i++)
+	{
+		var resource = BARTER_RESOURCES[i];
+		// One pass for 'sell' row and another for 'buy'
+		for (var j = 0; j < 2; j++)
+		{
+			var selectedResourceIndex = [g_barterSell, g_barterBuy][j];
+			var action = BARTER_ACTIONS[j];
+
+			var imageNameSuffix = (i == selectedResourceIndex) ? "selected" : "inactive";
+			var icon = getGUIObjectByName("unitBarter" + action + "Icon["+i+"]");
+
+			var button = getGUIObjectByName("unitBarter" + action + "Button["+i+"]");
+			button.size = (i * 46) + " 0 " + ((i + 1) * 46) + " 46";
+			var amountToBuy;
+			// In 'buy' row show black icon in place corresponding to selected resource in 'sell' row
+			if (j == 1 && i == g_barterSell)
+			{
+				button.enabled = false;
+				button.tooltip = "";
+				icon.sprite = "";
+				amountToBuy = "";
+			}
+			else
+			{
+				button.enabled = true;
+				button.tooltip = action + " " + resource;
+				icon.sprite = "stretched:session/resources/" + resource + "_" + imageNameSuffix + ".png";
+				var sellPrice = unitEntState.barterMarket.prices["sell"][BARTER_RESOURCES[g_barterSell]];
+				var buyPrice = unitEntState.barterMarket.prices["buy"][resource];
+				amountToBuy = "+" + Math.round(sellPrice / buyPrice * amountToSell);
+			}
+			var amount;
+			if (j == 0)
+			{
+				button.onpress = (function(i){ return function() { selectBarterResourceToSell(i); } })(i);
+				amount = (i == g_barterSell) ? "-" + amountToSell : "";
+			}
+			else
+			{
+				button.onpress = (function(i){ return function() { g_barterBuy = i; } })(i);
+				amount = amountToBuy;
+			}
+			getGUIObjectByName("unitBarter" + action + "Amount["+i+"]").caption = amount;
+		}
+	}
+	var performDealButton = getGUIObjectByName("PerformDealButton");
+	var exchangeResourcesParameters = { "sell": BARTER_RESOURCES[g_barterSell], "buy": BARTER_RESOURCES[g_barterBuy], "amount": amountToSell };
+	performDealButton.onpress = (function(exchangeResourcesParameters){ return function() { exchangeResources(exchangeResourcesParameters) } })(exchangeResourcesParameters);
+}
+
 // Updates right Unit Commands Panel - runs in the main session loop via updateSelectionDetails()
 function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection)
 {
@@ -424,6 +503,13 @@
 				function (item) { performStance(entState.id, item); } );
 		}
 
+		getGUIObjectByName("unitBarterPanel").hidden = !entState.barterMarket;
+		if (entState.barterMarket)
+		{
+			usedPanels["Barter"] = 1;
+			setupUnitBarterPanel(entState);
+		}
+
 		if (entState.buildEntities && entState.buildEntities.length)
 		{
 			setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement);
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js	(revision 10574)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(working copy)
@@ -392,6 +392,11 @@
 		}
 		break;
 
+	case "barter":
+		var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
+		cmpBarter.ExchangeResources(playerEnt, cmd.sell, cmd.buy, cmd.amount);
+		break;
+
 	default:
 		error("Invalid command: unknown command type: "+uneval(cmd));
 	}
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(revision 10574)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -254,6 +254,12 @@
 		};
 	}
 
+	if (!cmpFoundation && cmpIdentity.HasClass("BarterMarket"))
+	{
+		var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
+		ret.barterMarket = { "prices": cmpBarter.GetPrices() };
+	}
+
 	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
 	ret.visibility = cmpRangeManager.GetLosVisibility(ent, player);
 
Index: binaries/data/mods/public/simulation/components/interfaces/Barter.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Barter.js	(revision 0)
+++ binaries/data/mods/public/simulation/components/interfaces/Barter.js	(revision 0)
@@ -0,0 +1 @@
+Engine.RegisterInterface("Barter");
Index: binaries/data/mods/public/simulation/components/Barter.js
===================================================================
--- binaries/data/mods/public/simulation/components/Barter.js	(revision 0)
+++ binaries/data/mods/public/simulation/components/Barter.js	(revision 0)
@@ -0,0 +1,138 @@
+// True price of 100 units of resource (for case if some resource is more worth).
+// With current bartering system only relative values makes sense
+// so if for example stone is two times more expensive than wood,
+// there will 2:1 exchange rate.
+const TRUE_PRICES = { "food": 100, "wood": 100, "stone": 100, "metal": 100 };
+
+// Constant part of price difference between true price and buy/sell price.
+// In percents.
+// Buy price equal to true price plus constant difference.
+// Sell price equal to true price minus constant difference.
+const CONSTANT_DIFFERENCE = 10;
+
+// Additional difference of prices, added after each deal to specified resource price.
+// In percents.
+const DIFFERENCE_PER_DEAL = 5;
+
+// Price difference which restored each restore timer tick
+// In percents.
+const DIFFERENCE_RESTORE = 2;
+
+// Interval of timer which slowly restore prices after deals
+const RESTORE_TIMER_INTERVAL = 5000;
+
+// Array of resource names
+const RESOURCES = ["food", "wood", "stone", "metal"];
+
+function Barter() {}
+
+Barter.prototype.Schema =
+	"<a:component type='system'/><empty/>";
+
+Barter.prototype.Init = function()
+{
+	this.priceDifferences = {};
+	for each (var resource in RESOURCES)
+		this.priceDifferences[resource] = 0;
+	this.restoreTimer = undefined;
+};
+
+Barter.prototype.GetPrices = function()
+{
+	var prices = { "buy": {}, "sell": {} };
+	for each (var resource in RESOURCES)
+	{
+		prices["buy"][resource] = TRUE_PRICES[resource] * (100 + CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
+		prices["sell"][resource] = TRUE_PRICES[resource] * (100 - CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
+	}
+	return prices;
+};
+
+Barter.prototype.PlayerHasMarket = function(playerEntity)
+{
+	var cmpPlayer = Engine.QueryInterface(playerEntity, IID_Player);
+	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	var entities = cmpRangeManager.GetEntitiesByPlayer(cmpPlayer.GetPlayerID());
+	for each (var entity in entities)
+	{
+		var cmpFoundation = Engine.QueryInterface(entity, IID_Foundation);
+		var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+		if (!cmpFoundation && cmpIdentity.HasClass("BarterMarket"))
+			return true;
+	}
+	return false;
+}
+
+Barter.prototype.ExchangeResources = function(playerEntity, resourceToSell, resourceToBuy, amount)
+{
+	// Data verification
+	if (amount <= 0)
+	{
+		warn("ExchangeResources: incorrect amount: " + uneval(amount));
+		return;
+	}
+	if (RESOURCES.indexOf(resourceToSell) == -1)
+	{
+		warn("ExchangeResources: incorrect resource to sell: " + uneval(resourceToSell));
+		return;
+	}
+	if (RESOURCES.indexOf(resourceToBuy) == -1)
+	{
+		warn("ExchangeResources: incorrect resource to buy: " + uneval(resourceToBuy));
+		return;
+	}
+	if (!this.PlayerHasMarket(playerEntity))
+	{
+		warn("ExchangeResources: player has no markets");
+		return;
+	}
+
+	var cmpPlayer = Engine.QueryInterface(playerEntity, IID_Player);
+	var prices = this.GetPrices();
+	var amountsToSubtract = {};
+	amountsToSubtract[resourceToSell] = amount;
+	if (cmpPlayer.TrySubtractResources(amountsToSubtract))
+	{
+		var amountToAdd = Math.round(prices["sell"][resourceToSell] / prices["buy"][resourceToBuy] * amount);
+		cmpPlayer.AddResource(resourceToBuy, amountToAdd);
+		var numberOfDeals = Math.round(amount / 100);
+
+		// Increase price difference for both exchange resources.
+		// Overal price difference (constant + dynamic) can't exceed +-99%
+		// so both buy/sell prices limited to [1%; 199%] interval.
+		this.priceDifferences[resourceToSell] -= DIFFERENCE_PER_DEAL * numberOfDeals;
+		this.priceDifferences[resourceToSell] = Math.min(99-CONSTANT_DIFFERENCE, Math.max(CONSTANT_DIFFERENCE-99, this.priceDifferences[resourceToSell]));
+		this.priceDifferences[resourceToBuy] += DIFFERENCE_PER_DEAL * numberOfDeals;
+		this.priceDifferences[resourceToBuy] = Math.min(99-CONSTANT_DIFFERENCE, Math.max(CONSTANT_DIFFERENCE-99, this.priceDifferences[resourceToBuy]));
+	}
+
+	if (this.restoreTimer == undefined)
+	{
+		var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+		this.restoreTimer = cmpTimer.SetInterval(this.entity, IID_Barter, "ProgressTimeout", RESTORE_TIMER_INTERVAL, RESTORE_TIMER_INTERVAL, {});
+	}
+};
+
+Barter.prototype.ProgressTimeout = function(data)
+{
+	var needRestore = false;
+	for each (var resource in RESOURCES)
+	{
+		// Calculate value to restore, it should be limited to [-DIFFERENCE_RESTORE; DIFFERENCE_RESTORE] interval
+		var differenceRestore = Math.min(DIFFERENCE_RESTORE, Math.max(-DIFFERENCE_RESTORE, this.priceDifferences[resource]));
+		differenceRestore = -differenceRestore;
+		this.priceDifferences[resource] += differenceRestore;
+		// If price difference still exists then set flag to run timer again
+		if (this.priceDifferences[resource] != 0)
+			needRestore = true;
+	}
+
+	if (!needRestore)
+	{
+		var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+		cmpTimer.CancelTimer(this.restoreTimer);
+	}
+}
+
+Engine.RegisterComponentType(IID_Barter, "Barter", Barter);
+
Index: binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- binaries/data/mods/public/simulation/components/Identity.js	(revision 10574)
+++ binaries/data/mods/public/simulation/components/Identity.js	(working copy)
@@ -82,6 +82,7 @@
 						"<value>CivCentre</value>" +
 						"<value>Economic</value>" +
 						"<value>Defensive</value>" +
+						"<value>BarterMarket</value>" +
 						"<value>Village</value>" +
 						"<value>Town</value>" +
 						"<value>City</value>" +
Index: binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml	(revision 10574)
+++ binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml	(working copy)
@@ -23,8 +23,8 @@
   </Health>
   <Identity>
     <GenericName>Market</GenericName>
-    <Tooltip>Create Trade units and Barter resources. (Currently a useless structure)</Tooltip>
-    <Classes datatype="tokens">Town</Classes>
+    <Tooltip>Create Trade units and Barter resources.</Tooltip>
+    <Classes datatype="tokens">Town BarterMarket</Classes>
     <Icon>structures/market.png</Icon>
   </Identity>
   <Obstruction>
Index: binaries/data/mods/public/art/textures/ui/session/resources/food_selected.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: binaries/data/mods/public/art/textures/ui/session/resources/food_selected.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/resources/wood_selected.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: binaries/data/mods/public/art/textures/ui/session/resources/wood_selected.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/resources/stone_selected.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: binaries/data/mods/public/art/textures/ui/session/resources/stone_selected.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/resources/metal_inactive.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: binaries/data/mods/public/art/textures/ui/session/resources/metal_inactive.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/resources/food_inactive.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: binaries/data/mods/public/art/textures/ui/session/resources/food_inactive.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/resources/wood_inactive.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: binaries/data/mods/public/art/textures/ui/session/resources/wood_inactive.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/resources/stone_inactive.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: binaries/data/mods/public/art/textures/ui/session/resources/stone_inactive.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/resources/metal_selected.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: binaries/data/mods/public/art/textures/ui/session/resources/metal_selected.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg	(revision 10574)
+++ binaries/data/config/default.cfg	(working copy)
@@ -179,6 +179,7 @@
 hotkey.session.garrison = Ctrl              ; Modifier to garrison when clicking on building
 hotkey.session.queue = Shift                ; Modifier to queue unit orders instead of replacing
 hotkey.session.batchtrain = Shift           ; Modifier to train units in batches
+hotkey.session.massbarter = Shift           ; Modifier to barter bunch of resources
 hotkey.session.deselectgroup = Ctrl         ; Modifier to deselect units when clicking group icon, instead of selecting
 hotkey.session.rotate.cw = RightBracket     ; Rotate building placement preview clockwise
 hotkey.session.rotate.ccw = LeftBracket     ; Rotate building placement preview anticlockwise
