Index: binaries/data/mods/public/art/textures/ui/session/icons/bell_level0.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: binaries/data/mods/public/art/textures/ui/session/icons/bell_level0.png
===================================================================
--- binaries/data/mods/public/art/textures/ui/session/icons/bell_level0.png	(revision 0)
+++ binaries/data/mods/public/art/textures/ui/session/icons/bell_level0.png	(working copy)

Property changes on: binaries/data/mods/public/art/textures/ui/session/icons/bell_level0.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: binaries/data/mods/public/art/textures/ui/session/icons/bell_level1.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: binaries/data/mods/public/art/textures/ui/session/icons/bell_level1.png
===================================================================
--- binaries/data/mods/public/art/textures/ui/session/icons/bell_level1.png	(revision 0)
+++ binaries/data/mods/public/art/textures/ui/session/icons/bell_level1.png	(working copy)

Property changes on: binaries/data/mods/public/art/textures/ui/session/icons/bell_level1.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: binaries/data/mods/public/art/textures/ui/session/icons/bell_level2.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: binaries/data/mods/public/art/textures/ui/session/icons/bell_level2.png
===================================================================
--- binaries/data/mods/public/art/textures/ui/session/icons/bell_level2.png	(revision 0)
+++ binaries/data/mods/public/art/textures/ui/session/icons/bell_level2.png	(working copy)

Property changes on: binaries/data/mods/public/art/textures/ui/session/icons/bell_level2.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js	(revision 13992)
+++ binaries/data/mods/public/gui/session/input.js	(working copy)
@@ -1841,6 +1841,15 @@
 			case "back-to-work":
 				backToWork();
 				break;
+			case "ring-town-bell-yellow":
+				ringYellowAlert();
+				break;
+			case "ring-town-bell-red":
+				ringRedAlert();
+				break;
+			case "ring-town-bell-end":
+				ringEndOfAlert();
+				break;
 			default:
 				break;
 			}
@@ -2115,6 +2124,42 @@
 	
 }
 
+function ringYellowAlert()
+{
+	var strikers = g_Selection.toList().filter(function(e) {
+		var state = GetEntityState(e);
+		if (state && state.townBellStriker && state.townBellStriker.level == "End")
+			return true;
+		return false;
+	});
+	
+	Engine.PostNetworkCommand({"type": "ring-town-bell-yellow", "strikers": strikers});	
+}
+
+function ringRedAlert()
+{
+	var strikers = g_Selection.toList().filter(function(e) {
+		var state = GetEntityState(e);
+		if (state && state.townBellStriker && state.townBellStriker.level == "Yellow")
+			return true;
+		return false;
+	});
+	
+	Engine.PostNetworkCommand({"type": "ring-town-bell-red", "strikers": strikers});	
+}
+
+function ringEndOfAlert()
+{
+	var strikers = g_Selection.toList().filter(function(e) {
+		var state = GetEntityState(e);
+		if (state && state.townBellStriker && (state.townBellStriker.level == "Yellow" || state.townBellStriker.level == "Red"))
+			return true;
+		return false;
+	});
+	
+	Engine.PostNetworkCommand({"type": "ring-town-bell-end", "strikers": strikers});	
+}
+
 function clearSelection()
 {
 	if(inputState==INPUT_BUILDING_PLACEMENT || inputState==INPUT_BUILDING_WALL_PATHING)
Index: binaries/data/mods/public/gui/session/utility_functions.js
===================================================================
--- binaries/data/mods/public/gui/session/utility_functions.js	(revision 13992)
+++ binaries/data/mods/public/gui/session/utility_functions.js	(working copy)
@@ -302,6 +302,41 @@
 		    "icon": "production.png"
 		});
 	}
+	
+	if(entState.townBellStriker)
+	{
+		switch(entState.townBellStriker.level)
+		{
+			case "End":
+				commands.push({
+					"name": "ring-town-bell-yellow",
+					"tooltip": "Town Bell : Garrison all female citizens in the first nearby building",
+					"icon": "bell_level1.png"
+				});
+				break;
+			case "Yellow":
+				commands.push({
+					"name": "ring-town-bell-red",
+					"tooltip": "Red Alert : Garrison also the citizen-soldiers",
+					"icon": "bell_level2.png"
+				});
+				commands.push({
+					"name": "ring-town-bell-end",
+					"tooltip": "End of Alert",
+					"icon": "bell_level0.png"
+				});
+				break;
+			case "Red":
+				commands.push({
+					"name": "ring-town-bell-end",
+					"tooltip": "End of Alert",
+					"icon": "bell_level0.png"
+				});
+				break;
+			default:
+				break;
+		}
+	}
 
 	return commands;
 }
Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js	(revision 13992)
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js	(working copy)
@@ -261,6 +261,7 @@
 
 /**
  * Ejects units and orders them to move to the Rally Point.
+ * If a unit was garrisoned by a Town Bell and remembers any work, sends it back to this work.
  * Returns true if successful, false if not
  */
 GarrisonHolder.prototype.PerformEject = function(entities, forced)
