Index: binaries/data/mods/public/gui/session/unit_commands.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_commands.js	(revision 11045)
+++ binaries/data/mods/public/gui/session/unit_commands.js	(working copy)
@@ -129,7 +129,7 @@
 }
 
 // Sets up "unit panels" - the panels with rows of icons (Helper function for updateUnitDisplay)
-function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
+function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, callback)
 {
 	usedPanels[guiName] = 1;
 	var numberOfItems = items.length;
@@ -253,7 +253,6 @@
 					tooltip += "\n" + getEntitySpeed(template);
 
 				tooltip += "\n\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train " + trainNum + ".[/font]";
-
 				break;
 
 			case CONSTRUCTION:
@@ -338,6 +337,13 @@
 			icon.sprite = "stretched:session/icons/single/" + item.icon;
 
 		}
+		else if (guiName == "Training")
+		{
+			if (template.icon)
+			{
+				icon.sprite = "stretched:session/portraits/" + template.icon;
+			}
+		}
 		else if (template.icon)
 		{
 			icon.sprite = "stretched:session/portraits/" + template.icon;
@@ -390,7 +396,7 @@
 }
 
 // Sets up "unit barter panel" - special case for setupUnitPanel
-function setupUnitBarterPanel(unitEntState)
+function setupUnitBarterPanel(unitEntState, playerState)
 {
 	// Amount of player's resource to exchange
 	var amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
@@ -460,6 +466,11 @@
 	var player = Engine.GetPlayerID();
 	if (entState.player == player || g_DevSettings.controlAll)
 	{
+		// Get player state to check some constraints
+		// e.g. presence of a hero or build limits
+		var simState = Engine.GuiInterfaceCall("GetSimulationState");
+		var playerState = simState.players[player];
+
 		if (entState.attack) // TODO - this should be based on some AI properties
 		{
 			//usedPanels["Stance"] = 1;
@@ -472,26 +483,26 @@
 		}
 
 		if (selection.length > 1)
-			setupUnitPanel("Selection", usedPanels, entState, g_Selection.groups.getTemplateNames(),
+			setupUnitPanel("Selection", usedPanels, entState, playerState, g_Selection.groups.getTemplateNames(),
 				function (entType) { changePrimarySelectionGroup(entType); } );
 
 		var commands = getEntityCommandsList(entState);
 		if (commands.length)
-			setupUnitPanel("Command", usedPanels, entState, commands,
+			setupUnitPanel("Command", usedPanels, entState, playerState, commands,
 				function (item) { performCommand(entState.id, item.name); } );
 
 		if (entState.garrisonHolder)
 		{
 			var groups = new EntityGroups();
 			groups.add(entState.garrisonHolder.entities);
-			setupUnitPanel("Garrison", usedPanels, entState, groups.getTemplateNames(),
+			setupUnitPanel("Garrison", usedPanels, entState, playerState, groups.getTemplateNames(),
 				function (item) { unload(entState.id, groups.getEntsByName(item)[0]); } );
 		}
 
 		var formations = getEntityFormationsList(entState);
 		if (isUnit(entState) && !isAnimal(entState) && !entState.garrisonHolder && formations.length)
 		{
-			setupUnitPanel("Formation", usedPanels, entState, formations,
+			setupUnitPanel("Formation", usedPanels, entState, playerState, formations,
 				function (item) { performFormation(entState.id, item); } );
 		}
 
@@ -500,7 +511,7 @@
 		var stances = ["violent", "aggressive", "passive", "defensive", "stand"];
 		if (isUnit(entState) && !isAnimal(entState) && !entState.garrisonHolder && stances.length)
 		{
-			setupUnitPanel("Stance", usedPanels, entState, stances,
+			setupUnitPanel("Stance", usedPanels, entState, playerState, stances,
 				function (item) { performStance(entState.id, item); } );
 		}
 
@@ -508,24 +519,24 @@
 		if (entState.barterMarket)
 		{
 			usedPanels["Barter"] = 1;
-			setupUnitBarterPanel(entState);
+			setupUnitBarterPanel(entState, playerState);
 		}
 
 		if (entState.buildEntities && entState.buildEntities.length)
 		{
-			setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement);
+			setupUnitPanel("Construction", usedPanels, entState, playerState, entState.buildEntities, startBuildingPlacement);
 //			isInvisible = false;
 		}
 
 		if (entState.training && entState.training.entities.length)
 		{
-			setupUnitPanel("Training", usedPanels, entState, entState.training.entities,
+			setupUnitPanel("Training", usedPanels, entState, playerState, entState.training.entities,
 				function (trainEntType) { addToTrainingQueue(entState.id, trainEntType); } );
 //			isInvisible = false;
 		}
 
 		if (entState.training && entState.training.queue.length)
-			setupUnitPanel("Queue", usedPanels, entState, entState.training.queue,
+			setupUnitPanel("Queue", usedPanels, entState, playerState, entState.training.queue,
 				function (item) { removeFromTrainingQueue(entState.id, item.id); } );
 
 //		supplementalDetailsPanel.hidden = false;
Index: binaries/data/mods/public/simulation/components/Player.js
===================================================================
--- binaries/data/mods/public/simulation/components/Player.js	(revision 11045)
+++ binaries/data/mods/public/simulation/components/Player.js	(working copy)
@@ -24,6 +24,7 @@
 	this.state = "active"; // game state - one of "active", "defeated", "won"
 	this.diplomacy = [];	// array of diplomatic stances for this player with respect to other players (including gaia and self)
 	this.conquestCriticalEntitiesCount = 0; // number of owned units with ConquestCritical class
+	this.heroesCount = 0; // number of owned units with Hero class
 	this.phase = "village";
 	this.startCam = undefined;
 	this.controlAllUnits = false;
@@ -328,23 +329,23 @@
 Player.prototype.OnGlobalOwnershipChanged = function(msg)
 {
 	var isConquestCritical = false;
-
+	var isHero = false;
 	// Load class list only if we're going to need it
 	if (msg.from == this.playerID || msg.to == this.playerID)
 	{
 		var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
 		if (cmpIdentity)
 		{
-			var classes = cmpIdentity.GetClassesList();
-			isConquestCritical = classes.indexOf("ConquestCritical") != -1;
+			isConquestCritical = cmpIdentity.HasClass("ConquestCritical");
+			isHero = cmpIdentity.HasClass("Hero");
 		}
 	}
-	
 	if (msg.from == this.playerID)
 	{
 		if (isConquestCritical)
-			this.conquestCriticalEntitiesCount--;	
-
+			this.conquestCriticalEntitiesCount--;
+		if (isHero)
+			this.heroesCount--;
 		var cost = Engine.QueryInterface(msg.entity, IID_Cost);
 		if (cost)
 		{
@@ -352,12 +353,12 @@
 			this.popBonuses -= cost.GetPopBonus();
 		}
 	}
-	
 	if (msg.to == this.playerID)
 	{
 		if (isConquestCritical)
 			this.conquestCriticalEntitiesCount++;
-			
+		if (isHero)
+			this.heroesCount++;
 		var cost = Engine.QueryInterface(msg.entity, IID_Cost);
 		if (cost)
 		{
@@ -382,4 +383,19 @@
 	}
 };
 
+Player.prototype.IncreaseHeroesCount = function()
+{
+	this.heroesCount++;
+}
+
+Player.prototype.DecreaseHeroesCount = function()
+{
+	this.heroesCount--;
+}
+
+Player.prototype.HasHero = function()
+{
+	return this.heroesCount != 0;
+}
+
 Engine.RegisterComponentType(IID_Player, "Player", Player);
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(revision 11045)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -70,6 +70,7 @@
 			"state": cmpPlayer.GetState(),
 			"team": cmpPlayer.GetTeam(),
 			"phase": cmpPlayer.GetPhase(),
+			"hasHero": cmpPlayer.HasHero(),
 			"isAlly": allies,
 			"isEnemy": enemies,
 			"buildLimits": cmpPlayerBuildLimits.GetLimits(),
Index: binaries/data/mods/public/simulation/components/TrainingQueue.js
===================================================================
--- binaries/data/mods/public/simulation/components/TrainingQueue.js	(revision 11045)
+++ binaries/data/mods/public/simulation/components/TrainingQueue.js	(working copy)
@@ -90,6 +90,11 @@
 		if (!cmpPlayer.TrySubtractResources(totalCosts))
 			return;
 
+		// If we train a hero, state this in player compoment
+		var isHero = TemplateHasIdentityClass(template, "Hero");
+		if (isHero)
+			cmpPlayer.IncreaseHeroesCount();
+
 		this.queue.push({
 			"id": this.nextID++,
 			"player": cmpPlayer.GetPlayerID(),
@@ -102,6 +107,7 @@
 			"timeTotal": time*1000,
 			"timeRemaining": time*1000,
 		});
+
 		Engine.PostMessage(this.entity, MT_TrainingQueueChanged, { });
 
 		// If this is the first item in the queue, start the timer
@@ -142,6 +148,13 @@
 
 		var cmpPlayer = QueryPlayerIDInterface(item.player, IID_Player);
 
+		// If we trained a hero, state this in player compoment
+		var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+		var template = cmpTempMan.GetTemplate(item.template);
+		var isHero = TemplateHasIdentityClass(template, "Hero");
+		if (isHero)
+			cmpPlayer.DecreaseHeroesCount();
+
 		// Refund the resource cost for this batch
 		var totalCosts = {};
 		for each (var r in ["food", "wood", "stone", "metal"])
@@ -243,7 +256,16 @@
 		//	so only create them once and use as needed
 		for (var i = 0; i < count; ++i)
 		{
-			this.entityCache.push(Engine.AddEntity(templateName));
+			var ent = Engine.AddEntity(templateName);
+			this.entityCache.push(ent);
+			// If we create a hero entity - decrease heroes count, since it will be
+			// increased by Player.OnGlobalOwnershipChanged function,
+			// i.e. we replace 'trained' hero to a 'alive' one
+			var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
+			var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
+			var isHero = cmpIdentity.HasClass('Hero');
+			if (isHero)
+				cmpPlayer.DecreaseHeroesCount();
 		}
 	}
 
Index: binaries/data/mods/public/simulation/helpers/Templates.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Templates.js	(revision 0)
+++ binaries/data/mods/public/simulation/helpers/Templates.js	(revision 0)
@@ -0,0 +1,11 @@
+/**
+ * Check whether template.Identity.Classes contains specified class
+ */
+function TemplateHasIdentityClass(template, className)
+{
+	var hasClass = template.Identity && template.Identity.Classes && "_string" in template.Identity.Classes && template.Identity.Classes._string.indexOf(className) != -1;
+	return hasClass;
+}
+
+Engine.RegisterGlobal("TemplateHasIdentityClass", TemplateHasIdentityClass);
+
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js	(revision 11045)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(working copy)
@@ -119,16 +119,42 @@
 
 	case "train":
 		// Verify that the building can be controlled by the player
-		if (CanControlUnit(cmd.entity, player, controlAllUnits))
+		if (!CanControlUnit(cmd.entity, player, controlAllUnits))
 		{
-			var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue);
-			if (queue)
-				queue.AddBatch(cmd.template, +cmd.count, cmd.metadata);
+			if (g_DebugCommands)
+			{
+				warn("Invalid command: training building cannot be controlled by player "+player+": "+uneval(cmd));
+			}
+			break;
 		}
-		else if (g_DebugCommands)
+		// Check hero restrictions
+		var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+		var template = cmpTempMan.GetTemplate(cmd.template);
+		var isHero = TemplateHasIdentityClass(template, "Hero");
+		if (isHero)
 		{
-			warn("Invalid command: training building cannot be controlled by player "+player+": "+uneval(cmd));
+			var cmpPlayer = QueryOwnerInterface(cmd.entity, IID_Player);
+			if (+cmd.count > 1)
+			{
+				if (g_DebugCommands)
+				{
+					warn("Invalid command: batch training isn't allowed for heroes: "+uneval(cmd));
+				}
+				break;
+			}
+			if (cmpPlayer.HasHero())
+			{
+				if (g_DebugCommands)
+				{
+					warn("Invalid command: can't train hero because player already has one: "+uneval(cmd));
+				}
+				break;
+			}
 		}
+
+		var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue);
+		if (queue)
+			queue.AddBatch(cmd.template, +cmd.count, cmd.metadata);
 		break;
 
 	case "stop-train":
