Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js	(revision 10258)
+++ 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", "action": command.action, "resource": command.resource});
+}
+
+
 // 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 10258)
+++ binaries/data/mods/public/gui/session/unit_commands.js	(working copy)
@@ -332,7 +332,9 @@
 	{
 		layoutButtonRowCentered(0, guiName, 0, numButtons, COMMANDS_PANEL_WIDTH);
 	}
-	else
+	// exclude case when there is only barter buttons on training panel,
+	// which is processed separatedly in addBarterButtonsToTrainingPanel function
+	else if (!(guiName == "Training" && numButtons == 0))
 	{
 		for (var i = 0; i < numRows; i++)
 			layoutButtonRow(i, guiName, buttonSideLength, buttonSpacer, rowLength*i, rowLength*(i+1) );
@@ -347,13 +349,68 @@
 		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;
+	if (guiName == "Training" && unitEntState.barterMarket)
+	{
+		addBarterButtonsToTrainingPanel(usedPanels, unitEntState, numButtons);
+	}
+	else
+	{
+		// Hide any buttons we're no longer using
+		for (i = numButtons; i < g_unitPanelButtons[guiName]; ++i)
+			getGUIObjectByName("unit"+guiName+"Button["+i+"]").hidden = true;
 
-	g_unitPanelButtons[guiName] = numButtons;
+		g_unitPanelButtons[guiName] = numButtons;
+	}
 }
 
+// add barter buttons to training panel to next row after last row with trainable units
+function addBarterButtonsToTrainingPanel(usedPanels, unitEntState, newButtonsCount)
+{
+	var previousButtonsCount = g_unitPanelButtons["Training"];
+	var rowLength = 8;
+	// index of row where we are going to add barter buttons
+	var rowIndex = 0;
+	// if Training panel have some buttons, calculate row index
+	// for barter buttons
+	if (newButtonsCount > 0)
+	{
+		rowIndex = Math.ceil(newButtonsCount / rowLength);
+	}
+
+	for (var i = 0; i < 6; i++)
+	{
+		var action = ["buy", "sell"][Math.floor(i/3)];
+		var resource = ["food", "wood", "stone"][i%3];
+
+		var buttonIndex = i + rowLength*rowIndex;
+
+		var button = getGUIObjectByName("unitTrainingButton["+buttonIndex+"]");
+		button.hidden = false;
+		button.tooltip = action + " 100 " + resource + " for " + unitEntState.barterPrices[action][resource] + " metal";
+		var command = { "action": action, "resource": resource };
+		button.onpress = (function(e) { return function() { exchangeResources(e) } })(command);
+		
+		var icon = getGUIObjectByName("unitTrainingIcon["+buttonIndex+"]");
+		icon.sprite = "stretched:session/actions/" + action + "_" + resource + ".png";
+	}
+
+	var buttonSideLength = getGUIObjectByName("unitTrainingButton[0]").size.bottom;
+	var buttonSpacer = buttonSideLength+1;
+	layoutButtonRow(rowIndex, "Training", buttonSideLength, buttonSpacer, rowLength*rowIndex, rowLength*(rowIndex+1));
+
+	// Hide any buttons we're no longer using
+	// hide buttons between last unit train button and first barter button
+	for (i = newButtonsCount; i < rowIndex * rowLength; ++i)
+		getGUIObjectByName("unitTrainingButton["+i+"]").hidden = true;
+	// hide buttons after last barter button
+	for (i = rowLength*rowIndex + 6; i < previousButtonsCount; ++i)
+		getGUIObjectByName("unitTrainingButton["+i+"]").hidden = true;
+	// update number of buttons, we suppose that all rows before one with barter buttons
+	// are fully filled (indeed there can be some empty places) and barter buttons row
+	// always have 6 buttons
+	g_unitPanelButtons["Training"] = rowIndex * rowLength + 6;
+}
+
 // Updates right Unit Commands Panel - runs in the main session loop via updateSelectionDetails()
 function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection)
 {
@@ -422,6 +479,10 @@
 				function (trainEntType) { addToTrainingQueue(entState.id, trainEntType); } );
 //			isInvisible = false;
 		}
+		else if (entState.barterMarket)
+		{
+			setupUnitPanel("Training", usedPanels, entState, [], null);
+		}
 
 		if (entState.training && entState.training.queue.length)
 			setupUnitPanel("Queue", usedPanels, entState, entState.training.queue,
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js	(revision 10258)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(working copy)
@@ -319,6 +319,11 @@
 		}
 		break;
 
+	case "barter":
+		var cmpPlayerBarter = Engine.QueryInterface(playerEnt, IID_PlayerBarter);
+		cmpPlayerBarter.ExchangeResources(cmd.action, cmd.resource);
+		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 10258)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -135,6 +135,15 @@
 			"classes": cmpIdentity.GetClassesList(),
 			"selectionGroupName": cmpIdentity.GetSelectionGroupName()
 		};