@@ -268,10 +269,19 @@
 	if (!this.IsGarrisoningAllowed() && !forced)
 		return false;
 
-	var ejectedEntities = [];
+	var ejectedEntities = [];	
+	var workers = [];
+	var nonWorkers = [];
 	var success = true;
 	for each (var entity in entities)
 	{
+		// Filter the units who shall go back to work after a Town Bell
+		var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
+		if (cmpUnitAI && cmpUnitAI.GetTownBellStriker() && cmpUnitAI.GetLastWorkOrder())
+			workers.push(entity);
+		else
+			nonWorkers.push(entity);
+		
 		if (this.Eject(entity, forced))
 		{
 			var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
@@ -282,8 +292,12 @@
 		else
 			success = false;
 	}
-
-	this.OrderWalkToRallyPoint(ejectedEntities);
+	
+	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	ProcessCommand(cmpOwnership.GetOwner(), {"type": "back-to-work", "workers": workers});
+	
+	this.OrderWalkToRallyPoint(nonWorkers);
+	
 	this.UpdateGarrisonFlag();
 
 	return success;
@@ -291,7 +305,7 @@
 
 /**
  * Unload unit from the garrisoning entity and order them
- * to move to the Rally Point
+ * to move to the Rally Point (or to its work after a Town Bell)
  * Returns true if successful, false if not
  */
 GarrisonHolder.prototype.Unload = function(entity, forced)
@@ -301,7 +315,7 @@
 
 /**
  * Unload one or all units that match a template and owner from
- * the garrisoning entity and order them to move to the Rally Point
+ * the garrisoning entity and order them to move to the Rally Point (or to their work after a Town Bell)
  * Returns true if successful, false if not
  * 
  * extendedTemplate has the format "p"+ownerid+"&"+template
@@ -342,7 +356,7 @@
 
 /**
  * Unload all units with same owner as the entity
- * and order them to move to the Rally Point
+ * and order them to move to the Rally Point (or to their work after a Town Bell)
  * Returns true if all successful, false if not
  */
 GarrisonHolder.prototype.UnloadAllOwn = function(forced)
@@ -366,7 +380,7 @@
 
 /**
  * Unload all units from the entity
- * and order them to move to the Rally Point
+ * and order them to move to the Rally Point (or to their work after a Town Bell)
  * Returns true if all successful, false if not
  */
 GarrisonHolder.prototype.UnloadAll = function(forced)
@@ -376,6 +390,24 @@
 };
 
 /**
+ * Unload all units garrisoned by the TownBellStriker striker
+ * and order them to move to the Rally Point (or to their work)
+ * Returns true if all successful, false if not
+ */
+GarrisonHolder.prototype.UnloadAllTownBell = function(striker, forced)
+{
+	var entities = [];
+	for each (var entity in this.entities)
+	{
+		var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
+		if (cmpUnitAI && cmpUnitAI.GetTownBellStriker() == striker)
+			entities.push(entity);
+	}
+	
+	return this.PerformEject(entities, forced);
+};
+
+/**
  * Used to check if the garrisoning entity's health has fallen below
  * a certain limit after which all garrisoned units are unloaded
  */
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js	(revision 13992)
+++ binaries/data/mods/public/simulation/components/GuiInterface.js	(working copy)
@@ -377,6 +377,14 @@
 			"healableClasses": cmpHeal.GetHealableClasses(),
 		};
 	}
+	
+	var cmpTownBellStriker = Engine.QueryInterface(ent, IID_TownBellStriker);
+	if(cmpTownBellStriker)
+	{
+		ret.townBellStriker = {
+			"level": cmpTownBellStriker.GetLevel(),
+		};
+	}
 
 	ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
 
