Index: binaries/data/mods/public/gui/session/unit_commands.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_commands.js	(revision 11909)
+++ binaries/data/mods/public/gui/session/unit_commands.js	(working copy)
@@ -145,7 +145,7 @@
  * @param items Panel-specific data to construct the icons with.
  * @param callback Callback function to argument to execute when an item's icon gets clicked. Takes a single 'item' argument.
  */
-function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
+function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, callback)
 {
 	usedPanels[guiName] = 1;
 
@@ -343,7 +343,6 @@
 					tooltip += "\n" + getEntitySpeed(template);
 
 				tooltip += "\n\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train " + trainNum + ".[/font]";
-
 				break;
 				
 			case RESEARCH:
@@ -492,6 +491,13 @@
 			icon.sprite = "stretched:session/icons/" + item.icon;
 
 		}
+		else if (guiName == "Training")
+		{
+			if (template.icon)
+			{
+				icon.sprite = "stretched:session/portraits/" + template.icon;
+			}
+		}
 		else if (template.icon)
 		{
 			var grayscale = "";
@@ -636,7 +642,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;
@@ -714,27 +720,32 @@
 	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 (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)); } );
 		}
 
 		var formations = Engine.GuiInterfaceCall("GetAvailableFormations");
 		if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length)
 		{
-			setupUnitPanel(FORMATION, usedPanels, entState, formations,
+			setupUnitPanel(FORMATION, usedPanels, entState, playerState, formations,
 				function (item) { performFormation(entState.id, item); } );
 		}
 
@@ -743,7 +754,7 @@
 		var stances = ["violent", "aggressive", "passive", "defensive", "standground"];
 		if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && stances.length)
 		{
-			setupUnitPanel(STANCE, usedPanels, entState, stances,
+			setupUnitPanel(STANCE, usedPanels, entState, playerState, stances,
 				function (item) { performStance(entState.id, item); } );
 		}
 
@@ -751,7 +762,7 @@
 		if (entState.barterMarket)
 		{
 			usedPanels["Barter"] = 1;
-			setupUnitBarterPanel(entState);
+			setupUnitBarterPanel(entState, playerState);
 		}
 
 		var buildableEnts = [];
@@ -771,19 +782,19 @@
 		removeDupes(trainableEnts);
 
 		if (buildableEnts.length && ((trainableEnts.length && hasClass(entState, "Unit")) || !trainableEnts.length))
-			setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
+			setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts, startBuildingPlacement);
 		else if (trainableEnts.length)
-			setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
+			setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
 				function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
 		
 		if (entState.production && entState.production.technologies.length && selection.length == 1)
 		{
-			setupUnitPanel(RESEARCH, usedPanels, entState, entState.production.technologies,
+			setupUnitPanel(RESEARCH, usedPanels, entState, playerState, entState.production.technologies,
 				function (researchType) { addResearchToQueue(entState.id, researchType); } );
 		}
 
 		if (entState.production && entState.production.queue.length)
-			setupUnitPanel(QUEUE, usedPanels, entState, entState.production.queue,
+			setupUnitPanel(QUEUE, usedPanels, entState, playerState, entState.production.queue,
 				function (item) { removeFromProductionQueue(entState.id, item.id); } );
 
 		if (entState.trader)
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 11909)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(working copy)
@@ -142,11 +142,38 @@
 
 	case "train":
 		var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
+		
+		// Check hero restrictions
+		var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+		var template = cmpTempMan.GetTemplate(cmd.template);
+		var isHero = TemplateHasIdentityClass(template, "Hero");
+		if (isHero && +cmd.count > 1)
+		{
+			if (g_DebugCommands)
+			{
+				warn("Invalid command: batch training isn't allowed for heroes: "+uneval(cmd));
+			}
+			return;
+		}
+	
 		// Verify that the building(s) can be controlled by the player
 		if (entities.length > 0)
 		{
 			for each (var ent in entities)
 			{
+				if (isHero)
+				{
+					var cmpPlayer = QueryOwnerInterface(ent, IID_Player);
+					if (cmpPlayer.HasHero())
+					{
+						if (g_DebugCommands)
+						{
+							warn("Invalid command: can't train hero because player already has one: "+uneval(cmd));
+						}
+						continue;
+					}
+				}
+
 				var cmpTechMan = QueryOwnerInterface(ent, IID_TechnologyManager);
 				// TODO: Enable this check once the AI gets technology support
 				if (cmpTechMan.CanProduce(cmd.template) || true)
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(revision 11909)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -82,6 +82,7 @@
 			"state": cmpPlayer.GetState(),
 			"team": cmpPlayer.GetTeam(),
 			"phase": phase,
+			"hasHero": cmpPlayer.HasHero(),
 			"isAlly": allies,
 			"isEnemy": enemies,
 			"buildLimits": cmpPlayerBuildLimits.GetLimits(),
Index: binaries/data/mods/public/simulation/components/ProductionQueue.js
===================================================================
--- binaries/data/mods/public/simulation/components/ProductionQueue.js	(revision 11909)
+++ binaries/data/mods/public/simulation/components/ProductionQueue.js	(working copy)
@@ -199,6 +199,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(),
@@ -292,6 +297,16 @@
 		
 		var cmpPlayer = QueryPlayerIDInterface(item.player, IID_Player);
 
+		// If we trained a hero, state this in player compoment
+		if (item.unitTemplate)
+		{
+			var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+			var template = cmpTempMan.GetTemplate(item.unitTemplate);
+			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"])
@@ -401,7 +416,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/components/Player.js
===================================================================
--- binaries/data/mods/public/simulation/components/Player.js	(revision 11909)
+++ 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.formations = [];
 	this.startCam = undefined;
@@ -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);
