Index: source/simulation2/Simulation2.cpp
===================================================================
--- source/simulation2/Simulation2.cpp	(revision 10304)
+++ source/simulation2/Simulation2.cpp	(working copy)
@@ -101,6 +101,7 @@
 				LOGERROR(L"Can't find component type " L##name); \
 			m_ComponentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
 
+			LOAD_SCRIPTED_COMPONENT("GlobalBarter");
 			LOAD_SCRIPTED_COMPONENT("AIInterface");
 			LOAD_SCRIPTED_COMPONENT("EndGameManager");
 			LOAD_SCRIPTED_COMPONENT("GuiInterface");
Index: binaries/data/mods/public/gui/session/session.xml
===================================================================
--- binaries/data/mods/public/gui/session/session.xml	(revision 10304)
+++ binaries/data/mods/public/gui/session/session.xml	(working copy)
@@ -493,6 +493,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" z="100">
+								<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" z="100">
+								<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" z="100">
+						<object ghost="true" style="statsText" type="text" size="0 0 100% 100%">Perform deal</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 10304)
+++ 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 10304)
+++ binaries/data/mods/public/gui/session/unit_commands.js	(working copy)
@@ -14,11 +14,15 @@
 const UNIT_PANEL_HEIGHT = 44; // QUEUE: The height needed for a row of buttons
 
 // 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 +112,15 @@
 	}
 }
 
+function selectBarterResourceToSell(resourceIndex)
+{
+	g_barterSell = resourceIndex;
+	// barter to should be set to different value in case if it is the same
+	// we change cyclic it to next value
+	if (g_barterBuy == g_barterSell) 
+		g_barterBuy = (g_barterBuy + 5) % 4;
+}
+
 // Sets up "unit panels" - the panels with rows of icons (Helper function for updateUnitDisplay)
 function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
 {
@@ -323,30 +336,82 @@
 	else if (guiName == "Formation" || guiName == "Garrison" || guiName == "Command")
 		rowLength = 4;
 
-	var numRows = Math.ceil(numButtons / rowLength);
-	var buttonSideLength = getGUIObjectByName("unit"+guiName+"Button[0]").size.bottom;
-	var buttonSpacer = buttonSideLength+1;
+	if (guiName == "Barter")
+	{
+		// amount of player's resource to exchange
+		var amountToSell = Engine.HotkeyIsPressed("session.massbarter") ? 500 : 100;
+		// for each resource
+		for (var i = 0; i < 4; i++)
+		{
+			var resources = ["food", "wood", "stone", "metal"];
+			var resource = resources[i%4];
+			// one pass for 'sell' row and another for 'buy'
+			for (var j = 0; j < 2; j++)
+			{
+				var selectedResourceIndex = [g_barterSell, g_barterBuy][j%2];
+				var action = ["Sell", "Buy"][j%2];
 
-	// Layout buttons
-	if (guiName == "Command")
-	{
-		layoutButtonRowCentered(0, guiName, 0, numButtons, COMMANDS_PANEL_WIDTH);
+				var button = getGUIObjectByName("unitBarter" + action + "Button["+i+"]");
+
+				button.size = (i * 46) + " 0 " + ((i + 1) * 46) + " 46";
+				if (j == 0)
+					button.onpress = (function(e){ return function() { selectBarterResourceToSell(e); } })(i);
+				else
+					button.onpress = (function(e){ return function() { g_barterBuy = e; } })(i);
+
+				var imageNameSuffix = (i == selectedResourceIndex) ? "selected" : "inactive";
+				var icon = getGUIObjectByName("unitBarter" + action + "Icon["+i+"]");
+
+				getGUIObjectByName("unitBarterSellAmount["+i+"]").caption = (i == g_barterSell) ? "-" + amountToSell : "";
+				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";
+					amountToBuy = "+" + Math.round(unitEntState.barterPrices["sell"][resources[g_barterSell]] / unitEntState.barterPrices["buy"][resource] * amountToSell);
+				}
+				getGUIObjectByName("unitBarterBuyAmount["+i+"]").caption = amountToBuy;
+			}
+
+			performDealButton = getGUIObjectByName("PerformDealButton");
+			performDealButton.onpress = (function(e){ return function() { exchangeResources(e) } })({"sell": resources[g_barterSell], "buy": resources[g_barterBuy], "amount": amountToSell});
+		}
 	}
 	else
 	{
-		for (var i = 0; i < numRows; i++)
-			layoutButtonRow(i, guiName, buttonSideLength, buttonSpacer, rowLength*i, rowLength*(i+1) );
-	}
+		var numRows = Math.ceil(numButtons / rowLength);
+		var buttonSideLength = getGUIObjectByName("unit"+guiName+"Button[0]").size.bottom;
+		var buttonSpacer = buttonSideLength+1;
 
-	// Resize Queue panel if needed
-	if (guiName == "Queue") // or garrison
-	{
-		var panel = getGUIObjectByName("unitQueuePanel");
-		var size = panel.size;
-		size.top = (UNIT_PANEL_BASE - ((numRows-1)*UNIT_PANEL_HEIGHT));
-		panel.size = size;
+		// Layout buttons
+		if (guiName == "Command")
+		{
+			layoutButtonRowCentered(0, guiName, 0, numButtons, COMMANDS_PANEL_WIDTH);
+		}
+		else
+		{
+			for (var i = 0; i < numRows; i++)
+				layoutButtonRow(i, guiName, buttonSideLength, buttonSpacer, rowLength*i, rowLength*(i+1) );
+		}
+
+		// Resize Queue panel if needed
+		if (guiName == "Queue") // or garrison
+		{
+			var panel = getGUIObjectByName("unitQueuePanel");
+			var size = panel.size;
+			size.top = (UNIT_PANEL_BASE - ((numRows-1)*UNIT_PANEL_HEIGHT));
+			panel.size = size;
+		}
 	}
-
 	// Hide any buttons we're no longer using
 	for (i = numButtons; i < g_unitPanelButtons[guiName]; ++i)
 		getGUIObjectByName("unit"+guiName+"Button["+i+"]").hidden = true;
@@ -410,6 +475,12 @@
 				function (item) { performStance(entState.id, item); } );
 		}
 
