changeset 337:3c4a6403a88c

* Fix capitalisation so that new files are in the namespace no-open-ticket
author IBBoard <dev@ibboard.co.uk>
date Sun, 03 Apr 2011 18:50:32 +0000
parents 3631c1493c7f
children 4497ebce9a57
files API/AbstractWarFoundryLoader.cs API/Commands/AbstractReplaceUnitEquipmentCommand.cs API/Commands/AbstractSetUnitEquipmentAmountCommand.cs API/Commands/CreateAndAddUnitCommand.cs API/Commands/RemoveUnitCommand.cs API/Commands/ReplaceUnitEquipmentWithNumericAmountItemCommand.cs API/Commands/ReplaceUnitEquipmentWithRatioAmountItemCommand.cs API/Commands/SetNameCommand.cs API/Commands/SetUnitEquipmentNumericAmountCommand.cs API/Commands/SetUnitEquipmentRatioAmountCommand.cs API/Commands/SetUnitSizeCommand.cs API/DefaultWarFoundryLoader.cs API/Delegates.cs API/Exporters/IWarFoundryExporter.cs API/Exporters/WarFoundryHtmlExporter.cs API/Factories/AbstractNativeWarFoundryFactory.cs API/Factories/AbstractNonNativeFileExtensionWarFoundryFactory.cs API/Factories/AbstractNonNativeWarFoundryFactory.cs API/Factories/AbstractWarFoundryFactory.cs API/Factories/DummyWarFoundryFactory.cs API/Factories/INativeWarFoundryFactory.cs API/Factories/INonNativeWarFoundryFactory.cs API/Factories/IWarFoundryFactory.cs API/Factories/RequiredDataMissingException.cs API/Factories/Xml/AbstractStagedLoadedSubFactory.cs API/Factories/Xml/WarFoundryXmlArmyFactory.cs API/Factories/Xml/WarFoundryXmlArmyParser.cs API/Factories/Xml/WarFoundryXmlElementName.cs API/Factories/Xml/WarFoundryXmlFactory.cs API/Factories/Xml/WarFoundryXmlFactoryUtils.cs API/Factories/Xml/WarFoundryXmlGameSystemFactory.cs API/Factories/Xml/WarFoundryXmlLimitParser.cs API/Factories/Xml/WarFoundryXmlRaceFactory.cs API/Factories/Xml/Zip/StringZipEntrySource.cs API/FileLoadFailure.cs API/Objects/Ability.cs API/Objects/AbstractUnitEquipmentItemSelection.cs API/Objects/Army.cs API/Objects/ArmyCategory.cs API/Objects/Category.cs API/Objects/CompositeEquipmentItem.cs API/Objects/DuplicateItemException.cs API/Objects/EquipmentItem.cs API/Objects/GameSystem.cs API/Objects/ICostedWarFoundryObject.cs API/Objects/IWarFoundryNativeSourceObject.cs API/Objects/IWarFoundryObject.cs API/Objects/IWarFoundryStagedLoadObject.cs API/Objects/InvalidContainershipException.cs API/Objects/Race.cs API/Objects/Requirement/RequiresAtLeastNUnitsRequirement.cs API/Objects/Requirement/RequiresNoMoreThanNOfUnitTypeRequirement.cs API/Objects/Requirement/UnitCountRequirementData.cs API/Objects/Requirement/UnitRequiresAtLeastNUnitsRequirement.cs API/Objects/Stat.cs API/Objects/StatSlot.cs API/Objects/Stats.cs API/Objects/SystemStats.cs API/Objects/Unit.cs API/Objects/UnitEquipmentItem.cs API/Objects/UnitEquipmentNumericSelection.cs API/Objects/UnitEquipmentRatioSelection.cs API/Objects/UnitMemberType.cs API/Objects/UnitType.cs API/Objects/WarFoundryLoadedObject.cs API/Objects/WarFoundryObject.cs API/Objects/WarFoundryStagedLoadingObject.cs API/Requirements/AbstractArmyRequirement.cs API/Requirements/AbstractFailedRequirement.cs API/Requirements/AbstractRequirement.cs API/Requirements/AbstractUnitRequirement.cs API/Requirements/Delegates.cs API/Requirements/FailedRequirement.cs API/Requirements/FailedUnitRequirement.cs API/Requirements/RequirementAND.cs API/Requirements/RequirementOR.cs API/Requirements/UnitExcludesRequirement.cs API/Requirements/UnitRequirement.cs API/Requirements/UnitRequirementItem.cs API/Requirements/UnitRequirementMaxNumber.cs API/Requirements/UnitRequirementMinNumber.cs API/Requirements/UnitRequiresAtLeastRequirement.cs API/Savers/IWarFoundryFileSaver.cs API/Savers/WarFoundrySaver.cs API/Savers/Xml/WarFoundryXmlArmySaver.cs API/Savers/Xml/WarFoundryXmlFileSaver.cs API/Savers/Xml/WarFoundryXmlGameSystemSaver.cs API/Util/UnitEquipmentUtil.cs API/WarFoundryCore.cs API/WarFoundryLoader.cs IBBoard.WarFoundry.API.csproj api/AbstractWarFoundryLoader.cs api/Commands/AbstractReplaceUnitEquipmentCommand.cs api/Commands/AbstractSetUnitEquipmentAmountCommand.cs api/Commands/CreateAndAddUnitCommand.cs api/Commands/RemoveUnitCommand.cs api/Commands/ReplaceUnitEquipmentWithNumericAmountItemCommand.cs api/Commands/ReplaceUnitEquipmentWithRatioAmountItemCommand.cs api/Commands/SetNameCommand.cs api/Commands/SetUnitEquipmentNumericAmountCommand.cs api/Commands/SetUnitEquipmentRatioAmountCommand.cs api/Commands/SetUnitSizeCommand.cs api/DefaultWarFoundryLoader.cs api/Delegates.cs api/Exporters/IWarFoundryExporter.cs api/Exporters/WarFoundryHtmlExporter.cs api/Factories/AbstractNativeWarFoundryFactory.cs api/Factories/AbstractNonNativeFileExtensionWarFoundryFactory.cs api/Factories/AbstractNonNativeWarFoundryFactory.cs api/Factories/AbstractWarFoundryFactory.cs api/Factories/DummyWarFoundryFactory.cs api/Factories/INativeWarFoundryFactory.cs api/Factories/INonNativeWarFoundryFactory.cs api/Factories/IWarFoundryFactory.cs api/Factories/RequiredDataMissingException.cs api/Factories/Xml/AbstractStagedLoadedSubFactory.cs api/Factories/Xml/WarFoundryXmlArmyFactory.cs api/Factories/Xml/WarFoundryXmlArmyParser.cs api/Factories/Xml/WarFoundryXmlElementName.cs api/Factories/Xml/WarFoundryXmlFactory.cs api/Factories/Xml/WarFoundryXmlFactoryUtils.cs api/Factories/Xml/WarFoundryXmlGameSystemFactory.cs api/Factories/Xml/WarFoundryXmlLimitParser.cs api/Factories/Xml/WarFoundryXmlRaceFactory.cs api/Factories/Xml/Zip/StringZipEntrySource.cs api/FileLoadFailure.cs api/Objects/Ability.cs api/Objects/AbstractUnitEquipmentItemSelection.cs api/Objects/Army.cs api/Objects/ArmyCategory.cs api/Objects/Category.cs api/Objects/CompositeEquipmentItem.cs api/Objects/DuplicateItemException.cs api/Objects/EquipmentItem.cs api/Objects/GameSystem.cs api/Objects/ICostedWarFoundryObject.cs api/Objects/IWarFoundryNativeSourceObject.cs api/Objects/IWarFoundryObject.cs api/Objects/IWarFoundryStagedLoadObject.cs api/Objects/InvalidContainershipException.cs api/Objects/Race.cs api/Objects/Requirement/RequiresAtLeastNUnitsRequirement.cs api/Objects/Requirement/RequiresNoMoreThanNOfUnitTypeRequirement.cs api/Objects/Requirement/UnitCountRequirementData.cs api/Objects/Requirement/UnitRequiresAtLeastNUnitsRequirement.cs api/Objects/Stat.cs api/Objects/StatSlot.cs api/Objects/Stats.cs api/Objects/SystemStats.cs api/Objects/Unit.cs api/Objects/UnitEquipmentItem.cs api/Objects/UnitEquipmentNumericSelection.cs api/Objects/UnitEquipmentRatioSelection.cs api/Objects/UnitMemberType.cs api/Objects/UnitType.cs api/Objects/WarFoundryLoadedObject.cs api/Objects/WarFoundryObject.cs api/Objects/WarFoundryStagedLoadingObject.cs api/Requirements/AbstractArmyRequirement.cs api/Requirements/AbstractFailedRequirement.cs api/Requirements/AbstractRequirement.cs api/Requirements/AbstractUnitRequirement.cs api/Requirements/Delegates.cs api/Requirements/FailedRequirement.cs api/Requirements/FailedUnitRequirement.cs api/Requirements/RequirementAND.cs api/Requirements/RequirementOR.cs api/Requirements/UnitExcludesRequirement.cs api/Requirements/UnitRequirement.cs api/Requirements/UnitRequirementItem.cs api/Requirements/UnitRequirementMaxNumber.cs api/Requirements/UnitRequirementMinNumber.cs api/Requirements/UnitRequiresAtLeastRequirement.cs api/Savers/IWarFoundryFileSaver.cs api/Savers/WarFoundrySaver.cs api/Savers/Xml/WarFoundryXmlArmySaver.cs api/Savers/Xml/WarFoundryXmlFileSaver.cs api/Savers/Xml/WarFoundryXmlGameSystemSaver.cs api/Util/UnitEquipmentUtil.cs api/WarFoundryCore.cs api/WarFoundryLoader.cs
diffstat 181 files changed, 9436 insertions(+), 9436 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/AbstractWarFoundryLoader.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,695 @@
+// This file (AbstractWarFoundryLoader.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using IBBoard.Collections;
+using IBBoard.IO;
+using IBBoard.Logging;
+using IBBoard.WarFoundry.API.Factories;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API
+{
+	/// <summary>
+	/// The base abstract implementation of a WarFoundry file loader
+	/// </summary>
+	public abstract class AbstractWarFoundryLoader
+	{
+		private ICollection<DirectoryInfo> directories;
+		private ICollection<INativeWarFoundryFactory> factories;
+		private ICollection<INonNativeWarFoundryFactory> nonNativeFactories;
+		private Dictionary<IWarFoundryFactory, SimpleSet<IWarFoundryObject>> loadedObjects;
+
+		public delegate void FileLoadingCompleteDelegate(List<FileLoadFailure> failures);
+
+		public event MethodInvoker FileLoadingStarted;
+
+		public event FileLoadingCompleteDelegate FileLoadingFinished;
+
+		protected AbstractWarFoundryLoader()
+		{
+			directories = new List<DirectoryInfo>();
+			factories = new List<INativeWarFoundryFactory>();
+			nonNativeFactories = new List<INonNativeWarFoundryFactory>();
+			loadedObjects = new Dictionary<IWarFoundryFactory,SimpleSet<IWarFoundryObject>>();
+		}
+		
+		/// <summary>
+		/// Adds a directory to the collection of directories that will be searched for WarFoundry data files.
+		/// </summary>
+		/// <param name="directory">
+		/// The <see cref="DirectoryInfo"/> to add to the list for searching for data files
+		/// </param>
+		public void AddLoadDirectory(DirectoryInfo directory)
+		{
+			if (!directories.Contains(directory))
+			{
+				directories.Add(directory);
+			}
+		}
+		
+		/// <summary>
+		/// Removes a directory from the collection of directories that will be searched for WarFoundry data files.
+		/// </summary>
+		/// <param name="directory">
+		/// A <see cref="DirectoryInfo"/>
+		/// </param>
+		public void RemoveLoadDirectory(DirectoryInfo directory)
+		{
+			if (directories.Contains(directory))
+			{
+				directories.Remove(directory);
+			}
+		}
+		
+		/// <summary>
+		/// Registers a <see cref="INativeWarFoundryFactory"/> as a factory that can parse native data files.
+		/// </summary>
+		/// <param name="factory">
+		/// The <see cref="INativeWarFoundryFactory"/> to register to parse native data files.
+		/// </param>
+		public virtual void RegisterFactory(INativeWarFoundryFactory factory)
+		{
+			if (!factories.Contains(factory))
+			{
+				factories.Add(factory);
+			}
+		}
+		
+		/// <summary>
+		/// Unregisters a <see cref="INativeWarFoundryFactory"/> so that it will no longer be used to try to parse native data files.
+		/// </summary>
+		/// <param name="factory">
+		/// The <see cref="INativeWarFoundryFactory"/> to remove from the collection of factories that are used to try to parse native data files.
+		/// </param>
+		public virtual void UnregisterFactory(INativeWarFoundryFactory factory)
+		{
+			if (factories.Contains(factory))
+			{
+				factories.Remove(factory);
+			}
+		}
+		
+		/// <summary>
+		/// Registers a <see cref="INonNativeWarFoundryFactory"/> so that it will be used to try to parse non-native data files from other applications.
+		/// </summary>
+		/// <param name="factory">
+		/// The <see cref="INonNativeWarFoundryFactory"/> to register to parse non-native data files.
+		/// </param>
+		public virtual void RegisterNonNativeFactory(INonNativeWarFoundryFactory factory)
+		{
+			if (!nonNativeFactories.Contains(factory))
+			{
+				nonNativeFactories.Add(factory);
+			}
+		}
+		
+		/// <summary>
+		/// Unregisters a <see cref="INonNativeWarFoundryFactory"/> so that it will no longer be used to try to parse non-native data files from other applications.
+		/// </summary>
+		/// <param name="factory">
+		/// The <see cref="INonNativeWarFoundryFactory"/> to remove from the collection of factories that are used to try to parse non-native data files.
+		/// </param>
+		public virtual void UnregisterNonNativeFactory(INonNativeWarFoundryFactory factory)
+		{
+			if (nonNativeFactories.Contains(factory))
+			{
+				nonNativeFactories.Remove(factory);
+			}
+		}
+		
+		/// <summary>
+		/// Loads all of the data files in the registered directories.
+		/// </summary>
+		/// <returns>
+		/// A <see cref="List"/> of <see cref="FileLoadFailure"/> for files that failed to load
+		/// </returns>
+		public List<FileLoadFailure> LoadFiles()
+		{
+			PrepareForFileLoad();
+			Dictionary<FileInfo, IWarFoundryFactory> loadableRaces = new Dictionary<FileInfo, IWarFoundryFactory>();
+			Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems = new Dictionary<FileInfo, IWarFoundryFactory>();
+			List<FileLoadFailure> failedLoads = FillLoadableFiles(loadableRaces, loadableGameSystems);
+			failedLoads.AddRange(LoadGameSystems(loadableGameSystems));
+			failedLoads.AddRange(LoadRaces(loadableRaces));
+			OnFileLoadingFinished(failedLoads);
+			FinishFileLoad();
+			return failedLoads;
+		}
+
+		private void OnFileLoadingFinished(List<FileLoadFailure> failures)
+		{
+			if (FileLoadingFinished != null)
+			{
+				FileLoadingFinished(failures);
+			}
+		}
+
+		protected virtual void PrepareForFileLoad()
+		{
+			//Do nothing special
+		}
+
+		protected virtual void FinishFileLoad()
+		{
+			//Do nothing special
+		}
+
+		private List<FileLoadFailure> FillLoadableFiles(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems)
+		{			
+			List<FileLoadFailure> fails = new List<FileLoadFailure>();
+			
+			foreach (DirectoryInfo directory in directories)
+			{
+				directory.Refresh();
+
+				if (directory.Exists)
+				{
+					List<FileLoadFailure> directoryFails = FillLoadableFilesForDirectory(loadableRaces, loadableGameSystems, directory);
+					fails.AddRange(directoryFails);
+				}
+				else
+				{
+					LogNotifier.WarnFormat(GetType(), "Load for {0} failed because directory didn't exist", directory.FullName);
+				}
+			}
+			
+			return fails;
+		}
+
+		private List<FileLoadFailure> FillLoadableFilesForDirectory(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems, DirectoryInfo directory)
+		{
+			List<FileLoadFailure> fails = new List<FileLoadFailure>();
+			LogNotifier.Debug(GetType(), "Load from " + directory.FullName);
+		
+			foreach (FileInfo file in directory.GetFiles())
+			{
+				IWarFoundryFactory factory = GetGameSystemLoadingFactoryForFile(file);
+				
+				if (factory != null)
+				{
+					loadableGameSystems.Add(file, factory);
+				}
+				else
+				{
+					factory = GetRaceLoadingFactoryForFile(file);
+	
+					if (factory != null)
+					{
+						loadableRaces.Add(file, factory);
+					}
+					else
+					{
+						FileLoadFailure failure = new FileLoadFailure(file, "File not handled as a Race or Game System definition: {0}", "FileNotHandled");
+						fails.Add(failure);
+						LogNotifier.Info(GetType(), failure.Message);
+					}
+				}
+			}
+			
+			foreach (DirectoryInfo subdir in directory.GetDirectories())
+			{
+				fails.AddRange(FillLoadableFilesForDirectory(loadableRaces, loadableGameSystems, subdir));
+			}
+
+			return fails;
+		}
+
+		private IWarFoundryFactory GetGameSystemLoadingFactoryForFile(FileInfo file)
+		{
+			IWarFoundryFactory loadingFactory = null;
+			
+			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
+			{
+				if (factory.CanHandleFileAsGameSystem(file))
+				{
+					loadingFactory = factory;
+					break;
+				}
+			}
+
+			if (loadingFactory == null)
+			{
+				foreach (INativeWarFoundryFactory factory in factories)
+				{
+					if (factory.CanHandleFileAsGameSystem(file))
+					{
+						loadingFactory = factory;
+						break;
+					}
+				}
+			}
+
+			return loadingFactory;
+		}
+
+		private IWarFoundryFactory GetRaceLoadingFactoryForFile(FileInfo file)
+		{
+			IWarFoundryFactory loadingFactory = null;
+			
+			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
+			{
+				if (factory.CanHandleFileAsRace(file))
+				{
+					loadingFactory = factory;
+					break;
+				}
+			}
+
+			if (loadingFactory == null)
+			{
+				foreach (INativeWarFoundryFactory factory in factories)
+				{
+					if (factory.CanHandleFileAsRace(file))
+					{
+						loadingFactory = factory;
+						break;
+					}
+				}
+			}
+
+			return loadingFactory;
+		}
+
+		private List<FileLoadFailure> LoadGameSystems(Dictionary<FileInfo, IWarFoundryFactory> gameSystemFiles)
+		{
+			List<FileLoadFailure> fails = new List<FileLoadFailure>();
+
+			
+			foreach (FileInfo file in gameSystemFiles.Keys)
+			{
+				FileLoadFailure failure = null;
+				
+				try
+				{
+					bool loaded = LoadObject(file, gameSystemFiles[file]);
+	
+					if (!loaded)
+					{
+						failure = new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as GameSystem using {1}");
+					}
+				}
+				catch (Exception ex)
+				{
+					failure = new FileLoadFailure(file, null, ex.Message, null, ex);
+				}
+						
+				if (failure != null)
+				{
+					fails.Add(failure);
+					LogNotifier.Warn(GetType(), failure.Message, failure.Exception);
+				}
+			}
+			
+			return fails;
+		}
+
+		private List<FileLoadFailure> LoadRaces(Dictionary<FileInfo, IWarFoundryFactory> raceFiles)
+		{
+			List<FileLoadFailure> fails = new List<FileLoadFailure>();
+			
+			foreach (FileInfo file in raceFiles.Keys)
+			{
+				FileLoadFailure failure = null;
+				
+				try
+				{
+					bool loaded = LoadObject(file, raceFiles[file]);
+	
+					if (!loaded)
+					{
+						failure = new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as Race using {1}");
+					}
+				}
+				catch (Exception ex)
+				{
+					failure = new FileLoadFailure(file, null, ex.Message, null, ex);
+				}
+						
+				if (failure != null)
+				{
+					fails.Add(failure);
+					LogNotifier.Warn(GetType(), failure.Message, failure.Exception);
+				}
+			}
+			
+			return fails;
+		}
+
+		private bool LoadObject(FileInfo file, IWarFoundryFactory factory)
+		{
+			LogNotifier.DebugFormat(GetType(), "Loading {0} using {1}", file.FullName, factory.GetType().Name);
+			factory.RaceLoaded+= StoreRace;
+			factory.GameSystemLoaded+= StoreGameSystem;
+			ICollection<IWarFoundryObject> objects = factory.CreateObjectsFromFile(file);
+			return objects.Count > 0;
+		}
+		
+		
+		/// <summary>
+		/// Loads a single file through the registered WarFoundryFactories, if a factory exists that supports the file format.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to attempt to load
+		/// </param>
+		/// <returns>
+		/// An ICollection of IWarFoundryObjects loaded from <code>file</code>
+		/// </returns>
+		public ICollection<IWarFoundryObject> LoadFile(FileInfo file)
+		{
+			ICollection<IWarFoundryObject> objs = null;
+			IWarFoundryFactory loadFactory = null;
+			
+			try
+			{
+				objs = LoadFileWithNonNativeFactories(file, out loadFactory);
+				
+				if (objs == null)
+				{
+					objs = LoadFileWithNativeFactories(file, out loadFactory);
+				}
+			}
+			catch (InvalidFileException ex)
+			{
+				LogNotifier.Error(GetType(), file.FullName + " failed to load", ex);
+			}
+				
+			if (objs != null)
+			{
+				AddLoadedObjects(objs, loadFactory);
+			}
+			else
+			{
+				objs = new List<IWarFoundryObject>();
+			}
+
+			return objs;
+		}
+
+		private ICollection<IWarFoundryObject> LoadFileWithNonNativeFactories(FileInfo file, out IWarFoundryFactory loadFactory)
+		{
+			ICollection<IWarFoundryObject> objs = null;
+			loadFactory = null;
+			
+			if (nonNativeFactories.Count > 0)
+			{
+				LogNotifier.Debug(GetType(), "Attempting to load " + file.FullName + " as a non-native file");
+				
+				foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
+				{
+					bool canLoad = factory.CanHandleFileFormat(file);
+					LogNotifier.Debug(GetType(), "Load using " + factory.GetType().FullName + "? " + (canLoad ? "yes" : "no"));
+					
+					if (canLoad)
+					{
+						objs = factory.CreateObjectsFromFile(file);
+						
+						if (objs != null)
+						{
+							loadFactory = factory;
+							break;
+						}
+					}			         
+				}
+			}
+			
+			return objs;
+		}
+
+		private ICollection<IWarFoundryObject> LoadFileWithNativeFactories(FileInfo file, out IWarFoundryFactory loadFactory)
+		{
+			ICollection<IWarFoundryObject> objs = null;
+			loadFactory = null;
+			
+			if (factories.Count > 0)
+			{
+				LogNotifier.Debug(GetType(), "Attempting to load " + file.FullName + " as native file");
+						
+				foreach (INativeWarFoundryFactory factory in factories)
+				{
+					if (factory.CanHandleFileFormat(file))
+					{
+						objs = factory.CreateObjectsFromFile(file);
+						
+						if (objs != null)
+						{
+							loadFactory = factory;
+							break;
+						}
+					}
+				}
+			}
+			
+			return objs;
+		}
+
+		private void AddLoadedObjects(ICollection<IWarFoundryObject> loadedObjs, IWarFoundryFactory factory)
+		{
+			SimpleSet<IWarFoundryObject> objs;
+			loadedObjects.TryGetValue(factory, out objs);
+			
+			if (objs == null)
+			{
+				objs = new SimpleSet<IWarFoundryObject>();
+				loadedObjects.Add(factory, objs);
+			}
+				
+			objs.AddRange(loadedObjs);
+			StoreObjects(loadedObjs);
+		}
+
+		private void StoreObjects(ICollection<IWarFoundryObject> loadedObjects)
+		{
+			foreach (IWarFoundryObject loadedObject in loadedObjects)
+			{
+				if (loadedObject is GameSystem)
+				{
+					StoreGameSystem((GameSystem)loadedObject);
+				}
+				else if (loadedObject is Race)
+				{
+					StoreRace((Race)loadedObject);
+				}
+			}
+		}
+
+		protected void StoreGameSystem(GameSystem system)
+		{
+			GameSystem existingSystem = GetExistingSystemForSystem(system);
+			
+			if (existingSystem != null)
+			{				
+				if (!system.Equals(existingSystem))
+				{
+					//TODO: Raise an event to say we got a different duplicate
+					//We can't just fail, because failing is for completely unhandled files, not for objects in a file
+				}
+			}
+			else
+			{
+				DoStoreGameSystem(system);
+			}
+		}
+		
+		/// <summary>
+		/// Gets a game system that has already been loaded that duplicates the supplied game system's ID, if one exists.
+		/// </summary>
+		/// <param name="system">
+		/// The <see cref="GameSystem"/> to find pre-existing duplicates of
+		/// </param>
+		/// <returns>
+		/// <code>null</code> if no existing duplicate exists, else the duplicate <see cref="GameSystem"/>
+		/// </returns>
+		protected abstract GameSystem GetExistingSystemForSystem(GameSystem system);
+		
+		/// <summary>
+		/// Stores a GameSystem in the loader's relevant storage structure
+		/// </summary>
+		/// <param name="system">
+		/// The loaded <see cref="GameSystem"/> to store
+		/// </param>
+		protected abstract void DoStoreGameSystem(GameSystem system);
+
+		protected void StoreRace(Race race)
+		{
+			if (race.GameSystem == null)
+			{
+				throw new InvalidOperationException("Race cannot have null game system. Game system should be loaded before race.");
+			}
+			
+			DoStoreRace(race);
+		}
+		
+		/// <summary>
+		/// Performs the implementation specific storage of a race
+		/// </summary>
+		/// <param name="race">
+		/// The <see cref="Race"/> to store
+		/// </param>
+		protected abstract void DoStoreRace(Race race);
+		
+		/// <summary>
+		/// Gets all <see cref="GameSystem"/>s that are currently available, determined by those that can be loaded with the current <see cref="IWarFoundryFactory"/>s. 
+		/// </summary>
+		/// <returns>
+		/// An array of <see cref="GameSystem"/>s that are currently available.
+		/// </returns>
+		public abstract GameSystem[] GetGameSystems();
+
+		/// <summary>
+		/// Gets a single <see cref="GameSystem"/> with a given ID. 
+		/// </summary>
+		/// <param name="systemID">
+		/// The ID of the <see cref="GameSystem"/> to get, as a <see cref="System.String"/>.
+		/// </param>
+		/// <returns>
+		/// The <see cref="GameSystem"/> with the given ID, or <code>null</code> if one doesn't exist.
+		/// </returns>
+		public abstract GameSystem GetGameSystem(string systemID);
+
+		/// <summary>
+		/// Removes a loaded <see cref="GameSystem"/>. Used when a GameSystem fails to complete loading
+		/// </summary>
+		/// <param name="system">The GameSystem to remove</param>
+		protected internal abstract void RemoveGameSystem(GameSystem system);
+
+		/// <summary>
+		/// Gets an array of the races for the specified <see cref="GameSystem"/>.
+		/// </summary>
+		/// <param name="system">
+		/// The <see cref="GameSystem"/> to get the available races for.
+		/// </param>
+		/// <returns>
+		/// An array of <see cref="Race"/>s for the <see cref="GameSystem"/>
+		/// </returns>
+		public abstract Race[] GetRaces(GameSystem system);
+
+		/// <summary>
+		/// Gets a single race for a given <see cref="GameSystem"/> by ID of the race.
+		/// </summary>
+		/// <param name="system">
+		/// The <see cref="GameSystem"/> that the race is part of.
+		/// </param>
+		/// <param name="raceID">
+		/// A <see cref="System.String"/> ID for the race to load.
+		/// </param>
+		/// <returns>
+		/// A <see cref="Race"/> with the specified ID from the <see cref="GameSystem"/>, or <code>null</code> if one doesn't exist.
+		/// </returns>
+		public abstract Race GetRace(GameSystem system, string raceID);
+
+		/// <summary>
+		/// Gets a single race for a given <see cref="GameSystem"/> by the race's ID and sub-race ID.
+		/// </summary>
+		/// <param name="system">
+		/// The <see cref="GameSystem"/> that the race is part of.
+		/// </param>
+		/// <param name="raceID">
+		/// The <see cref="System.String"/> ID for the race to load.
+		/// </param>
+		/// <param name="raceSubID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="Race"/>
+		/// </returns>
+		public abstract Race GetRace(GameSystem system, string raceID, string raceSubID);
+
+		protected internal abstract void RemoveRace(Race race);
+
+		/// <summary>
+		/// Gets the IDs of all of the game systems currently available.
+		/// </summary>
+		/// <returns>
+		/// An array of <see cref="System.String"/>s representing the IDs of the game systems.
+		/// </returns>
+		public virtual string[] GetGameSystemIDs()
+		{
+			GameSystem[] systems = GetGameSystems();
+			return GetWarFoundryObjectIDs(systems);
+		}
+
+		protected string[] GetWarFoundryObjectIDs(WarFoundryObject[] objs)
+		{
+			int objCount = objs.Length;
+			string[] keys = new string[objCount];
+
+			for (int i = 0; i < objCount; i++)
+			{
+				keys[i] = objs[i].ID;
+			}
+
+			return keys;
+		}
+		
+		/// <summary>
+		/// Gets the IDs of all of the races of a specified game system.
+		/// </summary>
+		/// <param name="system">
+		/// The <see cref="GameSystem"/> to get the available races for.
+		/// </param>
+		/// <returns>
+		/// An array of <see cref="System.String"/>s representing the IDs of the races of the specified game system.
+		/// </returns>
+		public virtual string[] GetSystemRaceIDs(GameSystem system)
+		{
+			Race[] races = GetRaces(system);
+			return GetWarFoundryObjectIDs(races);
+		}
+
+		public Army LoadArmy(FileInfo file)
+		{
+			IWarFoundryFactory factory = GetArmyLoadingFactoryForFile(file);			
+			Army loadedArmy = null;
+			
+			if (factory != null)
+			{
+				ICollection<IWarFoundryObject> objs = factory.CreateObjectsFromFile(file);
+								
+				if (objs.Count == 1)
+				{
+					foreach (IWarFoundryObject systemCount in objs)
+					{
+						if (systemCount is Army)
+						{
+							loadedArmy = (Army)systemCount;
+						}
+					}
+				}
+			}
+			
+			return loadedArmy;
+		}
+
+		private IWarFoundryFactory GetArmyLoadingFactoryForFile(FileInfo file)
+		{
+			IWarFoundryFactory loadingFactory = null;
+			
+			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
+			{
+				if (factory.CanHandleFileAsArmy(file))
+				{
+					loadingFactory = factory;
+					break;
+				}
+			}
+
+			if (loadingFactory == null)
+			{
+				foreach (INativeWarFoundryFactory factory in factories)
+				{
+					if (factory.CanHandleFileAsArmy(file))
+					{
+						loadingFactory = factory;
+						break;
+					}
+				}
+			}
+
+			return loadingFactory;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/AbstractReplaceUnitEquipmentCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,74 @@
+// This file (AbstractReplaceUnitEquipmentCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Commands;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// An abstract implementation of the core method for replacing one equipment item with another
+	/// </summary>
+	public abstract class AbstractReplaceUnitEquipmentCommand : Command
+	{
+		private SetUnitEquipmentNumericAmountCommand removeOldCommand;
+		private AbstractSetUnitEquipmentAmountCommand addNewCommand;
+
+		public AbstractReplaceUnitEquipmentCommand(Unit unit, UnitEquipmentItem oldItem, AbstractSetUnitEquipmentAmountCommand addNewEquipmentCommand)
+		{
+			//We can get away with a numeric amount here even if it is a ratio item because we're setting it to 0
+			removeOldCommand = new SetUnitEquipmentNumericAmountCommand(unit, oldItem, 0);
+			addNewCommand = addNewEquipmentCommand;
+		}
+
+		public override bool CanExecute()
+		{
+			return removeOldCommand.CanExecute() && addNewCommand.CanExecute();
+		}
+
+		public override string Description
+		{
+			get
+			{
+				return Translation.GetTranslation("replaceUnitEquipmentCommandDescription", "replace {0} with {1} for {2}", removeOldCommand.EquipItem.Name, addNewCommand.EquipItem.Name, removeOldCommand.Unit.Name);
+			}
+		}
+
+		public override string UndoDescription
+		{
+			get
+			{
+				return Translation.GetTranslation("replaceUnitEquipmentCommandUndoDescription", "replace {0} with {1} for {2}", addNewCommand.EquipItem.Name, removeOldCommand.EquipItem.Name, removeOldCommand.Unit.Name);
+			}
+		}
+
+		public override bool Execute()
+		{
+			this.Redo();
+			return true;
+		}
+
+		public override void Redo()
+		{
+			removeOldCommand.Redo();
+			addNewCommand.Redo();
+		}
+
+		public override void Undo()
+		{
+			addNewCommand.Undo();
+			removeOldCommand.Undo();
+		}
+
+		public override string Name
+		{
+			get
+			{
+				return "Replace required equipment";
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/AbstractSetUnitEquipmentAmountCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,120 @@
+//  This file (AbstractSetUnitEquipmentAmountCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Commands;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+using IBBoard.WarFoundry.API.Util;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// Abstract parent class for commands that set the amount of an equipment item a unit has to a fixed numeric or ratio value
+	/// </summary>
+	public abstract class AbstractSetUnitEquipmentAmountCommand : Command
+	{
+		private Unit unit;
+		private UnitEquipmentItem equip;
+		private double oldAmount;
+		private bool oldAmountWasRatio;
+
+		public AbstractSetUnitEquipmentAmountCommand(Unit unit, UnitEquipmentItem item)
+		{
+			this.unit = unit;
+			equip = item;
+			oldAmount = UnitEquipmentUtil.GetEquipmentAmount(unit, equip);
+			oldAmountWasRatio = UnitEquipmentUtil.GetEquipmentAmountIsRatio(unit, equip);
+		}
+
+		public override bool CanExecute()
+		{
+			return (unit != null && equip != null);
+		}
+
+		public override string Description
+		{
+			get
+			{
+				return Translation.GetTranslation("setEquipmentAmountCommandDescription", "set {0} amount for {1} to {2}", equip.Name, unit.Name, GetNewAmountString());
+			}
+		}
+
+		/// <summary>
+		/// Gets the string representation for the new amount of the equipment item to take
+		/// </summary>
+		/// <returns>
+		/// the string representation for the new amount of the equipment item to take
+		/// </returns>
+		protected abstract string GetNewAmountString();
+
+		public override string UndoDescription
+		{
+			get
+			{
+				return Translation.GetTranslation("setEquipmentAmountCommandUndoDescription", "set {0} amount for {1} to {2}", equip.Name, unit.Name, GetOldAmountString());
+			}
+		}
+
+		/// <summary>
+		/// Gets the string representation for the old amount of the equipment item to take
+		/// </summary>
+		/// <returns>
+		/// the string representation for the old amount of the equipment item to take
+		/// </returns>
+		protected string GetOldAmountString()
+		{
+			return oldAmountWasRatio ? GetRatioAmountString(oldAmount, UnitEquipmentRatioSelection.CalculateNumberTaken(Unit, EquipItem, oldAmount)) : GetNumberAmountString((int)oldAmount);
+		}
+
+		protected string GetNumberAmountString(int number)
+		{
+			return Translation.GetTranslation("equipmentAmountNumber", "{0}", number);
+		}
+
+		protected string GetRatioAmountString(double amount, int number)
+		{
+			string amountString;
+			
+			if (amount == 100)
+			{
+				amountString = Translation.GetTranslation("equipmentAmountAll", "all ({1})", amount, number);
+			}
+			else
+			{
+				amountString = Translation.GetTranslation("equipmentAmountPercentage", "{0}% ({1})", amount, number);
+			}
+			
+			return amountString;
+		}
+
+		public override bool Execute()
+		{
+			this.Redo();
+			return true;
+		}
+
+		public override void Undo()
+		{
+			if (oldAmountWasRatio)
+			{
+				unit.SetEquipmentRatio(equip, oldAmount);
+			}
+			else
+			{
+				unit.SetEquipmentAmount(equip, (int)oldAmount);
+			}
+		}
+
+		public UnitEquipmentItem EquipItem
+		{
+			get { return equip; }
+		}
+
+		public Unit Unit
+		{
+			get { return unit; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/CreateAndAddUnitCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,72 @@
+// This file (CreateAndAddUnitCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Commands;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	public class CreateAndAddUnitCommand : Command
+	{
+		private UnitType addedUnitType;
+		private ArmyCategory armyCat;
+		private Unit addedUnit;
+
+		public CreateAndAddUnitCommand(UnitType toAdd, ArmyCategory armyCatTo)
+		{
+			addedUnitType = toAdd;
+			armyCat = armyCatTo;
+		}
+
+		public override bool CanExecute()
+		{
+			return (addedUnitType != null && armyCat != null);
+		}
+
+		public override string Description
+		{
+			get
+			{
+				return Translation.GetTranslation("createAndAddUnitCommandDescription", "add unit of {0} to the army", addedUnitType.Name);
+			}
+		}
+
+		public override string UndoDescription
+		{
+			get
+			{
+				return Translation.GetTranslation("createAndAddUnitCommandUndoDescription", "remove unit of {0} from army", addedUnitType.Name);
+			}
+		}
+
+		public override bool Execute()
+		{
+			addedUnit = new Unit(addedUnitType, armyCat);
+			this.Redo();
+			return true;
+		}
+
+		public override void Redo()
+		{
+			armyCat.AddUnit(addedUnit);
+		}
+
+		public override void Undo()
+		{
+			armyCat.RemoveUnit(addedUnit);
+		}
+
+		public override string Name
+		{
+			get { return "Add new unit"; }
+		}
+
+		public Unit Unit
+		{
+			get { return addedUnit; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/RemoveUnitCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,68 @@
+// This file (RemoveUnitCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Commands;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// Summary description for RemoveUnitCommand.
+	/// </summary>
+	public class RemoveUnitCommand : Command
+	{
+		private Unit unit;
+		private ArmyCategory cat;
+
+		public RemoveUnitCommand(Unit toRemove)
+		{
+			unit = toRemove;
+			cat = unit.Category;
+		}
+
+		public override bool CanExecute()
+		{
+			return (unit != null);
+		}
+
+		public override string Description
+		{
+			get
+			{
+				return Translation.GetTranslation("removeUnitCommandDescription", "remove {0} from the army", unit.Name);
+			}
+		}
+
+		public override string UndoDescription
+		{
+			get
+			{
+				return Translation.GetTranslation("removeUnitCommandUndoDescription", "re-add {0} to the army", unit.Name);
+			}
+		}
+
+		public override bool Execute()
+		{
+			this.Redo();
+			return true;
+		}
+
+		public override void Redo()
+		{
+			cat.RemoveUnit(unit);
+		}
+
+		public override void Undo()
+		{
+			cat.AddUnit(unit);
+		}
+
+		public override string Name
+		{
+			get { return "Remove unit"; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/ReplaceUnitEquipmentWithNumericAmountItemCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,21 @@
+//  This file (ReplaceUnitEquipmentWithNumericAmountItemCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+// 
+
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// A concrete implementation of an equipment replacing command that replaces a given equipment item with a different item that has an absolute numeric amount.
+	/// </summary>
+	public class ReplaceUnitEquipmentWithNumericAmountItemCommand : AbstractReplaceUnitEquipmentCommand
+	{	
+		public ReplaceUnitEquipmentWithNumericAmountItemCommand(Unit unit, UnitEquipmentItem oldItem, UnitEquipmentItem newItem, int newItemAmount) : base(unit, oldItem, new SetUnitEquipmentNumericAmountCommand(unit, newItem, newItemAmount))
+		{
+			//Do nothing special
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/ReplaceUnitEquipmentWithRatioAmountItemCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,21 @@
+//  This file (ReplaceUnitEquipmentWithRatioAmountItemCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+// 
+
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// A concrete implementation of an equipment replacing command that replaces a given equipment item with a different item that has a ratio/percentage amount.
+	/// </summary>
+	public class ReplaceUnitEquipmentWithRatioAmountItemCommand : AbstractReplaceUnitEquipmentCommand
+	{	
+		public ReplaceUnitEquipmentWithRatioAmountItemCommand(Unit unit, UnitEquipmentItem oldItem, UnitEquipmentItem newItem, double newItemRatio) : base(unit, oldItem, new SetUnitEquipmentRatioAmountCommand(unit, newItem, newItemRatio))
+		{
+			//Do nothing special
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/SetNameCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,69 @@
+// This file (SetNameCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Commands;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// Summary description for SetNameCommand.
+	/// </summary>
+	public class SetNameCommand : Command
+	{
+		private WarFoundryObject obj;
+		private string newName, oldName;
+
+		public SetNameCommand(WarFoundryObject toRename, string name)
+		{
+			obj = toRename;
+			newName = name;
+			oldName = obj.Name;
+		}
+
+		public override bool CanExecute()
+		{
+			return (obj != null && newName != null && newName != "");
+		}
+
+		public override string Description
+		{
+			get
+			{
+				return Translation.GetTranslation("setUnitNameCommandDescription", "rename \"{0}\" to \"{1}\"", oldName, newName);
+			}
+		}
+
+		public override string UndoDescription
+		{
+			get
+			{
+				return Translation.GetTranslation("setUnitNameCommandUndoDescription", "rename \"{0}\" to \"{1}\"", newName, oldName);
+			}
+		}
+
+		public override bool Execute()
+		{
+			this.Redo();
+			return true;
+		}
+
+		public override void Redo()
+		{
+			obj.Name = newName;
+		}
+
+		public override void Undo()
+		{
+			obj.Name = oldName;
+		}
+
+		public override string Name
+		{
+			get { return "Rename item"; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/SetUnitEquipmentNumericAmountCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,39 @@
+// This file (SetUnitEquipmentNumericAmountCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Commands;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// Summary description for SetUnitEquipmentNumericAmountCommand.
+	/// </summary>
+	public class SetUnitEquipmentNumericAmountCommand : AbstractSetUnitEquipmentAmountCommand
+	{
+		private int newAmount;
+		
+		public SetUnitEquipmentNumericAmountCommand(Unit unit, UnitEquipmentItem item, int amount) : base(unit, item)
+		{
+			newAmount = amount;
+		}
+
+		protected override string GetNewAmountString ()
+		{
+			return GetNumberAmountString(newAmount);
+		}
+		
+		public override void Redo()
+		{
+			Unit.SetEquipmentAmount(EquipItem, newAmount);
+		}
+
+		public override string Name
+		{
+			get { return "Set equipment amount"; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/SetUnitEquipmentRatioAmountCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,39 @@
+// This file (SetUnitEquipmentRatioAmountCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Commands;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// Summary description for SetUnitEquipmentRatioAmountCommand.
+	/// </summary>
+	public class SetUnitEquipmentRatioAmountCommand : AbstractSetUnitEquipmentAmountCommand
+	{
+		private double newAmount;
+		
+		public SetUnitEquipmentRatioAmountCommand(Unit unit, UnitEquipmentItem item, double amount) : base(unit, item)
+		{
+			newAmount = amount;
+		}
+
+		protected override string GetNewAmountString ()
+		{
+			return GetRatioAmountString(newAmount, UnitEquipmentRatioSelection.CalculateNumberTaken(Unit, EquipItem, newAmount));
+		}
+
+		public override void Redo()
+		{
+			Unit.SetEquipmentRatio(EquipItem, newAmount);
+		}
+
+		public override string Name
+		{
+			get { return "Set equipment ratio"; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Commands/SetUnitSizeCommand.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,69 @@
+// This file (SetUnitSizeCommand.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Commands;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Commands
+{
+	/// <summary>
+	/// Summary description for SetNameCommand.
+	/// </summary>
+	public class SetUnitSizeCommand : Command
+	{
+		private Unit unit;
+		private int newSize, oldSize;
+
+		public SetUnitSizeCommand(Unit toResize, int size)
+		{
+			unit = toResize;
+			newSize = size;
+			oldSize = unit.Size;
+		}
+
+		public override bool CanExecute()
+		{
+			return (unit != null && newSize > 0 && oldSize > 0);
+		}
+
+		public override string Description
+		{
+			get
+			{
+				return Translation.GetTranslation("setUnitSizeCommandDescription", "set size of {0} to {1}", unit.Name, newSize);
+			}
+		}
+
+		public override string UndoDescription
+		{
+			get
+			{
+				return Translation.GetTranslation("setUnitSizeCommandUndoDescription", "set size of {0} to {1}", unit.Name, oldSize);
+			}
+		}
+
+		public override bool Execute()
+		{
+			this.Redo();
+			return true;
+		}
+
+		public override void Redo()
+		{
+			unit.Size = newSize;
+		}
+
+		public override void Undo()
+		{
+			unit.Size = oldSize;
+		}
+
+		public override string Name
+		{
+			get { return "Change unit size"; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/DefaultWarFoundryLoader.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,304 @@
+// This file (DefaultWarFoundryLoader.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API
+{
+	/// <summary>
+	/// The default implementation of a <see cref="AbstractWarFoundryLoader"/>
+	/// </summary>
+	public class DefaultWarFoundryLoader : AbstractWarFoundryLoader
+	{
+		private Dictionary<string, GameSystem> systemsTable;
+		private Dictionary<string, Dictionary<string, Dictionary<string, Race>>> racesTable; //Keys are: System, Race, SubRace
+		private bool loaded = false;
+		private bool loading = false;
+
+		public DefaultWarFoundryLoader()
+		{
+			systemsTable = new Dictionary<string,GameSystem>();
+			racesTable = new Dictionary<string,Dictionary<string,Dictionary<string,Race>>>();
+		}
+
+		protected override void PrepareForFileLoad()
+		{
+			loading = true;
+			systemsTable.Clear();
+			racesTable.Clear();
+		}
+
+		protected override void FinishFileLoad()
+		{
+			loaded = true;
+			loading = false;
+		}
+
+		protected override GameSystem GetExistingSystemForSystem(GameSystem system)
+		{
+			return DictionaryUtils.GetValue(systemsTable, system.ID.ToLower());
+		}
+
+		protected override void DoStoreGameSystem(GameSystem system)
+		{
+			systemsTable[system.ID.ToLower()] = system;
+		}
+
+		protected override void DoStoreRace(Race race)
+		{
+			Dictionary<string, Dictionary<string, Race>> systemRaces;
+			
+			string systemID = race.GameSystem.ID.ToLower();
+			racesTable.TryGetValue(systemID, out systemRaces);
+			
+			if (systemRaces == null)
+			{
+				systemRaces = new Dictionary<string,Dictionary<string,Race>>();
+				racesTable.Add(systemID, systemRaces);
+			}
+			
+			Dictionary<string, Race> subRaces;
+			systemRaces.TryGetValue(race.ID.ToLower(), out subRaces);
+			
+			if (subRaces == null)
+			{
+				subRaces = new Dictionary<string,Race>();
+				systemRaces.Add(race.ID.ToLower(), subRaces);
+			}
+			
+			string subID = race.SubID.ToLower();
+
+			if (subRaces.ContainsKey(subID))
+			{
+				Race existingRace = subRaces[subID];
+				
+				if (!race.Equals(existingRace))
+				{
+					//TODO: Raise an event to say we got a different duplicate
+					//We can't just fail, because failing is for completely unhandled files, not for objects in a file
+				}
+			}
+			else
+			{
+				subRaces.Add(race.SubID.ToLower(), race);
+			}
+		}
+
+		public override GameSystem[] GetGameSystems()
+		{
+			if (!loaded && !loading)
+			{
+				LoadFiles();
+			}
+			
+			return DictionaryUtils.ToArray<string, GameSystem>(systemsTable);
+		}
+
+		public override GameSystem GetGameSystem(string systemID)
+		{
+			if (!loaded && !loading)
+			{
+				LoadFiles();
+			}
+			
+			GameSystem system;
+			systemsTable.TryGetValue(systemID.ToLower(), out system);
+			return system;
+		}
+
+		protected internal override void RemoveGameSystem(GameSystem system)
+		{
+			systemsTable.Remove(system.ID.ToLower());
+		}
+
+		public override Race[] GetRaces(GameSystem system)
+		{
+			return GetRaces(system.ID);
+		}
+
+		/// <summary>
+		/// Gets an array of the races for a game system by ID.
+		/// </summary>
+		/// <param name="systemID">
+		/// The <see cref="System.String"/> ID of the game system to get races for
+		/// </param>
+		/// <returns>
+		/// An array of <see cref="Race"/>s for the specified game system
+		/// </returns>
+		public Race[] GetRaces(string systemID)
+		{
+			if (!loaded && !loading)
+			{
+				LoadFiles();
+			}
+			
+			systemID = systemID.ToLower();
+			Dictionary<string, Dictionary<string, Race>> system;
+			racesTable.TryGetValue(systemID, out system);
+			
+			if (system == null)
+			{
+				return new Race[0];
+			}
+
+			int count = 0;
+
+			foreach (Dictionary<string, Race> racesDict in system.Values)
+			{
+				count += racesDict.Count;
+			}
+
+			Race[] races = new Race[count];
+			int i = 0;
+
+			foreach (string raceID in system.Keys)
+			{
+				foreach (string raceSubId in system[raceID].Keys)
+				{
+					races[i++] = GetRace(systemID, raceID, raceSubId);
+				}
+			}
+
+			return races;
+		}
+
+		public override Race GetRace(GameSystem system, string raceID)
+		{
+			return GetRace(system.ID, raceID);
+		}
+
+		/// <summary>
+		/// Gets a single race for a given game system by ID of the game system and race.
+		/// </summary>
+		/// <param name="systemID">
+		/// The <see cref="System.String"/> ID of the game system that the race is part of.
+		/// </param>
+		/// <param name="raceID">
+		/// The <see cref="System.String"/> ID for the race to load.
+		/// </param>
+		/// <returns>
+		/// A <see cref="Race"/> with the specified ID from the game system with the specified ID, or <code>null</code> if there is no race or game system with those IDs.
+		/// </returns>
+		public Race GetRace(string systemID, string raceID)
+		{
+			return GetRace(systemID, raceID, "");
+		}
+
+		public override Race GetRace(GameSystem system, string raceID, string raceSubID)
+		{
+			return GetRace(system.ID, raceID, raceSubID);
+		}
+
+		/// <summary>
+		/// Gets a single race for a given game system by the game system's ID and the race's ID and sub-race ID.
+		/// </summary>
+		/// <param name="systemID">
+		/// The <see cref="System.String"/> ID of the game system that the race is part of.
+		/// </param>
+		/// <param name="raceID">
+		/// The <see cref="System.String"/> ID for the race to load.
+		/// </param>
+		/// <param name="raceSubID">
+		/// A <see cref="System.String"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="Race"/>
+		/// </returns>
+		public Race GetRace(string systemID, string raceID, string raceSubID)
+		{
+			if (!loaded && !loading)
+			{
+				LoadFiles();
+			}
+			
+			Race race = null;
+			
+			Dictionary<string, Race> subraces = GetRaceTable(systemID, raceID);
+
+			if (subraces != null)
+			{
+				subraces.TryGetValue(raceSubID.ToLower(), out race);
+			}
+			
+			return race;
+		}
+
+		private Dictionary<string, Race> GetRaceTable(string systemID, string raceID)
+		{
+			Dictionary<string, Dictionary<string, Race>> races;
+			racesTable.TryGetValue(systemID.ToLower(), out races);
+			Dictionary<string, Race> subraces = null;
+
+			if (races != null)
+			{
+				races.TryGetValue(raceID.ToLower(), out subraces);
+			}
+
+			return subraces;
+		}
+
+		protected internal override void RemoveRace(Race race)
+		{
+			Dictionary<string, Race> subraces = GetRaceTable(race.GameSystem.ID, race.ID);
+
+			if (subraces != null)
+			{
+				subraces.Remove(race.SubID.ToLower());
+			}
+		}
+
+		public override string[] GetGameSystemIDs()
+		{
+			if (!loaded && !loading)
+			{
+				LoadFiles();
+			}
+
+			return DictionaryUtils.ToKeyArray(systemsTable);
+		}
+
+		public override string[] GetSystemRaceIDs(GameSystem system)
+		{
+			return GetSystemRaceIDs(system.ID);
+		}
+
+		/// <summary>
+		/// Gets the IDs of all of the races of a specified game system.
+		/// </summary>
+		/// <param name="systemID">
+		/// The <see cref="System.String"/> ID of the game system to get the available races for.
+		/// </param>
+		/// <returns>
+		/// An array of <see cref="System.String"/>s representing the IDs of the races of the specified game system.
+		/// </returns>
+		public string[] GetSystemRaceIDs(string systemID)
+		{
+			if (!loaded && !loading)
+			{
+				LoadFiles();
+			}
+
+			Dictionary<string, Dictionary<string, Race>> races = racesTable[systemID.ToLower()];
+
+			if (races == null)
+			{
+				return new string[0];
+			}
+			else
+			{
+				string[] keys = new string[races.Keys.Count];
+				int i = 0;
+
+				foreach (string key in races.Keys)
+				{
+					keys[i++] = key;
+				}
+
+				return keys;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Delegates.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,23 @@
+// This file (Delegates.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API
+{
+	public delegate void ObjectChangedDelegate(WarFoundryObject oldValue, WarFoundryObject newValue);
+	public delegate void ArmyChangedDelegate(Army oldValue, Army newValue);
+	public delegate void GameSystemChangedDelegate(GameSystem oldValue, GameSystem newValue);
+	public delegate void ObjectAddDelegate(WarFoundryObject val);
+	public delegate void ObjectRemoveDelegate(WarFoundryObject val);	
+	public delegate void UnitAddDelegate(Unit val);
+	public delegate void UnitRemoveDelegate(Unit val);
+	public delegate void ObjectUpdatedDelegate(WarFoundryObject val, string updatedValName);
+	public delegate void DoubleValChangedDelegate(WarFoundryObject obj, double oldValue, double newValue);
+	public delegate void FloatValChangedDelegate(WarFoundryObject obj, float oldValue, float newValue);
+	public delegate void StringValChangedDelegate(WarFoundryObject obj, string oldValue, string newValue);
+	public delegate void IntValChangedDelegate(WarFoundryObject obj, int oldValue, int newValue);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Exporters/IWarFoundryExporter.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,26 @@
+// This file (IWarFoundryExporter.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Exporters
+{
+	/// <summary>
+	/// An interface to be implemented by classes that export WarFoundry armies to other formats than "saved armies" (e.g. HTML)
+	/// </summary>
+	public interface IWarFoundryExporter
+	{
+		/// <summary>
+		/// Exports the army to the specified path
+		/// </summary>
+		/// <param name="army">
+		/// The <see cref="Army"/> to export
+		/// </param>
+		/// <param name="path">
+		/// The file path to export to
+		/// </param>
+		void ExportArmy(Army army, string path);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Exporters/WarFoundryHtmlExporter.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,396 @@
+// This file (WarFoundryHtmlExporter.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Xml;
+using System.Xml.Schema;
+using IBBoard.Lang;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Objects;
+using IBBoard.WarFoundry.API.Util;
+
+namespace IBBoard.WarFoundry.API.Exporters
+{
+	/// <summary>
+	/// Custom exporter that exports an army as a basic HTML file
+	/// </summary>
+	public class WarFoundryHtmlExporter : IWarFoundryExporter
+	{
+		private static WarFoundryHtmlExporter exporter;
+		private delegate string GetStatCellTextDelegate(Stat stat);
+		
+		public static WarFoundryHtmlExporter GetDefault()
+		{
+			if (exporter == null)
+			{
+				exporter = new WarFoundryHtmlExporter();
+			}
+			
+			return exporter;
+		}
+		
+		private WarFoundryHtmlExporter()
+		{
+			//Hide constructor
+		}
+		
+		public void ExportArmy(Army army, string path)
+		{
+			XmlDocument doc = new XmlDocument();
+			CustomXmlResolver resolver = new CustomXmlResolver();
+			resolver.AddMapping("-//W3C//DTD XHTML 1.0 Strict//EN", new Uri("file://" + IBBoard.Constants.ExecutablePath + "/schemas/xhtml1-strict.dtd"));
+			doc.XmlResolver = resolver;
+			doc.AppendChild(doc.CreateDocumentType("html", "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd", null));
+			XmlElement html = doc.CreateElement("html");
+			doc.AppendChild(html);
+			XmlElement head = doc.CreateElement("head");
+			html.AppendChild(head);
+			XmlElement title = doc.CreateElement("title");
+			title.InnerXml = army.Name;
+			head.AppendChild(title);
+			XmlElement metaCharset = doc.CreateElement("meta");
+			metaCharset.SetAttribute("http-equiv", "Content-Type");
+			metaCharset.SetAttribute("content", "text/html;charset=UTF-8");
+			head.AppendChild(metaCharset);
+			XmlElement style = doc.CreateElement("style");
+			style.InnerText = "table, th, td { border: 1px solid #000; border-spacing: 0; border-collapse: collapse; margin: 0 }\n"
+				+"table table { width: 100%; border-width: 0; margin: -2px }\n"
+				+"table table td { border-width:0 1px }";
+			head.AppendChild(style);
+			XmlElement body = doc.CreateElement("body");
+			html.AppendChild(body);
+			XmlElement header = doc.CreateElement("h1");
+			header.InnerText = Translation.GetTranslation("armyHtmlOutputBodyHeader", "{0} - {1}pts", army.Name, army.Points);
+			body.AppendChild(header);
+			
+			foreach (XmlElement table in CreateTables(army, doc))
+			{
+				if (!IsTableOnlyHeader(table))
+				{
+					body.AppendChild(table);
+				}
+			}
+			
+			StreamWriter writer = new StreamWriter(path, false);
+			
+			try
+			{
+				writer.Write(doc.OuterXml);
+			}
+			finally
+			{
+				writer.Close();
+			}
+		}
+
+		private bool IsTableOnlyHeader(XmlElement table)
+		{
+			return table.ChildNodes.Count == 1;
+		}
+		
+		private XmlElement[] CreateTables(Army army, XmlDocument doc)
+		{
+			Dictionary<string, XmlElement> tables = new Dictionary<string, XmlElement>();
+			
+			foreach (SystemStats statSets in army.GameSystem.SystemStats)
+			{
+				tables[statSets.ID] = CreateTable(statSets, doc);
+			}
+			
+			foreach (Unit unit in army.GetUnits())
+			{
+				CreateUnitRow(unit, tables[GetFirstStatType(unit)]);
+			}
+			
+			return DictionaryUtils.ToArray(tables);
+		}
+
+		private static string GetFirstStatType(Unit unit)
+		{
+			string[] unitStatIDs = unit.UnitStatsArrayIDs;
+			return GetFirstStatType(unitStatIDs);
+		}
+		
+		public static string GetFirstStatType(string[] unitStatIDs)
+		{
+			return unitStatIDs[0];
+		}
+		
+		private XmlElement CreateTable(SystemStats stats, XmlDocument doc)
+		{
+			XmlElement table = doc.CreateElement("table");
+			XmlElement headerRow = doc.CreateElement("tr");
+			table.AppendChild(headerRow);
+			XmlElement name = doc.CreateElement("th");
+			name.InnerText = Translation.GetTranslation("armyHtmlOutputTableHeaderUnitName", "name");
+			headerRow.AppendChild(name);
+			
+			XmlElement unitTypeName = doc.CreateElement("th");
+			unitTypeName.InnerText = Translation.GetTranslation("armyHtmlOutputTableHeaderUnitTypeName", "type name");
+			headerRow.AppendChild(unitTypeName);
+			
+			foreach (StatSlot stat in stats.StatSlots)
+			{
+				XmlElement statHeader = doc.CreateElement("th");
+				statHeader.InnerText = stat.Name;
+				headerRow.AppendChild(statHeader);
+			}
+			
+			XmlElement notes = doc.CreateElement("th");
+			notes.InnerText = Translation.GetTranslation("armyHtmlOutputTableHeaderUnitNotes", "name");;
+			headerRow.AppendChild(notes);
+			
+			XmlElement points = doc.CreateElement("th");
+			points.InnerText = Translation.GetTranslation("armyHtmlOutputTableHeaderUnitPoints", "name");;
+			headerRow.AppendChild(points);
+			
+			return table;
+		}
+		
+		private XmlElement CreateUnitRow(Unit unit, XmlElement tableElem)
+		{
+			XmlDocument doc = tableElem.OwnerDocument;
+			XmlElement row = doc.CreateElement("tr");
+			tableElem.AppendChild(row);
+			Stat[][] memberStats = unit.UnitStatsArraysWithName;
+			string[] statTypeIDs = unit.UnitStatsArrayIDs;
+			string defaultStatType = GetFirstStatType(statTypeIDs);
+			int statRowCount = 0;
+			bool hasOther = false;
+			
+			foreach (string statTypeID in statTypeIDs)
+			{
+				if (statTypeID.Equals(defaultStatType))
+				{
+					statRowCount++;
+				}
+				else if (!hasOther)
+				{
+					statRowCount++;
+					hasOther = true;
+				}
+			}
+			
+			XmlElement name = doc.CreateElement("td");
+			name.InnerText = unit.Name;
+			SetRowSpan(name, statRowCount);
+			row.AppendChild(name);
+			CreateStatsBlock(row, memberStats, statTypeIDs);
+
+			StringBuilder sb = new StringBuilder();
+			UnitEquipmentItem[] unitEquipment = unit.GetEquipment();
+			
+			if (unitEquipment.Length > 0)
+			{
+				bool addSeparator = false;
+				
+				foreach (UnitEquipmentItem equip in unitEquipment)
+				{
+					if (!addSeparator)
+					{
+						addSeparator = true;
+					}
+					else
+					{
+						sb.Append(", ");
+					}
+
+					string amountString;
+					double amount = UnitEquipmentUtil.GetEquipmentAmount(unit, equip);
+
+					if (UnitEquipmentUtil.GetEquipmentAmountIsRatio(unit, equip))
+					{
+
+						if (amount == 100)
+						{
+							amountString = GetEquipmentAmountAllTranslation(unit);
+						}
+						else
+						{
+							int number = UnitEquipmentUtil.GetEquipmentAmountTaken(unit, equip);
+							amountString = GetEquipmentAmountRatioTranslation(amount, number);
+						}
+					}
+					else
+					{
+						if (amount == -1)
+						{
+							amountString = GetEquipmentAmountAllTranslation(unit);
+						}
+						else
+						{
+							amountString = GetEquipmentAmountNumberTranslation((int)amount);
+						}
+					}
+
+					sb.Append(Translation.GetTranslation("armyHtmlExportEquipAmountRatio", "{0} for {1}", equip.Name, amountString));
+				}
+				
+				sb.Append(". ");
+			}
+			
+			ICollection<Ability> abilities = unit.Abilities;
+			
+			if (abilities.Count > 0)
+			{
+				bool addSeparator = false;
+				
+				foreach (Ability ability in abilities)
+				{
+					if (!addSeparator)
+					{
+						addSeparator = true;
+					}
+					else
+					{
+						sb.Append(", ");
+					}
+					
+					sb.Append(ability.Name);
+				}
+				
+				sb.Append(". ");
+			}
+			
+			XmlElement notes = doc.CreateElement("td");
+			notes.InnerText = sb.ToString();
+			SetRowSpan(notes, statRowCount);
+			row.AppendChild(notes);
+			
+			XmlElement points = doc.CreateElement("td");
+			points.InnerText = unit.Points.ToString();
+			SetRowSpan(points, statRowCount);
+			row.AppendChild(points);
+			
+			return row;
+		}
+		
+		private static void SetRowSpan(XmlElement xmlElement, int statRowCount)
+		{
+			if (statRowCount > 1)
+			{
+				xmlElement.SetAttribute("rowspan", statRowCount.ToString());
+			}
+		}
+		
+		private void CreateStatsBlock(XmlElement unitRow, Stat[][] memberStats, string[] statTypeIDs)
+		{
+			XmlDocument doc = unitRow.OwnerDocument;
+			string defaultStatType = GetFirstStatType(statTypeIDs);
+			
+			Stat[] defaultStatLine = memberStats[0];
+			int defaultStatLineCount = defaultStatLine.Length;
+			AddStatCell(defaultStatLine[0].SlotValueString, unitRow);
+			
+			for (int i = 1; i < defaultStatLineCount; i++)
+			{
+				string statText = GetDefaultStatCellText(defaultStatLine[i]);
+				AddStatCell(statText, unitRow);
+			}
+			
+			int statCount = statTypeIDs.Length;
+			
+			if (statCount > 1)
+			{
+				XmlElement unitTable = (XmlElement)unitRow.ParentNode;
+				Dictionary<string, XmlElement> statParents = CreateStatsParentElements(statTypeIDs, unitTable);
+				
+				for (int i = 1; i < statCount; i++)
+				{
+					Stat[] statLine = memberStats[i];
+					string statTypeID = statTypeIDs[i];
+					XmlElement tableElement = DictionaryUtils.GetValue(statParents, statTypeID);
+					int statLineCount = statLine.Length;
+					XmlElement statRow = doc.CreateElement("tr");
+					tableElement.AppendChild(statRow);
+					GetStatCellTextDelegate statCellTextDelegate = (statTypeID.Equals(defaultStatType) ? new GetStatCellTextDelegate(GetDefaultStatCellText) : new GetStatCellTextDelegate(GetOtherStatCellText));
+					AddStatCell(statLine[0].SlotValueString, statRow);
+				
+					for (int j = 1; j < statLineCount; j++)
+					{
+						string statText = statCellTextDelegate(statLine[j]);
+						AddStatCell(statText, statRow);
+					}
+				}
+				
+				if (statParents.Count > 1)
+				{
+					AddOtherUnitStatTables(statParents, unitTable, defaultStatLineCount);
+				}
+			}
+		}
+		
+		private static void AddOtherUnitStatTables(Dictionary<string, XmlElement> statParents, XmlElement unitTable, int defaultStatLineCount)
+		{
+			XmlDocument doc = unitTable.OwnerDocument;
+			XmlElement otherStatsRow = doc.CreateElement("tr");
+			unitTable.AppendChild(otherStatsRow);
+			XmlElement otherStatsCell = doc.CreateElement("td");
+			otherStatsCell.SetAttribute("colspan", defaultStatLineCount.ToString());
+			otherStatsRow.AppendChild(otherStatsCell);
+			
+			foreach (XmlElement tableElem in statParents.Values)
+			{
+				if (tableElem != unitTable)
+				{
+					otherStatsCell.AppendChild(tableElem);
+				}
+			}
+		}
+
+		private Dictionary<string, XmlElement> CreateStatsParentElements(string[] statTypeIDs, XmlElement parentTable)
+		{
+			Dictionary<string, XmlElement> statParents = new Dictionary<string, XmlElement>();
+			XmlDocument doc = parentTable.OwnerDocument;
+			string defaultStatTypeID = GetFirstStatType(statTypeIDs);
+			statParents[defaultStatTypeID] = parentTable;
+			
+			foreach (string statTypeID in statTypeIDs)
+			{
+				if (!statParents.ContainsKey(statTypeID))
+				{
+					XmlElement tableElement = doc.CreateElement("table");
+					statParents[statTypeID] = tableElement;
+				}
+			}
+			
+			return statParents;
+		}
+
+		private string GetDefaultStatCellText(Stat stat)
+		{
+			return Translation.GetTranslation("armyHtmlExportDefaultStatCellText", "{0}", stat.SlotValueString, stat.ParentSlotName);
+		}
+
+		private string GetOtherStatCellText(Stat stat)
+		{
+			return Translation.GetTranslation("armyHtmlExportOtherStatCellText", "{1}: {0}", stat.SlotValueString, stat.ParentSlotName);
+		}
+		
+		private static void AddStatCell(string statValue, XmlElement row)
+		{
+			XmlElement statCell = row.OwnerDocument.CreateElement("td");
+			statCell.InnerText = statValue;
+			row.AppendChild(statCell);
+		}
+		
+		private string GetEquipmentAmountRatioTranslation (double amount, int number)
+		{
+			return Translation.GetTranslation ("armyHtmlExportEquipAmountPercentage", "{0}% ({1})", amount, number);
+		}
+		
+		private string GetEquipmentAmountNumberTranslation(int amount)
+		{
+			return Translation.GetTranslation("armyHtmlExportEquipAmountNumber", "{0}", amount);
+		}
+		
+		private string GetEquipmentAmountAllTranslation(Unit unit)
+		{
+			return Translation.GetTranslation("armyHtmlExportEquipAmountAll", "all ({1})", 100, unit.Size);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/AbstractNativeWarFoundryFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,194 @@
+// This file (AbstractNativeWarFoundryFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Schema;
+using System.Collections.Generic;
+using System.Text;
+using IBBoard;
+using IBBoard.IO;
+using IBBoard.Lang;
+using IBBoard.Logging;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Objects;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace IBBoard.WarFoundry.API.Factories
+{
+	/// <summary>
+	/// Base abstract class for all factories that load native WarFoundry data.
+	/// </summary>
+	public abstract class AbstractNativeWarFoundryFactory : AbstractWarFoundryFactory<ZipFile>, INativeWarFoundryFactory
+	{				
+		protected AbstractNativeWarFoundryFactory()
+		{
+			//Do nothing - just make the constructor non-public
+		}
+				
+		protected override ZipFile GetFileAsSupportedType (FileInfo file)
+		{
+			ZipFile zip = null;
+			
+			try
+			{
+				zip = new ZipFile(file.FullName);
+			}
+			catch(ZipException)
+			{
+				//Silently dispose as per spec for the method
+			}
+			catch (IOException)
+			{
+				//Silently dispose as per spec for the method
+			}
+			
+			return zip;
+		}
+		
+		protected override bool CheckCanHandleFileFormat (ZipFile file)
+		{	
+			return CheckCanHandleFileAsGameSystem(file) || CheckCanHandleFileAsRace(file) || CheckCanHandleFileAsArmy(file); 
+		}
+		
+		protected override bool CheckCanHandleFileAsGameSystem(ZipFile file)
+		{
+			return CheckCanFindSystemFileContent(file);
+		}
+		
+		protected abstract bool CheckCanFindSystemFileContent(ZipFile file);
+		
+		protected override bool CheckCanHandleFileAsRace(ZipFile file)
+		{
+			return CheckCanFindRaceFileContent(file);
+		}
+		
+		protected abstract bool CheckCanFindRaceFileContent(ZipFile file);
+		
+		protected override bool CheckCanHandleFileAsArmy(ZipFile file)
+		{
+			return CheckCanFindArmyFileContent(file);
+		}
+		
+		protected abstract bool CheckCanFindArmyFileContent(ZipFile file);
+		
+		protected override ICollection<IWarFoundryObject> DoCreateObjectsFromFile (ZipFile file)
+		{
+			ICollection<IWarFoundryObject> objects = new List<IWarFoundryObject>();
+
+			try
+			{
+				if (CheckCanFindSystemFileContent(file))
+				{
+					foreach (GameSystem system in CreateGameSystemsFromFile(file))
+					{
+						OnGameSystemLoaded(system);
+						objects.Add(system);
+					}
+				}
+				
+				if (CheckCanFindRaceFileContent(file))
+				{
+					foreach(Race race in CreateRacesFromFile(file))
+					{
+						OnRaceLoaded(race);
+						objects.Add(race);
+					}
+				}
+				
+				if (CheckCanFindArmyFileContent(file))
+				{
+					foreach (Army army in CreateArmiesFromFile(file))
+					{
+						OnArmyLoaded(army);
+						objects.Add(army);
+					}
+				}
+			}
+			finally
+			{
+				file.Close();
+			}
+			
+			return objects;
+		}
+		
+		protected ICollection<Army> CreateArmiesFromFile(ZipFile file)
+		{
+			ICollection<ZipEntry> dataStreams = GetArmyZipEntries(file);
+			ICollection<Army> armies = new List<Army>();
+
+			foreach (ZipEntry entry in dataStreams)
+			{
+				using (Stream dataStream = file.GetInputStream(entry))
+				{
+				armies.Add(CreateArmyFromStream(file, dataStream));
+				}
+			}
+			
+			return armies;
+		}
+		
+		protected abstract ICollection<ZipEntry> GetArmyZipEntries(ZipFile file);
+		protected abstract Army CreateArmyFromStream(ZipFile file, Stream dataStream);
+		
+		protected ICollection<Race> CreateRacesFromFile(ZipFile file)
+		{
+			ICollection<ZipEntry> dataStreams = GetRaceZipEntries(file);
+			ICollection<Race> races = new List<Race>();
+
+			foreach (ZipEntry entry in dataStreams)
+			{					
+				using (Stream dataStream = file.GetInputStream(entry))
+				{
+					races.Add(CreateRaceFromStream(file, dataStream));
+				}
+			}
+			
+			return races;
+		}
+		
+		protected abstract ICollection<ZipEntry> GetRaceZipEntries(ZipFile file);
+		protected abstract Race CreateRaceFromStream(ZipFile file, Stream dataStream);
+		
+		protected ICollection<GameSystem> CreateGameSystemsFromFile(ZipFile file)
+		{
+			ICollection<ZipEntry> dataStreams = GetGameSystemZipEntries(file);
+			ICollection<GameSystem> systems = new List<GameSystem>();
+
+			foreach (ZipEntry entry in dataStreams)
+			{
+				using (Stream dataStream = file.GetInputStream(entry))
+				{
+					systems.Add(CreateGameSystemFromStream(file, dataStream));
+				}
+			}
+			
+			return systems;
+		}
+		
+		protected abstract ICollection<ZipEntry> GetGameSystemZipEntries(ZipFile file);
+		protected abstract GameSystem CreateGameSystemFromStream(ZipFile file, Stream dataStream);
+		
+		public override bool Equals (object o)
+		{
+			if (o == this)
+			{
+				return true;
+			}
+			else if (o == null || !(this.GetType().Equals(o.GetType())))
+			{
+				return false;
+			}
+			
+			return true;
+		}
+		
+		public override int GetHashCode ()
+		{
+			return GetType().FullName.GetHashCode();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/AbstractNonNativeFileExtensionWarFoundryFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,85 @@
+// This file (AbstractNonNativeFileExtensionWarFoundryFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using IBBoard.Logging;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories
+{	
+	public abstract class AbstractNonNativeFileExtensionWarFoundryFactory : AbstractNonNativeWarFoundryFactory<FileInfo>
+	{
+		protected abstract string ArmyFileExtension { get; }
+		protected abstract string RaceFileExtension { get; }
+		protected abstract string GameSystemFileExtension { get; }
+		
+		protected override bool CheckCanHandleFileFormat (FileInfo file)
+		{
+			return CheckCanHandleFileAsArmy(file) || CheckCanHandleFileAsRace(file) || CheckCanHandleFileAsGameSystem(file);
+		}
+		
+		protected override bool CheckCanHandleFileAsArmy(FileInfo file)
+		{
+			return ArmyFileExtension!=null && file.Name.ToLower().EndsWith(ArmyFileExtension);
+		}
+		
+		protected override bool CheckCanHandleFileAsRace(FileInfo file)
+		{
+			return RaceFileExtension!=null && file.Name.ToLower().EndsWith(RaceFileExtension);
+		}
+		
+		protected override bool CheckCanHandleFileAsGameSystem(FileInfo file)
+		{
+			return GameSystemFileExtension!=null && file.Name.ToLower().EndsWith(GameSystemFileExtension);
+		}
+		
+		protected override FileInfo GetFileAsSupportedType (FileInfo file)
+		{
+			return file;
+		}		
+		
+		protected abstract Army CreateArmyFromFile(FileInfo file);
+		protected abstract Race CreateRaceFromFile(FileInfo file);
+		protected abstract GameSystem CreateGameSystemFromFile(FileInfo file);
+		
+		protected override ICollection<IWarFoundryObject> DoCreateObjectsFromFile (FileInfo file)
+		{
+			IWarFoundryObject obj = null;
+			
+			if (CheckCanHandleFileAsGameSystem(file))
+			{
+				GameSystem gameSystem = CreateGameSystemFromFile (file);
+				OnGameSystemLoaded(gameSystem);
+				obj = gameSystem;
+			}
+			else if (CheckCanHandleFileAsRace(file))
+			{
+				Race race = CreateRaceFromFile (file);
+				OnRaceLoaded(race);
+				obj = race;
+			}
+			else if (CheckCanHandleFileAsArmy(file))
+			{
+				Army army = CreateArmyFromFile (file);
+				OnArmyLoaded(army);
+				obj = army;
+			}
+			else
+			{
+				LogNotifier.Warn(GetType(), "Failed trying to create from "+file.FullName+" - not a Race, Army or GameSystem");
+			}
+			
+			ICollection<IWarFoundryObject> objects = new List<IWarFoundryObject>();
+			
+			if (obj != null)
+			{
+				objects.Add(obj);
+			}
+			
+			return objects;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/AbstractNonNativeWarFoundryFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,15 @@
+// This file (AbstractNonNativeWarFoundryFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories
+{
+	public abstract class AbstractNonNativeWarFoundryFactory<FILE_TYPE> : AbstractWarFoundryFactory<FILE_TYPE>, INonNativeWarFoundryFactory
+	{
+		public abstract string NonNativeDataType { get;	}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/AbstractWarFoundryFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,181 @@
+// This file (AbstractWarFoundryFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories
+{
+	public abstract class AbstractWarFoundryFactory<FILE_TYPE> : IWarFoundryFactory
+	{		
+		public event SingleArgMethodInvoker<GameSystem> GameSystemLoaded;
+		
+		public event SingleArgMethodInvoker<Race> RaceLoaded;
+		
+		public event SingleArgMethodInvoker<Army> ArmyLoaded;
+		
+		public virtual void CompleteLoading(IWarFoundryStagedLoadObject obj)
+		{
+			//Pretend we've fully loaded, as this will probably be standard for non-native factories and some native factories
+			obj.SetAsFullyLoaded();
+		}
+
+		public bool CanHandleFileFormat (FileInfo file)
+		{
+			FILE_TYPE typedFile = GetFileAsSupportedType(file);
+			bool canHandle = typedFile != null && CheckCanHandleFileFormat(typedFile);
+
+			if (typedFile != null)
+			{
+				CleanUpFileAsSupportedType(typedFile);
+			}
+
+			return canHandle;
+		}
+
+		public bool CanHandleFileAsRace(FileInfo file)
+		{
+			FILE_TYPE typedFile = GetFileAsSupportedType(file);
+			bool canHandle = typedFile != null && CheckCanHandleFileAsRace(typedFile);
+
+			if (typedFile != null)
+			{
+				CleanUpFileAsSupportedType(typedFile);
+			}
+
+			return canHandle;
+		}
+
+		public bool CanHandleFileAsGameSystem(FileInfo file)
+		{
+			FILE_TYPE typedFile = GetFileAsSupportedType(file);
+			bool canHandle = typedFile != null && CheckCanHandleFileAsGameSystem(typedFile);
+
+			if (typedFile != null)
+			{
+				CleanUpFileAsSupportedType(typedFile);
+			}
+
+			return canHandle;
+		}
+
+		public bool CanHandleFileAsArmy(FileInfo file)
+		{
+			FILE_TYPE typedFile = GetFileAsSupportedType(file);
+			bool canHandle = typedFile != null && CheckCanHandleFileAsArmy(typedFile);
+
+			if (typedFile != null)
+			{
+				CleanUpFileAsSupportedType(typedFile);
+			}
+
+			return canHandle;
+		}
+
+		protected virtual void CleanUpFileAsSupportedType(FILE_TYPE typedFile)
+		{
+			//Do nothing by default
+		}
+		
+		/// <summary>
+		/// Converts the <see cref="FileInfo"/> object in to the appropriate type for this class so that it can perform its checks. If no conversion is required (the test can be performed on a <see cref="FileInfo"/> object) the object should be returned with no modification. 
+		/// If the file is not of supported type the <code>null</code> should be returned.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> to get the supported source object from.
+		/// </param>
+		/// <returns>
+		/// An object of type <see cref="FILE_TYPE"/> that has been converted from the input <see cref="FileInfo"/> object, or <code>null</code> if the conversion cannot be made.
+		/// </returns>
+		protected abstract FILE_TYPE GetFileAsSupportedType(FileInfo file);
+		
+		/// <summary>
+		/// Checks whether the factory thinks it can load data from the file in its paramaterised type.
+		/// </summary>
+		/// <param name="file">
+		/// An object of the converted <see cref="FILE_TYPE"/> to check support for
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the factory thinks it can support the file, else <code>false</code>
+		/// </returns>
+		protected abstract bool CheckCanHandleFileFormat(FILE_TYPE file);
+
+		/// <summary>
+		/// Checks whether the factory thinks it can load data from the file in its paramaterised type as a Race object.
+		/// </summary>
+		/// <param name="file">
+		/// An object of the converted <see cref="FILE_TYPE"/> to check support for
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the factory thinks it can support the file as a Race, else <code>false</code>
+		/// </returns>
+		protected abstract bool CheckCanHandleFileAsRace(FILE_TYPE file);
+
+		/// <summary>
+		/// Checks whether the factory thinks it can load data from the file in its paramaterised type as a GameSystem object.
+		/// </summary>
+		/// <param name="file">
+		/// An object of the converted <see cref="FILE_TYPE"/> to check support for
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the factory thinks it can support the file as a GameSystem, else <code>false</code>
+		/// </returns>
+		protected abstract bool CheckCanHandleFileAsGameSystem(FILE_TYPE file);
+
+		/// <summary>
+		/// Checks whether the factory thinks it can load data from the file in its paramaterised type as an Army object.
+		/// </summary>
+		/// <param name="file">
+		/// An object of the converted <see cref="FILE_TYPE"/> to check support for
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the factory thinks it can support the file as a Army, else <code>false</code>
+		/// </returns>
+		protected abstract bool CheckCanHandleFileAsArmy(FILE_TYPE file);
+		
+		
+		public ICollection<IWarFoundryObject> CreateObjectsFromFile(FileInfo file)
+		{
+			return DoCreateObjectsFromFile(GetFileAsSupportedType(file));
+		}
+		
+		/// <summary>
+		/// Reads the data from the supplied converted <see cref="FILE_TYPE"/> object and returns it as a collection of loadable objects.
+		/// In addition, the method fires the appropriate XxxLoaded event for each object created for asynchronous use.
+		/// </summary>
+		/// <param name="file">
+		/// An object of the converted <see cref="FILE_TYPE"/> for the file to load data from
+		/// </param>
+		/// <returns>
+		/// A <see cref="ICollection`1"/> of <see cref="IWarFoundryObject"/>s that were loaded from the file object
+		/// </returns>
+		protected abstract ICollection<IWarFoundryObject> DoCreateObjectsFromFile(FILE_TYPE file);
+		
+		protected void OnGameSystemLoaded(GameSystem system)
+		{
+			if (GameSystemLoaded != null)
+			{
+				GameSystemLoaded(system);
+			}
+		}
+		
+		protected void OnRaceLoaded(Race race)
+		{
+			if (RaceLoaded != null)
+			{
+				RaceLoaded(race);
+			}
+		}
+		
+		protected void OnArmyLoaded(Army army)
+		{
+			if (ArmyLoaded != null)
+			{
+				ArmyLoaded(army);
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/DummyWarFoundryFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,59 @@
+//  This file (DummyWarFoundryFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2010 IBBoard
+// 
+//  The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using IBBoard.WarFoundry.API.Factories;
+using IBBoard.WarFoundry.API.Objects;
+using System.IO;
+using System.Collections.Generic;
+
+namespace IBBoard.WarFoundry.API.Factories
+{
+	///<summary>
+	///A dummy factory for use with <see cref="WarFoundryStagedLoadingObject"/>s that implements the bare minimum of the methods but won't load anything
+	///</summary>
+	public class DummyWarFoundryFactory : IWarFoundryFactory
+	{
+		public event SingleArgMethodInvoker<GameSystem> GameSystemLoaded;
+
+		public event SingleArgMethodInvoker<Race> RaceLoaded;
+
+		public event SingleArgMethodInvoker<Army> ArmyLoaded;
+		
+		public DummyWarFoundryFactory()
+		{
+			//Public constructor
+		}
+
+		public void CompleteLoading(IWarFoundryStagedLoadObject obj)
+		{
+			obj.SetAsFullyLoaded();
+		}
+
+		public bool CanHandleFileFormat(FileInfo file)
+		{
+			return false;
+		}
+
+		public bool CanHandleFileAsRace(FileInfo file)
+		{
+			return false;
+		}
+
+		public bool CanHandleFileAsGameSystem(FileInfo file)
+		{
+			return false;
+		}
+
+		public bool CanHandleFileAsArmy(FileInfo file)
+		{
+			return false;
+		}
+
+		public ICollection<IWarFoundryObject> CreateObjectsFromFile(FileInfo file)
+		{
+			return new List<IWarFoundryObject>();
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/INativeWarFoundryFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,13 @@
+// This file (INativeWarFoundryFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Factories
+{
+	public interface INativeWarFoundryFactory : IWarFoundryFactory
+	{
+		//Marker interface
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/INonNativeWarFoundryFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,13 @@
+// This file (INonNativeWarFoundryFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Factories
+{
+	public interface INonNativeWarFoundryFactory : IWarFoundryFactory
+	{
+		string NonNativeDataType { get; } 
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/IWarFoundryFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,86 @@
+// This file (IWarFoundryFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories
+{
+	public interface IWarFoundryFactory
+	{		
+		event SingleArgMethodInvoker<GameSystem> GameSystemLoaded;
+		
+		event SingleArgMethodInvoker<Race> RaceLoaded;
+		
+		event SingleArgMethodInvoker<Army> ArmyLoaded;
+		
+		/// <summary>
+		/// Completes the loading of an object if it is loaded in stages.
+		/// </summary>
+		/// <param name="obj">
+		/// The <see cref="IWarFoundryStagedLoadObject"/> that should be fully loaded.
+		/// </param>
+		void CompleteLoading(IWarFoundryStagedLoadObject obj);
+		
+		/// <summary>
+		/// Checks if the factory thinks it can handle the supplied file. Checks can be performed on file extension or some basic check of file content, or some other method.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to check support for.
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the file appears to be supported for loading by this factory, else returns <code>false</code>
+		/// </returns>
+		bool CanHandleFileFormat(FileInfo file);
+
+		/// <summary>
+		/// Checks if the factory thinks it can handle the supplied file as a Race. Checks can be performed on file extension or some basic check of file content, or some other method.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to check support for as a file containing Race information.
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the file appears to be supported for loading by this factory as a Race, else returns <code>false</code>
+		/// </returns>
+		bool CanHandleFileAsRace(FileInfo file);
+
+		/// <summary>
+		/// Checks if the factory thinks it can handle the supplied file as a GameSystem. Checks can be performed on file extension or some basic check of file content, or some other method.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to check support for as a file containing GameSystem information.
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the file appears to be supported for loading by this factory as a GameSystem, else returns <code>false</code>
+		/// </returns>
+		bool CanHandleFileAsGameSystem(FileInfo file);
+
+		/// <summary>
+		/// Checks if the factory thinks it can handle the supplied file as a Army. Checks can be performed on file extension or some basic check of file content, or some other method.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to check support for as a file containing Army information.
+		/// </param>
+		/// <returns>
+		/// <code>true</code> if the file appears to be supported for loading by this factory as a Army, else returns <code>false</code>
+		/// </returns>
+		bool CanHandleFileAsArmy(FileInfo file);
+		
+		/// <summary>
+		/// Reads the data from the supplied file and returns it as a collection of loadable objects. In addition, it fires the appropriate XxxLoaded event
+		/// for each object loaded for asynchronous use.
+		/// 
+		/// May throw a <see cref=" IBBoard.IO.InvalidFileException"/> if the file is supported by the Factory but the content is invalid.
+		/// </summary>
+		/// <param name="file">
+		/// A <see cref="FileInfo"/> for the file to load data from
+		/// </param>
+		/// <returns>
+		/// A <see cref="ICollection`1"/> of <see cref="IWarFoundryObject"/>s that were loaded from the file
+		/// </returns>
+		ICollection<IWarFoundryObject> CreateObjectsFromFile(FileInfo file);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/RequiredDataMissingException.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,19 @@
+// This file (RequiredDataMissingException.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Factories
+{
+	/// <summary>
+	/// An exception that is thrown when a file cannot be loaded because one of the data files that it depends on has
+	/// not been loaded. Normally occurs when loading an army file without having the correct game system or race.
+	/// </summary>	
+	public class RequiredDataMissingException : Exception
+	{
+		public RequiredDataMissingException(String file, String missingObjectType, String requiredValue) : base(String.Format("Could not find data for {1} object with ID {2} required by {0}", file, missingObjectType, requiredValue))
+		{
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/AbstractStagedLoadedSubFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,33 @@
+//  This file (AbstractStagedLoadedSubFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Xml;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	public class AbstractStagedLoadedSubFactory
+	{
+		protected WarFoundryXmlFactory mainFactory;
+		
+		protected AbstractStagedLoadedSubFactory(WarFoundryXmlFactory factory)
+		{
+			mainFactory = factory;
+		}
+		
+		protected Category CreateCategoryFromElement(XmlElement elem)
+		{
+			string id = elem.GetAttribute("id");
+			string name = elem.GetAttribute("name");
+			Category cat = new Category(id, name);
+			cat.MaximumPercentage = XmlTools.GetIntValueFromAttribute(elem, "maxPercentage");
+			cat.MinimumPercentage = XmlTools.GetIntValueFromAttribute(elem, "minPercentage");
+			cat.MaximumPoints = XmlTools.GetIntValueFromAttribute(elem, "maxPoints");
+			cat.MinimumPoints = XmlTools.GetIntValueFromAttribute(elem, "minPoints");
+			return cat;
+		}	
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/WarFoundryXmlArmyFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,23 @@
+//  This file (WarFoundryXmlArmyFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Xml;
+using IBBoard.Xml;
+using ICSharpCode.SharpZipLib.Zip;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// A sub-factory for loading WarFoundry Army XML files
+	/// </summary>
+	public class WarFoundryXmlArmyFactory
+	{			
+		public Army CreateArmyFromElement(ZipFile file, XmlElement elem)
+		{
+			return new WarFoundryXmlArmyParser(file, elem).GetArmy();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/WarFoundryXmlArmyParser.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,131 @@
+//  This file (WarFoundryXmlArmyParser.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using IBBoard.IO;
+using IBBoard.Xml;
+using ICSharpCode.SharpZipLib.Zip;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	public class WarFoundryXmlArmyParser
+	{
+		private ZipFile file;
+		private XmlElement elem;
+		private Army army;
+		private Dictionary<String, Unit> units;
+		
+		public WarFoundryXmlArmyParser(ZipFile file, XmlElement elem)
+		{
+			this.file = file;
+			this.elem = elem;
+		}
+
+		public Army GetArmy()
+		{
+			if (army == null)
+			{
+				ParseArmy();
+			}
+
+			return army;
+		}
+
+		private void ParseArmy()
+		{
+			string name = elem.GetAttribute("name");
+			string systemID = elem.GetAttribute("system");
+			GameSystem system = WarFoundryLoader.GetDefault().GetGameSystem(systemID);
+			
+			if (system == null)
+			{
+				throw new RequiredDataMissingException(file.Name, "Game System", systemID);
+			}
+			
+			string raceID = elem.GetAttribute("race");
+			Race race = WarFoundryLoader.GetDefault().GetRace(system, raceID);
+			
+			if (race == null)
+			{
+				throw new RequiredDataMissingException(file.Name, "Race", raceID);
+			}
+			
+			int points = XmlTools.GetIntValueFromAttribute(elem, "maxPoints");			
+			army = new Army(race, name, points, file);
+			LoadUnits();
+		}
+
+		private void LoadUnits()
+		{
+			units = new Dictionary<string, Unit>();
+
+			foreach (XmlElement unitElem in WarFoundryXmlFactoryUtils.SelectNodes(elem, "/army:army/army:units/army:unit"))
+			{
+				string id = unitElem.GetAttribute("id");
+
+				if (!units.ContainsKey(id))
+				{
+					string unitTypeId = unitElem.GetAttribute("unitType");
+					UnitType unitType = army.Race.GetUnitType(unitTypeId);
+
+					if (unitType == null)
+					{
+						throw new RequiredDataMissingException(file.Name, "Unit Type", unitTypeId);
+					}
+					
+					string name = unitElem.GetAttribute("unitName");
+					int size = XmlTools.GetIntValueFromAttribute(unitElem, "size");
+					
+					string catID = unitElem.GetAttribute("category");
+					Category cat = army.Race.GetCategory(catID);
+					
+					if (cat == null)
+					{
+						cat = unitType.MainCategory;
+					}
+
+					Unit unit = new Unit(id, name, size, unitType, army.GetCategory(cat));
+					army.AddUnit(unit, cat);
+					units.Add(id, unit);
+
+					LoadUnitEquipment(unitElem, unit);
+				}
+				else
+				{
+					throw new InvalidFileException("Duplicate unit ID found in army file: "+id);
+				}
+			}
+		}
+
+		private void LoadUnitEquipment(XmlElement unitElem, Unit unit)
+		{
+			foreach (XmlElement elem in WarFoundryXmlFactoryUtils.SelectNodes(unitElem, "army:equipment/army:equipItem"))
+			{
+				string equipID = elem.GetAttribute("id");
+				UnitEquipmentItem item = unit.UnitType.GetEquipmentItem(equipID);
+	
+				if (item == null)
+				{
+					throw new RequiredDataMissingException(file.Name, "Equipment Item", equipID);
+				}
+
+				double amount = XmlTools.GetDoubleValueFromAttribute(elem, "amount");
+				string equipTypeString = elem.GetAttribute("amountType");
+
+				if (equipTypeString == "ratio")
+				{
+					unit.SetEquipmentRatio(item, amount);
+				}
+				else
+				{
+					//amount should be a whole number, so do type-cast rounding
+					unit.SetEquipmentAmount(item, (int) amount);
+				}
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/WarFoundryXmlElementName.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,67 @@
+// This file (WarFoundryXmlElementName.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using IBBoard.Xml;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// An enumeration class for valid WarFoundry XML elements, designed to imitate Java's extensible complex object enumerations. 
+	/// </summary>
+	public class WarFoundryXmlElementName : IXmlElementName, IExtendedEnum<string>
+	{
+		public static WarFoundryXmlElementName SYSTEM_ELEMENT = new WarFoundryXmlElementName("SYSTEM_ELEMENT", "system");
+		public static WarFoundryXmlElementName ARMY_ELEMENT = new WarFoundryXmlElementName("ARMY_ELEMENT", "army");
+		public static WarFoundryXmlElementName RACE_ELEMENT = new WarFoundryXmlElementName("RACE_ELEMENT", "race");
+        public static WarFoundryXmlElementName ARMY_DEFAULTNAME_ELEMENT = new WarFoundryXmlElementName("ARMY_DEFAULTNAME_ELEMENT", "defaultName");
+		public static WarFoundryXmlElementName CATEGORIES_ELEMENT = new WarFoundryXmlElementName("CATEGORIES_ELEMENT", "categories");
+		public static WarFoundryXmlElementName CATEGORY_ELEMENT = new WarFoundryXmlElementName("CATEGORY_ELEMENT", "cat");
+		public static WarFoundryXmlElementName UNITTYPES_ELEMENT = new WarFoundryXmlElementName("UNITTYPES_ELEMENT", "units");
+		public static WarFoundryXmlElementName UNITTYPE_ELEMENT = new WarFoundryXmlElementName("UNITTYPE_ELEMENT", "unit");
+		public static WarFoundryXmlElementName RACE_EQUIPMENT_ITEMS_ELEMENT = new WarFoundryXmlElementName("RACE_EQUIPMENT_ITEMS_ELEMENT", "equipment");
+		public static WarFoundryXmlElementName RACE_EQUIPMENT_ITEM_ELEMENT = new WarFoundryXmlElementName("RACE_EQUIPMENT_ITEMS_ELEMENT", "equipmentItem");
+		
+		private static ICollection<WarFoundryXmlElementName> enumValues;
+		private string name;
+		private string val;
+		
+		private WarFoundryXmlElementName(string elemName, string elemVal)
+		{
+			name = elemName;
+			val = elemVal;
+		}
+		
+		public string Name
+		{
+			get {
+				return name;
+			}
+		}
+		
+		public string Value
+		{
+			get {
+				return val;
+			}
+		}
+		
+		/// <summary>
+		/// Gets an ICollection of the values so that they can be looped over like a standard enumeration.
+		/// </summary>
+		/// <returns>
+		/// A <see cref="ICollection`1"/> of all of the static 'enumeration' values of the class.
+		/// </returns>
+		public static ICollection<WarFoundryXmlElementName> GetEnumValues()
+		{
+			if (enumValues == null)
+			{
+				enumValues = new WarFoundryXmlElementName[]{SYSTEM_ELEMENT, ARMY_ELEMENT, RACE_ELEMENT, CATEGORIES_ELEMENT, CATEGORY_ELEMENT, UNITTYPES_ELEMENT, UNITTYPE_ELEMENT};
+			}
+			
+			return enumValues;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/WarFoundryXmlFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,191 @@
+// This file (WarFoundryXmlFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Schema;
+using System.Xml.XPath;
+using System.Collections.Generic;
+using System.Text;
+using IBBoard;
+using IBBoard.IO;
+using IBBoard.Lang;
+using IBBoard.Logging;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Requirements;
+using IBBoard.WarFoundry.API.Objects;
+using ICSharpCode.SharpZipLib.Zip;
+using System.Text.RegularExpressions;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// The WarFoundryXmlFactory loads WarFoundry classes from the native "XML in a zip" file format. Files are validated using the schema for the file type, so structurally invalid files should be identified at initial load.
+	/// </summary>
+	public class WarFoundryXmlFactory : AbstractNativeWarFoundryFactory
+	{
+		private static WarFoundryXmlFactory factory;
+		private WarFoundryXmlGameSystemFactory gameSystemFactory;
+		private WarFoundryXmlRaceFactory raceFactory;
+		private WarFoundryXmlArmyFactory armyFactory;
+
+		public static WarFoundryXmlFactory GetFactory()
+		{
+			if (factory == null)
+			{
+				factory = new WarFoundryXmlFactory();
+			}
+			
+			return factory;
+		}
+		
+		private WarFoundryXmlFactory() : base()
+		{
+			gameSystemFactory = new WarFoundryXmlGameSystemFactory(this);
+			raceFactory = new WarFoundryXmlRaceFactory(this);
+			armyFactory = new WarFoundryXmlArmyFactory();
+		}
+		
+		public WarFoundryXmlGameSystemFactory GetSystemFactory()
+		{
+			return gameSystemFactory;
+		}
+		
+		public WarFoundryXmlRaceFactory GetRaceFactory()
+		{
+			return raceFactory;
+		}
+		
+		public WarFoundryXmlArmyFactory GetArmyFactory()
+		{
+			return armyFactory;
+		}
+		
+		protected override bool CheckCanFindArmyFileContent(ZipFile file)
+		{
+			return FindEntries(file, "*.armyx").Count > 0;
+		}
+		
+		protected override bool CheckCanFindSystemFileContent(ZipFile file)
+		{
+			return FindEntries(file, "*.systemx").Count > 0;
+		}
+		
+		protected override bool CheckCanFindRaceFileContent(ZipFile file)
+		{
+			return FindEntries(file, "*.racex").Count > 0;
+		}
+		
+		protected override ICollection<ZipEntry> GetArmyZipEntries(ZipFile file)
+		{
+			return FindEntries(file, "*.armyx");
+		}
+		
+		private ICollection<ZipEntry> FindEntries(ZipFile file, string wildcardPattern)
+		{
+			Regex re = new Regex("^" + Regex.Escape(wildcardPattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$", RegexOptions.IgnoreCase | RegexOptions.Singleline);
+			ICollection<ZipEntry> entries = new List<ZipEntry>();
+				
+			foreach (ZipEntry entry in file)
+			{
+				if (re.IsMatch(entry.Name))
+				{
+					entries.Add(entry);
+				}
+			}
+			
+			return entries;
+		}
+		
+		protected override Army CreateArmyFromStream (ZipFile file, Stream dataStream)
+		{
+			XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.ARMY_ELEMENT);
+			return armyFactory.CreateArmyFromElement(file, elem);
+		}
+		
+		private XmlElement GetRootElementFromStream(Stream stream, WarFoundryXmlElementName elementName)
+		{
+			XmlDocument doc = WarFoundryXmlFactoryUtils.CreateXmlDocumentFromStream(stream);
+
+			XmlElement elem = (XmlElement)doc.LastChild;
+			
+			if (!elem.LocalName.Equals(elementName.Value))
+			{
+				throw new InvalidFileException(String.Format("Root element of XML was not valid. Expected {0} but got {1}", elementName.Value, elem.Name));
+			}
+			
+			return elem;
+		}
+
+		protected override ICollection<ZipEntry> GetGameSystemZipEntries(ZipFile file)
+		{
+			return FindEntries(file, "*.systemx");
+		}
+		
+		protected override GameSystem CreateGameSystemFromStream (ZipFile file, Stream dataStream)
+		{
+			XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.SYSTEM_ELEMENT);
+			LogNotifier.Debug(GetType(), "Create GameSystem");
+			return gameSystemFactory.CreateSystemFromElement(file, elem);
+		}
+		
+		protected override ICollection<ZipEntry> GetRaceZipEntries(ZipFile file)
+		{
+			return FindEntries(file, "*.racex");
+		}
+		
+		protected override Race CreateRaceFromStream (ZipFile file, Stream dataStream)
+		{
+			XmlElement elem = GetRootElementFromStream(dataStream, WarFoundryXmlElementName.RACE_ELEMENT);
+			LogNotifier.Debug(GetType(), "Create Race");
+			return raceFactory.CreateRaceFromElement(file, elem);
+		}
+
+		protected override void CleanUpFileAsSupportedType(ZipFile typedFile)
+		{
+			typedFile.Close();
+		}
+
+		public override void CompleteLoading(IWarFoundryStagedLoadObject obj)
+		{			
+			LogNotifier.DebugFormat(GetType(), "Complete loading of {0} with ID {1}", obj.GetType().Name, obj.ID);
+
+			if (obj is GameSystem)
+			{
+				CompleteLoadingGameSystem((GameSystem) obj);
+			}
+			else if (obj is Race)
+			{
+				CompleteLoadingRace((Race) obj);
+			}
+		}
+
+		private void CompleteLoadingRace(Race race)
+		{
+			try
+			{
+				raceFactory.CompleteLoading(race);
+			}
+			catch (InvalidFileException ex)
+			{
+				WarFoundryLoader.GetDefault().RemoveRace(race);
+				throw;
+			}
+		}
+
+		private void CompleteLoadingGameSystem(GameSystem system)
+		{
+			try
+			{
+				gameSystemFactory.CompleteLoading(system);
+			}
+			catch (InvalidFileException ex)
+			{
+				WarFoundryLoader.GetDefault().RemoveGameSystem(system);
+				throw;
+			}
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/WarFoundryXmlFactoryUtils.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,149 @@
+//  This file (WarFoundryXmlFactoryUtils.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using System.Xml;
+using System.Xml.Schema;
+using IBBoard.WarFoundry.API.Objects;
+using IBBoard.IO;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// A collection of useful utility methods for loading WarFoundry data from XML files
+	/// </summary>
+	public class WarFoundryXmlFactoryUtils
+	{
+		public static readonly string NS_BASE = "http://ibboard.co.uk/warfoundry/";
+		private static XmlReaderSettings settings;
+		private static XmlNamespaceManager nsManager;
+		
+		public static XmlNodeList SelectNodes(XmlNode element, string xpathQuery)
+		{
+			return element.SelectNodes(xpathQuery, GetNamespaceManager());
+		}
+		
+		public static XmlNode SelectSingleNode(XmlNode element, string xpathQuery)
+		{
+			return element.SelectSingleNode(xpathQuery, GetNamespaceManager());
+		}
+		
+		public static XmlElement SelectSingleElement(XmlNode element, string xpathQuery)
+		{
+			XmlNode node = SelectSingleNode(element, xpathQuery);
+			return (node is XmlElement) ? (XmlElement) node : null;
+		}
+				
+		public static XmlNamespaceManager GetNamespaceManager()
+		{
+			if (nsManager == null)
+			{
+				nsManager = new XmlNamespaceManager(new NameTable());
+				nsManager.AddNamespace("core", NS_BASE + "core");
+				nsManager.AddNamespace("cat", NS_BASE + "cats");
+				nsManager.AddNamespace("race", NS_BASE + "race");
+				nsManager.AddNamespace("system", NS_BASE + "system");
+				nsManager.AddNamespace("army", NS_BASE + "army");
+			}
+			
+			return nsManager;
+		}
+		
+		/// <summary>
+		/// Lazy-getter for XML reader settings. May throw a <see cref="InvalidDataException"/> if there is a problem with the translation schema.
+		/// </summary>
+		/// <returns>
+		/// A <see cref="XmlReaderSettings"/> with the default values for validating the translation document against the translation schema
+		/// </returns>
+		public static XmlReaderSettings GetReaderSettings()
+		{
+			if (settings == null)
+			{
+				settings = new XmlReaderSettings();
+				settings.ValidationType = ValidationType.Schema;
+				//settings.ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings;
+				settings.ProhibitDtd = true;
+				settings.ValidationEventHandler+= new ValidationEventHandler(ValidationEventMethod);
+				XmlSchemaSet cache = new XmlSchemaSet();
+				string path =  IBBoard.Constants.ExecutablePath + "/schemas/";
+				AddSchemaToCache(cache, NS_BASE + "core", path + "warfoundry-core.xsd");
+				AddSchemaToCache(cache, NS_BASE + "cats", path + "warfoundry-cats.xsd");
+				AddSchemaToCache(cache, NS_BASE + "race", path + "race.xsd");
+				AddSchemaToCache(cache, NS_BASE + "system", path + "system.xsd");
+				AddSchemaToCache(cache, NS_BASE + "army", path + "army.xsd");
+				settings.Schemas.Add(cache);
+				settings.Schemas.CompilationSettings.EnableUpaCheck = false;
+			}
+			
+			return settings;
+		}
+		
+		private static void ValidationEventMethod(object sender, ValidationEventArgs e)
+		{
+			if (e.Severity == XmlSeverityType.Error)
+			{
+				throw new InvalidFileException("Problem validating against schema for WarFoundry data: " + e.Message, e.Exception);
+			}
+			else
+			{
+				//TODO: Fire some kind of warning event
+			}
+		}
+		
+		private static void AddSchemaToCache(XmlSchemaSet cache, string xmlNamespace, string schemaLocation)
+		{
+			try
+			{
+				cache.Add(xmlNamespace, schemaLocation);
+			}
+			catch (IOException ex)
+			{
+				//TODO: Warn on schema failure
+			}
+			catch (XmlSchemaException ex)
+			{
+				//TODO: Warn on schema failure
+			}
+			catch (XmlException ex)
+			{
+				//TODO: Warn on schema failure
+			}
+		}
+		
+		public static XmlDocument CreateXmlDocumentFromStream(Stream stream)
+		{
+			XmlDocument doc = new XmlDocument();
+			XmlReader reader = XmlReader.Create(stream, GetReaderSettings());
+			
+			try
+			{
+				doc.Load(reader);
+			}
+			//Don't catch XMLSchemaExceptions - let them get thrown out
+			finally
+			{
+				reader.Close();
+			}
+
+			return doc;
+		}
+		
+		public static bool CanCompleteLoading(IWarFoundryStagedLoadObject obj)
+		{
+			bool canLoad = true;			
+			
+			if (obj.IsFullyLoaded)
+			{
+				canLoad = false;
+			}
+			else if (obj.IsLoading)
+			{
+				canLoad = false;
+			}
+			
+			return canLoad;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/WarFoundryXmlGameSystemFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,102 @@
+//  This file (WarFoundryXmlGameSystemFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using ICSharpCode.SharpZipLib.Zip;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// A sub-factory specifically for loading GameSystems from WarFoundry XML files
+	/// </summary>
+	public class WarFoundryXmlGameSystemFactory : AbstractStagedLoadedSubFactory
+	{	
+		private Dictionary<GameSystem, XmlDocument> extraData = new Dictionary<GameSystem, XmlDocument>();
+		
+		public WarFoundryXmlGameSystemFactory(WarFoundryXmlFactory factory) : base(factory)
+		{
+		}
+		
+		private void StoreExtraData(GameSystem wfObject, XmlElement elem)
+		{
+			extraData[wfObject] = elem.OwnerDocument;
+		}
+	
+		private XmlDocument GetExtraData(GameSystem obj)
+		{
+			XmlDocument extra = null;
+			extraData.TryGetValue(obj, out extra);			
+			return extra;
+		}
+		
+		public GameSystem CreateSystemFromElement(ZipFile file, XmlElement elem)
+		{
+			string id = elem.GetAttribute("id");
+			string name = elem.GetAttribute("name");
+			GameSystem system = new GameSystem(id, name, mainFactory);
+            system.SystemArmyDefaultSize =  XmlTools.GetIntValueFromAttribute (elem, "defaultArmySize");
+            system.SystemPtsAbbrevSingle = elem.GetAttribute ("defaultPtsAbbreviationSingular");
+            system.SystemPtsAbbrevPlural = elem.GetAttribute ("defaultPtsAbbreviationPlural");
+            system.SystemPtsNameSingle = elem.GetAttribute ("defaultPtsNameSingular");
+            system.SystemPtsNamePlural = elem.GetAttribute ("defaultPtsNamePlural");
+			StoreExtraData(system, elem);
+			return system;
+		}		
+		
+		public void CompleteLoading(GameSystem system)
+		{
+			if (!WarFoundryXmlFactoryUtils.CanCompleteLoading(system))
+			{
+				return;
+			}
+			
+			system.SetAsLoading();			
+			XmlDocument extraData = GetExtraData(system);
+			LoadCategoriesForSystem(system, extraData);
+			XmlElement statsElem = WarFoundryXmlFactoryUtils.SelectSingleElement(extraData, "/system:system/system:sysStatsList");
+			string defaultStatsID = statsElem.GetAttribute("defaultStats");
+			LoadSystemStatsForSystem(system, extraData);
+			system.StandardSystemStatsID = defaultStatsID;
+			XmlElement systemElement = WarFoundryXmlFactoryUtils.SelectSingleElement(extraData, "/system:system");
+			system.WarnOnError = XmlTools.GetBoolValueFromAttribute(systemElement, "warn");
+			system.AllowAllies = XmlTools.GetBoolValueFromAttribute(systemElement, "allowAllies");
+			system.SetAsFullyLoaded();
+		}
+
+		
+		private void LoadCategoriesForSystem(GameSystem system, XmlNode elem)
+		{
+			foreach (XmlElement cat in WarFoundryXmlFactoryUtils.SelectNodes(elem, "/system:system/system:categories/cat:cat"))
+			{
+				system.AddCategory(CreateCategoryFromElement(cat));
+			}
+		}	
+		
+		private void LoadSystemStatsForSystem(GameSystem system, XmlNode elem)
+		{
+			foreach (XmlElement stats in WarFoundryXmlFactoryUtils.SelectNodes(elem, "/system:system/system:sysStatsList/system:sysStats"))
+			{
+				SystemStats sysStats = CreateSystemStatsFromElement(stats);
+				system.AddSystemStats(sysStats);
+			}
+		}
+		
+		private SystemStats CreateSystemStatsFromElement(XmlElement elem)
+		{
+			SystemStats sysStats = new SystemStats(elem.GetAttribute("id"));
+			
+			foreach (XmlElement slot in WarFoundryXmlFactoryUtils.SelectNodes(elem, "system:sysStat"))
+			{
+				sysStats.AddStatSlot(slot.GetAttribute("name"));
+			}
+
+			return sysStats;
+		}	
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/WarFoundryXmlLimitParser.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,91 @@
+//  This file (WarFoundryXmlLimitParser.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2010 IBBoard
+// 
+//  The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+using System.Collections.Generic;
+using System.Xml;
+using IBBoard.Limits;
+using IBBoard.Xml;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	public class WarFoundryXmlLimitParser
+	{
+		public ILimit GetMinLimit(XmlElement elem)
+		{
+			XmlElement limitElem = WarFoundryXmlFactoryUtils.SelectSingleElement(elem, "race:minLimit/*[1]");
+			return GetLimitFromElement(limitElem);
+		}
+
+		public ILimit GetMaxLimit(XmlElement equipSlot)
+		{
+			XmlElement limitElem = WarFoundryXmlFactoryUtils.SelectSingleElement(equipSlot, "race:maxLimit/*[1]");
+			return GetLimitFromElement(limitElem);
+		}
+
+		public ILimit GetLimitFromElement(XmlElement limitElem)
+		{
+			ILimit limit = null;
+			
+			if (limitElem != null)
+			{
+				switch (limitElem.LocalName)
+				{
+					case "percentageLimit":
+						double limitPercent = XmlTools.GetDoubleValueFromAttribute(limitElem, "limit");
+						bool roundUp = limitElem.GetAttribute("round").Equals("up");
+						limit = new SimpleRoundedPercentageLimit(limitPercent, roundUp);
+						break;
+					case "sizeConstrainedLimit":
+						limit = new NumericSizeConstrainedLimit(XmlTools.GetIntValueFromAttribute(limitElem, "limit"));
+						break;
+					case "absoluteLimit":
+						limit = new AbsoluteNumericLimit(XmlTools.GetIntValueFromAttribute(limitElem, "limit"));
+						break;
+					case "unitSizeLimit":
+						limit = new SimpleRoundedPercentageLimit(100);
+						break;
+					case "compositeMaxLimit":
+						ICollection<ILimit> maxSubLimits = GetSubLimits(limitElem);
+						limit = new CompositeMaximumLimit(maxSubLimits);
+						break;
+					case "compositeMinLimit":
+						ICollection<ILimit> minSubLimits = GetSubLimits(limitElem);
+						limit = new CompositeMinimumLimit(minSubLimits);
+						break;
+					default:
+						//TODO: Warn of missing handler for when we've extended the limit list
+						break;
+				}
+			}
+			
+			return limit;
+		}
+
+		private ICollection<ILimit> GetSubLimits(XmlElement limitElem)
+		{
+			XmlNodeList subLimitNodes = GetSubLimitElements(limitElem);
+			ICollection<ILimit> limits = new List<ILimit>();
+			
+			foreach (XmlNode node in subLimitNodes)
+			{
+				if (node is XmlElement)
+				{
+					ILimit limit = GetLimitFromElement((XmlElement)node);
+					
+					if (limit != null)
+					{
+						limits.Add(limit);
+					}
+				}
+			}
+			
+			return limits;
+		}
+
+		private XmlNodeList GetSubLimitElements(XmlElement limitElem)
+		{
+			return limitElem.ChildNodes;
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/WarFoundryXmlRaceFactory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,481 @@
+//  This file (WarFoundryXmlRaceFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using IBBoard.Xml;
+using IBBoard.IO;
+using IBBoard.Limits;
+using IBBoard.CustomMath;
+using ICSharpCode.SharpZipLib.Zip;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml
+{
+	/// <summary>
+	/// A sub-factory for loading WarFoundry Race XML files
+	/// </summary>
+	public class WarFoundryXmlRaceFactory : AbstractStagedLoadedSubFactory
+	{
+		private Dictionary<Race, XmlDocument> extraData = new Dictionary<Race, XmlDocument>();
+		private WarFoundryXmlLimitParser limitParser = new WarFoundryXmlLimitParser();
+		
+		public WarFoundryXmlRaceFactory(WarFoundryXmlFactory factory) : base (factory)
+		{
+			//Do nothing special
+		}
+		
+		private void StoreExtraData(Race wfObject, XmlElement elem)
+		{
+			extraData[wfObject] = elem.OwnerDocument;
+		}
+	
+		private XmlDocument GetExtraData(Race obj)
+		{
+			XmlDocument extra = null;
+			extraData.TryGetValue(obj, out extra);
+			return extra;
+		}
+		
+		public Race CreateRaceFromElement(ZipFile file, XmlElement elem)
+		{
+			string id = elem.GetAttribute("id");
+			string subid = elem.GetAttribute("subid");
+			string systemID = elem.GetAttribute("system");
+			string name = elem.GetAttribute("name");
+            string armyDefaultName = elem.GetAttribute("defaultArmyName");
+			GameSystem gameSystem = WarFoundryLoader.GetDefault ().GetGameSystem (systemID);
+
+			if (gameSystem == null)
+			{
+				throw new InvalidFileException("Referenced game system, '"+systemID+"', did not exist");
+			}
+
+            Race race = new Race(id, subid, name, gameSystem, mainFactory);
+			race.ArmyDefaultName = armyDefaultName;
+			StoreExtraData(race, elem);
+			return race;
+		}
+		
+		public void CompleteLoading(Race race)
+		{
+			if (!WarFoundryXmlFactoryUtils.CanCompleteLoading(race))
+			{
+				return;
+			}
+			
+			race.SetAsLoading();			
+			XmlDocument extraData = GetExtraData(race);
+			
+			foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:categories/cat:cat"))
+			{
+				CreateCategoryFromElement(node, race);
+			}
+							
+			foreach (XmlElement node  in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:equipment/race:equipmentItem"))
+			{
+				CreateEquipmentItemFromElement(node, race);
+			}
+							
+			foreach (XmlElement node  in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:abilities/race:ability"))
+			{
+				CreateAbilityFromElement(node, race);
+			}
+							
+			foreach (XmlElement node  in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:memberTypes/race:memberType"))
+			{
+				CreateMemberTypeFromElement(node, race);
+			}
+			
+			foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:units/race:unit"))
+			{
+				GetUnitTypeForElement(node, race);
+			}
+			
+			race.SetAsFullyLoaded();
+		}
+
+		private Category CreateCategoryFromElement(XmlElement elem, Race parentRace)
+		{
+			Category cat = CreateCategoryFromElement(elem);
+			parentRace.AddCategory(cat);
+			return cat;
+		}
+
+		private UnitType GetUnitTypeFromDocument(XmlDocument doc, string id, Race parentRace)
+		{
+			XmlElement unitWithId = WarFoundryXmlFactoryUtils.SelectSingleElement (doc, "/race:race/race:units/race:unit[@id='" + id + "']");
+			
+			if (unitWithId == null)
+			{
+				throw new InvalidFileException("Could not find unit with ID "+id);
+			}
+			
+			return GetUnitTypeForElement(unitWithId, parentRace);
+		}
+						
+		private UnitType GetUnitTypeForElement(XmlElement elem, Race parentRace)
+		{
+			string id = elem.GetAttribute("id");
+			UnitType type = parentRace.GetUnitType(id);
+
+			if (type==null)
+			{
+				type = CreateUnitTypeFromElement(elem, id, parentRace);
+			}
+			
+			return type;
+		}
+
+		private UnitType CreateUnitTypeFromElement(XmlElement elem, string id, Race parentRace)
+		{
+			string name = elem.GetAttribute("typeName");
+			UnitType type = new UnitType(id, name, parentRace);
+			LoadCoreValuesForUnitType(elem, type);
+			LoadEquipmentSlotsForUnitType(elem, type);
+			LoadEquipmentForUnitType(elem, type);
+			LoadAbilitiesForUnitType(elem, type);
+			LoadContainedUnitsForUnitType(elem, type);
+			LoadRequirementsForUnitType(elem, type);
+			LoadExtraDataForUnitType(elem, type);
+			LoadNotesForUnitType(elem, type);
+			parentRace.AddUnitType(type);
+			return type;
+		}
+
+		private void LoadCoreValuesForUnitType(XmlElement elem, UnitType type)
+		{
+			try
+			{
+				type.MaxNumber = XmlTools.GetIntValueFromAttribute(elem, "maxNum");
+				type.MinNumber = XmlTools.GetIntValueFromAttribute(elem, "minNum");
+				type.MaxSize = XmlTools.GetIntValueFromAttribute(elem, "maxSize");
+				type.MinSize = XmlTools.GetIntValueFromAttribute(elem, "minSize");
+				type.BaseSize = XmlTools.GetIntValueFromAttribute(elem, "baseSize");
+				type.CostPerTrooper = XmlTools.GetDoubleValueFromAttribute(elem, "points");
+				type.BaseUnitCost = XmlTools.GetDoubleValueFromAttribute(elem, "basePoints");
+			}
+			catch (FormatException ex)
+			{
+				throw new InvalidFileException(ex.Message, ex);
+			}
+
+			Race race = type.Race;
+			string mainCatID = elem.GetAttribute("cat");
+			Category cat = race.GetCategory(mainCatID);
+			
+			if (cat == null)
+			{
+				throw new InvalidFileException(String.Format("Category with ID '{1}' did not exist for UnitType '{0}'", type.Name, mainCatID));
+			}
+			
+			type.MainCategory = cat;
+			
+			XmlNodeList unitCategories = WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitCategories/race:unitCategory");
+			
+			foreach (XmlElement unitCategory in unitCategories)
+			{
+				string catID = unitCategory.GetAttribute("catID");
+				Category unitCat = race.GetCategory(catID);
+				
+				if (unitCat == null)
+				{
+					throw new InvalidFileException(String.Format("Category with ID '{1}' did not exist for UnitType '{0}'", type.Name, catID));
+				}
+				
+				type.AddCategory(unitCat);
+			}
+			
+			XmlElement statsElement = WarFoundryXmlFactoryUtils.SelectSingleElement(elem, "race:stats");
+			
+			if (statsElement!=null)
+			{
+				Stats unitStats = ParseUnitStats(statsElement, type.GameSystem);
+				type.SetUnitStats(unitStats);
+			}
+			
+			XmlNodeList unitMemberReferences = WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitMembers/race:unitMember");
+			
+			foreach (XmlElement unitMemberRef in unitMemberReferences)
+			{
+				string typeID = unitMemberRef.GetAttribute("typeID");
+				UnitMemberType unitMemberType = race.GetUnitMemberType(typeID);
+				type.AddUnitMemberType(unitMemberType);
+			}
+		}
+
+		private void LoadEquipmentSlotsForUnitType(XmlElement elem, UnitType type)
+		{
+			foreach (XmlElement equipSlot in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:equipmentSlots/race:equipmentSlot"))
+			{
+				LoadEquipmentSlotForUnitType (type, equipSlot);
+			}
+		}
+
+		private void LoadEquipmentSlotForUnitType(UnitType type, XmlElement equipSlot)
+		{
+			string slotName = equipSlot.GetAttribute("name");
+			ILimit limit = GetMaxLimit(equipSlot);
+			
+			if (limit != null)
+			{
+				type.AddEquipmentSlot(slotName, limit);
+			}
+		}
+		
+		private ILimit GetMinLimit(XmlElement elem)
+		{
+			return limitParser.GetMinLimit(elem);
+		}
+
+		private ILimit GetMaxLimit(XmlElement elem)
+		{
+			return limitParser.GetMaxLimit(elem);
+		}
+
+		private void LoadEquipmentForUnitType(XmlElement elem, UnitType type)
+		{
+			foreach (XmlElement equip in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitEquipment/race:unitEquipmentItem"))
+			{
+				string id = equip.GetAttribute("id");
+				EquipmentItem equipItem = type.Race.GetEquipmentItem(id);
+				
+				if (equipItem!=null)
+				{
+					string mutexGroupString = equip.GetAttribute("exclusivityGroups");
+					string[] mutexGroups;
+
+					if (mutexGroupString == "")
+					{
+						mutexGroupString = equip.GetAttribute("exclusivityGroup");
+					}
+
+					if (mutexGroupString != "")
+					{
+						string[] groups = mutexGroupString.Split(',');
+						int groupCount = groups.Length;
+
+						for (int i = 0; i < groupCount; i++)
+						{
+							groups[i] = groups[i].Trim();
+						}
+
+						mutexGroups = groups;
+					}
+					else
+					{
+						mutexGroups = new string[0];
+					}
+
+					UnitEquipmentItem unitEquipItem = new UnitEquipmentItem(equipItem, type, mutexGroups);
+
+					string equipSlot = equip.GetAttribute("equipmentSlot");
+
+					if (equipSlot != "")
+					{
+						if (type.HasEquipmentSlot(equipSlot))
+						{
+							unitEquipItem.SlotName = equipSlot;
+						}
+						else
+						{
+							throw new InvalidFileException("Attribute 'equipmentSlot' of unit equipment item " + id + " for " + type.Name + " was not a valid slot name");
+						}
+					}
+
+					ILimit limit = GetMaxLimit(equip);
+
+					if (limit != null)
+					{
+						unitEquipItem.MaxLimit = limit;
+					}
+
+					limit = GetMinLimit(equip);
+
+					if (limit != null)
+					{
+						unitEquipItem.MinLimit = limit;
+					}
+					
+					unitEquipItem.RoundNumberUp = equip.GetAttribute("roundDirection").Equals("up");
+					
+					try
+					{
+						unitEquipItem.IsRequired = XmlTools.GetBoolValueFromAttribute(equip, "required");
+					}
+					catch(FormatException e)
+					{
+						throw new InvalidFileException("Attribute 'required' of unit equipment item " + id + " for " + type.Name + " was not a valid boolean", e);
+					}
+					
+					try
+					{
+						unitEquipItem.CostMultiplier = XmlTools.GetDoubleValueFromAttribute(equip, "costMultiplier");
+					}
+					catch (FormatException e)
+					{
+						throw new InvalidFileException("Attribute 'costMultiplier' of unit equipment item " + id + " for " + type.Name + " was not a valid decimal number", e);
+					}
+					
+					try
+					{
+						unitEquipItem.CostRoundType = (RoundType) Enum.Parse(typeof(RoundType), equip.GetAttribute("costRounding"));
+					}
+					catch (ArgumentException e)
+					{
+						throw new InvalidFileException("Attribute 'costRounding' of unit equipment item " + id + " for " + type.Name + " was not a valid rounding type", e);
+					}
+				}
+				else
+				{
+					throw new InvalidFileException("Equipment item with ID '" + id + "' was required by " + type.Name + " but was not found");
+				}
+			}		
+		}
+		
+		private void LoadAbilitiesForUnitType(XmlElement elem, UnitType type)
+		{
+			foreach (XmlElement abilityElem in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitAbilities/race:unitAbility"))
+			{
+				string id = abilityElem.GetAttribute("abilityID");
+				Ability ability = type.Race.GetAbility(id);
+				
+				if (ability == null)
+				{
+					throw new InvalidFileException("Ability for "+type.Name+ " with ID "+id+ " did not exist in race definition");
+				}
+
+				bool required = XmlTools.GetBoolValueFromAttribute(abilityElem, "required");
+				type.AddAbility(ability, required);
+			}
+		}
+		
+		private void LoadContainedUnitsForUnitType(XmlElement elem, UnitType type)
+		{
+			foreach (XmlElement containedUnitType in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:contains/race:containedUnit"))
+			{
+				string id = containedUnitType.GetAttribute("containedID");
+				UnitType containedType = GetUnitTypeFromDocument(elem.OwnerDocument, id, type.Race);
+
+				if (containedType!=null)
+				{
+					type.AddContainedUnitType(containedType);
+				}
+				else
+				{
+					throw new InvalidFileException("Unit type " + type.Name + " tried to contain undefined unit with ID "+id);
+				}
+			}
+		}
+
+		private void LoadRequirementsForUnitType(XmlElement elem, UnitType type)
+		{
+			//TODO: Load requirements
+		}
+		
+		private void LoadExtraDataForUnitType(XmlElement elem, UnitType type)
+		{
+			foreach (XmlElement extraData in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:extraData/race:data"))
+			{
+				string id = extraData.GetAttribute("id");
+				string data = extraData.InnerXml;
+				type.AddExtraData(id, data);
+			}
+		}
+		
+		private void LoadNotesForUnitType(XmlElement elem, UnitType type)
+		{
+			XmlNode node = WarFoundryXmlFactoryUtils.SelectSingleNode(elem, "race:notes");
+
+			if (node!=null)
+			{
+				type.Notes = node.InnerText;
+			}
+		}
+		
+		private Stats ParseUnitStats(XmlElement elem, GameSystem system)
+		{
+			if (elem == null)
+			{
+				return null;
+			}
+			
+			String statsID = elem.GetAttribute("statSet");
+			SystemStats statsSet;
+			
+			if (statsID == "")
+			{
+				statsSet = system.StandardSystemStats;
+			}
+			else
+			{
+				statsSet = system.GetSystemStatsForID(statsID);
+			}
+			
+			Stats stats = new Stats(statsSet);
+			
+			foreach (XmlElement stat in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:stat"))
+			{
+				String statName = stat.GetAttribute("name");
+				stats.SetStatValue(statName, stat.InnerText);
+			}
+			
+			return stats;
+		}
+		
+		private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, Race race)
+		{
+			string id = elem.GetAttribute("id");
+			EquipmentItem item = race.GetEquipmentItem(id);
+
+			if (item == null)
+			{
+				item = CreateEquipmentItemFromElement(elem, id, race);
+			}
+			
+			return item;
+		}
+
+		private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, string id, Race race)
+		{
+			string name = elem.GetAttribute("name");
+			EquipmentItem item = new EquipmentItem(id, name, race);
+			double cost = 0;
+			
+			try
+			{
+				cost = XmlTools.GetDoubleValueFromAttribute(elem, "cost");
+			}
+			catch(FormatException ex)
+			{
+				throw new InvalidFileException("Attribute 'cost' of equipment item "+id+" was not a valid number", ex);
+			}			
+						
+			//TODO: Parse equipment stats if there are any
+			item.Cost = cost;
+			race.AddEquipmentItem(item);			
+			return item;
+		}
+		
+		private Ability CreateAbilityFromElement(XmlElement elem, Race race)
+		{
+			string id = elem.GetAttribute("id");
+			string name = elem.GetAttribute("name");
+			Ability ability = new Ability(id, name);
+			XmlNode node = WarFoundryXmlFactoryUtils.SelectSingleNode(elem, "race:description");
+			ability.Description = (node == null) ? "" : node.InnerText;
+			race.AddAbility(ability);
+			return ability;
+		}		
+
+		private void CreateMemberTypeFromElement(XmlElement elem, Race race)
+		{
+			Stats stats = ParseUnitStats(WarFoundryXmlFactoryUtils.SelectSingleElement(elem, "race:stats"), race.GameSystem);
+			UnitMemberType unitMemberType = new UnitMemberType(elem.GetAttribute("id"), elem.GetAttribute("name"), stats);
+			race.AddUnitMemberType(unitMemberType);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Factories/Xml/Zip/StringZipEntrySource.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,30 @@
+// This file (StringZipEntrySource.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+
+using System;
+using System.IO;
+using IBBoard.Lang;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace IBBoard.WarFoundry.API.Factories.Xml.Zip
+{
+	/// <summary>
+	/// A simple implementation of IStaticDataSource that lets us add a string directly to a Zip file
+	/// </summary>
+	public class StringZipEntrySource : IStaticDataSource
+	{
+		private byte[] entryContent;
+		
+		public StringZipEntrySource(String content)
+		{
+			entryContent = StringManipulation.StringToBytes(content);
+		}
+
+		public Stream GetSource()
+		{
+			return new MemoryStream(entryContent);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/FileLoadFailure.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,121 @@
+// This file (FileLoadFailure.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Factories;
+
+namespace IBBoard.WarFoundry.API
+{
+	/// <summary>
+	/// A container class that holds information about file load failures. Core information covers the file that failed and a message. Additional information includes the factory loading the file and the excetion that was thrown. Messages are passed through <code>String.Format</code> and supplied with the failed file path and the failing factory
+	/// </summary>
+	public class FileLoadFailure
+	{
+		private FileInfo failedFile;
+		private IWarFoundryFactory loadingFactory;
+		private string defaultMessage;
+		private string messageTranslationID;
+		private string message;
+		private Exception cause;
+		
+		/// <summary>
+		/// Constructor for a failed file load where no factory was found. Translatable messages can be providied through a <code>translationID</code> or skipped by passing <code>null</code>.
+		/// </summary>
+		/// <param name="file">
+		/// The <see cref="FileInfo"/> that failed to load
+		/// </param>
+		/// <param name="message">
+		/// A message about the failure in English - used as a default fall-back message.
+		/// </param>
+		/// <param name="translationID">
+		/// The ID of a translation for the message.
+		/// </param>
+		public FileLoadFailure(FileInfo file, string message, string translationID) : this (file, null, message, "")
+		{
+		}
+		
+		/// <summary>
+		/// Constructor for a failed file load where a factory was identified as supporting the file but failed to load it. Translatable messages can be providied through a <code>translationID</code> or skipped by passing <code>null</code>.
+		/// </summary>
+		/// <param name="file">
+		/// The <see cref="FileInfo"/> that failed to load
+		/// </param>
+		/// <param name="factory">
+		/// The <see cref="IWarFoundryFactory"/> that failed to load the file
+		/// </param>
+		/// <param name="message">
+		/// A message about the failure in English - used as a default fall-back message.
+		/// </param>
+		/// <param name="translationID">
+		/// The ID of a translation for the message.
+		/// </param>
+		public FileLoadFailure(FileInfo file, IWarFoundryFactory factory, string message, string translationID) : this(file, factory, message, translationID, null)
+		{
+		}
+		
+		/// <summary>
+		/// Constructor for a failed file load where a factory was identified as supporting the file but an exception occurred while loading it. Translatable messages can be providied through a <code>translationID</code> or skipped by passing <code>null</code>.
+		/// </summary>
+		/// <param name="file">
+		/// The <see cref="FileInfo"/> that failed to load
+		/// </param>
+		/// <param name="factory">
+		/// The <see cref="IWarFoundryFactory"/> that failed to load the file
+		/// </param>
+		/// <param name="message">
+		/// A message about the failure in English - used as a default fall-back message.
+		/// </param>
+		/// <param name="translationID">
+		/// The ID of a translation for the message.
+		/// </param>
+		/// <param name="exception">
+		/// The <see cref="Exception"/> that occurred to cause the load to fail
+		/// </param>
+		public FileLoadFailure(FileInfo file, IWarFoundryFactory factory, string message, string translationID, Exception exception)
+		{
+			failedFile = file;
+			loadingFactory = factory;
+			defaultMessage = message;
+			messageTranslationID = translationID;
+			cause = exception;
+		}
+
+		public FileInfo FailedFile
+		{
+			get
+			{
+				return failedFile;
+			}
+		}
+
+		public string Message
+		{
+			get
+			{
+				if (message == null)
+				{
+					string fileName = FailedFile.FullName;
+					string factoryType = (loadingFactory == null ? "" : loadingFactory.GetType().Name);
+					if (messageTranslationID == "" || messageTranslationID == null)
+					{
+						message = String.Format(defaultMessage, fileName, factoryType);
+				 	}
+					else
+				 	{
+						message = Translation.GetTranslation(messageTranslationID, defaultMessage, fileName, factoryType);
+					}
+				}
+				
+				return message;
+			}
+		}
+		
+		public Exception Exception
+		{
+			get { return cause; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Ability.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,32 @@
+// This file (Ability.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// An Ability is a special rule that a UnitType has, made up of an ability name and a description.
+	/// </summary>
+	public class Ability : WarFoundryObject
+	{
+		private string description;
+		
+		public Ability(String id, String name) : base(id, name)
+		{
+		}
+		
+		public string Description
+		{
+			get { return description; }
+			set
+			{
+				if (value!=null)
+				{
+					description = value.Trim();
+				}
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/AbstractUnitEquipmentItemSelection.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,92 @@
+// This file (AbstractUnitEquipmentItemSelection.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// An abstract class that defines a selection of equipment for a unit
+	/// </summary>	
+	public abstract class AbstractUnitEquipmentItemSelection
+	{
+		private Unit selectionForUnit;
+		private UnitEquipmentItem selectedItem;
+		private double amountTaken;
+		
+		public AbstractUnitEquipmentItemSelection(Unit unit, UnitEquipmentItem item, double amount)
+		{
+			selectionForUnit = unit;
+			selectedItem = item;
+			AmountTaken = amount;
+		}
+		
+		public Unit EquipmentForUnit
+		{
+			get
+			{
+				return selectionForUnit;
+			}
+		}
+		
+		public UnitEquipmentItem EquipmentItem
+		{
+			get
+			{
+				return selectedItem;
+			}
+		}
+		
+		public double AmountTaken
+		{
+			get 
+			{
+				return amountTaken;
+			}
+			set
+			{
+				amountTaken = value;
+				
+				if (!IsValidValue(value))
+				{
+					//Fire validation failed event (once we have one)
+				}
+			}
+		}
+		
+		public bool IsValid
+		{
+			get
+			{
+				return IsValidValue(AmountTaken) && IsInRange(AmountTaken);
+			}	
+		}
+		
+		protected virtual bool IsValidValue(double newValue)
+		{
+			return true;
+		}
+		
+		protected bool IsInRange(double newValue)
+		{
+			int unitSize = EquipmentForUnit.Size;
+			int minLimit = EquipmentItem.MinLimit.GetLimit(unitSize);
+			int maxLimit = EquipmentItem.MaxLimit.GetLimit(unitSize);
+			return (minLimit <= newValue) && (newValue <= maxLimit);
+		}
+		
+		public double TotalCost
+		{
+			get
+			{
+				return NumberTaken * EquipmentItem.Cost;
+			}
+		}
+		
+		public abstract int NumberTaken
+		{
+			 get;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Army.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,277 @@
+// This file (Army.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+using IBBoard.WarFoundry.API;
+using IBBoard.WarFoundry.API.Factories;
+using IBBoard.WarFoundry.API.Requirements;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for Army.
+	/// </summary>
+	public class Army : WarFoundryLoadedObject, ICostedWarFoundryObject
+	{
+		//private GameSystem system;
+		private Race armyRace;
+		private int maxPoints;
+		private double pointsTotal;
+		private Dictionary<Category, ArmyCategory> categories;
+
+		public event ObjectAddDelegate UnitAdded;
+		public event ObjectRemoveDelegate UnitRemoved;
+		public event FailedUnitRequirementDelegate FailedRequirement;
+		public event DoubleValChangedDelegate PointsValueChanged;
+		private DoubleValChangedDelegate PointsValueChangedMethod;
+		
+		public Army(Race race, string armyName, int maxArmyPoints) : this(race, armyName, maxArmyPoints, null)
+		{
+		}
+
+		public Army(Race race, string armyName, int maxArmyPoints, ZipFile file) : base(armyName)
+		{
+			armyRace = race;
+			Name = armyName;
+			maxPoints = maxArmyPoints;
+			PointsValueChangedMethod = new DoubleValChangedDelegate(PointsValueChangedHandler);
+		}
+		
+		public ArmyCategory GetCategory(Category cat)
+		{
+			ArmyCategory armyCat = null;
+			ArmyCategories.TryGetValue(cat, out armyCat);
+			return armyCat;
+		}
+		
+		private Dictionary<Category, ArmyCategory> ArmyCategories
+		{
+			get
+			{
+				if (categories==null)
+				{
+					categories = new Dictionary<Category, ArmyCategory>();
+					Category[] raceCats = Race.Categories;
+					ArmyCategory cat;
+					int raceCatCount = raceCats.Length;
+
+					for (int i = 0; i < raceCatCount; i++)
+					{
+						Category raceCat = raceCats[i];
+						cat = new ArmyCategory(this, raceCat);
+						categories[raceCat] = cat;
+						cat.PointsValueChanged+= PointsValueChangedMethod;
+						cat.UnitAdded+=new ObjectAddDelegate(Army_UnitAdded);
+						cat.UnitRemoved+=new ObjectRemoveDelegate(Army_UnitRemoved);
+						cat.FailedRequirement+=new FailedUnitRequirementDelegate(Army_FailedRequirement);
+					}
+				}
+				
+				return categories;
+			}
+		}
+
+		public ArmyCategory[] Categories
+		{
+			get 
+			{
+				return DictionaryUtils.ToArray<Category, ArmyCategory>(ArmyCategories);
+			}
+		}
+
+		public Race Race
+		{
+			get { return armyRace; }
+		}
+
+		public GameSystem GameSystem
+		{
+			get { return (armyRace!=null ? armyRace.GameSystem : null); }
+		}
+
+		protected void OnUnitAdded(Unit unit)
+		{
+			OnUnitAdded(unit, null);
+		}
+
+		protected void OnUnitAdded(Unit unit, List<FailedUnitRequirement> failedReqs)
+		{
+			if (UnitAdded != null)
+			{
+				UnitAdded(unit);
+			}
+
+			OnFailedRequirement(failedReqs);
+		}
+
+		protected void OnUnitRemoved(Unit unit)
+		{
+			OnUnitRemoved(unit, null);
+		}
+
+		protected void OnUnitRemoved(Unit unit, List<FailedUnitRequirement> failedReqs)
+		{
+			if (UnitRemoved!=null)
+			{
+				UnitRemoved(unit);
+			}
+
+			OnFailedRequirement(failedReqs);
+		}
+
+		protected void OnFailedRequirement(List<FailedUnitRequirement> failedReqs)
+		{
+			if (FailedRequirement != null && failedReqs != null && failedReqs.Count > 0)
+			{
+				FailedRequirement(failedReqs);
+			}
+		}
+
+		private void OnPointsValueChanged(double oldValue, double newValue)
+		{
+			if (PointsValueChanged!=null)
+			{
+				PointsValueChanged(this, oldValue, newValue);
+			}
+		}
+		
+		private double TotalPoints
+		{
+			get { return pointsTotal; }
+			set
+			{
+				double oldPoints = pointsTotal;
+				pointsTotal = value;
+
+				if (oldPoints!=pointsTotal)
+				{
+					OnPointsValueChanged(oldPoints, pointsTotal);
+				}
+			}
+		}
+		
+		public double Points
+		{
+			get { return TotalPoints; }
+		}
+		
+		public void AddUnit(Unit unit)
+		{
+			Category category = unit.UnitType.MainCategory;
+			AddUnit(unit, category);
+		}
+		
+		public void AddUnit(Unit unit, Category category)
+		{			
+			ArmyCategory armyCat = GetCategory(category);
+			armyCat.AddUnit(unit);
+		}
+		
+		public void RemoveUnit(Unit unit)
+		{
+			unit.Category.RemoveUnit(unit);
+		}
+
+		public Unit[] GetUnits(Category cat)
+		{
+			return GetUnits(this.GetCategory(cat));
+		}
+
+		public Unit[] GetUnits(ArmyCategory cat)
+		{
+			return cat.GetUnits();
+		}
+
+		public Unit[] GetUnits()
+		{
+			List<Unit> fullList = new List<Unit>();
+
+			foreach(ArmyCategory cat in Categories)
+			{
+				fullList.AddRange(cat.GetUnits());
+			}
+
+			return fullList.ToArray();
+		}
+
+		public int MaxPoints
+		{
+			get { return maxPoints; }
+			set 
+			{
+				if (value > 0)
+				{
+					maxPoints = value;
+				}
+			}
+		}
+
+		private void PointsValueChangedHandler(WarFoundryObject obj, double oldVal, double newVal)
+		{
+			if (obj is ArmyCategory)
+			{
+				double points = 0;
+
+				foreach (ArmyCategory cat in Categories)
+				{
+					points+= cat.Points;
+				}
+
+				TotalPoints = points;
+			}
+		}
+
+		public List<FailedUnitRequirement> CanAddUnit(Unit unit)
+		{
+			return CanAddUnitType(unit.UnitType);
+		}
+
+		public List<FailedUnitRequirement> CanAddUnitType(UnitType unitType)
+		{
+			return unitType.CanAddToArmy(this);
+		}
+
+		public List<FailedUnitRequirement> CanRemoveUnit(Unit unit)
+		{
+			return CanRemoveUnitType(unit.UnitType);
+		}
+
+		public List<FailedUnitRequirement> CanRemoveUnitType(UnitType unitType)
+		{
+			return unitType.CanRemoveFromArmy(this);
+		}
+
+		public int GetUnitTypeCount(UnitType unitType)
+		{
+			int count = 0;
+
+			foreach (ArmyCategory cat in Categories)
+			{
+				count+= cat.GetUnitTypeCount(unitType);
+			}
+
+			return count;
+		}
+
+		private void Army_UnitAdded(WarFoundryObject val)
+		{
+			OnUnitAdded((Unit)val);
+		}
+
+		private void Army_UnitRemoved(WarFoundryObject val)
+		{
+			OnUnitRemoved((Unit)val);
+		}
+
+		private void Army_FailedRequirement(List<FailedUnitRequirement> val)
+		{
+			OnFailedRequirement(val);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/ArmyCategory.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,184 @@
+// This file (ArmyCategory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Requirements;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for ArmyCategory.
+	/// </summary>
+	public class ArmyCategory : WarFoundryObject, ICostedWarFoundryObject
+	{
+		private Category category;
+		private Army parentArmy;
+		private double pointsTotal;
+		private List<Unit> units;
+		private Dictionary<string, int> unitTypes;
+		private DoubleValChangedDelegate PointsValueChangedMethod;
+		public event ObjectAddDelegate UnitAdded;
+		public event ObjectRemoveDelegate UnitRemoved;
+		public event FailedUnitRequirementDelegate FailedRequirement;
+		public event DoubleValChangedDelegate PointsValueChanged;
+
+		public ArmyCategory(Army army, Category cat) : base()
+		{
+			parentArmy = army;
+			category = cat;
+			cat.NameChanged+=new StringValChangedDelegate(cat_NameChanged);
+			PointsValueChangedMethod = new DoubleValChangedDelegate(PointsValueChangedHandler);
+			units = new List<Unit>();
+			unitTypes = new Dictionary<string,int>();
+		}
+
+		public Category Category
+		{
+			get { return category; }
+		}
+
+		public Army ParentArmy
+		{
+			get { return parentArmy; }
+		}
+
+		public override string ID
+		{
+			get
+			{
+				return Category.ID;
+			}
+			set
+			{
+				Category.ID = value;
+			}
+		}
+
+		public override string Name
+		{
+			get { return category.Name; }
+			set 
+			{
+				category.Name = value;
+			}
+		}
+
+		internal void AddUnit(Unit unit)
+		{
+			List<FailedUnitRequirement> failedReqs = ParentArmy.CanAddUnit(unit);
+			units.Add(unit);
+			unit.Category = this;
+			unit.PointsValueChanged+= PointsValueChangedMethod;
+			int unitTypeCount;
+			unitTypes.TryGetValue(unit.UnitType.ID, out unitTypeCount);
+			unitTypes[unit.UnitType.ID] = (int)unitTypeCount + 1;
+			TotalPoints+= unit.Points;
+			OnUnitAdded(unit, failedReqs);
+		}
+
+		internal void RemoveUnit(Unit unit)
+		{
+			List<FailedUnitRequirement> failedReqs = ParentArmy.CanRemoveUnit(unit);
+			units.Remove(unit);
+			unitTypes[unit.UnitType.ID] = ((int)unitTypes[unit.UnitType.ID])-1;
+			TotalPoints-= unit.Points;
+			unit.PointsValueChanged-= PointsValueChangedMethod;
+			OnUnitRemoved(unit, failedReqs);
+		}
+
+		public int GetUnitTypeCount(UnitType unitType)
+		{
+			return unitTypes.ContainsKey(unitType.ID) ? (int)unitTypes[unitType.ID] : 0;
+		}
+
+		public Unit[] GetUnits()
+		{
+			return units.ToArray();
+		}
+
+		private double TotalPoints
+		{
+			get { return pointsTotal; }
+			set 
+			{
+				double oldVal = pointsTotal;
+				pointsTotal = value;
+
+				if (oldVal!=pointsTotal)
+				{
+					OnPointsValueChanged(oldVal, pointsTotal);
+				}
+			}
+		}
+
+		public double Points
+		{
+			get { return TotalPoints; }
+		}
+
+		private void PointsValueChangedHandler(WarFoundryObject obj, double oldVal, double newVal)
+		{
+			if (obj is Unit)
+			{
+				double diff = newVal - oldVal;
+				TotalPoints+= diff;
+			}
+		}
+
+		protected void OnUnitAdded(Unit unit)
+		{
+			OnUnitAdded(unit, null);
+		}
+
+		protected void OnUnitAdded(Unit unit, List<FailedUnitRequirement> failedReqs)
+		{
+			if (UnitAdded != null)
+			{
+				UnitAdded(unit);
+			}
+
+			if (FailedRequirement != null && failedReqs != null && failedReqs.Count > 0)
+			{
+				FailedRequirement(failedReqs);
+			}
+		}
+
+		protected void OnUnitRemoved(Unit unit)
+		{
+			OnUnitRemoved(unit, null);
+		}
+
+		protected void OnUnitRemoved(Unit unit, List<FailedUnitRequirement> failedReqs)
+		{
+			if (UnitRemoved != null)
+			{
+				UnitRemoved(unit);
+			}
+
+			if (FailedRequirement != null && failedReqs != null && failedReqs.Count > 0)
+			{
+				FailedRequirement(failedReqs);
+			}
+		}
+
+		protected virtual void OnPointsValueChanged(double oldValue, double newValue)
+		{
+			if (PointsValueChanged!=null)
+			{
+				PointsValueChanged(this, oldValue, newValue);
+			}
+		}
+
+		protected void cat_NameChanged(WarFoundryObject obj, string oldValue, string newValue)
+		{
+			OnNameChanged(oldValue, newValue);
+		}
+				
+		public int GetPointsPercentage()
+		{
+			return (int)Math.Round((Points / ParentArmy.MaxPoints) * 100, 0);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Category.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,119 @@
+// This file (Category.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Xml;
+using IBBoard.Logging;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// A Category is a definition at the <see cref=" GameSystem"/> or <see cref=" Race"/> level of a group that <see cref=" UnitType"/>s belong to. Each category has a name and a min/max limit on points or percentage of a total army cost that units in the category can total.
+	/// </summary>
+	public class Category : WarFoundryObject
+	{
+		private int minPts = 0;
+		private int maxPts = WarFoundryCore.INFINITY;
+		private int minPc = 0;
+		private int maxPc = 100;
+
+		
+		public Category(string id, string name) : base(id, name)
+		{
+		}
+
+		protected override string DefaultName()
+		{
+			return "";
+		}
+		
+		/// <value>
+		/// Gets or sets the minimum number of points that the units of this category can cost. Note: This should be set AFTER MaximumPoints, otherwise an unintended default value may be set for the minimum
+		/// </value>
+		public int MinimumPoints
+		{
+			get { return minPts; }
+			set
+			{
+				minPts = (value >= 0 ? value : 0);
+				CheckMinimumPoints();
+			}
+		}
+		
+		/// <value>
+		/// Gets or sets the maximum number of points that the units of this category can cost. Note: This should be set BEFORE MinimumPoints, otherwise an unintended default value may be set for the minimum
+		/// </value>
+		public int MaximumPoints
+		{
+			get { return maxPts; }
+			set
+			{
+				maxPts = (value >= 0 ? value : WarFoundryCore.INFINITY);
+				CheckMinimumPoints();
+			}
+		}
+		
+		/// <summary>
+		/// Makes sure that the minimum points value isn't more than the maximum points value, hence the warning on the properties
+		/// </summary>
+		private void CheckMinimumPoints()
+		{
+			if (MinimumPoints > MaximumPoints && MaximumPoints!=WarFoundryCore.INFINITY)
+			{
+				MinimumPoints = MaximumPoints;
+				LogNotifier.WarnFormat(GetType(), "Category {0} ({1}) had a minimum points limit greater than its maximum points limit.", Name, ID);
+			}
+		}
+		
+		/// <value>
+		/// Gets or sets the minimum percentage of the total army points value that the units of this category can cost. Note: This should be set AFTER MaximumPercentage, otherwise an unintended default value may be set for the minimum
+		/// </value>
+		public int MinimumPercentage
+		{
+			get { return minPc; }
+			set
+			{
+				minPc = (value >= 0 ? value : 0);
+				CheckMinimumPercentage();
+			}
+		}
+		
+		/// <value>
+		/// Gets or sets the maximum percentage of the total army points value that the units of this category can cost. Note: This should be set BEFORE MinimumPercentage, otherwise an unintended default value may be set for the minimum
+		/// </value>
+		public int MaximumPercentage
+		{
+			get { return maxPc; }
+			set
+			{
+				if (value < 0)
+				{
+					maxPc = 0;
+				}
+				else if (value > 100)
+				{
+					maxPc = 100;
+				}
+				else
+				{
+					maxPc = value;
+				}
+				
+				CheckMinimumPercentage();
+			}
+		}
+		
+		/// <summary>
+		/// Makes sure that the minimum percentage value isn't more than the maximum points value, hence the warning on the properties
+		/// </summary>
+		private void CheckMinimumPercentage()
+		{
+			if (MinimumPercentage > MaximumPercentage)
+			{
+				MinimumPercentage = MaximumPercentage;
+				LogNotifier.WarnFormat(GetType(), "Category {0} ({1}) had a minimum percentage limit greater than its maximum percentage limit.", Name, ID);
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/CompositeEquipmentItem.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,39 @@
+//  This file (CompositeEquipmentItem.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// A special <see cref=" EquipmentItem"/> that is made up of a number of other <code>EquipmentItem</code>s
+	/// </summary>
+	public class CompositeEquipmentItem : EquipmentItem
+	{
+		private List<EquipmentItem> compositeItems;
+			
+		public CompositeEquipmentItem(string id, string name, Race race) : base(id, name, race)
+		{
+			compositeItems = new List<EquipmentItem>();
+		}
+		
+		public void AddItem(EquipmentItem item)
+		{
+			compositeItems.Add(item);
+			Cost+= item.Cost;
+		}
+		
+		public void RemoveItem(EquipmentItem item)
+		{
+			compositeItems.Remove(item);
+			Cost-= item.Cost;
+		}
+		
+		public EquipmentItem[] Items
+		{
+			get { return compositeItems.ToArray(); }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/DuplicateItemException.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,15 @@
+// This file (DuplicateItemException.cs) is a part of IBBoard.WarFoundry.API and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry
+{
+	public class DuplicateItemException
+	{
+		public DuplicateItemException()
+		{
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/EquipmentItem.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,58 @@
+// This file (EquipmentItem.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Xml;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for EquipmentItem.
+	/// </summary>
+	public class EquipmentItem : WarFoundryObject
+	{
+		private double cost;
+		private string description;
+		private Race equipForRace;
+		
+		public EquipmentItem(string id, string name, Race race) : base(id, name)
+		{
+			equipForRace = race;
+			description = "";
+		}
+
+		public double Cost
+		{
+			get { return cost; }
+			set
+			{
+				if (value >= 0)
+				{
+					cost = value;
+				}
+			}
+		}
+		
+		public string Description
+		{
+			get { return description; }
+			set { description = (value == null ? "" : value); }
+		}
+		
+		public Race EquipmentForRace
+		{
+			get { return equipForRace; }
+		}
+		
+		public GameSystem GameSystem
+		{
+			get { return equipForRace.GameSystem; }
+		}
+
+		public bool CanBeUsedWithItem(EquipmentItem item)
+		{
+			return true;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/GameSystem.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,354 @@
+// This file (GameSystem.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009, 2010, 2011 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Factories;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for GameSystem.
+	/// </summary>
+	public class GameSystem : WarFoundryStagedLoadingObject
+	{
+		private static int SYSTEM_DEFAULT_ARMY_SIZE = 1000;
+		private bool warnOnError;
+		private bool allowAllies;
+		private Dictionary<string, Category> categories = new Dictionary<string, Category>();
+		private Dictionary<string, SystemStats> stats = new Dictionary<string, SystemStats>();
+		private string defaultStats;
+		private int defaultArmySize;
+
+		public GameSystem(string systemID, string systemName, IWarFoundryFactory creatingFactory) : base(systemID, systemName, creatingFactory)
+		{
+			stats = new Dictionary<string, SystemStats>();
+			defaultStats = "";
+		}
+
+		public int SystemArmyDefaultSize
+		{
+			get { return defaultArmySize; }
+			set
+			{
+				if (value == 0)
+				{
+					defaultArmySize = SYSTEM_DEFAULT_ARMY_SIZE;
+				}
+				else
+				{
+					defaultArmySize = value;
+				}
+			}
+			}
+
+		public string SystemPtsAbbrevSingle
+		{
+			get; set;
+		}
+		public string SystemPtsAbbrevPlural
+		{
+			get; set;
+		}
+		public string SystemPtsNameSingle
+		{
+			get; set;
+		}
+		public string SystemPtsNamePlural
+		{
+			get; set;
+		}
+		public bool AllowAllies
+		{
+			get { return allowAllies; }
+			set { allowAllies = value; }
+		}
+
+		public void AddCategory(Category cat)
+		{
+			RawCategories[cat.ID] = cat;
+		}
+
+		public Category GetCategory(string id)
+		{
+			EnsureFullyLoaded();
+			Category cat = null;
+			RawCategories.TryGetValue(id, out cat);
+			return cat;
+		}
+
+		public void SetCategory(Category cat)
+		{
+			Category old;
+			RawCategories.TryGetValue(cat.ID, out old);
+
+			if (old == null)
+			{
+				AddCategory(cat);
+			}
+			else
+			{
+				if (!old.Equals(cat))
+				{
+					RawCategories[old.ID] = cat;
+				}
+			}
+		}
+
+		public void RemoveCategory(string id)
+		{
+			RawCategories.Remove(id);
+		}
+
+		public Category[] Categories
+		{
+			get
+			{
+				EnsureFullyLoaded();
+				return DictionaryUtils.ToArray<string, Category>(RawCategories);
+			}
+		}
+
+		protected Dictionary<string, Category> RawCategories
+		{
+			get { return categories; }
+		}
+
+		public bool WarnOnError
+		{
+			get
+			{
+				return warnOnError;
+			}
+			set { warnOnError = value; }
+		}
+
+		public void AddSystemStats(SystemStats sysStats)
+		{
+			stats[sysStats.ID] = sysStats;
+		}
+
+		public SystemStats StandardSystemStats
+		{
+			get
+			{
+				EnsureFullyLoaded();
+				return stats[defaultStats];
+			}
+		}
+
+		public string StandardSystemStatsID
+		{
+			get
+			{
+				EnsureFullyLoaded();
+				return defaultStats;
+			}
+
+			set
+			{
+				if (value != null && value.Trim().Length > 0)
+				{
+					defaultStats = value;
+				}
+			}
+		}
+
+		public SystemStats[] SystemStats
+		{
+			get
+			{
+				EnsureFullyLoaded();
+				SystemStats[] statsArray = new SystemStats[stats.Count];
+				stats.Values.CopyTo(statsArray, 0);
+				return statsArray;
+			}
+		}
+
+		public SystemStats GetSystemStatsForID(string id)
+		{
+			EnsureFullyLoaded();
+			SystemStats statsForID;
+			stats.TryGetValue(id, out statsForID);
+			return statsForID;
+		}
+
+		public void SetSystemStats(SystemStats newStats)
+		{
+			SystemStats old;
+			stats.TryGetValue(newStats.ID, out old);
+
+			if (old == null)
+			{
+				AddSystemStats(newStats);
+			}
+			else
+			{
+				if (!old.Equals(newStats))
+				{
+					stats[old.ID] = newStats;
+				}
+			}
+		}
+
+		public void RemoveSystemStats(string id)
+		{
+			stats.Remove(id);
+		}
+
+		public Race SystemDefaultRace
+		{
+			get { return WarFoundryLoader.GetDefault().GetRace(this, Race.SYSTEM_DEFAULT_RACE_ID); }
+		}
+
+		public bool Matches(GameSystem otherSystem)
+		{
+			if (otherSystem == null)
+			{
+				return false;
+			}
+
+			return this.ID == otherSystem.ID;
+		}
+
+		public override bool Equals(object obj)
+		{
+			if (obj == null)
+			{
+				return false;
+			}
+
+			if (obj.GetType().Equals(this.GetType()))
+			{
+				GameSystem otherSystem = (GameSystem)obj;
+
+				return this.ID == otherSystem.ID && this.Name == otherSystem.Name && ((this.RawCategories == null && otherSystem.RawCategories == null) || this.RawCategories.Equals(otherSystem.RawCategories));
+			}
+			else
+			{
+				return false;
+			}
+		}
+
+		public override int GetHashCode()
+		{
+			return ID.GetHashCode() + Name.GetHashCode() + (RawCategories != null ? RawCategories.GetHashCode() : 0) + warnOnError.GetHashCode();
+		}
+
+		public bool UnitTypeMaxed(UnitType unitType, Army army)
+		{
+			return unitType.MaxNumber != WarFoundryCore.INFINITY && army.GetUnitTypeCount(unitType) >= unitType.MaxNumber;
+		}
+
+		public bool UnitTypeMinned(UnitType unitType, Army army)
+		{
+			return army.GetUnitTypeCount(unitType) <= unitType.MinNumber;
+		}
+
+		public List<EquipmentItem> GetSystemEquipmentList()
+		{
+			List<EquipmentItem> items = new List<EquipmentItem>();
+			Race defaultRace = SystemDefaultRace;
+
+			if (defaultRace != null)
+			{
+				items = defaultRace.GetEquipmentList();
+			}
+
+			return items;
+		}
+
+		public EquipmentItem GetSystemEquipmentItem(string id)
+		{
+			EquipmentItem item = null;
+			Race defaultRace = SystemDefaultRace;
+
+			if (defaultRace != null)
+			{
+				item = defaultRace.GetEquipmentItem(id);
+			}
+
+			return item;
+		}
+
+		public UnitType[] GetSystemUnitTypes(Category cat)
+		{
+			UnitType[] items = new UnitType[0];
+			Race defaultRace = SystemDefaultRace;
+
+			if (defaultRace != null)
+			{
+				items = defaultRace.GetUnitTypes(cat);
+			}
+
+			return items;
+		}
+
+		public UnitType GetSystemUnitType(string id)
+		{
+			UnitType unit = null;
+			Race defaultRace = SystemDefaultRace;
+
+			if (defaultRace != null)
+			{
+				unit = defaultRace.GetUnitType(id);
+			}
+
+			return unit;
+		}
+
+		public List<Ability> GetSystemAbilityList()
+		{
+			List<Ability> items = new List<Ability>();
+			Race defaultRace = SystemDefaultRace;
+
+			if (defaultRace != null)
+			{
+				items = defaultRace.GetAbilityList();
+			}
+
+			return items;
+		}
+
+		public Ability GetSystemAbility(string id)
+		{
+			Ability ability = null;
+			Race defaultRace = SystemDefaultRace;
+
+			if (defaultRace != null)
+			{
+				ability = defaultRace.GetAbility(id);
+			}
+
+			return ability;
+		}
+		
+		public string GetPointsAbbrev(double pointTemp)
+		{
+			string abbrev = (pointTemp == 1 ? GetPreferredString(SystemPtsAbbrevSingle, SystemPtsAbbrevPlural) : GetPreferredString(SystemPtsAbbrevPlural, SystemPtsAbbrevSingle));
+			return abbrev;
+		}
+
+		public string GetPointsName(double pointTemp)
+		{
+			string ptsName = (pointTemp == 1 ? GetPreferredString(SystemPtsNameSingle, SystemPtsNamePlural) : GetPreferredString(SystemPtsNamePlural, SystemPtsNameSingle));
+			return ptsName;
+		}
+		
+		private string GetPreferredString(string str1, string str2)
+		{
+			string preferred = "";
+			
+			if (str1 != null)
+			{
+				preferred = str1;
+			}
+			else if (str2 != null)
+			{
+				preferred = str2;
+			}
+			
+			return preferred;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/ICostedWarFoundryObject.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,24 @@
+// This file (ICostedNamedObject.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// An interface for WarFoundry objects that have a points value (cost)
+	/// </summary>
+	public interface ICostedWarFoundryObject : IWarFoundryObject
+	{
+		/// <summary>
+		/// A getter for the points value of the costed object
+		/// </summary>
+		double Points { get; }
+		
+		/// <summary>
+		/// An event that is fired when the points value of the object changes
+		/// </summary>
+		event DoubleValChangedDelegate PointsValueChanged;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/IWarFoundryNativeSourceObject.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,17 @@
+// This file (IWarFoundryNativeSourceObject.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Interface for native WarFoundry objects that are the main object in a file and need to reference the file at a later date.
+	/// </summary>
+	public interface IWarFoundryNativeSourceObject : IWarFoundryObject
+	{
+		ZipFile SourceFile { get; set; }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/IWarFoundryObject.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,22 @@
+// This file (IWarFoundryObject.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.WarFoundry.API.Factories;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	public interface IWarFoundryObject
+	{
+		/// <value>
+		/// The unique identifier for the object
+		/// </value>
+		string ID { get; set; }
+
+		/// <value>
+		/// The display name of the WarFoundry object
+		/// </value>
+		string Name { get; set;	}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/IWarFoundryStagedLoadObject.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,44 @@
+// This file (IWarFoundryStagedLoadObject.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using ICSharpCode.SharpZipLib.Zip;
+using IBBoard.WarFoundry.API.Factories;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	public interface IWarFoundryStagedLoadObject : IWarFoundryObject
+	{	
+		/// <summary>
+		/// Checks whether the object has been fully loaded or whether only the first stage of loading has been performed.
+		/// If the object is not fully loaded then the method must finish loading the object.
+		/// </summary>
+		void EnsureFullyLoaded();
+		
+		/// <value>
+		/// Gets the <code>AbstractNativeWarFoundryFactory</code> that created the object.
+		/// </value>
+		IWarFoundryFactory Factory	{ get; }
+		
+		/// <value>
+		/// Returns <code>true</code> if the object has been fully loaded with all data, else returns <code>false</code>
+		/// </value>
+		bool IsFullyLoaded { get; }
+		
+		/// <value>
+		/// Returns <code>true</code> if the object is in the process of being fully loaded with all data, else returns <code>false</code>
+		/// </value>
+		bool IsLoading { get; }
+		
+		/// <summary>
+		/// Marks the object as fully loaded so that no more load checking is required.
+		/// </summary>
+		void SetAsFullyLoaded();
+		
+		/// <summary>
+		/// Markes the object as being in the process of being fully loaded.
+		/// </summary>
+		void SetAsLoading();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/InvalidContainershipException.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,46 @@
+//  This file (InvalidContainershipException.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Lang;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// A custom exception for when a unit was added as a sub-unit of another unit, but was not of a <see cref=" UnitType"/> that can be contained
+	/// by that unit.
+	/// </summary>
+	public class InvalidContainershipException : Exception
+	{
+		private Unit containing;
+		private Unit contained;
+		
+		public InvalidContainershipException(Unit containingUnit, Unit containedUnit) : base(CreateMessageString(containingUnit, containedUnit))
+		{
+			containing = containingUnit;
+			contained = containedUnit;
+		}
+		
+		private static string CreateMessageString(Unit containingUnit, Unit containedUnit)
+		{
+			return String.Format("{0} cannot contain {1} because units of type {2} cannot contain units of type {3}", containingUnit.Name, containedUnit.Name, containingUnit.UnitType.Name, containedUnit.UnitType.Name);
+		}
+		
+		/// <value>
+		/// The <see cref=" Unit"/> that the contained unit was added to
+		/// </value>
+		public Unit ContainingUnit
+		{
+			get { return containing; }
+		}
+		
+		/// <value>
+		/// The <see cref=" Unit"/> that was added as a contained unit, but which was not of an allowed type
+		/// </value>
+		public Unit ContainedUnit
+		{
+			get { return contained; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Race.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,308 @@
+// This file (Race.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using IBBoard.IO;
+using IBBoard.WarFoundry.API.Factories;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	public class Race : WarFoundryStagedLoadingObject
+	{
+		public static string SYSTEM_DEFAULT_RACE_ID = "GameDefault";
+
+		private string subID;
+		private GameSystem system;
+		private string defaultArmyName = "";
+		private Dictionary<Category, Dictionary<string, UnitType>> unitTypesByCat;
+		private Dictionary<string, UnitType> unitTypes = new Dictionary<string,UnitType>();
+		private Dictionary<string, EquipmentItem> equipment = new Dictionary<string,EquipmentItem>();
+		private Dictionary<string, Ability> abilities = new Dictionary<string,Ability>();
+		private Dictionary<string, Category> categories = new Dictionary<string,Category>();
+		private Dictionary<string, UnitMemberType> memberTypes = new Dictionary<string, UnitMemberType>();
+
+		public Race(string raceID, string raceName, GameSystem gameSystem, IWarFoundryFactory creatingFactory) : this(raceID, "", raceName, gameSystem, creatingFactory)
+		{
+		}
+
+		public Race(string raceID, string raceSubID, string raceName, GameSystem gameSystem, IWarFoundryFactory creatingFactory) : base(raceID + (raceSubID != "" ? "_" + raceSubID : ""), raceName, creatingFactory)
+		{
+			subID = (raceSubID == null ? "" : raceSubID);
+			system = gameSystem;
+		}
+
+		public string SubID
+		{
+			get { return subID; }
+			set { subID = (value == null ? "" : value.Trim()); }
+		}
+
+		public GameSystem GameSystem
+		{
+			get { return system; }
+			set
+			{
+				if (value == null)
+				{
+					throw new ArgumentException("Game system for a race cannot be null");
+				}
+
+				system = value;
+			}
+		}
+
+        public string ArmyDefaultName
+        {
+            get { return defaultArmyName; }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentException("No default army name");
+                }
+
+                defaultArmyName = value;
+            }
+        }
+
+		public void AddCategory(Category cat)
+		{
+			categories[cat.ID] = cat;
+		}
+
+		/// <summary>
+		/// Gets a category from its ID. Attempts to get the category from the race's overrides, or else it falls back to getting the Game System's category with that ID.
+		/// </summary>
+		/// <param name="id">
+		/// The ID of the category to get
+		/// </param>
+		/// <returns>
+		/// The <code>Category</code> with the specified ID, or null if one doesn't exist.
+		/// </returns>
+		public Category GetCategory(string id)
+		{
+			EnsureFullyLoaded();
+			Category cat = null;
+			categories.TryGetValue(id, out cat);
+
+			if (cat == null)
+			{
+				cat = GameSystem.GetCategory(id);
+			}
+
+			return cat;
+		}
+
+		public Category[] Categories
+		{
+			get
+			{
+				EnsureFullyLoaded();
+				Category[] cats;
+
+				if (!HasCategoryOverrides())
+				{
+					cats = GameSystem.Categories;
+				}
+				else
+				{
+					cats = DictionaryUtils.ToArray<string, Category>(categories);
+				}
+
+				return cats;
+			}
+		}
+
+		public bool HasCategoryOverrides()
+		{
+			EnsureFullyLoaded();
+			return categories.Count > 0;
+		}
+
+		public void AddEquipmentItem(EquipmentItem item)
+		{
+			//TODO: Throw DuplicateItemException
+			equipment.Add(item.ID, item);
+		}
+
+		public EquipmentItem GetEquipmentItem(string id)
+		{
+			EnsureFullyLoaded();
+			return DictionaryUtils.GetValue(equipment, id);
+		}
+
+		public List<EquipmentItem> GetEquipmentList()
+		{
+			EnsureFullyLoaded();
+			List<EquipmentItem> items = new List<EquipmentItem>();
+
+			foreach (EquipmentItem item in equipment.Values)
+			{
+				items.Add(item);
+			}
+
+			return items;
+		}
+
+		public void AddUnitType(UnitType type)
+		{
+			CacheUnitType(type);
+			unitTypes.Add(type.ID, type);
+		}
+
+		public UnitType[] GetUnitTypes(Category cat)
+		{
+			EnsureFullyLoaded();
+			BuildUnitTypesByCategoryCache();
+			Dictionary<string, UnitType> unitTypesDictionary;
+			unitTypesByCat.TryGetValue(cat, out unitTypesDictionary);
+			UnitType[] unitTypesArray;
+
+			if (unitTypesDictionary == null)
+			{
+				unitTypesArray = new UnitType[0];
+			}
+			else
+			{
+				unitTypesArray = DictionaryUtils.ToArray<string, UnitType>(unitTypesDictionary);
+			}
+
+			return unitTypesArray;
+		}
+
+		private void CacheUnitType(UnitType unit)
+		{
+			BuildUnitTypesByCategoryCache();
+
+			foreach (Category cat in unit.Categories)
+			{
+				Dictionary<string, UnitType> catUnitTypes = DictionaryUtils.GetValue(unitTypesByCat, cat);
+
+				if (catUnitTypes == null)
+				{
+					throw new InvalidFileException(String.Format("Unit type {0} with name {1} is a unit of an undefined category ({2})", unit.ID, unit.Name, cat.ID));
+				}
+
+				catUnitTypes.Add(unit.ID, unit);
+			}
+		}
+
+		private void BuildUnitTypesByCategoryCache()
+		{
+			if (unitTypesByCat == null)
+			{
+				DoBuildUnitTypesByCategoryCache();
+			}
+		}
+
+		private void DoBuildUnitTypesByCategoryCache()
+		{
+			unitTypesByCat = new Dictionary<Category,Dictionary<string,UnitType>>();
+
+			foreach (Category category in Categories)
+			{
+				unitTypesByCat.Add(category, new Dictionary<string, UnitType>());
+			}
+
+			foreach (UnitType unit in unitTypes.Values)
+			{
+				CacheUnitType(unit);
+			}
+		}
+
+		public UnitType GetUnitType(string id)
+		{
+			EnsureFullyLoaded();
+			return DictionaryUtils.GetValue(unitTypes, id);
+		}
+
+		public List<Ability> GetAbilityList()
+		{
+			EnsureFullyLoaded();
+			List<Ability> items = new List<Ability>();
+			items.AddRange(abilities.Values);
+			return items;
+		}
+
+		public void AddAbility(Ability newAbility)
+		{
+			//TODO: Throw DuplicateItemException
+			abilities.Add(newAbility.ID, newAbility);
+		}
+
+		public Ability GetAbility(string id)
+		{
+			EnsureFullyLoaded();
+			return DictionaryUtils.GetValue(abilities, id);
+		}
+
+		protected virtual Dictionary<string, UnitType> RaceUnitTypes
+		{
+			get { return RaceRawUnitTypes; }
+			set	{ RaceRawUnitTypes = value; }
+		}
+
+		protected virtual Dictionary<string, EquipmentItem> RaceEquipment
+		{
+			get { return RaceRawEquipment; }
+			set { RaceRawEquipment = value; }
+		}
+
+		protected virtual Dictionary<string, Ability> RaceAbilities
+		{
+			get { return RaceRawAbilities; }
+			set { RaceRawAbilities = value; }
+		}
+
+		protected Dictionary<string, UnitType> RaceRawUnitTypes
+		{
+			get { return unitTypes; }
+			set	{ unitTypes = value; }
+		}
+
+		protected Dictionary<string, EquipmentItem> RaceRawEquipment
+		{
+			get { return equipment; }
+			set { equipment = value; }
+		}
+
+		protected Dictionary<string, Ability> RaceRawAbilities
+		{
+			get { return abilities; }
+			set { abilities = value; }
+		}
+
+		public void AddUnitMemberType(UnitMemberType memberType)
+		{
+			memberTypes[memberType.ID] = memberType;
+		}
+
+		/// <summary>
+		/// Gets a unit member type by its ID.
+		/// </summary>
+		/// <param name="id">
+		/// The ID of the unit member type to get
+		/// </param>
+		/// <returns>
+		/// The <code>UnitMemberType</code> with the specified ID, or null if one doesn't exist.
+		/// </returns>
+		public UnitMemberType GetUnitMemberType(string id)
+		{
+			EnsureFullyLoaded();
+			return DictionaryUtils.GetValue(memberTypes, id);
+		}
+
+		public UnitMemberType[] UnitMemberTypes
+		{
+			get
+			{
+				EnsureFullyLoaded();
+				return DictionaryUtils.ToArray(memberTypes);
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Requirement/RequiresAtLeastNUnitsRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,95 @@
+// This file (UnitRequiresAtLeastNUnitsRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2011 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+using System;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Objects.Requirement
+{
+	/// <summary>
+	/// A requirement where a WarFoundryObject requires at least N units of one or more unit types before any number of that object can be taken in an army.
+	/// </summary>
+	public class RequiresAtLeastNUnitsRequirement
+	{
+		private List<UnitCountRequirementData> requiredTypes;
+
+		public RequiresAtLeastNUnitsRequirement(params UnitType[] requiredUnitTypes)
+		{
+			requiredTypes = new List<UnitCountRequirementData>();
+
+			foreach (UnitType unitType in requiredUnitTypes)
+			{
+				AddUnitTypeRequirement(unitType);
+			}
+		}
+
+		/// <summary>
+		/// Checks whether the supplied WarFoundryObject can be added to the supplied army.
+		/// </summary>
+		/// <returns>
+		/// <c>true</c> if the object can be added, else <c>false</c>
+		/// </returns>
+		/// <param name='wfObject'>
+		/// The object that we want to add. This may be involved in the check, or it may not affect the evaluation of the requirement
+		/// </param>
+		/// <param name='toArmy'>
+		/// The army to add the object to.
+		/// </param>
+		public virtual bool AllowsAdding(WarFoundryObject wfObject, Army toArmy)
+		{
+			return this.ValidatesArmy(toArmy);
+		}
+
+		/// <summary>
+		/// Adds a requirement for there to be at least minCount of a given UnitType
+		/// </summary>
+		/// <param name='unitType'>
+		/// The unit type to require.
+		/// </param>
+		/// <param name='minCount'>
+		/// The minimum number of that type that must exist.
+		/// </param>
+		public void AddUnitTypeRequirement(UnitType unitType, int minCount)
+		{
+			requiredTypes.Add(new UnitCountRequirementData(unitType, minCount));
+		}
+
+		/// <summary>
+		/// Adds a requirement for there to be one or more of a given UnitType
+		/// </summary>
+		/// <param name='unitType'>
+		/// The unit type to require.
+		/// </param>
+		public void AddUnitTypeRequirement (UnitType unitType)
+		{
+			AddUnitTypeRequirement(unitType, 1);
+		}
+
+		/// <summary>
+		/// Checks whether the supplied army is currently valid according to this requirement.
+		/// </summary>
+		/// <returns>
+		/// <c>true</c> if the army is valid, else <c>false</c>
+		/// </returns>
+		/// <param name='toValidate'>
+		/// The army to validate
+		/// </param>
+		public bool ValidatesArmy(Army toValidate)
+		{
+			bool canAdd = true;
+
+			foreach (UnitCountRequirementData requirement in requiredTypes)
+			{
+				if (toValidate.GetUnitTypeCount(requirement.UnitType) < requirement.Count)
+				{
+					canAdd = false;
+					break;
+				}
+			}
+
+			return canAdd;
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Requirement/RequiresNoMoreThanNOfUnitTypeRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,70 @@
+// This file (RequiresNoMoreThanNOfUnitTypeRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2011 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+using System;
+using IBBoard.WarFoundry.API.Objects;
+using System.Collections.Generic;
+
+namespace IBBoard.WarFoundry.API.Objects.Requirement
+{
+	/// <summary>
+	/// A requirement where a WarFoundryObject cannot be taken in an army if more than N of a UnitType will be in the army.
+	/// </summary>
+	public class RequiresNoMoreThanNOfUnitTypeRequirement
+	{
+		private List<UnitCountRequirementData> limitedTypes;
+
+		public RequiresNoMoreThanNOfUnitTypeRequirement(params UnitType[] limitedUnitTypes)
+		{
+			limitedTypes = new List<UnitCountRequirementData>();
+
+			foreach (UnitType unitType in limitedUnitTypes)
+			{
+				AddUnitTypeRequirement(unitType, 0);
+			}
+		}
+
+		/// <summary>
+		/// Checks whether the supplied WarFoundryObject can be added to the supplied army.
+		/// </summary>
+		/// <returns>
+		/// <c>true</c> if the object can be added, else <c>false</c>
+		/// </returns>
+		/// <param name='wfObject'>
+		/// The object that we want to add. This may be involved in the check, or it may not affect the evaluation of the requirement
+		/// </param>
+		/// <param name='toArmy'>
+		/// The army to add the object to.
+		/// </param>
+		public bool AllowsAdding(WarFoundryObject wfObject, Army toArmy)
+		{
+			bool canAdd = true;
+
+			foreach (UnitCountRequirementData limit in limitedTypes)
+			{
+				if (toArmy.GetUnitTypeCount(limit.UnitType) > limit.Count)
+				{
+					canAdd = false;
+					break;
+				}
+			}
+
+			return canAdd;
+		}
+
+		/// <summary>
+		/// Adds a requirement for there to be at least minCount of a given UnitType
+		/// </summary>
+		/// <param name='unitType'>
+		/// The unit type to require.
+		/// </param>
+		/// <param name='minCount'>
+		/// The minimum number of that type that must exist.
+		/// </param>
+		public void AddUnitTypeRequirement(UnitType unitType, int minCount)
+		{
+			limitedTypes.Add(new UnitCountRequirementData(unitType, minCount));
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Requirement/UnitCountRequirementData.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,31 @@
+// This file (UnitCountRequirementData.cs) is a part of the IBBoard.WarFoundry.API.Tests project and is copyright 2011 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Objects.Requirement
+{
+	public class UnitCountRequirementData
+	{
+		private UnitType unitType;
+		private int count;
+
+		public UnitCountRequirementData(UnitType unitType, int count)
+		{
+			this.unitType = unitType;
+			this.count = count;
+		}
+
+		public UnitType UnitType
+		{
+			get { return unitType; }
+		}
+
+		public int Count
+		{
+			get { return count; }
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Requirement/UnitRequiresAtLeastNUnitsRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,45 @@
+// This file (UnitRequiresAtLeastNUnitsRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2011 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+using System;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Objects.Requirement
+{
+	/// <summary>
+	/// A requirement where a UnitType requires at least N units of one or more unit types before any number of that object can be taken in an army.
+	/// </summary>
+	public class UnitRequiresAtLeastNUnitsRequirement : RequiresAtLeastNUnitsRequirement
+	{
+		private UnitType requirementOnType;
+
+		public UnitRequiresAtLeastNUnitsRequirement(UnitType requirementOn) : base()
+		{
+			requirementOnType = requirementOn;
+		}
+
+		/// <summary>
+		/// Checks whether the supplied WarFoundryObject can be added to the supplied army.
+		/// </summary>
+		/// <returns>
+		/// <c>true</c> if the object can be added, else <c>false</c>
+		/// </returns>
+		/// <param name='wfObject'>
+		/// The object that we want to add. This may be involved in the check, or it may not affect the evaluation of the requirement
+		/// </param>
+		/// <param name='toArmy'>
+		/// The army to add the object to.
+		/// </param>
+		public override bool AllowsAdding(WarFoundryObject wfObject, Army toArmy)
+		{
+			return IsApplicable(wfObject, toArmy) ? base.ValidatesArmy(toArmy) : true;
+		}
+
+		public bool IsApplicable (WarFoundryObject wfObject, Army toArmy)
+		{
+			return toArmy.GetUnitTypeCount(requirementOnType) > 0 || requirementOnType.Equals(wfObject) || (wfObject is Unit && requirementOnType.Equals(((Unit)wfObject).UnitType));
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Stat.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,40 @@
+// This file (Stat.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for Stat.
+	/// </summary>
+	public class Stat
+	{
+		private StatSlot parentStatSlot;
+		private string statString;
+
+		public Stat(StatSlot parentSlot, string statValue)
+		{
+				parentStatSlot = parentSlot;
+				statString = statValue;
+		}
+
+		public StatSlot ParentSlot
+		{
+			get { return parentStatSlot; }
+			set { parentStatSlot = value; }
+		}
+
+		public string ParentSlotName
+		{
+			get { return ParentSlot.Name; }
+		}
+
+		public string SlotValueString
+		{
+			get { return statString; }
+			set { statString = (value == null ? "" : value); }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/StatSlot.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,37 @@
+// This file (StatSlot.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Text.RegularExpressions;
+using System.Xml;
+using IBBoard;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for StatSlot.
+	/// </summary>
+	public class StatSlot
+	{
+		private string name;
+		private SystemStats sysStats;
+
+		public StatSlot(String statName)
+		{
+			name = statName;
+		}
+
+		public string Name
+		{
+			get { return name; }
+			set { value = name; }
+		}
+		
+		public SystemStats SystemStats
+		{
+			get { return sysStats; }
+			set { sysStats = value; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Stats.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,104 @@
+// This file (Stats.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Stats defines the statistic/attribute values for an entity (for example a unit or any of their equipment that has a stat line) paired against a <see cref=" SystemStats"/> stat line definition.
+	/// </summary>
+	public class Stats
+	{
+		private List<Stat> stats;
+		private SystemStats sysStats;
+		
+		public Stats(SystemStats systemStats)
+		{
+			sysStats = systemStats;
+			int statCount = sysStats.SlotCount;
+			stats = new List<Stat>(statCount);
+
+			foreach (StatSlot slot in sysStats.StatSlots)
+			{
+				stats.Add(new Stat(slot, ""));
+			}
+		}
+		
+		public Stat[] StatsArray
+		{
+			get { return stats.ToArray(); }
+		}
+
+		public void SetStatValue(string statName, string statValue)
+		{
+			StatSlot slot = sysStats[statName.ToLower()];
+
+			if (slot!=null)
+			{
+				int pos = sysStats.GetStatSlotPosition(slot);
+
+				if (pos > -1)
+				{
+					stats[pos] = new Stat(slot, statValue);
+				}
+			}				
+		}
+		
+		public Stat this[string id]
+		{
+			get
+			{
+				StatSlot slot = sysStats[id.ToLower()];
+				int pos = sysStats.GetStatSlotPosition(slot);
+				Stat stat = null;
+				
+				try
+				{
+					stat = this[pos];
+				}
+				catch (ArgumentException ex)
+				{
+					throw new ArgumentException(String.Format("Invalid statistic ID {0} for stats based on system stats set {1}", new object[]{id, sysStats.ID}), ex);
+				}
+				
+				return stat;
+			}
+		}
+		
+		public Stat this[int pos]
+		{
+			get
+			{
+				if (pos < stats.Count && pos >= 0)
+				{
+					return stats[pos];
+				}
+				else
+				{
+					throw new ArgumentException(String.Format("Invalid statistic position {0} for stats based on system stats set {1}", new object[]{pos, sysStats.ID})); 
+				}
+			}
+		}
+
+		public string GetStatValue(string id)
+		{
+			return this[id.ToLower()].SlotValueString;
+		}
+		
+		public int StatCount
+		{
+			get { return stats.Count; }
+		}
+		
+		public string StatsID
+		{
+			get
+			{
+				return sysStats.ID;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/SystemStats.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,71 @@
+// This file (SystemStats.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// SystemStats defines the available statistics/attributes that entity types can use (either a unit or an equipment item that has a stats line). Statistic/attribute values will be defined by a <see cref="Stats"/> object.
+	/// </summary>
+	public class SystemStats
+	{
+		private Dictionary<string, StatSlot> statsByName;
+		private List<StatSlot> stats;
+		private string id;
+
+		public SystemStats(string statsID)
+		{
+			id = statsID;
+			statsByName = new Dictionary<string, StatSlot>();
+			stats = new List<StatSlot>();
+		}
+
+		public void AddStatSlot(string slotName)
+		{
+			StatSlot slot = new StatSlot(slotName);
+			slot.SystemStats = this;
+			statsByName[slot.Name.ToLower()] = slot;
+			stats.Add(slot);
+		}		
+
+		public StatSlot[] StatSlots
+		{
+			get
+			{
+				return stats.ToArray();
+			}
+		}
+		
+		public StatSlot this[string statName]
+		{
+			get 
+			{
+				return DictionaryUtils.GetValue(statsByName, statName.ToLower());
+			}
+		}
+
+		public int GetStatSlotPosition(StatSlot slot)
+		{
+			return stats.IndexOf(slot);
+		}
+		
+		public void RemoveStatSlot(string name)
+		{
+			statsByName.Remove(name);
+			stats.Remove(this[name]);
+		}
+		
+        public int SlotCount
+        {
+            get { return stats.Count; }
+        }
+		
+		public string ID
+		{
+			get { return id; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/Unit.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,544 @@
+// This file (Unit.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 2007, 2008, IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+using IBBoard.Lang;
+using IBBoard.Limits;
+using IBBoard.WarFoundry.API.Util;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for UnitInstance.
+	/// </summary>
+	public class Unit : WarFoundryObject, ICostedWarFoundryObject
+	{
+		private UnitType type;
+		private int size;
+		private Unit parentUnit;
+		private double points;
+		private ArmyCategory cat;
+		private Dictionary<UnitEquipmentItem, AbstractUnitEquipmentItemSelection> equipment = new Dictionary<UnitEquipmentItem, AbstractUnitEquipmentItemSelection>();
+		private Dictionary<string, List<AbstractUnitEquipmentItemSelection>> equipmentSlots = new Dictionary<string, List<AbstractUnitEquipmentItemSelection>>();
+		private List<Unit> containedUnits = new List<Unit>();
+
+		public event DoubleValChangedDelegate PointsValueChanged;
+		public event IntValChangedDelegate UnitSizeChanged;
+		public event DoubleValChangedDelegate UnitEquipmentAmountChanged;
+
+		public Unit(UnitType unitType, ArmyCategory parentArmyCat) : this(unitType, unitType.MinSize, parentArmyCat)
+		{
+			//Do nothing extra
+		}
+
+		public Unit(UnitType unitType, int startSize, ArmyCategory parentArmyCat) : this("", "", startSize, unitType, parentArmyCat)
+		{
+			SetInitialEquipment();
+			UnitSizeChanged += new IntValChangedDelegate(RefreshUnitEquipmentAmounts);
+		}	
+
+		public Unit(string id, string name, int startSize, UnitType unitType, ArmyCategory parentArmyCat) : base(id, name)
+		{
+			Category = parentArmyCat;
+			type = unitType;
+			Size = startSize;
+			CalcCost();
+			UnitEquipmentAmountChanged += new DoubleValChangedDelegate(UnitEquipmentAmountChangedHandler);
+			UnitSizeChanged += new IntValChangedDelegate(UnitSizeChangedHandler);
+			Translation.TranslationChanged += HandleTranslationChanged;
+		}
+
+		private void UnitEquipmentAmountChangedHandler(WarFoundryObject obj, double oldVal, double newVal)
+		{
+			CalcCost();
+		}
+
+		private void UnitSizeChangedHandler(WarFoundryObject obj, int oldVal, int newVal)
+		{
+			CalcCost();
+			
+			if (HasDefaultName())
+			{
+				OnNameChanged("", Name);
+			}
+		}
+
+		protected override string DefaultName()
+		{
+			if (type != null)
+			{
+				if (size == 1)
+				{
+					return type.Name;
+				}
+				else
+				{
+					return String.Format(Translation.GetTranslation("defaultUnitName"), size, type.Name);
+				}
+			}
+			else
+			{
+				return "Unknown Unit";
+			}
+		}
+
+		private void HandleTranslationChanged()
+		{
+			if (type != null && HasDefaultName() && size != 1)
+			{
+				OnNameChanged(null, DefaultName());
+			}
+		}
+
+		private void SetInitialEquipment()
+		{
+			foreach (UnitEquipmentItem unitEquip in UnitType.GetEquipmentItems())
+			{
+				if (unitEquip.IsRequired)
+				{
+					if (CanEquipWithItem(unitEquip))
+					{
+						ILimit minLimit = unitEquip.MinLimit;
+						
+						if (minLimit is IPercentageLimit)
+						{
+							SetEquipmentRatio(unitEquip, UnitEquipmentUtil.GetMinEquipmentPercentage(this, unitEquip));
+						}
+						else
+						{
+							SetEquipmentAmount(unitEquip, UnitEquipmentUtil.GetMinEquipmentCount(this, unitEquip));
+						}
+					}
+				}
+			}
+		}
+
+		private void CalcCost()
+		{
+			double oldpoints = points;
+			points = type.CostPerTrooper * AdditionalTroopers + type.BaseUnitCost;
+
+			foreach (AbstractUnitEquipmentItemSelection equipSelection in equipment.Values)
+			{
+				points += equipSelection.TotalCost;
+			}
+
+			if (oldpoints != points)
+			{
+				OnPointsValueChanged(oldpoints, points);
+			}
+		}
+
+		public int AdditionalTroopers
+		{
+			get { return Math.Max(Size - type.BaseSize, 0); }
+		}
+
+		public int Size
+		{
+			get { return size; }
+			set
+			{
+				if (value != size)
+				{
+					int oldValue = size;
+					size = (value > 0 ? value : 1);
+					OnUnitSizeChanged(oldValue, size);
+				}
+			}
+		}
+
+		public UnitType UnitType
+		{
+			get { return type; }
+		}
+
+		public Army Army
+		{
+			get { return (Category == null ? null : Category.ParentArmy); }
+		}
+
+		public Race Race
+		{
+			get { return UnitType.Race; }
+		}
+
+		public ArmyCategory Category
+		{
+			get
+			{
+				return cat;
+			}
+			set { cat = value; }
+		}
+
+		public double Points
+		{
+			get
+			{
+				if (points == 0)
+				{
+					CalcCost();
+				}
+
+				return points;
+			}
+		}
+
+		public Unit[] ContainedUnits
+		{
+			get { return containedUnits.ToArray(); }
+		}
+
+		public void AddContainedUnit(Unit unit)
+		{
+			if (UnitType.CanContainUnit(unit))
+			{
+				if (!containedUnits.Contains(unit))
+				{
+					containedUnits.Add(unit);
+				}
+				
+				unit.ParentUnit = this;
+			}
+			else
+			{
+				throw new InvalidContainershipException(this, unit);
+			}
+		}
+
+		public void RemoveContainedUnit(Unit unit)
+		{
+			containedUnits.Remove(unit);
+		}
+
+		public Unit ParentUnit
+		{
+			get { return parentUnit; }
+			set
+			{
+				if (!(parentUnit == value || (parentUnit != null && parentUnit.Equals(value))))
+				{
+					parentUnit = value;
+					
+					if (value != null)
+					{
+						value.AddContainedUnit(this);
+					}
+				}
+			}
+		}
+
+		public UnitEquipmentItem[] GetEquipment()
+		{
+			return DictionaryUtils.ToKeyArray(equipment);
+		}
+
+		public EquipmentItem[] GetRequiredEquipment()
+		{
+			List<EquipmentItem> list = new List<EquipmentItem>();
+
+			foreach (UnitEquipmentItem item in GetEquipment())
+			{
+				if (item.IsRequired)
+				{
+					list.Add(item.EquipmentItem);
+				}
+			}
+
+			return list.ToArray();
+		}
+
+		internal AbstractUnitEquipmentItemSelection GetEquipmentSelection(UnitEquipmentItem item)
+		{
+			return DictionaryUtils.GetValue(equipment, item);
+		}
+
+		public void SetEquipmentAmount(UnitEquipmentItem equip, int amount)
+		{
+			if (amount < 1 && amount != WarFoundryCore.INFINITY)
+			{
+				amount = 0;
+			}
+			
+			if (amount == 0)
+			{
+				RemoveEquipmentItem(equip);
+			}
+			else
+			{
+				AbstractUnitEquipmentItemSelection currSelection = DictionaryUtils.GetValue(equipment, equip);
+				double oldAmount = (currSelection == null ? 0 : currSelection.AmountTaken);
+	
+				if (amount != oldAmount)
+				{
+					if (oldAmount == 0)
+					{
+						AddEquipmentAmount(equip, amount);
+					}
+					else if (currSelection is UnitEquipmentNumericSelection)
+					{
+						//A UnitEquipmentItem shouldn't change its IsRatio value, so assume we already have the right sub-type
+						currSelection.AmountTaken = amount;
+					}
+					else
+					{
+						RemoveEquipmentItem(equip);
+						AddEquipmentAmount(equip, amount);
+					}
+	
+					OnUnitEquipmentAmountChanged(equip, oldAmount, amount);
+				}
+			}
+		}
+
+		private void AddEquipmentAmount(UnitEquipmentItem equip, int amount)
+		{
+			AbstractUnitEquipmentItemSelection newItem = new UnitEquipmentNumericSelection(this, equip, amount);			
+			equipment[equip] = newItem;
+			List<AbstractUnitEquipmentItemSelection> selections = DictionaryUtils.GetValue(equipmentSlots, equip.SlotName);
+			
+			if (selections == null)
+			{
+				selections = new List<AbstractUnitEquipmentItemSelection>();
+				equipmentSlots[equip.SlotName] = selections;
+			}
+			
+			selections.Add(newItem);
+		}
+
+		public void SetEquipmentRatio(UnitEquipmentItem equip, double ratio)
+		{
+			if (!equip.IsRatioLimit)
+			{
+				throw new InvalidOperationException("Equipment with ID " + equip.ID + " for unit of type " + UnitType.ID + " has an absolute limit, not a ratio limit");
+			}
+			
+			if (ratio > 100)
+			{
+				ratio = 100;
+			}
+			else if (ratio < 0)
+			{
+				ratio = 0;
+			}
+			
+			if (ratio == 0)
+			{
+				RemoveEquipmentItem(equip);
+			}
+			else
+			{
+				AbstractUnitEquipmentItemSelection currSelection = DictionaryUtils.GetValue(equipment, equip);
+				double oldRatio = (currSelection == null ? 0 : currSelection.AmountTaken);
+	
+				if (ratio != oldRatio)
+				{
+					if (oldRatio == 0)
+					{
+						AddEquipmentRatio(equip, ratio);
+					}
+					else if (currSelection is UnitEquipmentRatioSelection)
+					{
+						currSelection.AmountTaken = ratio;
+					}
+					else
+					{
+						RemoveEquipmentItem(equip);
+						AddEquipmentRatio(equip, ratio);
+					}
+	
+					OnUnitEquipmentAmountChanged(equip, oldRatio, ratio);
+				}
+			}
+		}
+
+		private void AddEquipmentRatio(UnitEquipmentItem equip, double ratio)
+		{
+			UnitEquipmentRatioSelection newItem = new UnitEquipmentRatioSelection(this, equip, ratio);
+			equipment[equip] = newItem;
+			List<AbstractUnitEquipmentItemSelection> selections = DictionaryUtils.GetValue(equipmentSlots, equip.SlotName);
+			
+			if (selections == null)
+			{
+				selections = new List<AbstractUnitEquipmentItemSelection>();
+				equipmentSlots[equip.SlotName] = selections;
+			}
+			
+			selections.Add(newItem);
+		}
+
+		private void RemoveEquipmentItem(UnitEquipmentItem equip)
+		{
+			double oldAmount = UnitEquipmentUtil.GetEquipmentAmount(this, equip);
+		
+			if (oldAmount != 0)
+			{
+				AbstractUnitEquipmentItemSelection selection = DictionaryUtils.GetValue(equipment, equip);
+				equipment.Remove(equip);
+				List<AbstractUnitEquipmentItemSelection> slotSelections = DictionaryUtils.GetValue(equipmentSlots, equip.SlotName);
+				slotSelections.Remove(selection);
+				OnUnitEquipmentAmountChanged(equip, oldAmount, 0);
+			}
+		}
+
+		public bool CanEquipWithItem(UnitEquipmentItem item)
+		{
+			string[] mutexes = item.MutexGroups;
+			bool canEquip = false;
+
+			if (mutexes.Length == 0)
+			{
+				canEquip = true;
+			}
+			else
+			{
+				canEquip = UnitEquipmentUtil.GetBlockingEquipmentItems(this, item).Count == 0;
+			}
+
+			return canEquip;
+		}
+
+		public bool CanEquipWithItem(string equipID)
+		{
+			return CanEquipWithItem(UnitType.GetEquipmentItem(equipID));
+		}
+
+		private void OnPointsValueChanged(double oldValue, double newValue)
+		{
+			if (PointsValueChanged != null)
+			{
+				PointsValueChanged(this, oldValue, newValue);
+			}
+		}
+
+		private void OnUnitSizeChanged(int oldValue, int newValue)
+		{
+			if (UnitSizeChanged != null)
+			{
+				UnitSizeChanged(this, oldValue, newValue);
+			}
+		}
+
+		private void OnUnitEquipmentAmountChanged(UnitEquipmentItem equip, double oldValue, double newValue)
+		{
+			if (UnitEquipmentAmountChanged != null)
+			{
+				UnitEquipmentAmountChanged(equip, oldValue, newValue);
+			}
+		}
+
+		public Stat[][] UnitStatsArrays
+		{
+			get { return UnitType.UnitStatsArrays; }
+		}
+
+		public Stat[][] UnitStatsArraysWithName
+		{
+			get { return UnitType.UnitStatsArraysWithName; }
+		}
+
+		public string[] UnitStatsArrayIDs
+		{
+			get
+			{
+				return UnitType.UnitStatsArrayIDs;
+			}
+		}
+
+		public string GetStatValue(string statName)
+		{
+			return UnitType.GetStatValue(statName);
+		}
+
+		public int GetEquipmentAmountInSlot(string slotName)
+		{
+			int amount = 0;
+
+			List<AbstractUnitEquipmentItemSelection> selections = GetEquipmentSlotSelections(slotName);
+			
+			if (selections != null)
+			{
+				amount = GetSelectionTotal(selections);
+			}			
+			
+			return amount;
+		}
+
+		internal List<AbstractUnitEquipmentItemSelection> GetEquipmentSlotSelections(string slotName)
+		{
+			return DictionaryUtils.GetValue(equipmentSlots, slotName);
+		}
+
+		/// <summary>
+		/// Gets the total amount of items taken for the item's slot, excluding the provided item
+		/// </summary>
+		/// <param name="item">the item to exclude from the count</param>
+		/// <returns>the total number of items</returns>
+		public int GetEquipmentAmountInSlotExcludingItem(UnitEquipmentItem item)
+		{
+			int amount = 0;
+
+			List<AbstractUnitEquipmentItemSelection> selections = DictionaryUtils.GetValue(equipmentSlots, item.SlotName);
+
+			if (selections != null)
+			{
+				selections = new List<AbstractUnitEquipmentItemSelection>(selections);
+				RemoveSelectionFromList(item, selections);
+				amount = GetSelectionTotal(selections);
+			}
+
+			return amount;
+		}
+
+		private void RemoveSelectionFromList(UnitEquipmentItem item, List<AbstractUnitEquipmentItemSelection> selections)
+		{
+			AbstractUnitEquipmentItemSelection selection = GetEquipmentSelection(item);
+
+			if (selection != null)
+			{
+				selections.Remove(selection);
+			}
+		}
+
+		private int GetSelectionTotal(List<AbstractUnitEquipmentItemSelection> selections)
+		{
+			int amount = 0;
+			
+			foreach (AbstractUnitEquipmentItemSelection selection in selections)
+			{
+				amount += selection.NumberTaken;
+			}
+			
+			return amount;
+		}
+		
+		/// <summary>
+		/// Default stub implementation of getting the unit's abilities - defaults to just the unit type's required abilities until we get a way to modify them
+		/// </summary>
+		public ICollection<Ability> Abilities
+		{
+			get
+			{
+				return UnitType.GetRequiredAbilities();
+			}
+		}
+
+		private void RefreshUnitEquipmentAmounts(WarFoundryObject obj, int oldValue, int newValue)
+		{
+			foreach (UnitEquipmentItem item in equipment.Keys)
+			{
+				AbstractUnitEquipmentItemSelection selection = equipment[item];
+				
+				if (selection is UnitEquipmentRatioSelection)
+				{
+					OnUnitEquipmentAmountChanged(item, selection.AmountTaken, selection.AmountTaken);
+				}
+			}
+		}	
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/UnitEquipmentItem.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,248 @@
+// This file (UnitEquipmentItem.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.CustomMath;
+using IBBoard.Limits;
+using IBBoard.WarFoundry.API.Util;
+using IBBoard.Lang;
+//using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for UnitEquipmentItem.
+	/// </summary>
+	public class UnitEquipmentItem : WarFoundryObject
+	{
+		private EquipmentItem item;
+		private bool required;
+		private bool roundUp;
+		private double costMultiplier;
+		private RoundType roundType;
+		private string[] mutexGroups;
+		private UnitType unitType;
+		private string slotName = "";
+		private ILimit minLimit;
+		private ILimit maxLimit;
+		public UnitEquipmentItem(EquipmentItem equipmentItem, UnitType equipmentFor)
+			: this(equipmentItem, equipmentFor, new string[0])
+		{
+			//Do nothing extra
+		}
+
+		public UnitEquipmentItem(EquipmentItem equipmentItem, UnitType equipmentFor, params string[] mutexGroups)
+		{
+			item = equipmentItem;
+			unitType = equipmentFor;
+			this.mutexGroups = mutexGroups;
+			unitType.AddEquipmentItem(this);
+		}
+
+		public override string Name
+		{
+			get
+			{
+				return item.Name;
+			}
+			set
+			{
+				base.Name = value;
+			}
+		}
+
+		public override string ID
+		{
+			get
+			{
+				return (EquipmentForUnit == null ? base.ID : EquipmentForUnit.ID) + EquipmentItemID;
+			}
+			set
+			{
+				base.ID = value;
+			}
+		}
+
+		public string EquipmentItemID
+		{
+			get { return item.ID; }
+		}
+
+		public double Cost
+		{
+			get
+			{
+				return IBBMath.Round(EquipmentItem.Cost * CostMultiplier, CostRoundType);
+			}
+		}
+
+		public double CostMultiplier
+		{
+			get { return costMultiplier; }
+			set
+			{
+				costMultiplier = value;
+			}
+		}
+
+		public RoundType CostRoundType
+		{
+			get { return roundType; }
+			set
+			{
+				roundType = value;
+			}
+		}
+
+		public bool IsRequired
+		{
+			get { return required; }
+			set { required = value; }
+		}
+
+		public bool RoundNumberUp
+		{
+			get { return roundUp; }
+			set { roundUp = value; }
+		}
+		
+		public GameSystem GameSystem
+		{
+			get { return EquipmentItem.GameSystem; }
+		}
+
+		public String[] MutexGroups
+		{
+			get { return (string[]) mutexGroups.Clone(); }
+		}
+
+		public UnitType EquipmentForUnit
+		{
+			get { return unitType; }
+		}
+
+		public bool IsRatioLimit
+		{
+			get { return MinLimit is IPercentageLimit && MaxLimit is IPercentageLimit; }
+		}
+		
+		/// <summary>
+		/// Gets the Limit object for the minimum number of items that can be taken
+		/// </summary>
+		public ILimit MinLimit
+		{
+			get
+			{
+				ILimit limit = minLimit;
+				
+				if (limit == null)
+				{
+					if (maxLimit != null)
+					{
+						limit = maxLimit;
+					}
+					else
+					{
+						limit = new SimpleRoundedPercentageLimit(100, false);
+					}
+				}
+				
+				return limit;
+			}
+			set
+			{
+				if (value != null)
+				{
+					minLimit = value;
+				}
+			}
+		}
+		
+		/// <summary>
+		/// Gets the Limit object for the maximum number of items that can be taken
+		/// </summary>
+		public ILimit MaxLimit
+		{
+			get
+			{
+				ILimit limit = maxLimit;
+				
+				if (limit == null)
+				{
+					if (minLimit != null)
+					{
+						limit = minLimit;
+					}
+					else
+					{
+						limit = new SimpleRoundedPercentageLimit(100, false);
+					}
+				}
+				
+				return limit;
+			}
+			set
+			{
+				if (value != null)
+				{
+					maxLimit = value;
+				}
+			}
+		}
+
+		public EquipmentItem EquipmentItem
+		{
+			get { return item; }
+		}
+
+		public override string ToString()
+		{
+            return Translation.GetTranslation("UnitEquipmentItemName", "{0} ({1}{2} each)", Name, Cost, GameSystem.GetPointsAbbrev(Cost));
+		}
+
+		public bool HasAlternatives()
+		{
+			if (MutexGroups.Length == 0)
+			{
+				return false;
+			}
+			else if (EquipmentForUnit == null)
+			{
+				return false;
+			}
+			else
+			{
+				//If the number of items in the MutEx group is greater than one then it must be this item plus another
+				return EquipmentForUnit.GetEquipmentItemsByExclusionGroups(MutexGroups).Length > 1;
+			}
+		}
+
+		public string Description
+		{
+			get { return EquipmentItem.Description; }
+		}
+
+		public Race EquipmentForRace
+		{
+			get { return EquipmentItem.EquipmentForRace; }
+		}
+
+		public bool CanBeUsedWithItem(EquipmentItem item)
+		{
+			return EquipmentItem.CanBeUsedWithItem(item);
+		}
+
+		public string SlotName
+		{
+			get { return slotName; }
+			set
+			{
+				if (value != null && value != "")
+				{
+					slotName = value;
+				}
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/UnitEquipmentNumericSelection.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,40 @@
+// This file (UnitEquipmentNumericSelection.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Objects
+{	
+	/// <summary>
+	/// An object to hold the selection of a unit's equipment where the selection was made as an absolute number
+	/// </summary>
+	public class UnitEquipmentNumericSelection : AbstractUnitEquipmentItemSelection
+	{	
+		public UnitEquipmentNumericSelection(Unit unit, UnitEquipmentItem item, double amount) : base(unit, item, amount)
+		{
+		}
+		
+		public UnitEquipmentNumericSelection(Unit unit, UnitEquipmentItem item) : base(unit, item, item.MinLimit.GetLimit(unit.Size))
+		{
+		}
+					
+		public override int NumberTaken
+		{
+			get
+			{
+				return (int) AmountTaken;
+			}
+		}
+		
+		protected bool IsWholeNumber(double newValue)
+		{
+			return newValue == Math.Round(newValue);
+		}
+		
+		protected override bool IsValidValue (double newValue)
+		{
+			return base.IsValidValue(newValue) && IsWholeNumber(newValue);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/UnitEquipmentRatioSelection.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,43 @@
+// This file (UnitEquipmentRatioSelection.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
+// 
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.CustomMath;
+using IBBoard.Limits;
+using IBBoard.WarFoundry.API.Util;
+
+namespace IBBoard.WarFoundry.API.Objects
+{	
+	/// <summary>
+	/// An object to hold the selection of a unit's equipment where the selection was made as a percentage or ratio
+	/// of the total size of the unit
+	/// </summary>
+	public class UnitEquipmentRatioSelection : AbstractUnitEquipmentItemSelection
+	{	
+		public UnitEquipmentRatioSelection(Unit unit, UnitEquipmentItem item, double amount) : base(unit, item, amount)
+		{
+		}
+		
+		public UnitEquipmentRatioSelection(Unit unit, UnitEquipmentItem item) : base(unit, item, ((IPercentageLimit)item.MinLimit).Percentage)
+		{
+		}
+					
+		public override int NumberTaken
+		{
+			get
+			{
+				return CalculateNumberTaken (EquipmentForUnit, EquipmentItem, AmountTaken);
+			}
+		}
+
+		internal static int CalculateNumberTaken (Unit unit, UnitEquipmentItem item, double ratioTaken)
+		{
+			double exactNumberTaken = (ratioTaken / 100) * unit.Size;
+			int wholeNumberTaken = (int)IBBMath.Round (exactNumberTaken, item.RoundNumberUp);
+			int maxTaken = UnitEquipmentUtil.GetMaxEquipmentCount (unit, item);
+			int minTaken = UnitEquipmentUtil.GetMinEquipmentCount (unit, item);
+			return Math.Min (Math.Max (wholeNumberTaken, minTaken), maxTaken);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/UnitMemberType.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,53 @@
+//  This file (UnitMember.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2010 IBBoard
+// 
+//  // The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// A container object for representations of different unit member types, such as "Infantry", "Elite Infantry" and "Commoner".
+	/// The idea of the UnitMemberType is to define not just the <see>UnitType</see>s, but also the types of type, as Archer and Swordsmen
+	/// are often just differently equipped versions of the same member type (Infantry).
+	/// </summary>
+	public class UnitMemberType : WarFoundryObject
+	{
+		private Stats stats;
+		
+		public UnitMemberType(string typeID, string typeName, Stats typeStats) : base(typeID, typeName)
+		{
+			stats = typeStats;
+		}
+		
+		public string StatsID
+		{
+			get 
+			{
+				return stats.StatsID;
+			}
+		}
+
+		/// <value>
+		/// The set of <see cref="Stat"/>s for the unit member type in a format that is valid for the game system.
+		/// </value>
+		public Stat[] StatsArray
+		{
+			get 
+			{				
+				return stats.StatsArray;
+			}
+		}
+		
+		public Stat[] StatsArrayWithName
+		{
+			get
+			{
+				Stat[] extendedStats = new Stat[stats.StatCount+1];
+				extendedStats[0] = new Stat(new StatSlot("name"), Name);
+				stats.StatsArray.CopyTo(extendedStats, 1);
+				return extendedStats;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/UnitType.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,591 @@
+// This file (UnitType.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using IBBoard.Limits;
+using IBBoard.Logging;
+using IBBoard.WarFoundry.API.Requirements;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// A UnitType is a type for a <see cref=" Unit"/>, normally relating to an entry in an army list. The UnitType defines the name, cost, minimum and maximum limits of a unit, and the equipment units of the type can take.
+	/// </summary>
+	public class UnitType : WarFoundryObject
+	{
+		private Category mainCat;
+		private Race race;
+		private int min, max, baseSize = 0;
+		private int minSize, maxSize;
+		private double baseUnitCost;
+		private double costPerTrooper;
+		private Stats stats;
+		private List<UnitRequirement> requirements = new List<UnitRequirement>();
+		private Dictionary<string, UnitEquipmentItem> equipment = new Dictionary<string, UnitEquipmentItem>();
+		private Dictionary<string, List<UnitEquipmentItem>> equipmentExclusionGroups = new Dictionary<string, List<UnitEquipmentItem>>();
+		private List<string> equipmentKeyOrder = new List<string>();
+		private Dictionary<string, Ability> requiredAbilities = new Dictionary<string, Ability>();
+		private Dictionary<string, Ability> optionalAbilities = new Dictionary<string, Ability>();
+		private String notes = "";
+		private List<UnitType> containedTypes = new List<UnitType>();
+		private Dictionary<string, string> extraData = new Dictionary<string, string>();
+		private Dictionary<string, ILimit> slotLimits = new Dictionary<string, ILimit>();
+		private Dictionary<string, UnitMemberType> unitMemberTypes = new Dictionary<string, UnitMemberType>();
+		private List<Category> cats = new List<Category>();
+			
+
+		public UnitType(string id, string typeName, Race parentRace) : base(id, typeName)
+		{
+			race = parentRace;
+		}
+
+		public GameSystem GameSystem
+		{
+			get { return Race.GameSystem; }
+		}
+		
+		/// <value>
+		/// Gets the <see cref=" Race"/> that this unit belongs to.
+		/// </value>
+		public Race Race
+		{
+			get { return race; }
+		}
+
+		/// <value>
+		/// Gets or sets the default <see cref=" Category"/> that this unit type is a member of.
+		/// If it is not already in the collection of categories then it will be added.
+		/// </value>
+		public virtual Category MainCategory
+		{
+			get
+			{ 
+				return mainCat;
+			}
+			set
+			{
+				mainCat = value;
+				AddCategory(value);
+			}
+		}
+		/// <summary>
+		/// Gets the collection of <see cref="Category"/> objects that this UnitType can be a member of
+		/// </summary>
+		public Category[] Categories
+		{
+			get
+			{
+				return cats.ToArray();
+			}
+		}
+		
+		/// <summary>
+		/// Adds a category to the set of categories that this unit can be taken from. The first category added will automatically become the MainCategory.
+		/// </summary>
+		/// <param name="cat">
+		/// A <see cref="Category"/> that this unit can be taken from
+		/// </param>
+		public void AddCategory(Category cat)
+		{
+			if (!cats.Contains(cat))
+			{
+				cats.Add(cat);
+				
+				if (MainCategory == null)
+				{
+					MainCategory = cat;
+				}
+			}
+		}
+
+		/// <value>
+		/// Gets or sets the minimum size of each unit of this type. Note: This should be set AFTER MaxSize, otherwise an unintended default value may be set for the minimum
+		/// </value>
+		public int MinSize
+		{
+			get { return minSize; }
+			set
+			{
+				minSize = (value >= 0 ? value : 0);
+				CheckMinimumSize();
+			}
+		}
+
+		/// <value>
+		/// Gets or sets the maximum size of each unit of this type. Note: This should be set BEFORE MinSize, otherwise an unintended default value may be set for the minimum
+		/// </value>
+		public int MaxSize
+		{
+			get { return maxSize; }
+			set
+			{
+				maxSize = (value >= 0 ? value : WarFoundryCore.INFINITY);
+				CheckMinimumSize();
+			}
+		}
+		
+		/// <value>
+		/// Gets or sets the minimum number of units of this type that must be taken in an army. Note: This should be set AFTER MaxNumber, otherwise an unintended default value may be set for the minimum
+		/// </value>
+		public int MinNumber
+		{
+			get { return min; }
+			set
+			{
+				min = (value >= 0 ? value : 0);
+				CheckMinimumNumber();
+			}
+		}
+
+		/// <value>
+		/// Gets or sets the maximum number of units of this type that can be taken in an army. Note: This should be set BEFORE MinNumber, otherwise an unintended default value may be set for the minimum
+		/// </value>
+		public int MaxNumber
+		{
+			get { return max; }
+			set
+			{
+				max = (value >= 0 ? value : WarFoundryCore.INFINITY);
+				CheckMinimumNumber();
+			}
+		}
+
+		/// <summary>
+		/// Makes sure that the minimum number isn't more than the maximum number, hence the warning on the properties
+		/// </summary>
+		private void CheckMinimumNumber()
+		{
+			if (MinNumber > MaxNumber && MaxNumber!=WarFoundryCore.INFINITY)
+			{
+				MinNumber = MaxNumber;
+				LogNotifier.WarnFormat(GetType(), "Unit type {0} ({1}) had a minimum number greater than their maximum number.", Name, ID);
+			}
+		}
+
+		/// <summary>
+		/// Makes sure that the minimum unit size isn't more than the maximum unit size, hence the warning on the properties
+		/// </summary>
+		private void CheckMinimumSize()
+		{
+			if (MinSize > MaxSize && MaxSize!=WarFoundryCore.INFINITY)
+			{
+				MinSize = MaxSize;
+				LogNotifier.WarnFormat(GetType(), "Unit type {0} ({1}) had a minimum size greater than their maximum size.", Name, ID);
+			}
+		}
+		
+		//// <value>
+		/// Gets or sets the "base size" of a unit, which is the number of troopers the unit has in it for its "base cost". For a lot of units this value will be 0 as the cost is worked out based on the total number of members.
+		/// </value>
+		public int BaseSize
+		{
+			get { return baseSize; }
+			set { baseSize = (value >= 0 ? value : 0); }
+		}
+		
+		/// <value>
+		/// The number of points that a "base unit" of <code>BaseSize</code> models costs. Additional models are charged at <code>CostPerTrooper</code> each.
+		/// </value>
+		public double BaseUnitCost
+		{
+			get { return baseUnitCost; }
+			set { baseUnitCost = (value >= 0 ? value : 0); }
+		}
+
+		//// <value>
+		/// The cost of an individual trooper. This value is the cost for a basic trooper without weapons, which are added on top of the cost before calculating a unit cost.
+		/// </value>
+		public double CostPerTrooper
+		{
+			get { return costPerTrooper; }
+			set { costPerTrooper = (value >= 0 ? value : 0); }
+		}
+
+		protected override string DefaultName()
+		{
+			throw new InvalidOperationException("Unit type with id "+id+" did not have a name specified");
+		}
+		
+		/// <value>
+		/// The array of <see cref="Stat"/>s for each of the unit's stat lines
+		/// </value>
+		public Stat[][] UnitStatsArrays
+		{
+			get
+			{
+				Stat[][] statsArray;
+				
+				if (stats != null)
+				{
+					statsArray = new Stat[][]{ stats.StatsArray };
+				}
+				else if (unitMemberTypes.Count > 0)
+				{
+					int memTypeCount = unitMemberTypes.Count;
+					statsArray = new Stat[memTypeCount][];
+					int i = 0;
+					
+					foreach (UnitMemberType memType in unitMemberTypes.Values)
+					{
+						statsArray[i] = memType.StatsArray;
+						i++;
+					}
+				}
+				else
+				{
+					SystemStats systemStats = GameSystem.StandardSystemStats;
+					Stats tempStats = new Stats(systemStats);				
+					statsArray = new Stat[][]{ tempStats.StatsArray };
+				}
+				
+				return statsArray;
+			}
+		}
+		
+		public string[] UnitStatsArrayIDs
+		{
+			get 
+			{
+				string[] ids;
+				
+				if (stats != null)
+				{
+					ids = new string[]{ stats.StatsID };
+				}
+				else if (unitMemberTypes.Count > 0)
+				{
+					ids = new string[unitMemberTypes.Count];
+					int i = 0;
+					
+					foreach (UnitMemberType memType in unitMemberTypes.Values)
+					{
+						ids[i] = memType.StatsID;
+						i++;
+					}
+				}
+				else
+				{
+					ids = new string[]{ GameSystem.StandardSystemStatsID };
+				}
+				
+				return ids;
+			}
+		}
+
+		//// <value>
+		/// The array of <see cref="Stat"/>s for each of the unit's stat lines including an additional column that contains the unit type name
+		/// </value>
+		public Stat[][] UnitStatsArraysWithName
+		{
+			get
+			{				
+				Stat[][] statsArray;
+				
+				if (stats != null)
+				{
+					statsArray = new Stat[][]{ ExtendStatsArrayWithName(stats.StatsArray) };
+				}
+				else if (unitMemberTypes.Count > 0)
+				{
+					int memTypeCount = unitMemberTypes.Count;
+					statsArray = new Stat[memTypeCount][];
+					int i = 0;
+					
+					foreach (UnitMemberType memType in unitMemberTypes.Values)
+					{
+						statsArray[i] = memType.StatsArrayWithName;
+						i++;
+					}
+				}
+				else
+				{
+					SystemStats systemStats = GameSystem.StandardSystemStats;
+					Stats tempStats = new Stats(systemStats);				
+					statsArray = new Stat[][]{ ExtendStatsArrayWithName(tempStats.StatsArray) };
+				}
+				
+				return statsArray;
+			}
+		}
+		
+		public Stat[] ExtendStatsArrayWithName(Stat[] statsArray)
+		{
+			Stat[] extendedStats = new Stat[statsArray.Length+1];
+			extendedStats[0] = new Stat(new StatSlot("name"), Name);
+			statsArray.CopyTo(extendedStats, 1);
+			return extendedStats;
+		}
+
+		public void SetUnitStats(Stats newStats)
+		{
+			stats = newStats;
+		}
+
+		public string GetStatValue(string statName)
+		{
+			return stats.GetStatValue(statName.ToLower());
+		}
+		
+		internal void AddEquipmentItem(UnitEquipmentItem item)
+		{
+			if (!equipment.ContainsKey(item.ID))
+			{
+				equipment.Add(item.ID, item);
+				equipmentKeyOrder.Add(item.ID);
+				AddToMutexGroups(item);
+			}
+		}
+		
+		private void AddToMutexGroups(UnitEquipmentItem item)
+		{
+			string[] mutexGroups = item.MutexGroups;
+			
+			foreach (string mutexGroup in mutexGroups)
+			{
+				List<UnitEquipmentItem> items = DictionaryUtils.GetValue(equipmentExclusionGroups, mutexGroup);
+				
+				if (items == null)
+				{
+					items = new List<UnitEquipmentItem>();
+					equipmentExclusionGroups.Add(mutexGroup, items);
+				}
+				
+				items.Add(item);
+			}
+		}
+
+		/// <summary>
+		/// Gets a <see cref="UnitEquipmentItem"/> for the given ID string, or <code>null</code> if nothing exists for that ID
+		/// </summary>
+		/// <param name="id">
+		/// The ID of the UnitEquipmentItem to get
+		/// </param>
+		/// <returns>
+		/// The <see cref="UnitEquipmentItem"/> for the given ID string, or <code>null</code> if nothing exists for that ID
+		/// </returns>
+		public UnitEquipmentItem GetEquipmentItem(string id)
+		{
+			return DictionaryUtils.GetValue(equipment, id);
+		}
+		
+		/// <summary>
+		/// Gets a <see cref=" UnitEquipmentItem"/> for the given <see cref=" EquipmentItem"/>, or <code>null</code> if the unit can't take that <code>EquipmentItem</code>
+		/// </summary>
+		/// <param name="item">
+		/// The <see cref="EquipmentItem"/> to get the <see cref=" UnitEquipmentItem"/>
+		/// </param>
+		/// <returns>
+		/// The <see cref="UnitEquipmentItem"/> that definies the UnitType's restrictions for taking the <see cref=" EquipmentItem"/>
+		/// </returns>
+		public UnitEquipmentItem GetEquipmentItem(EquipmentItem item)
+		{
+			return GetEquipmentItem(item.ID);
+		}
+
+		/// <summary>
+		/// Gets an array of all available <see cref="UnitEquipmentItem"/>s for this UnitType
+		/// </summary>
+		/// <returns>
+		/// An array of all available <see cref="UnitEquipmentItem"/>s for this UnitType
+		/// </returns>
+		public UnitEquipmentItem[] GetEquipmentItems()
+		{
+			return DictionaryUtils.ToArray<string, UnitEquipmentItem>(equipment);
+		}
+
+		public UnitEquipmentItem[] GetEquipmentItemsByExclusionGroup(string group)
+		{
+			return GetEquipmentItemsByExclusionGroups(new string[] { group });
+		}
+
+		public UnitEquipmentItem[] GetEquipmentItemsByExclusionGroups(string[] groups)
+		{
+			List<UnitEquipmentItem> list = new List<UnitEquipmentItem>();
+
+			foreach (string group in groups)
+			{
+				List<UnitEquipmentItem> groupList = DictionaryUtils.GetValue(equipmentExclusionGroups, group);
+
+				if (groupList != null)
+				{
+					list.AddRange(groupList);
+				}
+			}
+
+			return list.ToArray();
+		}
+		
+		public bool IsRatioLimitedEquipmentItem(EquipmentItem item)
+		{
+			UnitEquipmentItem equip = GetEquipmentItem(item);
+			return equip != null && equip.IsRatioLimit;
+		}
+		
+		public bool IsAbsoluteLimitedEquipmentItem(EquipmentItem item)
+		{
+			UnitEquipmentItem equip = GetEquipmentItem(item);
+			return equip != null && !equip.IsRatioLimit;
+		}
+		
+		public ICollection<Ability> GetRequiredAbilities()
+		{
+			return requiredAbilities.Values;
+		}
+		
+		public ICollection<Ability> GetOptionalAbilities()
+		{
+			return optionalAbilities.Values;
+		}
+		
+		public void AddAbility(Ability ability, bool isRequired)
+		{
+			string id = ability.ID;
+			
+			if (!requiredAbilities.ContainsKey(id) && !optionalAbilities.ContainsKey(id))
+			{				
+				if (isRequired)
+				{
+					requiredAbilities[id] = ability;
+				}
+				else
+				{
+					optionalAbilities[id] = ability;
+				}
+			}
+		}
+
+		public void AddRequirement(UnitRequirement requirement)
+		{
+			requirements.Add(requirement);
+		}
+		
+		public UnitRequirement[] Requirements
+		{
+			get { return requirements.ToArray(); }
+		}
+		
+		public List<FailedUnitRequirement> CanAddToArmy(Army army)
+		{
+			List<FailedUnitRequirement> failures = new List<FailedUnitRequirement>();
+			
+			if (requirements!=null && requirements.Count > 0)
+			{
+				foreach (UnitRequirement requirement in requirements)
+				{
+					FailedUnitRequirement failure = (FailedUnitRequirement)requirement.CanAddToWarFoundryObject(army);
+					
+					if (failure!=null)
+					{
+						failures.Add(failure);
+					}
+				}
+			}
+			
+			return failures;
+		}
+		
+		public List<FailedUnitRequirement> CanRemoveFromArmy(Army army)
+		{
+			List<FailedUnitRequirement> failures = new List<FailedUnitRequirement>();
+			
+			if (requirements!=null && requirements.Count > 0)
+			{
+				foreach (UnitRequirement requirement in requirements)
+				{
+					FailedUnitRequirement failure = (FailedUnitRequirement)requirement.CanRemoveFromWarFoundryObject(army);
+					
+					if (failure!=null)
+					{
+						failures.Add(failure);
+					}
+				}
+			}
+			
+			return failures;
+		}
+		
+		public string Notes
+		{
+			get { return notes; }
+			set { notes = value; }
+		}
+				
+		public bool CanContainUnit(Unit unit)
+		{
+			return CanContainUnitType(unit.UnitType);
+		}
+		
+		public bool CanContainUnitType(UnitType unitType)
+		{
+			return containedTypes.Contains(unitType);
+		}
+		
+		public UnitType[] ContainedUnitTypes
+		{
+			get { return containedTypes.ToArray(); }
+		}
+		
+		public void AddContainedUnitType(UnitType containedType)
+		{
+			containedTypes.Add(containedType);
+		}
+		
+		public void AddExtraData(string id, string data)
+		{
+			extraData[id] = data;
+		}
+		
+		public string GetExtraData(string id)
+		{
+			return DictionaryUtils.GetValue(extraData, id);
+		}
+		
+		public string StatsID
+		{
+			get
+			{
+				return stats.StatsID;
+			}
+		}
+
+		public void AddEquipmentSlot(string slotName, ILimit slotLimit)
+		{
+			slotLimits.Add(slotName, slotLimit);
+		}
+
+		public bool HasEquipmentSlot(string slotName)
+		{
+			return slotLimits.ContainsKey(slotName);
+		}
+
+		/// <summary>
+		/// Gets the maximum limit on the number of items allowed in a single slot
+		/// </summary>
+		/// <param name="slotName">The name of the equipment slot to get the limit for</param>
+		/// <returns>The limit of the number of items allowed in a slot, or an infinite limit if the slot is the default one or has not been specified</returns>
+		public ILimit GetEquipmentSlotLimit(string slotName)
+		{
+			ILimit slotLimit = null;
+
+			if (HasEquipmentSlot(slotName))
+			{
+				slotLimit = DictionaryUtils.GetValue(slotLimits, slotName);
+			}
+			
+			if (slotLimit == null)
+			{
+				slotLimit = new UnlimitedLimit();
+			}
+
+			return slotLimit;
+		}
+
+		public void AddUnitMemberType(UnitMemberType unitMemberType)
+		{
+			unitMemberTypes.Add(unitMemberType.ID, unitMemberType);
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/WarFoundryLoadedObject.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,26 @@
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// A marker class for objects that are loaded from a file
+	/// </summary>
+	public class WarFoundryLoadedObject : WarFoundryObject
+	{
+		protected WarFoundryLoadedObject() : base()
+		{
+			//Do nothing
+		}
+				
+		protected WarFoundryLoadedObject(string objName) : base(objName)
+		{
+			//Do nothing
+		}
+		
+		protected WarFoundryLoadedObject(string objId, string objName) : base(objId, objName)
+		{
+			//Do nothing
+		}	
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/WarFoundryObject.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,100 @@
+// This file (WarFoundryObject.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.WarFoundry.API.Factories;
+
+namespace IBBoard.WarFoundry.API.Objects
+{
+	/// <summary>
+	/// Summary description for WarFoundryObject.
+	/// </summary>
+	public abstract class WarFoundryObject : IWarFoundryObject
+	{
+		protected string id;
+		protected string name;
+		public event StringValChangedDelegate NameChanged;
+		
+		protected WarFoundryObject()
+		{
+		}
+				
+		protected WarFoundryObject(string objName) : this()
+		{
+			Name = objName;
+		}
+		
+		protected WarFoundryObject(string objId, string objName) : this(objName)
+		{
+			ID = objId;
+		}	
+
+		public virtual string ID
+		{
+			get
+			{
+				if (id == null || id == "")
+				{
+					id = GenerateID();
+				}
+				
+				return id;
+			}
+			
+			set
+			{
+				string newId = (value == null ? "" : value.Trim());
+				id = (newId == "" ? GenerateID() : newId);
+			}
+		}
+
+		public virtual string Name
+		{
+			get 
+			{
+				if (HasDefaultName())
+				{
+					return DefaultName();
+				}
+				else
+				{
+					return name;
+				} 
+			}
+			set 
+			{ 
+				string oldValue = name;
+				name = value;
+
+				if (name!=oldValue)
+				{
+					OnNameChanged(oldValue, name);
+				}
+			}
+		}
+
+		public bool HasDefaultName()
+		{
+			return (name == null || name == "");
+		}
+
+		protected void OnNameChanged(string oldValue, string newValue)
+		{
+			if (NameChanged!=null)
+			{
+				NameChanged(this, oldValue, newValue);
+			}
+		}
+
+		protected virtual string DefaultName()
+		{
+			return "-";
+		}
+
+		protected string GenerateID()
+		{
+			return Name + UnixTimestamp.GetTimestamp(DateTime.Now) + "." + DateTime.Now.Millisecond;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Objects/WarFoundryStagedLoadingObject.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,80 @@
+// This file (WarFoundryStagedLoadingObject.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.IO;
+using IBBoard.WarFoundry.API.Factories;
+
+namespace IBBoard.WarFoundry.API.Objects
+{	
+	public class WarFoundryStagedLoadingObject : WarFoundryLoadedObject, IWarFoundryStagedLoadObject
+	{
+		private bool isFullyLoaded;
+		private bool isLoading;
+		private IWarFoundryFactory creatingFactory;
+		private FileInfo sourceFile;
+		
+		protected WarFoundryStagedLoadingObject(IWarFoundryFactory creatingFactory) : this (null, creatingFactory)
+		{
+		}
+				
+		protected WarFoundryStagedLoadingObject(string objName, IWarFoundryFactory creatingFactory) : this(null, objName, creatingFactory)
+		{
+		}
+		
+		protected WarFoundryStagedLoadingObject(string objId, string objName, IWarFoundryFactory creatingFactory) : base(objId, objName)
+		{
+			this.creatingFactory = creatingFactory;
+			isFullyLoaded = false;
+		}	
+		
+		public FileInfo SourceFile
+		{
+			get { return sourceFile; }
+			set { sourceFile = value; }
+		}
+		
+		public void EnsureFullyLoaded ()
+		{
+			if (!IsFullyLoaded && !IsLoading)
+			{
+				if (Factory == null)
+				{
+					throw new InvalidOperationException("No factory set for partially loaded object with ID "+ID);
+				}
+				
+				Factory.CompleteLoading(this);
+			}
+		}
+		
+		public IWarFoundryFactory Factory
+		{
+			get { return creatingFactory; }
+		}
+		
+		public bool IsFullyLoaded
+		{
+			get { return isFullyLoaded; }
+		}
+		
+		public bool IsLoading
+		{
+			get { return isLoading; }
+		}
+		
+		public void SetAsFullyLoaded()
+		{
+			isLoading = false;
+			isFullyLoaded = true;
+		}
+		
+		public void SetAsLoading()
+		{
+			if (!isFullyLoaded)
+			{
+				isLoading = true;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/AbstractArmyRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,52 @@
+// This file (AbstractArmyRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API.Objects;
+
+
+namespace IBBoard.WarFoundry.API.Requirements
+{	
+	/// <summary>
+	/// Abstract class for any requirement for adding an object to an army, e.g. adding units.
+	/// </summary>
+	public abstract class AbstractArmyRequirement : AbstractRequirement
+	{		
+		protected abstract AbstractFailedRequirement CanAddToArmy(Army army);
+		protected abstract AbstractFailedRequirement CanRemoveFromArmy(Army army);
+		
+		public override AbstractFailedRequirement CanAddToWarFoundryObject (WarFoundryObject obj)
+		{
+			AbstractFailedRequirement fail = null;
+			
+			if (obj is Army)
+			{
+				fail = CanAddToArmy((Army)obj);
+			}
+			else
+			{
+				fail = new FailedRequirement(this);
+			}
+			
+			return fail;
+		}
+		
+		public override AbstractFailedRequirement CanRemoveFromWarFoundryObject (WarFoundryObject obj)
+		{
+			AbstractFailedRequirement fail = null;
+			
+			if (obj is Army)
+			{
+				fail = CanRemoveFromArmy((Army)obj);
+			}
+			else
+			{
+				fail = new FailedRequirement(this);
+			}
+			
+			return fail;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/AbstractFailedRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,20 @@
+// This file (AbstractFailedRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	public abstract class AbstractFailedRequirement
+	{	
+		protected AbstractRequirement failedReq;
+		
+		public AbstractFailedRequirement(AbstractRequirement req)
+		{
+			failedReq = req;
+		}
+		
+		public abstract string Description { get; }
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/AbstractRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,19 @@
+// This file (AbstractRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{	
+	/// <summary>
+	/// The base class for Requirements. Specific types of abstract requirement should extend this class.
+	/// </summary>
+	public abstract class AbstractRequirement
+	{				
+		public abstract string Description { get; }
+		public abstract AbstractFailedRequirement CanAddToWarFoundryObject(WarFoundryObject obj);
+		public abstract AbstractFailedRequirement CanRemoveFromWarFoundryObject(WarFoundryObject obj);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/AbstractUnitRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,50 @@
+// This file (AbstractUnitRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	/// <summary>
+	/// Base abstract class for all requirements related to adding/removing something from a Unit (e.g. adding equipment or abilities)
+	/// </summary>
+	public abstract class AbstractUnitRequirement : AbstractRequirement
+	{		
+		protected abstract AbstractFailedRequirement CanAddToUnit(Unit unit);
+		protected abstract AbstractFailedRequirement CanRemoveFromUnit(Unit unit);
+		
+		public override AbstractFailedRequirement CanAddToWarFoundryObject (WarFoundryObject obj)
+		{
+			AbstractFailedRequirement fail = null;
+			
+			if (obj is Unit)
+			{
+				fail = CanAddToUnit((Unit)obj);
+			}
+			else
+			{
+				fail = new FailedRequirement(this);
+			}
+			
+			return fail;
+		}
+		
+		public override AbstractFailedRequirement CanRemoveFromWarFoundryObject (WarFoundryObject obj)
+		{
+			AbstractFailedRequirement fail = null;
+			
+			if (obj is Unit)
+			{
+				fail = CanRemoveFromUnit((Unit)obj);
+			}
+			else
+			{
+				fail = new FailedRequirement(this);
+			}
+			
+			return fail;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/Delegates.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,11 @@
+// This file (Delegates.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	public delegate void FailedUnitRequirementDelegate(List<FailedUnitRequirement> failedRequirements);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/FailedRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,20 @@
+// This file (FailedRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	public class FailedRequirement : AbstractFailedRequirement
+	{	
+		public FailedRequirement(AbstractRequirement req) : base(req)
+		{
+		}
+		
+		public override string Description
+		{
+			get { return failedReq.Description; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/FailedUnitRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,24 @@
+// This file (FailedUnitRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	/// <summary>
+	/// Summary description for UnitRequirement.
+	/// </summary>
+	public class FailedUnitRequirement : AbstractFailedRequirement
+	{
+		public FailedUnitRequirement(UnitRequirement requirement) : base(requirement)
+		{
+		}
+		
+		public override string Description
+		{
+			get { return failedReq.Description; }
+		}
+
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/RequirementAND.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,56 @@
+// This file (RequirementAND.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	/// <summary>
+	/// Summary description for RequirementAND.
+	/// </summary>
+	public class RequirementAND : AbstractRequirement
+	{
+		private static string and = Translation.GetTranslation("requirementAND", "{0} and {1}");
+
+		private AbstractRequirement reqA, reqB;
+
+		public RequirementAND(AbstractRequirement requirementA, AbstractRequirement requirementB)
+		{
+			reqA = requirementA;
+			reqB = requirementB;
+		}
+
+		public override AbstractFailedRequirement CanAddToWarFoundryObject(WarFoundryObject obj)
+		{	
+			FailedRequirement failed = null;
+			
+			if (reqA.CanAddToWarFoundryObject(obj) !=null || reqB.CanAddToWarFoundryObject(obj)!=null)
+			{
+				failed = new FailedRequirement(this);
+			}
+			
+			return failed;
+		}
+
+		public override AbstractFailedRequirement CanRemoveFromWarFoundryObject(WarFoundryObject obj)
+		{
+			FailedRequirement failed = null;
+			
+			if (reqA.CanRemoveFromWarFoundryObject(obj) !=null || reqB.CanRemoveFromWarFoundryObject(obj)!=null)
+			{
+				failed = new FailedRequirement(this);
+			}
+			
+			return failed;
+		}
+
+		public override string Description 
+		{
+			get { return String.Format(and, reqA.Description, reqB.Description); }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/RequirementOR.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,56 @@
+// This file (RequirementOR.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	/// <summary>
+	/// Summary description for UnitRequirementOR.
+	/// </summary>
+	public class RequirementOR : AbstractRequirement
+	{
+		private static string or = Translation.GetTranslation("requirementOR", "{0} or {1}");
+
+		private AbstractRequirement reqA, reqB;
+
+		public RequirementOR(AbstractRequirement requirementA, AbstractRequirement requirementB)
+		{
+			reqA = requirementA;
+			reqB = requirementB;
+		}
+
+		public override AbstractFailedRequirement CanAddToWarFoundryObject(WarFoundryObject obj)
+		{		
+			FailedRequirement failed = null;
+			
+			if (reqA.CanAddToWarFoundryObject(obj) !=null && reqB.CanAddToWarFoundryObject(obj)!=null)
+			{
+				failed = new FailedRequirement(this);
+			}
+			
+			return failed;
+		}
+
+		public override AbstractFailedRequirement CanRemoveFromWarFoundryObject(WarFoundryObject obj)
+		{		
+			FailedRequirement failed = null;
+			
+			if (reqA.CanRemoveFromWarFoundryObject(obj)!=null && reqB.CanRemoveFromWarFoundryObject(obj)!=null)
+			{
+				failed = new FailedRequirement(this);
+			}
+			
+			return failed;
+		}
+
+		public override string Description 
+		{
+			get { return String.Format(or, reqA.Description, reqB.Description); }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/UnitExcludesRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,68 @@
+// This file (UnitExcludesRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using IBBoard.WarFoundry.API.Objects;
+using IBBoard.Lang;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	/// <summary>
+	/// Summary description for UnitExcludesRequirement.
+	/// </summary>
+	public class UnitExcludesRequirement : UnitRequirement
+	{
+		private UnitRequirementItem[] excludingTypes;
+
+		public UnitExcludesRequirement(UnitType type, UnitRequirementItem[] excludingUnitTypes) : base(type)
+		{
+			excludingTypes = excludingUnitTypes;
+		}
+
+		public override string Description
+		{
+			get 
+			{
+				string otherUnits = GetOtherUnitTypeNames();
+				return Translation.GetTranslation("requirementUnitExcludesDescription", "{0} can only be taken if none of the following are taken: {1}", unitType.Name, otherUnits);
+			}
+		}
+		
+		private string GetOtherUnitTypeNames()
+		{
+			StringBuilder sb = new StringBuilder();
+			
+			foreach (UnitRequirementItem req in excludingTypes)
+			{
+				sb.Append(req.UnitType.Name);
+			}
+			
+			return sb.ToString();
+		}
+
+		protected override AbstractFailedRequirement CanAddToArmy(Army army, UnitType type)
+		{		
+			FailedUnitRequirement failed = null;
+
+			for (int i = 0; i<excludingTypes.Length; i++)
+			{
+				if (army.GetUnitTypeCount(excludingTypes[i].UnitType) > 0)
+				{
+					failed = new FailedUnitRequirement(this);
+					break;
+				}
+			}
+			
+			return failed;
+		}
+
+		protected override AbstractFailedRequirement CanRemoveFromArmy(Army army, UnitType type)
+		{
+			return null;
+		}
+
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/UnitRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,96 @@
+// This file (UnitRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using IBBoard.WarFoundry.API;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	/// <summary>
+	/// Summary description for UnitRequirement.
+	/// </summary>
+	public abstract class UnitRequirement : AbstractArmyRequirement
+	{
+		protected UnitType unitType;
+		
+		protected UnitRequirement(UnitType requirementFor)
+		{
+			unitType = requirementFor;	
+		}		
+		
+		/// <summary>
+		/// Checks whether the specified unit type can be added to the specified army. Returns a <see cref="FailedRequirement"> obejct if a unit of that type cannot legally be added, or no failure (null) if it can be added. 
+		/// </summary>
+		/// <param name="army">
+		/// The <see cref="Army"/> that the unit should be checked for adding to
+		/// </param>
+		/// <param name="type">
+		/// The <see cref="UnitType"/> that is being checked.
+		/// </param>
+		/// <returns>
+		/// A <see cref="AbstractFailedRequirement"/> if the requirement means the <see cref="UnitType"/> cannot be legally added, else <code>null</code>.
+		/// </returns>
+		protected abstract AbstractFailedRequirement CanAddToArmy(Army army, UnitType type);
+		
+		/// <summary>
+		/// Checks whether the specified unit can be added to the specified army. Returns a <see cref="FailedRequirement"> obejct if the unit cannot legally be added, or no failure (null) if it can be added. 
+		/// </summary>
+		/// <param name="army">
+		/// The <see cref="Army"/> that the unit should be checked for adding to
+		/// </param>
+		/// <param name="type">
+		/// The <see cref="Unit"/> that is being checked.
+		/// </param>
+		/// <returns>
+		/// A <see cref="AbstractFailedRequirement"/> if the requirement means the <see cref="Unit"/> cannot be legally added, else <code>null</code>.
+		/// </returns>
+		protected AbstractFailedRequirement CanAddToArmy(Army army, Unit unit)
+		{
+			return CanAddToArmy(army, unit.UnitType);
+		}
+		
+		/// <summary>
+		/// Checks whether the specified unit type can be removed from the specified army. Returns a <see cref="FailedRequirement"> obejct if a unit of that type cannot legally be removed, or no failure (null) if it can be removed. 
+		/// </summary>
+		/// <param name="army">
+		/// The <see cref="Army"/> that the unit should be checked for adding to
+		/// </param>
+		/// <param name="type">
+		/// The <see cref="UnitType"/> that is being checked.
+		/// </param>
+		/// <returns>
+		/// A <see cref="AbstractFailedRequirement"/> if the requirement means the <see cref="UnitType"/> cannot be legally added, else <code>null</code>.
+		/// </returns>
+		protected abstract AbstractFailedRequirement CanRemoveFromArmy(Army army, UnitType type);
+		
+		/// <summary>
+		/// Checks whether the specified unit can be removed from the specified army. Returns a <see cref="FailedRequirement"> obejct if the unit cannot legally be removed, or no failure (null) if it can be removed. 
+		/// </summary>
+		/// <param name="army">
+		/// The <see cref="Army"/> that the unit should be checked for adding to
+		/// </param>
+		/// <param name="type">
+		/// The <see cref="Unit"/> that is being checked.
+		/// </param>
+		/// <returns>
+		/// A <see cref="AbstractFailedRequirement"/> if the requirement means the <see cref="Unit"/> cannot be legally removed, else <code>null</code>.
+		/// </returns>
+		protected AbstractFailedRequirement CanRemoveFromArmy(Army army, Unit unit)
+		{
+			return CanRemoveFromArmy(army, unit.UnitType);
+		}
+				
+		protected override AbstractFailedRequirement CanAddToArmy(Army army)
+		{
+			return CanAddToArmy(army, unitType);
+		}
+		
+		protected override AbstractFailedRequirement CanRemoveFromArmy(Army army)
+		{
+			return CanRemoveFromArmy(army, unitType);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/UnitRequirementItem.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,36 @@
+// This file (UnitRequirementItem.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	/// <summary>
+	/// Summary description for UnitRequirementItem.
+	/// </summary>
+	public class UnitRequirementItem
+	{
+		private UnitType type;
+		private int requiredNum;
+
+		public UnitRequirementItem(UnitType unitType, int reqNumber)
+		{
+			type = unitType;
+			requiredNum = reqNumber;
+		}
+
+		public UnitRequirementItem(UnitType type) : this(type, 1) { }
+
+		public UnitType UnitType
+		{
+			get { return type; }
+		}
+
+		public int Amount
+		{
+			get { return requiredNum; }
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/UnitRequirementMaxNumber.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,42 @@
+// This file (UnitRequirementMaxNumber.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	public class UnitRequirementMaxNumber : UnitRequirement
+	{		
+		private int maxUnitCount;
+		
+		public UnitRequirementMaxNumber(UnitType type, int maxNumber) : base(type)
+		{
+			maxUnitCount = maxNumber;
+		}
+		
+		public override string Description
+		{
+			get { return Translation.GetTranslation("requirementUnitMaxNumber", "an army can contain up to {0} units of type {1}", maxUnitCount, unitType.Name); }
+		}
+		
+		protected override AbstractFailedRequirement CanAddToArmy (Army army, UnitType type)
+		{
+			FailedUnitRequirement failed = null;
+			
+			if (army.GetUnitTypeCount(type) >= maxUnitCount)
+			{
+				failed = new FailedUnitRequirement(this);
+			}
+			
+			return failed;
+		}
+
+		protected override AbstractFailedRequirement CanRemoveFromArmy (Army army, UnitType type)
+		{
+			return null;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/UnitRequirementMinNumber.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,42 @@
+// This file (UnitRequirementMinNumber.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	public class UnitRequirementMinNumber : UnitRequirement
+	{
+		private int minUnitCount;
+		
+		public UnitRequirementMinNumber(UnitType type, int minNumber) : base(type)
+		{
+			minUnitCount = minNumber;
+		}
+		
+		public override string Description
+		{
+			get { return Translation.GetTranslation("requirementUnitMinNumber", "you must include at least {0} of {1} in an army", minUnitCount, unitType.Name); }
+		}
+		
+		protected override AbstractFailedRequirement CanAddToArmy(Army army, UnitType type)
+		{
+			return null;
+		}
+
+		protected override AbstractFailedRequirement CanRemoveFromArmy (Army army, UnitType type)
+		{
+			FailedUnitRequirement failed = null;
+			
+			if (army.GetUnitTypeCount(type) <= minUnitCount)
+			{
+				failed = new FailedUnitRequirement(this);
+			}
+			
+			return failed;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Requirements/UnitRequiresAtLeastRequirement.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,73 @@
+// This file (UnitRequiresAtLeastRequirement.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using IBBoard.Lang;
+using IBBoard.WarFoundry.API;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Requirements
+{
+	/// <summary>
+	/// Summary description for UnitRequiresRequirement.
+	/// </summary>
+	public class UnitRequiresAtLeastRequirement : UnitRequirement
+	{
+		private UnitRequirementItem[] requiredTypes;
+		private String unitList;
+
+		public UnitRequiresAtLeastRequirement(UnitType type, UnitType requiredUnitType) : this(type, new UnitRequirementItem[]{new UnitRequirementItem(requiredUnitType)})
+		{
+		}
+		
+		public UnitRequiresAtLeastRequirement(UnitType type, UnitRequirementItem[] requiredUnitTypes) : base(type)
+		{			
+			requiredTypes = requiredUnitTypes;
+			bool first = true;
+			
+			foreach (UnitRequirementItem req in requiredTypes)
+			{
+				string reqString = Translation.GetTranslation("requirementUnitTypeAtLeastSingle", "{1} {0}", req.UnitType.Name, req.Amount);
+				
+				if (first)
+				{
+					first = false;
+					unitList = reqString;
+				}
+				else
+				{
+					unitList = Translation.GetTranslation("requirementUnitTypeAtLeastJoiner", "{0}, {1}", unitList, reqString);
+				}
+			}
+		}
+
+		public override string Description
+		{
+			get { return Translation.GetTranslation("requirementUnitTypeAtLeast", "the army must include at least the following units to include a unit of type {0}: {1}", unitType.Name, unitList); }
+		}
+
+		protected override AbstractFailedRequirement CanRemoveFromArmy(Army army, UnitType type)
+		{
+			return null;
+		}
+
+		protected override AbstractFailedRequirement CanAddToArmy(Army army, UnitType type)
+		{
+			FailedRequirement failure = null;
+			
+			foreach (UnitRequirementItem req in requiredTypes)
+			{
+				if (army.GetUnitTypeCount(req.UnitType) < req.Amount)
+				{				
+					failure = new FailedRequirement(this);
+					break;
+				}
+			}
+						
+			return failure;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Savers/IWarFoundryFileSaver.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,19 @@
+// This file (IWarFoundryFileSaver.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Savers
+{
+	/// <summary>
+	/// Saves one or more objects into a native-format zip file.
+	/// </summary>
+	public interface IWarFoundryFileSaver
+	{
+		bool Save(string path, params WarFoundryLoadedObject[] objects);
+		
+		string GetFileExtension (WarFoundryLoadedObject obj);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Savers/WarFoundrySaver.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,23 @@
+// This file (WarFoundrySaver.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+
+namespace IBBoard.WarFoundry.API.Savers
+{
+	public class WarFoundrySaver
+	{
+		private static IWarFoundryFileSaver fileSaver;
+		
+		public static IWarFoundryFileSaver GetSaver()
+		{
+			return fileSaver;
+		}
+		
+		public static void SetFileSaver(IWarFoundryFileSaver newFileSaver)
+		{
+			fileSaver = newFileSaver;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Savers/Xml/WarFoundryXmlArmySaver.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,143 @@
+// This file (WarFoundryXmlSaver.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using System.Xml.Schema;
+using IBBoard.Lang;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Factories.Xml.Zip;
+using IBBoard.WarFoundry.API.Objects;
+using IBBoard.WarFoundry.API.Savers;
+using IBBoard.WarFoundry.API.Util;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace IBBoard.WarFoundry.API.Savers.Xml
+{
+	public class WarFoundryXmlArmySaver
+	{
+		public string CreateXmlString(Army toSave)
+		{
+			XmlDocument doc = new XmlDocument();
+			XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
+			doc.AppendChild(declaration);
+			XmlSchema schema = new XmlSchema();
+			schema.Namespaces.Add("", "http://ibboard.co.uk/warfoundry/army");
+			schema.Namespaces.Add("core", "http://ibboard.co.uk/warfoundry/core");
+			doc.Schemas.Add(schema);
+			XmlElement root = doc.CreateElement("army");
+			root.SetAttribute("xmlns", "http://ibboard.co.uk/warfoundry/army");
+			root.SetAttribute("xmlns:core", "http://ibboard.co.uk/warfoundry/core");
+			doc.AppendChild(root);
+			root.SetAttribute("id", XmlTools.GetAsciiXmlIdForString(toSave.ID));
+			root.SetAttribute("name", toSave.Name);
+			//Don't convert system and race to ID format as they could be stored in non-XML file formats
+			//If they are in XML files then they'll already be valid
+			root.SetAttribute("system", toSave.GameSystem.ID);
+			root.SetAttribute("race", toSave.Race.ID);
+			root.SetAttribute("maxPoints", toSave.MaxPoints.ToString());
+			XmlElement units = doc.CreateElement("units");
+			root.AppendChild(units);
+			
+			foreach (Unit unit in toSave.GetUnits())
+			{
+				units.AppendChild(CreateUnitElement(unit, doc));
+			}
+			
+			return doc.OuterXml;
+		}
+
+		private XmlElement CreateUnitElement(Unit unit, XmlDocument doc)
+		{
+			XmlElement unitElem = doc.CreateElement("unit");
+			unitElem.SetAttribute("id", XmlTools.GetAsciiXmlIdForString(unit.ID));
+			unitElem.SetAttribute("unitName", (unit.HasDefaultName() ? "" : unit.Name));
+			unitElem.SetAttribute("unitType", unit.UnitType.ID);
+			unitElem.SetAttribute("size", unit.Size.ToString());
+			
+			if (!unit.Race.Equals(unit.Army.Race))
+			{
+				unitElem.SetAttribute("race", unit.Race.ID);
+			}
+			
+			Category unitCategory = unit.Category.Category;
+			if (!unit.UnitType.MainCategory.Equals(unitCategory))
+			{
+				unitElem.SetAttribute("category", unitCategory.ID);
+			}
+
+			XmlElement equipmentElem = CreateEquipmentItemsElement(unit, doc);
+
+			if (equipmentElem != null)
+			{
+				unitElem.AppendChild(equipmentElem);
+			}
+			
+			XmlElement containedElem = CreateContainedUnitsElement(unit, doc);
+
+			if (containedElem != null)
+			{
+				unitElem.AppendChild(containedElem);
+			}
+			
+			return unitElem;
+		}
+
+		private XmlElement CreateEquipmentItemsElement(Unit unit, XmlDocument doc)
+		{
+			UnitEquipmentItem[] equipItems = unit.GetEquipment();
+			int equipItemCount = equipItems.Length;
+			XmlElement equipmentElem = null;
+
+			if (equipItemCount > 0)
+			{
+				equipmentElem = doc.CreateElement("equipment");
+				
+				for (int i = 0; i < equipItemCount; i++)
+				{
+					equipmentElem.AppendChild(CreateEquipmentElement(equipItems[i], unit, doc));
+				}
+			}
+
+			return equipmentElem;
+		}
+
+		private XmlElement CreateEquipmentElement(UnitEquipmentItem item, Unit unit, XmlDocument doc)
+		{
+			XmlElement equipmentItemElem = doc.CreateElement("equipItem");
+			equipmentItemElem.SetAttribute("id", item.ID);
+			equipmentItemElem.SetAttribute("amount", UnitEquipmentUtil.GetEquipmentAmount(unit, item).ToString());
+			equipmentItemElem.SetAttribute("amountType", UnitEquipmentUtil.GetEquipmentAmountIsRatio(unit, item) ? "ratio" : "fixed");
+			return equipmentItemElem;
+		}
+		
+		private XmlElement CreateContainedUnitsElement(Unit unit, XmlDocument doc)
+		{
+			Unit[] containedUnits = unit.ContainedUnits;
+			int containedCount = containedUnits.Length;
+			XmlElement containedElem = null;
+
+			if (containedCount > 0)
+			{
+				containedElem = doc.CreateElement("contained");
+				
+				for (int i = 0; i < containedCount; i++)
+				{
+					containedElem.AppendChild(CreateContainedUnitElement(containedUnits[i], doc));
+				}
+			}
+
+			return containedElem;
+		}
+
+		private XmlElement CreateContainedUnitElement(Unit unit,  XmlDocument doc)
+		{
+			XmlElement containedUnitElem = doc.CreateElement("containedUnit");
+			containedUnitElem.SetAttribute("containedID", XmlTools.GetAsciiXmlIdForString(unit.ID));
+			return containedUnitElem;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Savers/Xml/WarFoundryXmlFileSaver.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,92 @@
+using System;
+using IBBoard.WarFoundry.API.Savers;
+using IBBoard.WarFoundry.API.Objects;
+using ICSharpCode.SharpZipLib.Zip;
+using System.IO;
+using IBBoard.IO;
+using IBBoard.Lang;
+
+namespace IBBoard.WarFoundry.API.Savers.Xml
+{
+	public class WarFoundryXmlFileSaver : IWarFoundryFileSaver
+	{
+		public WarFoundryXmlFileSaver()
+		{
+		}
+
+		public bool Save(string path, params WarFoundryLoadedObject[] objects)
+		{
+			ZipOutputStream zipStream = new ZipOutputStream(new FileStream(path, FileMode.Create));
+			AddFiles(zipStream, objects);
+			zipStream.Close();
+			return true;
+		}
+
+		public void AddFiles(ZipOutputStream zipStream, WarFoundryLoadedObject[] objects)
+		{
+			foreach (WarFoundryLoadedObject obj in objects)
+			{
+				AddFile(zipStream, obj);
+			}
+		}
+
+		public void AddFile(ZipOutputStream zipStream, WarFoundryLoadedObject obj)
+		{
+			ZipEntry entry = new ZipEntry(GetZipEntryName(obj));
+			zipStream.PutNextEntry(entry);
+			byte[] bytes = GetObjectBytes(obj);
+			zipStream.Write(bytes, 0, bytes.Length);
+		}
+
+		public byte[] GetObjectBytes(WarFoundryLoadedObject obj)
+		{
+			string xmlString = "";
+			
+			if (obj is GameSystem)
+			{
+				xmlString = GetGameSystemString((GameSystem)obj);
+			}
+			else if (obj is Army)
+			{
+				xmlString = GetArmyString((Army)obj);
+			}
+			
+			return StringManipulation.StringToBytes(xmlString);
+		}		
+
+		public string GetGameSystemString(GameSystem obj)
+		{
+			return new WarFoundryXmlGameSystemSaver().CreateXmlString(obj);
+		}
+		
+		public string GetArmyString(Army obj)
+		{
+			return new WarFoundryXmlArmySaver().CreateXmlString(obj);
+		}
+
+		private string GetZipEntryName(WarFoundryLoadedObject obj)
+		{
+			return obj.ID + GetFileExtension(obj);
+		}
+
+		public string GetFileExtension(WarFoundryLoadedObject obj)
+		{
+			string ext = "";
+			
+			if (obj is Army)
+			{
+				ext = ".armyx";
+			}
+			else if (obj is Race)
+			{
+				ext = ".racex";
+			}
+			else if (obj is GameSystem)
+			{
+				ext = ".systemx";
+			}
+			
+			return ext;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Savers/Xml/WarFoundryXmlGameSystemSaver.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,106 @@
+// This file (WarFoundryXmlGameSystemSaver.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+using System.Xml.Schema;
+using IBBoard.Lang;
+using IBBoard.Xml;
+using IBBoard.WarFoundry.API.Factories.Xml.Zip;
+using IBBoard.WarFoundry.API.Objects;
+using IBBoard.WarFoundry.API.Savers;
+using IBBoard.WarFoundry.API.Util;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace IBBoard.WarFoundry.API.Savers.Xml
+{
+	public class WarFoundryXmlGameSystemSaver
+	{
+		public string CreateXmlString(GameSystem toSave)
+		{
+			XmlDocument doc = new XmlDocument();
+			XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
+			doc.AppendChild(declaration);
+			XmlSchema schema = new XmlSchema();
+			schema.Namespaces.Add("", "http://ibboard.co.uk/warfoundry/system");
+			schema.Namespaces.Add("cats", "http://ibboard.co.uk/warfoundry/cats");
+			doc.Schemas.Add(schema);
+			XmlElement root = doc.CreateElement("system");
+			root.SetAttribute("xmlns", "http://ibboard.co.uk/warfoundry/system");
+			root.SetAttribute("xmlns:cats", "http://ibboard.co.uk/warfoundry/cats");
+			doc.AppendChild(root);
+			root.SetAttribute("id", XmlTools.GetAsciiXmlIdForString(toSave.ID));
+			root.SetAttribute("name", toSave.Name);
+			root.SetAttribute("defaultArmySize", toSave.SystemArmyDefaultSize.ToString());
+			root.SetAttribute("warn", toSave.WarnOnError.ToString().ToLowerInvariant());
+			root.SetAttribute("allowAllies", toSave.AllowAllies.ToString().ToLowerInvariant());
+			XmlElement cats = doc.CreateElement("categories");
+			root.AppendChild(cats);
+
+			foreach (Category cat in toSave.Categories)
+			{
+				cats.AppendChild(CreateCategoryElement(cat, doc));
+			}
+
+			XmlElement sysStatsList = doc.CreateElement("sysStatsList");
+			sysStatsList.SetAttribute("defaultStats", XmlTools.GetAsciiXmlIdForString(toSave.StandardSystemStatsID));
+			root.AppendChild(sysStatsList);
+
+			foreach(SystemStats stats in toSave.SystemStats)
+			{
+				sysStatsList.AppendChild(CreateSystemStatsElement(stats, doc));
+			}
+
+			return doc.OuterXml;
+		}
+
+		private XmlElement CreateCategoryElement(Category cat, XmlDocument doc)
+		{
+			XmlElement catElem = doc.CreateElement("cats:cat");
+			catElem.SetAttribute("id", XmlTools.GetAsciiXmlIdForString(cat.ID));
+			catElem.SetAttribute("name", (cat.HasDefaultName() ? "" : cat.Name));
+			if (cat.MinimumPoints > 0)
+			{
+				catElem.SetAttribute("minPoints", cat.MaximumPercentage.ToString());
+			}
+			if (cat.MaximumPoints < 100)
+			{
+				catElem.SetAttribute("maxPoints", cat.MaximumPercentage.ToString());
+			}
+			if(cat.MinimumPercentage > 0)
+			{
+				catElem.SetAttribute("minPercentage", cat.MaximumPercentage.ToString());
+			}
+			if(cat.MaximumPercentage < 100)
+			{
+				catElem.SetAttribute("maxPercentage", cat.MaximumPercentage.ToString());
+			}
+
+			return catElem;
+		}
+
+		private XmlElement CreateSystemStatsElement(SystemStats stats, XmlDocument doc)
+		{
+			XmlElement statsElem = doc.CreateElement("sysStats");
+			statsElem.SetAttribute("id", XmlTools.GetAsciiXmlIdForString(stats.ID));
+
+			foreach(StatSlot stat in stats.StatSlots)
+			{
+				statsElem.AppendChild(CreateSystemStatElement(stat, doc));
+			}
+
+			return statsElem;
+		}
+
+		private XmlElement CreateSystemStatElement(StatSlot stat, XmlDocument doc)
+		{
+			XmlElement statElem = doc.CreateElement("sysStat");
+			statElem.SetAttribute("name", stat.Name);
+
+			return statElem;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/Util/UnitEquipmentUtil.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,211 @@
+// This file (UnitEquipmentUtil.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using IBBoard.CustomMath;
+using IBBoard.Limits;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API.Util
+{
+	public class UnitEquipmentUtil
+	{
+		/// <summary>
+		/// Gets an array of allowed <see cref="UnitEquipmentItem"/>s based on the current selections of the unit, taking in to account Mutex groups and other limits.
+		/// </summary>
+		/// <param name="unit">The <see cref="Unit"/> to get equipment items for</param>
+		/// <returns>The array of allowed <see cref="UnitEquipmentItem"/>s</returns>
+		public static UnitEquipmentItem[] GetAllowedEquipmentItems(Unit unit)
+		{
+			List<UnitEquipmentItem> list = new List<UnitEquipmentItem>();
+			UnitEquipmentItem[] currItems = unit.GetEquipment();
+
+			foreach (UnitEquipmentItem item in GetAllEquipmentItems(unit))
+			{
+				bool allowed = IsAllowedByMutex(item, currItems);
+
+				if (allowed)
+				{
+					list.Add(item);
+				}
+			}
+
+			return list.ToArray();
+		}
+
+		private static bool IsAllowedByMutex(UnitEquipmentItem item, UnitEquipmentItem[] currItems)
+		{
+			bool allowed = true;
+
+			foreach (UnitEquipmentItem currItem in currItems)
+			{
+				if (ItemsAreMutuallyExclusive(currItem, item))
+				{
+					allowed = false;
+					break;
+				}
+			}
+
+			return allowed;
+		}
+
+		/// <summary>
+		/// Gets a list of all <see cref="UnitEquipmentItem"/>s that would stop the unit taking <code>item</code> because of mutex groups.
+		/// </summary>
+		/// <param name="unit">The unit that wants to take the equipment item</param>
+		/// <param name="item">The item to check blocking items for</param>
+		/// <returns>a list of all <see cref="UnitEquipmentItem"/>s that would stop the unit taking <code>item</code></returns>
+		public static List<UnitEquipmentItem> GetBlockingEquipmentItems(Unit unit, UnitEquipmentItem item)
+		{
+			List<UnitEquipmentItem> items = new List<UnitEquipmentItem>();
+			UnitEquipmentItem[] currItems = unit.GetEquipment();
+
+			foreach (UnitEquipmentItem unitItem in currItems)
+			{
+				if (ItemsAreMutuallyExclusive(unitItem, item))
+				{
+					items.Add(unitItem);
+				}
+			}
+
+			return items;
+		}
+
+		public static UnitEquipmentItem[] GetAllEquipmentItems(Unit unit)
+		{
+			return unit.UnitType.GetEquipmentItems();
+		}
+
+		public static bool ItemsAreMutuallyExclusive(UnitEquipmentItem item1, UnitEquipmentItem item2)
+		{
+			bool areMutex = false;
+			string[] item1mutex = item1.MutexGroups;
+			string[] item2mutex = item2.MutexGroups;
+
+			foreach (string mutex in item1mutex)
+			{
+				foreach (string otherMutex in item2mutex)
+				{
+					if (mutex.Equals(otherMutex))
+					{
+						areMutex = true;
+						goto postLoop;
+					}
+				}
+			}
+			postLoop:
+
+			return areMutex;
+		}
+
+		public static int GetMaxEquipmentCount (Unit unit, UnitEquipmentItem equip)
+		{
+			return GetEquipmentCountLimit (unit, equip.MaxLimit.GetLimit(unit.Size), equip);
+		}
+
+		private static int GetEquipmentCountLimit (Unit unit, int currLimit, UnitEquipmentItem equip)
+		{
+			int newLimit = currLimit;
+			ILimit limit = GetSlotLimitForItem(unit, equip);
+			
+			if (!(limit is UnlimitedLimit))
+			{
+				int slotMax = limit.GetLimit (unit.Size) - unit.GetEquipmentAmountInSlotExcludingItem(equip);
+				newLimit = Math.Min (slotMax, newLimit);
+			}
+			
+			return newLimit;
+		}
+
+		private static ILimit GetSlotLimitForItem(Unit unit, UnitEquipmentItem equip)
+		{
+			return unit.UnitType.GetEquipmentSlotLimit(equip.SlotName);
+		}
+
+		
+		public static int GetMinEquipmentCount (Unit unit, UnitEquipmentItem equip)
+		{
+			return GetEquipmentCountLimit (unit, equip.MinLimit.GetLimit(unit.Size), equip);
+		}
+		
+		public static bool IsEquipmentRatioLimited(Unit unit, UnitEquipmentItem equip)
+		{
+			ILimit limit = GetSlotLimitForItem(unit, equip);
+			return equip.IsRatioLimit && (limit is IPercentageLimit || limit is UnlimitedLimit);
+		}
+		
+		public static double GetMaxEquipmentPercentage(Unit unit, UnitEquipmentItem equip)
+		{
+			return GetMinOfSlotLimitAndEquipmentLimit(equip, equip.MaxLimit, unit);
+		}
+		
+		private static double GetPercentageOfUnitSize(int number, Unit unit)
+		{
+			return IBBMath.Percentage(number, unit.Size);
+		}
+		
+		private static double GetMinOfSlotLimitAndEquipmentLimit(UnitEquipmentItem equip, ILimit equipLimit, Unit unit)
+		{			
+			double limit = 0;
+			ILimit slotLimit = GetSlotLimitForItem(unit, equip);
+			
+			if (slotLimit is IPercentageLimit)
+			{
+				limit = ((IPercentageLimit)slotLimit).Percentage - GetPercentageOfUnitSize(unit.GetEquipmentAmountInSlotExcludingItem(equip), unit);
+			}
+			else
+			{
+				int remaining = slotLimit.GetLimit(unit.Size) - unit.GetEquipmentAmountInSlotExcludingItem(equip);
+				limit = GetPercentageOfUnitSize(remaining, unit);
+			}
+			
+			if (equip.IsRatioLimit)
+			{
+				limit = Math.Min(limit, ((IPercentageLimit)equipLimit).Percentage);
+			}
+			else
+			{
+				limit = Math.Min(limit, GetPercentageOfUnitSize(equipLimit.GetLimit(unit.Size), unit));
+			}
+			
+			return limit;
+		}
+		
+		public static double GetMinEquipmentPercentage(Unit unit, UnitEquipmentItem equip)
+		{
+			return GetMinOfSlotLimitAndEquipmentLimit(equip, equip.MinLimit, unit);
+		}
+
+		public static double GetEquipmentAmount(Unit unit, UnitEquipmentItem item)
+		{
+			double amount = 0;
+			AbstractUnitEquipmentItemSelection selection = unit.GetEquipmentSelection(item);
+
+			if (selection != null)
+			{
+				amount = selection.AmountTaken;
+			}
+
+			return amount;
+		}
+
+		public static bool GetEquipmentAmountIsRatio(Unit unit, UnitEquipmentItem item)
+		{
+			return (unit.GetEquipmentSelection(item) is UnitEquipmentRatioSelection);
+		}
+
+		public static int GetEquipmentAmountTaken(Unit unit, UnitEquipmentItem item)
+		{
+			AbstractUnitEquipmentItemSelection selection = unit.GetEquipmentSelection(item);
+			return (selection == null ? 0 : selection.NumberTaken);
+		}
+		
+		public static bool CanEditEquipmentAmount(Unit unit, UnitEquipmentItem item)
+		{
+			return item !=null && (item.MaxLimit.GetLimit(unit.Size) != item.MinLimit.GetLimit(unit.Size));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/WarFoundryCore.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,77 @@
+// This file (WarFoundryCore.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using IBBoard.Logging;
+using IBBoard.WarFoundry.API.Objects;
+
+namespace IBBoard.WarFoundry.API
+{
+	public class WarFoundryCore
+	{		
+		public static readonly int INFINITY = -1;
+		public static event GameSystemChangedDelegate GameSystemChanged;
+		public static event ArmyChangedDelegate ArmyChanged;
+		
+		private static GameSystem system;
+		private static Army currentArmy;
+				
+		public static GameSystem CurrentGameSystem
+		{
+			get { return system; }
+			set
+			{
+				if (system==null || !system.Equals(value))
+				{
+					GameSystem oldSystem = system;
+					system = value;
+					
+					if (system==null)
+					{
+						LogNotifier.Debug(typeof(WarFoundryCore), "Game system set to null");
+					}
+					else
+					{
+						LogNotifier.DebugFormat(typeof(WarFoundryCore), "Game system set to {0} with ID {1}", system.Name, system.ID); 
+					}
+
+					if (GameSystemChanged!=null)
+					{
+						GameSystemChanged(oldSystem, system);
+					}
+					
+					//If we've changed the game system then we can't keep the current army
+					CurrentArmy = null;
+				}
+			}
+		}
+		
+		public static Army CurrentArmy
+		{
+			get { return currentArmy; }
+			set
+			{
+				if (currentArmy==null || !currentArmy.Equals(value))
+				{
+					Army oldArmy = currentArmy;
+					
+					if (value != null)
+					{
+						CurrentGameSystem = value.GameSystem; //Set the game system in case the new army is from a different system
+						currentArmy = value;
+					}
+					else
+					{
+						currentArmy = null;
+					}
+					
+					if (ArmyChanged!=null)
+					{
+						ArmyChanged(oldArmy, currentArmy);
+					}
+				}
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/API/WarFoundryLoader.cs	Sun Apr 03 18:50:32 2011 +0000
@@ -0,0 +1,40 @@
+// This file (WarFoundryLoader.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2007, 2008, 2009 IBBoard.
+//
+// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
+
+using System;
+using ICSharpCode.SharpZipLib.Zip;
+
+namespace IBBoard.WarFoundry.API
+{
+	public class WarFoundryLoader
+	{		
+		private static AbstractWarFoundryLoader loader;
+		
+		/// <summary>
+		/// Gets the default <see cref="WarFoundryLoader"/> used to load WarFoundry data files.
+		/// </summary>
+		/// <returns>
+		/// The default <see cref="WarFoundryLoader"/>
+		/// </returns>
+		public static AbstractWarFoundryLoader GetDefault()
+		{
+			if (loader == null)
+			{
+				loader = new DefaultWarFoundryLoader();
+			}
+			
+			return loader;
+		}
+		
+		public static void SetDefault(AbstractWarFoundryLoader newLoader)
+		{
+			loader = newLoader;	
+		}
+		
+		private WarFoundryLoader()
+		{
+			//Hide constructor
+		}
+	}
+}
--- a/IBBoard.WarFoundry.API.csproj	Sun Apr 03 13:31:12 2011 +0000
+++ b/IBBoard.WarFoundry.API.csproj	Sun Apr 03 18:50:32 2011 +0000
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -62,84 +62,7 @@
   <ItemGroup>
     <None Include="app.config" />
     <None Include="COPYING" />
-    <Compile Include="api\Objects\ICostedWarFoundryObject.cs" />
-    <Compile Include="api\Commands\CreateAndAddUnitCommand.cs" />
-    <Compile Include="api\Commands\RemoveUnitCommand.cs" />
-    <Compile Include="api\Commands\AbstractReplaceUnitEquipmentCommand.cs" />
-    <Compile Include="api\Commands\SetNameCommand.cs" />
-    <Compile Include="api\Commands\SetUnitEquipmentNumericAmountCommand.cs" />
-    <Compile Include="api\Commands\SetUnitSizeCommand.cs" />
-    <Compile Include="api\Delegates.cs" />
-    <Compile Include="api\Factories\AbstractNativeWarFoundryFactory.cs" />
-    <Compile Include="api\Factories\AbstractNonNativeFileExtensionWarFoundryFactory.cs" />
-    <Compile Include="api\Factories\AbstractNonNativeWarFoundryFactory.cs" />
-    <Compile Include="api\Factories\AbstractWarFoundryFactory.cs" />
-    <Compile Include="api\Factories\INativeWarFoundryFactory.cs" />
-    <Compile Include="api\Factories\INonNativeWarFoundryFactory.cs" />
-    <Compile Include="api\Factories\IWarFoundryFactory.cs" />
-    <Compile Include="api\Factories\Xml\WarFoundryXmlElementName.cs" />
-    <Compile Include="api\Factories\Xml\WarFoundryXmlFactory.cs" />
-    <Compile Include="api\FileLoadFailure.cs" />
-    <Compile Include="api\Objects\Ability.cs" />
-    <Compile Include="api\Objects\Army.cs" />
-    <Compile Include="api\Objects\ArmyCategory.cs" />
-    <Compile Include="api\Objects\Category.cs" />
-    <Compile Include="api\Objects\DuplicateItemException.cs" />
-    <Compile Include="api\Objects\EquipmentItem.cs" />
-    <Compile Include="api\Objects\GameSystem.cs" />
-    <Compile Include="api\Objects\IWarFoundryNativeSourceObject.cs" />
-    <Compile Include="api\Objects\IWarFoundryObject.cs" />
-    <Compile Include="api\Objects\IWarFoundryStagedLoadObject.cs" />
-    <Compile Include="api\Objects\Race.cs" />
-    <Compile Include="api\Objects\Stat.cs" />
-    <Compile Include="api\Objects\Stats.cs" />
-    <Compile Include="api\Objects\StatSlot.cs" />
-    <Compile Include="api\Objects\SystemStats.cs" />
-    <Compile Include="api\Objects\Unit.cs" />
-    <Compile Include="api\Objects\UnitEquipmentItem.cs" />
-    <Compile Include="api\Objects\UnitType.cs" />
-    <Compile Include="api\Objects\WarFoundryObject.cs" />
-    <Compile Include="api\Objects\WarFoundryStagedLoadingObject.cs" />
-    <Compile Include="api\Requirements\AbstractArmyRequirement.cs" />
-    <Compile Include="api\Requirements\AbstractFailedRequirement.cs" />
-    <Compile Include="api\Requirements\AbstractRequirement.cs" />
-    <Compile Include="api\Requirements\AbstractUnitRequirement.cs" />
-    <Compile Include="api\Requirements\Delegates.cs" />
-    <Compile Include="api\Requirements\FailedRequirement.cs" />
-    <Compile Include="api\Requirements\FailedUnitRequirement.cs" />
-    <Compile Include="api\Requirements\RequirementAND.cs" />
-    <Compile Include="api\Requirements\RequirementOR.cs" />
-    <Compile Include="api\Requirements\UnitExcludesRequirement.cs" />
-    <Compile Include="api\Requirements\UnitRequirement.cs" />
-    <Compile Include="api\Requirements\UnitRequirementItem.cs" />
-    <Compile Include="api\Requirements\UnitRequirementMaxNumber.cs" />
-    <Compile Include="api\Requirements\UnitRequirementMinNumber.cs" />
-    <Compile Include="api\Requirements\UnitRequiresAtLeastRequirement.cs" />
-    <Compile Include="api\Savers\IWarFoundryFileSaver.cs" />
-    <Compile Include="api\Savers\WarFoundrySaver.cs" />
-    <Compile Include="api\Util\UnitEquipmentUtil.cs" />
-    <Compile Include="api\WarFoundryCore.cs" />
-    <Compile Include="api\WarFoundryLoader.cs" />
     <Compile Include="AssemblyInfo.cs" />
-    <Compile Include="api\Factories\Xml\WarFoundryXmlGameSystemFactory.cs" />
-    <Compile Include="api\Factories\Xml\WarFoundryXmlRaceFactory.cs" />
-    <Compile Include="api\Factories\Xml\WarFoundryXmlArmyFactory.cs" />
-    <Compile Include="api\Factories\Xml\WarFoundryXmlFactoryUtils.cs" />
-    <Compile Include="api\Factories\Xml\AbstractStagedLoadedSubFactory.cs" />
-    <Compile Include="api\Objects\InvalidContainershipException.cs" />
-    <Compile Include="api\Objects\CompositeEquipmentItem.cs" />
-    <Compile Include="api\Objects\AbstractUnitEquipmentItemSelection.cs" />
-    <Compile Include="api\Objects\UnitEquipmentNumericSelection.cs" />
-    <Compile Include="api\Objects\UnitEquipmentRatioSelection.cs" />
-    <Compile Include="api\Commands\SetUnitEquipmentRatioAmountCommand.cs" />
-    <Compile Include="api\Commands\AbstractSetUnitEquipmentAmountCommand.cs" />
-    <Compile Include="api\Commands\ReplaceUnitEquipmentWithNumericAmountItemCommand.cs" />
-    <Compile Include="api\Commands\ReplaceUnitEquipmentWithRatioAmountItemCommand.cs" />
-    <Compile Include="api\Factories\Xml\Zip\StringZipEntrySource.cs" />
-    <Compile Include="api\Factories\RequiredDataMissingException.cs" />
-    <Compile Include="api\Factories\Xml\WarFoundryXmlArmyParser.cs" />
-    <Compile Include="api\Exporters\IWarFoundryExporter.cs" />
-    <Compile Include="api\Exporters\WarFoundryHtmlExporter.cs" />
     <None Include="schemas\army.xsd">
       <Gettext-ScanForTranslations>false</Gettext-ScanForTranslations>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -176,19 +99,96 @@
       <Gettext-ScanForTranslations>false</Gettext-ScanForTranslations>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <Compile Include="api\AbstractWarFoundryLoader.cs" />
-    <Compile Include="api\DefaultWarFoundryLoader.cs" />
-    <Compile Include="api\Objects\UnitMemberType.cs" />
-    <Compile Include="api\Factories\Xml\WarFoundryXmlLimitParser.cs" />
-    <Compile Include="api\Factories\DummyWarFoundryFactory.cs" />
-    <Compile Include="api\Objects\WarFoundryLoadedObject.cs" />
-    <Compile Include="api\Savers\Xml\WarFoundryXmlArmySaver.cs" />
-    <Compile Include="api\Savers\Xml\WarFoundryXmlGameSystemSaver.cs" />
-    <Compile Include="api\Savers\Xml\WarFoundryXmlFileSaver.cs" />
-    <Compile Include="api\Objects\Requirement\RequiresAtLeastNUnitsRequirement.cs" />
-    <Compile Include="api\Objects\Requirement\UnitCountRequirementData.cs" />
-    <Compile Include="api\Objects\Requirement\RequiresNoMoreThanNOfUnitTypeRequirement.cs" />
-    <Compile Include="api\Objects\Requirement\UnitRequiresAtLeastNUnitsRequirement.cs" />
+    <Compile Include="API\Objects\ICostedWarFoundryObject.cs" />
+    <Compile Include="API\Commands\CreateAndAddUnitCommand.cs" />
+    <Compile Include="API\Commands\RemoveUnitCommand.cs" />
+    <Compile Include="API\Commands\AbstractReplaceUnitEquipmentCommand.cs" />
+    <Compile Include="API\Commands\SetNameCommand.cs" />
+    <Compile Include="API\Commands\SetUnitEquipmentNumericAmountCommand.cs" />
+    <Compile Include="API\Commands\SetUnitSizeCommand.cs" />
+    <Compile Include="API\Delegates.cs" />
+    <Compile Include="API\Factories\AbstractNativeWarFoundryFactory.cs" />
+    <Compile Include="API\Factories\AbstractNonNativeFileExtensionWarFoundryFactory.cs" />
+    <Compile Include="API\Factories\AbstractNonNativeWarFoundryFactory.cs" />
+    <Compile Include="API\Factories\AbstractWarFoundryFactory.cs" />
+    <Compile Include="API\Factories\INativeWarFoundryFactory.cs" />
+    <Compile Include="API\Factories\INonNativeWarFoundryFactory.cs" />
+    <Compile Include="API\Factories\IWarFoundryFactory.cs" />
+    <Compile Include="API\Factories\Xml\WarFoundryXmlElementName.cs" />
+    <Compile Include="API\Factories\Xml\WarFoundryXmlFactory.cs" />
+    <Compile Include="API\FileLoadFailure.cs" />
+    <Compile Include="API\Objects\Ability.cs" />
+    <Compile Include="API\Objects\Army.cs" />
+    <Compile Include="API\Objects\ArmyCategory.cs" />
+    <Compile Include="API\Objects\Category.cs" />
+    <Compile Include="API\Objects\DuplicateItemException.cs" />
+    <Compile Include="API\Objects\EquipmentItem.cs" />
+    <Compile Include="API\Objects\GameSystem.cs" />
+    <Compile Include="API\Objects\IWarFoundryNativeSourceObject.cs" />
+    <Compile Include="API\Objects\IWarFoundryObject.cs" />
+    <Compile Include="API\Objects\IWarFoundryStagedLoadObject.cs" />
+    <Compile Include="API\Objects\Race.cs" />
+    <Compile Include="API\Objects\Stat.cs" />
+    <Compile Include="API\Objects\Stats.cs" />
+    <Compile Include="API\Objects\StatSlot.cs" />
+    <Compile Include="API\Objects\SystemStats.cs" />
+    <Compile Include="API\Objects\Unit.cs" />
+    <Compile Include="API\Objects\UnitEquipmentItem.cs" />
+    <Compile Include="API\Objects\UnitType.cs" />
+    <Compile Include="API\Objects\WarFoundryObject.cs" />
+    <Compile Include="API\Objects\WarFoundryStagedLoadingObject.cs" />
+    <Compile Include="API\Requirements\AbstractArmyRequirement.cs" />
+    <Compile Include="API\Requirements\AbstractFailedRequirement.cs" />
+    <Compile Include="API\Requirements\AbstractRequirement.cs" />
+    <Compile Include="API\Requirements\AbstractUnitRequirement.cs" />
+    <Compile Include="API\Requirements\Delegates.cs" />
+    <Compile Include="API\Requirements\FailedRequirement.cs" />
+    <Compile Include="API\Requirements\FailedUnitRequirement.cs" />
+    <Compile Include="API\Requirements\RequirementAND.cs" />
+    <Compile Include="API\Requirements\RequirementOR.cs" />
+    <Compile Include="API\Requirements\UnitExcludesRequirement.cs" />
+    <Compile Include="API\Requirements\UnitRequirement.cs" />
+    <Compile Include="API\Requirements\UnitRequirementItem.cs" />
+    <Compile Include="API\Requirements\UnitRequirementMaxNumber.cs" />
+    <Compile Include="API\Requirements\UnitRequirementMinNumber.cs" />
+    <Compile Include="API\Requirements\UnitRequiresAtLeastRequirement.cs" />
+    <Compile Include="API\Savers\IWarFoundryFileSaver.cs" />
+    <Compile Include="API\Savers\WarFoundrySaver.cs" />
+    <Compile Include="API\Util\UnitEquipmentUtil.cs" />
+    <Compile Include="API\WarFoundryCore.cs" />
+    <Compile Include="API\WarFoundryLoader.cs" />
+    <Compile Include="API\Factories\Xml\WarFoundryXmlGameSystemFactory.cs" />
+    <Compile Include="API\Factories\Xml\WarFoundryXmlRaceFactory.cs" />
+    <Compile Include="API\Factories\Xml\WarFoundryXmlArmyFactory.cs" />
+    <Compile Include="API\Factories\Xml\WarFoundryXmlFactoryUtils.cs" />
+    <Compile Include="API\Factories\Xml\AbstractStagedLoadedSubFactory.cs" />
+    <Compile Include="API\Objects\InvalidContainershipException.cs" />
+    <Compile Include="API\Objects\CompositeEquipmentItem.cs" />
+    <Compile Include="API\Objects\AbstractUnitEquipmentItemSelection.cs" />
+    <Compile Include="API\Objects\UnitEquipmentNumericSelection.cs" />
+    <Compile Include="API\Objects\UnitEquipmentRatioSelection.cs" />
+    <Compile Include="API\Commands\SetUnitEquipmentRatioAmountCommand.cs" />
+    <Compile Include="API\Commands\AbstractSetUnitEquipmentAmountCommand.cs" />
+    <Compile Include="API\Commands\ReplaceUnitEquipmentWithNumericAmountItemCommand.cs" />
+    <Compile Include="API\Commands\ReplaceUnitEquipmentWithRatioAmountItemCommand.cs" />
+    <Compile Include="API\Factories\Xml\Zip\StringZipEntrySource.cs" />
+    <Compile Include="API\Factories\RequiredDataMissingException.cs" />
+    <Compile Include="API\Factories\Xml\WarFoundryXmlArmyParser.cs" />
+    <Compile Include="API\Exporters\IWarFoundryExporter.cs" />
+    <Compile Include="API\Exporters\WarFoundryHtmlExporter.cs" />
+    <Compile Include="API\AbstractWarFoundryLoader.cs" />
+    <Compile Include="API\DefaultWarFoundryLoader.cs" />
+    <Compile Include="API\Objects\UnitMemberType.cs" />
+    <Compile Include="API\Factories\Xml\WarFoundryXmlLimitParser.cs" />
+    <Compile Include="API\Factories\DummyWarFoundryFactory.cs" />
+    <Compile Include="API\Objects\WarFoundryLoadedObject.cs" />
+    <Compile Include="API\Savers\Xml\WarFoundryXmlArmySaver.cs" />
+    <Compile Include="API\Savers\Xml\WarFoundryXmlGameSystemSaver.cs" />
+    <Compile Include="API\Savers\Xml\WarFoundryXmlFileSaver.cs" />
+    <Compile Include="API\Objects\Requirement\RequiresAtLeastNUnitsRequirement.cs" />
+    <Compile Include="API\Objects\Requirement\UnitCountRequirementData.cs" />
+    <Compile Include="API\Objects\Requirement\RequiresNoMoreThanNOfUnitTypeRequirement.cs" />
+    <Compile Include="API\Objects\Requirement\UnitRequiresAtLeastNUnitsRequirement.cs" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="System.Xml" />
--- a/api/AbstractWarFoundryLoader.cs	Sun Apr 03 13:31:12 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,695 +0,0 @@
-// This file (AbstractWarFoundryLoader.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
-// 
-// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using IBBoard.Collections;
-using IBBoard.IO;
-using IBBoard.Logging;
-using IBBoard.WarFoundry.API.Factories;
-using IBBoard.WarFoundry.API.Objects;
-
-namespace IBBoard.WarFoundry.API
-{
-	/// <summary>
-	/// The base abstract implementation of a WarFoundry file loader
-	/// </summary>
-	public abstract class AbstractWarFoundryLoader
-	{
-		private ICollection<DirectoryInfo> directories;
-		private ICollection<INativeWarFoundryFactory> factories;
-		private ICollection<INonNativeWarFoundryFactory> nonNativeFactories;
-		private Dictionary<IWarFoundryFactory, SimpleSet<IWarFoundryObject>> loadedObjects;
-
-		public delegate void FileLoadingCompleteDelegate(List<FileLoadFailure> failures);
-
-		public event MethodInvoker FileLoadingStarted;
-
-		public event FileLoadingCompleteDelegate FileLoadingFinished;
-
-		protected AbstractWarFoundryLoader()
-		{
-			directories = new List<DirectoryInfo>();
-			factories = new List<INativeWarFoundryFactory>();
-			nonNativeFactories = new List<INonNativeWarFoundryFactory>();
-			loadedObjects = new Dictionary<IWarFoundryFactory,SimpleSet<IWarFoundryObject>>();
-		}
-		
-		/// <summary>
-		/// Adds a directory to the collection of directories that will be searched for WarFoundry data fi