+
+		if (cmpIdentity.GetClassesList().indexOf("BarterMarket") != -1)
+		{
+			ret.barterMarket = true;
+			var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+			var playerEntity = cmpPlayerManager.GetPlayerByID(player);
+			var cmpPlayerBarter = Engine.QueryInterface(playerEntity, IID_PlayerBarter);
+			ret.barterPrices = cmpPlayerBarter.GetPrices();
+		}
 	}
 	
 	var cmpPosition = Engine.QueryInterface(ent, IID_Position);
Index: binaries/data/mods/public/simulation/components/interfaces/PlayerBarter.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/PlayerBarter.js	(revision 0)
+++ binaries/data/mods/public/simulation/components/interfaces/PlayerBarter.js	(revision 0)
@@ -0,0 +1 @@
+Engine.RegisterInterface("PlayerBarter");
Index: binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- binaries/data/mods/public/simulation/components/Identity.js	(revision 10258)
+++ 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/PlayerBarter.js
===================================================================
--- binaries/data/mods/public/simulation/components/PlayerBarter.js	(revision 0)
+++ binaries/data/mods/public/simulation/components/PlayerBarter.js	(revision 0)
@@ -0,0 +1,109 @@
+// true price of 100 resource in metal (for case if some resource is more worth)
+const TRUE_PRICES = { "food": 100, "wood": 200, "stone": 300 };
+
+// constant part of price different 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 = 25;
+
+// 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 PlayerBarter() {}
+
+PlayerBarter.prototype.Schema =
+	"<a:component type='system'/><empty/>";
+
+PlayerBarter.prototype.Init = function()
+{
+	this.priceDifferences = {};
+	for each (var resource in ["food", "wood", "stone"])
+		this.priceDifferences[resource] = 0;
+	this.restoreTimer = undefined;
+};
+
+PlayerBarter.prototype.GetPrices = function()
+{
+	var prices = { "buy": {}, "sell": {} };
+	for each (var resource in ["food", "wood", "stone"])
+	{
+		prices["buy"][resource] = Math.round(TRUE_PRICES[resource] * (100 + CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100);
+		prices["sell"][resource] = Math.round(TRUE_PRICES[resource] * (100 - CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100);
+	}
+	return prices;
+};
+
+PlayerBarter.prototype.ExchangeResources = function(action, resource)
+{
+	var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
+	var amountsToSubtract = { 
+		"food": 0,
+		"wood": 0,
+		"stone": 0,
+		"metal": 0
+	};
+	var prices = this.GetPrices();
+	switch (action)
+	{
+	case "buy":
+		amountsToSubtract["metal"] = prices["buy"][resource];
+		if (cmpPlayer.TrySubtractResources(amountsToSubtract))
+		{
+			cmpPlayer.AddResource(resource, 100);
+			if (this.priceDifferences[resource] + DIFFERENCE_PER_DEAL < 100 - CONSTANT_DIFFERENCE)
+				this.priceDifferences[resource] += DIFFERENCE_PER_DEAL;
+		}
+		break;
+	case "sell":
+		amountsToSubtract[resource] = 100;
+		if (cmpPlayer.TrySubtractResources(amountsToSubtract))
+		{
+			cmpPlayer.AddResource("metal", prices["sell"][resource]);
+			if (this.priceDifferences[resource] - DIFFERENCE_PER_DEAL > CONSTANT_DIFFERENCE - 100)
+				this.priceDifferences[resource] -= DIFFERENCE_PER_DEAL;
+		}
+		break;
+	}
+	if (this.restoreTimer == undefined)
+	{
+		var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+		this.restoreTimer = cmpTimer.SetTimeout(this.entity, IID_PlayerBarter, "ProgressTimeout", RESTORE_TIMER_INTERVAL, {});
+	}
+};
+
+PlayerBarter.prototype.ProgressTimeout = function(data)
+{
+	this.restoreTimer = undefined;
+
+	var needRestore = false;
+	for each (var resource in ["food", "wood", "stone"])
+	{
+		// calculate value to restore, it should be:
+		// clamping into [-DIFFERENCE_RESTORE; DIFFERENCE_RESTORE] interval
+		var differenceRestore = Math.min(DIFFERENCE_RESTORE, Math.max(-DIFFERENCE_RESTORE, this.priceDifferences[resource]));
+		// change sign, because we need to restore price
+		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_PlayerBarter, "ProgressTimeout", RESTORE_TIMER_INTERVAL, data);
+	}
+}
+
+Engine.RegisterComponentType(IID_PlayerBarter, "PlayerBarter", PlayerBarter);
+
Index: binaries/data/mods/public/simulation/templates/special/player.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/special/player.xml	(revision 10258)
+++ binaries/data/mods/public/simulation/templates/special/player.xml	(working copy)
@@ -9,5 +9,6 @@
   	</Limits>
   </BuildLimits>
   <Player/>
+  <PlayerBarter/>
   <StatisticsTracker/>
 </Entity>
Index: binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml	(revision 10258)
+++ 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/actions/buy_food.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/actions/buy_food.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/actions/sell_food.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/actions/sell_food.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/actions/buy_wood.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/actions/buy_wood.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/actions/buy_stone.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/actions/buy_stone.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/actions/sell_wood.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/actions/sell_wood.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: binaries/data/mods/public/art/textures/ui/session/actions/sell_stone.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/actions/sell_stone.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