+		getGUIObjectByName("unitBarterPanel").hidden = !entState.barterMarket;
+		if (entState.barterMarket)
+		{
+			setupUnitPanel("Barter", usedPanels, entState, [], null);
+		}
+
 		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 10304)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(working copy)
@@ -319,6 +319,11 @@
 		}
 		break;
 
+	case "barter":
+		var cmpGlobalBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_GlobalBarter);
+		cmpGlobalBarter.ExchangeResources(playerEnt, cmd.sell, cmd.buy, cmd.amount);
+		break;
+
 	default:
 		error("Ignoring unrecognised command type '" + cmd.type + "'");
 	}
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(revision 10304)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -252,6 +252,13 @@
 		};
 	}
 
+	if (!cmpFoundation && cmpIdentity.GetClassesList().indexOf("BarterMarket") != -1)
+	{
+		ret.barterMarket = true;
+		var cmpGlobalBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_GlobalBarter);
+		ret.barterPrices = cmpGlobalBarter.GetPrices();
+	}
+
 	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
 	ret.visibility = cmpRangeManager.GetLosVisibility(ent, player);
 
Index: binaries/data/mods/public/simulation/components/interfaces/GlobalBarter.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/GlobalBarter.js	(revision 0)
+++ binaries/data/mods/public/simulation/components/interfaces/GlobalBarter.js	(revision 0)
@@ -0,0 +1 @@
+Engine.RegisterInterface("GlobalBarter");
Index: binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- binaries/data/mods/public/simulation/components/Identity.js	(revision 10304)
+++ binaries/data/mods/public/simulation/components/Identity.js	(working copy)
@@ -81,6 +81,7 @@
 						"<value>Civic</value>" +
 						"<value>CivCentre</value>" +
 						"<value>Economic</value>" +
+						"<value>BarterMarket</value>" +
 						"<value>Defensive</value>" +
 						"<value>Village</value>" +
 						"<value>Town</value>" +
Index: binaries/data/mods/public/simulation/components/GlobalBarter.js
===================================================================
--- binaries/data/mods/public/simulation/components/GlobalBarter.js	(revision 0)
+++ binaries/data/mods/public/simulation/components/GlobalBarter.js	(revision 0)
@@ -0,0 +1,107 @@
+// true price of 100 units of resource (for case if some resource is more worth)
+// with current bartering system only relarive 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;
+
+function GlobalBarter() {}
+
+GlobalBarter.prototype.Schema =
+	"<a:component type='system'/><empty/>";
+
+GlobalBarter.prototype.Init = function()
+{
+	this.priceDifferences = {};
+	for each (var resource in ["food", "wood", "stone", "metal"])
+		this.priceDifferences[resource] = 0;
+	this.restoreTimer = undefined;
+};
+
+GlobalBarter.prototype.GetPrices = function()
+{
+	var prices = { "buy": {}, "sell": {} };
+	for each (var resource in ["food", "wood", "stone", "metal"])
+	{
+		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;
+};
+
+GlobalBarter.prototype.ExchangeResources = function(playerEntity, resourceToSell, resourceToBuy, amount)
+{
+	var cmpPlayer = Engine.QueryInterface(playerEntity, IID_Player);
+	var amountsToSubtract = { 
+		"food": 0,
+		"wood": 0,
+		"stone": 0,
+		"metal": 0
+	};
+	
+	var prices = this.GetPrices();
+
+	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 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.SetTimeout(this.entity, IID_GlobalBarter, "ProgressTimeout", RESTORE_TIMER_INTERVAL, {});
+	}
+};
+
+GlobalBarter.prototype.ProgressTimeout = function(data)
+{
+	this.restoreTimer = undefined;
+
+	var needRestore = false;
+	for each (var resource in ["food", "wood", "stone", "metal"])
+	{
+		// 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);
+		this.restoreTimer = cmpTimer.SetTimeout(this.entity, IID_GlobalBarter, "ProgressTimeout", RESTORE_TIMER_INTERVAL, data);
+	}
+}
+
+Engine.RegisterComponentType(IID_GlobalBarter, "GlobalBarter", GlobalBarter);
+
Index: binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml	(revision 10304)
+++ 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/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/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/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/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/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/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/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg	(revision 10304)
+++ binaries/data/config/default.cfg	(working copy)
@@ -176,6 +176,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