Index: binaries/data/mods/public/simulation/components/TownBellStriker.js
===================================================================
--- binaries/data/mods/public/simulation/components/TownBellStriker.js	(revision 0)
+++ binaries/data/mods/public/simulation/components/TownBellStriker.js	(working copy)
@@ -0,0 +1,107 @@
+function TownBellStriker() {}
+
+TownBellStriker.prototype.Schema = "<element name='Range'><data type='nonNegativeInteger'/></element>";
+
+TownBellStriker.prototype.Init = function() 
+{
+    this.level = "End";
+};
+
+TownBellStriker.prototype.GetLevel = function()
+{
+	return this.level;
+};
+
+TownBellStriker.prototype.RingYellowAlert = function()
+{	
+	// Find units owned by this unit's player
+	var players = [];
+	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (cmpOwnership)
+		players.push(cmpOwnership.GetOwner());
+	
+	var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	var units = rangeMan.ExecuteQuery(this.entity, 0, this.template.Range, players, IID_UnitAI).filter( function(e){
+		var cmpIdentity = Engine.QueryInterface(e, IID_Identity);
+		return (cmpIdentity && cmpIdentity.HasClass("Female"));
+		});
+		
+	this.level = "Yellow";	
+	
+	for each(unit in units)
+	{
+		var cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI);
+		cmpUnitAI.AddOrder("TownBell", {"striker": this.entity}, false);
+	}
+	
+	return true;
+};
+
+TownBellStriker.prototype.RingRedAlert = function()
+{
+	// Find units owned by this unit's player
+	var players = [];
+	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (cmpOwnership)
+		players.push(cmpOwnership.GetOwner());
+	
+	// If a yellow alert has already been struck, only give orders to the units who don't already have orders.
+	var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	var units = rangeMan.ExecuteQuery(this.entity, 0, this.template.Range, players, IID_UnitAI).filter( function(e){
+		var cmpIdentity = Engine.QueryInterface(e, IID_Identity);
+		var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
+		return (cmpIdentity && (cmpIdentity.HasClass("Female") || cmpIdentity.HasClass("CitizenSoldier"))
+			&& !cmpUnitAI.GetTownBellStriker()
+			);
+		});
+		
+	this.level = "Red";	
+	
+	for each(unit in units)
+	{
+		var cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI);
+		cmpUnitAI.AddOrder("TownBell", {"striker": this.entity}, false);
+	}
+	
+	return true;
+};
+
+TownBellStriker.prototype.RingEndOfAlert = function()
+{
+	// Find buildings owned by this unit's player
+	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (cmpOwnership)
+		var player = cmpOwnership.GetOwner();
+	else
+		return false;
+	
+	var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	var holders = rangeMan.GetEntitiesByPlayer(player).filter( function(e) {
+		var cmpGarrisonHolder = Engine.QueryInterface(e, IID_GarrisonHolder);
+		if (cmpGarrisonHolder)
+			return true;
+		return false;
+	});
+	
+	this.level = "End";
+	
+	for each(holder in holders)
+	{
+		var cmpGarrisonHolder = Engine.QueryInterface(holder, IID_GarrisonHolder);
+		cmpGarrisonHolder.UnloadAllTownBell(this.entity, true);
+	}
+	
+	return true;
+};
+
+TownBellStriker.prototype.Deinit = function() 
+{
+    
+};
+
+TownBellStriker.prototype.OnUpdate = function(msg) 
+{
+    
+};
+
+Engine.RegisterComponentType(IID_TownBellStriker, "TownBellStriker", TownBellStriker);
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(revision 13992)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(working copy)
@@ -620,6 +620,29 @@
 		}
 	},
 
+    "Order.TownBell": function(msg) {
+		this.townBellStriker = this.order.data.striker;
+		
+		// Find a target to garrison into, if we don't already have one
+		if(!this.garrisoningTarget)
+		{
+			var nearby = this.FindNearbyGarrisonHolder();
+			if (nearby)
+				this.garrisoningTarget = nearby;
+			else
+				this.FinishOrder();
+		}
+		
+		if (this.MoveToTarget(this.garrisoningTarget))
+			this.SetNextState("INDIVIDUAL.GARRISON.WALKING");
+		else
+		{
+			// We do a range check before actually garrisoning
+			this.StopMoving();
+			this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED");
+		}
+    },
+
 	"Order.Cheering": function(msg) {
 		this.SetNextState("INDIVIDUAL.CHEERING");
 	},
@@ -2270,10 +2293,46 @@
 					this.StopTimer();
 				}
 			},
+			
+			// Walking to a good place to garrison, used by Town Bell
+			"WALKING": {
+				"enter": function() {
+					this.SelectAnimation("move");
+				},
 
+				"MoveCompleted": function(msg) {
+					// check that we can garrison in the building we're supposed to garrison in
+					var cmpGarrisonHolder = Engine.QueryInterface(this.garrisoningTarget, IID_GarrisonHolder);
+					if (!cmpGarrisonHolder || cmpGarrisonHolder.GetCapacity() <= cmpGarrisonHolder.GetEntities().length)
+					{
+						// Try to find another nearby building
+						var nearby = this.FindNearbyGarrisonHolder();
+						if (nearby)
+						{
+							this.garrisoningTarget = nearby;
+							if (this.MoveToTarget(this.garrisoningTarget))
+								this.SetNextState("WALKING");
+						}
+						else
+							this.FinishOrder();
+					}
+					else
+						this.SetNextState("GARRISONED");
+				},
+				
+				"leave": function() {
+					this.StopTimer();
+				},
+			},
+
 			"GARRISONED": {
 				"enter": function() {
-					var target = this.order.data.target;
+					// Target is not handled the same way with TownBell and direct garrisoning
+					if(this.order.data.target)
+						var target = this.order.data.target;
+					else
+						var target = this.garrisoningTarget;
+						
 					var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
 
 					// Check that we can garrison here
@@ -2528,6 +2587,10 @@
 	
 	// To go back to work later
 	this.lastWorkOrder = undefined;
+	
+	// Town Bell behaviour
+	this.townBellStriker = undefined;
+	this.garrisoningTarget = undefined;
 
 	// For preventing increased action rate due to Stop orders or target death.
 	this.lastAttacked = undefined;
@@ -2830,6 +2893,10 @@
 		var template = cmpTemplateManager.GetCurrentTemplateName(this.entity);
 		error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack);
 	}
