diff --git a/source/graphics/MapReader.cpp b/source/graphics/MapReader.cpp
index ed57c2c..68d052f 100644
--- a/source/graphics/MapReader.cpp
+++ b/source/graphics/MapReader.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,6 +19,8 @@
 
 #include "MapReader.h"
 
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
 #include "graphics/Camera.h"
 #include "graphics/CinemaTrack.h"
 #include "graphics/Entity.h"
@@ -28,6 +30,7 @@
 #include "graphics/Terrain.h"
 #include "graphics/TerrainTextureEntry.h"
 #include "graphics/TerrainTextureManager.h"
+#include "graphics/Unit.h"
 #include "lib/timer.h"
 #include "lib/external_libraries/libsdl.h"
 #include "maths/MathUtil.h"
@@ -60,7 +63,7 @@ CMapReader::CMapReader()
 	// Maps that don't override the default probably want the old lighting model
 	//m_LightEnv.SetLightingModel("old");
 	//pPostproc->SetPostEffect(L"default");
-	
+
 }
 
 // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
@@ -112,7 +115,7 @@ void CMapReader::LoadMap(const VfsPath& pathname, JSRuntime* rt,  JS::HandleValu
 	// delete all existing entities
 	if (pSimulation2)
 		pSimulation2->ResetState();
-	
+
 	// reset post effects
 	if (pPostproc)
 		pPostproc->SetPostEffect(L"default");
@@ -296,7 +299,7 @@ int CMapReader::ApplyTerrainData()
 
 						mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index];
 						mp.Priority = tileptr->m_Priority;
-	
+
 						tileptr++;
 					}
 				}
@@ -324,7 +327,7 @@ int CMapReader::ApplyData()
 	{
 		// Default to global camera (with constraints)
 		pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus());
-	
+
 		// TODO: Starting rotation?
 		CmpPtr<ICmpPlayer> cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID));
 		if (cmpPlayer && cmpPlayer->HasStartingCamera())