+	
+	// Forget all information about Town Bells (typically when finishing the TownBell order)
+	this.townBellStriker = undefined;
+	this.garrisoningTarget = undefined;
 
 	// Remove the order from the queue, then forget it if it was a work to avoid trying to go back to it later.
 	var finishedOrder = this.orderQueue.shift();
@@ -3008,6 +3075,11 @@
 	this.lastWorkOrder = order;
 };
 
+UnitAI.prototype.GetTownBellStriker = function()
+{
+	return this.townBellStriker;
+};
+
 UnitAI.prototype.TimerHandler = function(data, lateness)
 {
 	// Reset the timer
@@ -3252,6 +3324,32 @@
 };
 
 /**
+ * Returns the entity ID of the nearest building in which the unit can garrison,
+ * or undefined if none can be found close enough.
+ */
+UnitAI.prototype.FindNearbyGarrisonHolder = function()
+{
+	var range = 64; // TODO: what's a sensible number?
+
+	// Find buildings owned by this unit's player
+	var players = [];
+	var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (cmpOwnership)
+		players.push(cmpOwnership.GetOwner());
+
+	var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+	var nearby = rangeMan.ExecuteQuery(this.entity, 0, range, players, IID_GarrisonHolder);
+	for each (var ent in nearby)
+	{
+        var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
+        if (cmpGarrisonHolder.AllowedToGarrison(this.entity) && cmpGarrisonHolder.GetCapacity() > cmpGarrisonHolder.GetEntities().length)
+		    return ent;
+	}
+
+	return undefined;
+};
+
+/**
  * Play a sound appropriate to the current entity.
  */
 UnitAI.prototype.PlaySound = function(name)
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js	(revision 13992)
+++ binaries/data/mods/public/simulation/helpers/Commands.js	(working copy)
@@ -400,6 +400,35 @@
 				notifyBackToWorkFailure(player, worker)
 		}
 		break;
+	case "ring-town-bell-yellow":
+		var entities = FilterEntityList(cmd.strikers, player, controlAllUnits);
+		for each (var striker in entities)
+		{
+			var cmpTownBellStriker = Engine.QueryInterface(striker, IID_TownBellStriker);
+			if (!cmpTownBellStriker || !cmpTownBellStriker.RingYellowAlert())
+				notifyBellFailure(player)
+		}
+		break;
+	
+	case "ring-town-bell-red":
+		var entities = FilterEntityList(cmd.strikers, player, controlAllUnits);
+		for each (var striker in entities)
+		{
+			var cmpTownBellStriker = Engine.QueryInterface(striker, IID_TownBellStriker);
+			if (!cmpTownBellStriker || !cmpTownBellStriker.RingRedAlert())
+				notifyBellFailure(player)
+		}
+		break;
+	
+	case "ring-town-bell-end":
+		var entities = FilterEntityList(cmd.strikers, player, controlAllUnits);
+		for each (var striker in entities)
+		{
+			var cmpTownBellStriker = Engine.QueryInterface(striker, IID_TownBellStriker);
+			if (!cmpTownBellStriker || !cmpTownBellStriker.RingEndOfAlert())
+				notifyBellFailure(player)
+		}
+		break;
 
 	case "formation":
 		GetFormationUnitAIs(entities, player, cmd.name).forEach(function(cmpUnitAI) {
@@ -539,6 +568,17 @@
 }
 
 /**
+ * Sends a GUI notification about Town Bells that failed to be struck
+ */
+function notifyBellFailure(player)
+{
+	var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
+	var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Unable to strike the Town Bell" };
+	var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+	cmpGUIInterface.PushNotification(notification);
+}
+
+/**
  * Get some information about the formations used by entities.
  * The entities must have a UnitAI component.
  */
Index: binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml	(revision 13992)
+++ binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml	(working copy)
@@ -99,6 +99,9 @@
     <Radius>140</Radius>
     <Weight>65536</Weight>
   </TerritoryInfluence>
+  <TownBellStriker>
+    <Range>140</Range>
+  </TownBellStriker>
   <Vision>
     <Range>90</Range>
   </Vision>