@@ -388,7 +391,7 @@ void CMapSummaryReader::GetMapSettings(ScriptInterface& scriptInterface, JS::Mut
 {
 	JSContext* cx = scriptInterface.GetContext();
 	JSAutoRequest rq(cx);
-	
+
 	scriptInterface.Eval("({})", ret);
 	if (m_ScriptSettings.empty())
 		return;
@@ -439,6 +442,7 @@ private:
 	int at_angle;
 	int at_uid;
 	int at_seed;
+	int at_variation;
 
 	XMBElementList nodes; // children of root
 
@@ -490,6 +494,7 @@ void CXMLReader::Init(const VfsPath& xml_filename)
 	AT(angle);
 	AT(uid);
 	AT(seed);
+	AT(variation);
 #undef AT
 #undef EL
 
@@ -740,7 +745,7 @@ void CXMLReader::ReadEnvironment(XMBElement parent)
 					// graphics are disabled
 					if (!m_MapReader.pWaterMan)
 						continue;
-					
+
 					if (element_name == el_type)
 					{
 						if (waterelement.GetText() == "default")
@@ -863,12 +868,12 @@ void CXMLReader::ReadCinema(XMBElement parent)
 
 #undef EL
 #undef AT
-	
+
 	std::map<CStrW, CCinemaPath> pathList;
 	XERO_ITER_EL(parent, element)
 	{
 		int elementName = element.GetNodeName();
-			
+
 		if ( elementName == el_path )
 		{
 			XMBAttributeList attrs = element.GetAttributes();
@@ -891,7 +896,7 @@ void CXMLReader::ReadCinema(XMBElement parent)
 						pathData.m_Growth = attrs.GetNamedItem(at_growth).ToInt();
 						pathData.m_Switch = attrs.GetNamedItem(at_switch).ToInt();
 				}
-				
+
 				//Load node data used for spline
 				else if ( elementName == el_node )
 				{
@@ -900,7 +905,7 @@ void CXMLReader::ReadCinema(XMBElement parent)
 					{
 						elementName = nodeChild.GetNodeName();
 						attrs = nodeChild.GetAttributes();
-						
+
 						//Fix?:  assumes that time is last element
 						if ( elementName == el_position )
 						{
@@ -920,14 +925,14 @@ void CXMLReader::ReadCinema(XMBElement parent)
 							data.Distance = nodeChild.GetText().ToFloat();
 						else 
 							debug_warn(L"Invalid cinematic element for node child");
-					
+
 						backwardSpline.AddNode(data.Position, data.Rotation, data.Distance);
 					}
 				}
 				else
 					debug_warn(L"Invalid cinematic element for path child");
-				
-				
+
+
 			}
 
 			//Construct cinema path with data gathered
@@ -938,15 +943,15 @@ void CXMLReader::ReadCinema(XMBElement parent)
 				debug_warn(L"Failure loading cinematics");
 				return;
 			}
-					
+
 			for ( std::vector<SplineData>::const_reverse_iterator it = nodes.rbegin(); 
 															it != nodes.rend(); ++it )
 			{
 				spline.AddNode(it->Position, it->Rotation, it->Distance);
 			}
-				
+
 			CCinemaPath path(pathData, spline);
-			pathList[name] = path;	
+			pathList[name] = path;
 		}
 		else
 			ENSURE("Invalid cinema child");
@@ -986,6 +991,7 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
 		CFixedVector3D Position;
 		CFixedVector3D Orientation;
 		long Seed = -1;
+		std::set<CStr> actorSelections;
 
 		// Obstruction control groups.
 		entity_id_t ControlGroup = INVALID_ENTITY;
@@ -1035,11 +1041,20 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
 			else if (element_name == el_actor)
 			{
 				XMBAttributeList attrs = setting.GetAttributes();
-				CStr seedStr = attrs.GetNamedItem(at_seed);
-				if (!seedStr.empty())
+				CStr variationStr = attrs.GetNamedItem(at_variation);
+
+				if (!variationStr.empty())
 				{
-					Seed = seedStr.ToLong();
-					ENSURE(Seed >= 0);
+					boost::algorithm::split(actorSelections, variationStr, boost::is_any_of("|"));
+				}
+				else
+				{
+					CStr seedStr = attrs.GetNamedItem(at_seed);
+					if (!seedStr.empty())
+					{
+						Seed = seedStr.ToLong();
+						ENSURE(Seed >= 0);
+					}
 				}
 			}
 			else
@@ -1080,9 +1095,14 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
 			CmpPtr<ICmpVisual> cmpVisual(sim, ent);
 			if (cmpVisual)
 			{
-				if (Seed != -1)
+				if (!actorSelections.empty())
+				{
+					CUnit* unit = cmpVisual->GetUnit();
+					if (unit)
+						unit->SetActorSelections(actorSelections);
+				}
+				else if (Seed != -1)
 					cmpVisual->SetActorSeed((u32)Seed);
-				// TODO: variation/selection strings
 			}
 
 			if (PlayerID == m_MapReader.m_PlayerID && (boost::algorithm::ends_with(TemplateName, L"civil_centre") || m_MapReader.m_StartingCameraTarget == INVALID_ENTITY))
@@ -1260,20 +1280,20 @@ int CMapReader::GenerateMap()
 {
 	JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
 	JSAutoRequest rq(cx);
-	
+
 	if (!m_MapGen)
 	{
 		// Initialize map generator
 		m_MapGen = new CMapGenerator();
 
 		VfsPath scriptPath;
-		
+
 		if (m_ScriptFile.length())
 			scriptPath = L"maps/random/"+m_ScriptFile;
 
 		// Stringify settings to pass across threads
 		std::string scriptSettings = pSimulation2->GetScriptInterface().StringifyJSON(&m_ScriptSettings.get());
-		
+
 		// Try to generate map
 		m_MapGen->GenerateMap(scriptPath, scriptSettings);
 	}
@@ -1293,7 +1313,7 @@ int CMapReader::GenerateMap()
 		// Parse data into simulation context
 		JS::RootedValue data(cx); 
 		pSimulation2->GetScriptInterface().ReadStructuredClone(results, &data);
-		
+
 		if (data.isUndefined())
 		{
 			// RMS failed - return to main menu
@@ -1312,7 +1332,7 @@ int CMapReader::GenerateMap()
 		// to allow more CPU for the map generator thread
 		SDL_Delay(100);
 	}
-	
+
 	// return progress
 	return progress;
 };
@@ -1323,7 +1343,7 @@ int CMapReader::ParseTerrain()
 	TIMER(L"ParseTerrain");
 	JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
 	JSAutoRequest rq(cx);
-	
+
 	// parse terrain from map data
 	//	an error here should stop the loading process
 #define GET_TERRAIN_PROPERTY(val, prop, out)\
@@ -1376,7 +1396,7 @@ int CMapReader::ParseTerrain()
 		{
 			size_t patchY = y / PATCH_SIZE;
 			size_t offY = y % PATCH_SIZE;
-			
+
 			STileDesc tile;
 			tile.m_Tex1Index = tileIndex[y*size + x];
 			tile.m_Tex2Index = 0xFFFF;
@@ -1411,7 +1431,7 @@ int CMapReader::ParseEntities()
 
 	size_t entity_idx = 0;
 	size_t num_entities = entities.size();
-	
+
 	Entity currEnt;
 
 	while (entity_idx < num_entities)
@@ -1493,7 +1513,7 @@ int CMapReader::ParseEnvironment()
 
 	GET_ENVIRONMENT_PROPERTY(envObj, SunElevation, m_LightEnv.m_Elevation)
 	GET_ENVIRONMENT_PROPERTY(envObj, SunRotation, m_LightEnv.m_Rotation)
-	
+
 	CColor terrainAmbientColor;
 	GET_ENVIRONMENT_PROPERTY(envObj, TerrainAmbientColor, terrainAmbientColor)
 	m_LightEnv.m_TerrainAmbientColor = RGBColor(terrainAmbientColor.r, terrainAmbientColor.g, terrainAmbientColor.b);
diff --git a/source/graphics/MapWriter.cpp b/source/graphics/MapWriter.cpp
index 9d59ac0..fa6281f 100644
--- a/source/graphics/MapWriter.cpp
+++ b/source/graphics/MapWriter.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -27,7 +27,10 @@
 #include "Terrain.h"
 #include "TerrainTextureEntry.h"
 #include "TerrainTextureManager.h"
+#include "Unit.h"
 
+
+#include <boost/algorithm/string/join.hpp>
 #include "maths/MathUtil.h"
 #include "maths/NUSpline.h"
 #include "ps/CLogger.h"
@@ -103,7 +106,7 @@ void CMapWriter::EnumTerrainTextures(CTerrain *pTerrain,
 {
 	// the list of all handles in use
 	std::vector<CTerrainTextureEntry*> entries;
-	
+
 	// resize tile array to required size
 	tiles.resize(SQR(pTerrain->GetVerticesPerSide()-1));
 	STileDesc* tileptr=&tiles[0];
@@ -160,24 +163,24 @@ void CMapWriter::PackTerrain(CFilePacker& packer, CTerrain* pTerrain)
 	// pack map size
 	const ssize_t mapsize = pTerrain->GetPatchesPerSide();
 	packer.PackSize(mapsize);
-	
+
 	// pack heightmap
-	packer.PackRaw(pTerrain->GetHeightMap(),sizeof(u16)*SQR(pTerrain->GetVerticesPerSide()));	
+	packer.PackRaw(pTerrain->GetHeightMap(),sizeof(u16)*SQR(pTerrain->GetVerticesPerSide()));
 
 	// the list of textures used by map
 	std::vector<CStr> terrainTextures;
 	// descriptions of each tile
 	std::vector<STileDesc> tiles;
-	
+
 	// build lists by scanning through the terrain
 	EnumTerrainTextures(pTerrain, terrainTextures, tiles);
-	
+
 	// pack texture names
 	const size_t numTextures = terrainTextures.size();
 	packer.PackSize(numTextures);
 	for (size_t i=0;i<numTextures;i++)
 		packer.PackString(terrainTextures[i]);
-	
+
 	// pack tile data
 	packer.PackRaw(&tiles[0],sizeof(STileDesc)*tiles.size());
 }
@@ -270,7 +273,7 @@ void CMapWriter::WriteXML(const VfsPath& filename,
 					XML_Setting("WindAngle", pWaterMan->m_WindAngle);
 				}
 			}
-			
+
 			{
 				XML_Element("Postproc");
 				{
@@ -386,20 +389,30 @@ void CMapWriter::WriteXML(const VfsPath& filename,
 				CmpPtr<ICmpVisual> cmpVisual(sim, ent);
 				if (cmpVisual)
 				{
+					CUnit* unit = cmpVisual->GetUnit();
 					u32 seed = cmpVisual->GetActorSeed();
-					if (seed != (u32)ent)
+					bool variationsAdded = false;
+					XML_Element("Actor");
+					if (unit)
 					{
-						XML_Element("Actor");
-						XML_Attribute("seed", seed);
+						const std::set<CStr>& actorSelections = unit->GetActorSelections();
+
+						if (!actorSelections.empty())
+						{
+							CStr actorSelectionsStr = boost::algorithm::join(unit->GetActorSelections(), "|");
+							XML_Attribute("variation", actorSelectionsStr.c_str());
+							variationsAdded = true;
+						}
 					}
-					// TODO: variation/selection strings
+					if (!variationsAdded && seed != (u32)ent)
+						XML_Attribute("seed", seed);
 				}
 			}
 		}
 
 		const std::map<CStrW, CCinemaPath>& paths = pCinema->GetAllPaths();
 		std::map<CStrW, CCinemaPath>::const_iterator it = paths.begin();
-		
+
 		{
 			XML_Element("Paths");
 
@@ -407,14 +420,14 @@ void CMapWriter::WriteXML(const VfsPath& filename,
 			{
 				CStrW name = it->first;
 				float timescale = it->second.GetTimescale();
-				
+
 				XML_Element("Path");
 				XML_Attribute("name", name);
 				XML_Attribute("timescale", timescale);
 
 				const std::vector<SplineData>& nodes = it->second.GetAllNodes();
 				const CCinemaData* data = it->second.GetData();
-				
+
 				{
 					XML_Element("Distortion");
 					XML_Attribute("mode", data->m_Mode);
@@ -426,7 +439,7 @@ void CMapWriter::WriteXML(const VfsPath& filename,
 				for ( ssize_t j=nodes.size()-1; j >= 0; --j )
 				{
 					XML_Element("Node");
-					
+
 					{
 						XML_Element("Position");
 						XML_Attribute("x", nodes[j].Position.X);