Mercurial > repos > IBBoard.WarFoundry.API
view api/WarFoundryLoader.cs @ 101:f7b9423c2a5a
Big mess of updates, breaking our rules on "commit little and often" because the code was so ugly.
This revision will be broken for the WinForms UI, but as MonoDevelop/eSVN don't have a way of committing multiple projects in one go it can't be helped (Eclipse's Team Sync view could handle it)
Fixes #122: Make usage of percentage or ratio common
* All usage of ratio amounts for equipment items should now assume percentage
* Properly calculate number taken for ratio selection (divide by 0 now we're using percentages)
Fixes #118: Allow equipment amounts of "ratio" equipment to be define as absolute or ratio amounts
* Added extra commands that differentiate between ratio and absolute amounts
Fixes #120: Numeric limit equipment items show large percentages
* Now made formatting treat ratios as percentages (don't multiply by 100)
* Move string formatting to UnitEquipmentItem...Selection classes
* Add method to Unit to say whether an equipment item is a numeric or ratio amount
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Thu, 13 Aug 2009 21:09:20 +0000 |
parents | 3ea0ab04352b |
children | c85ea8988455 |
line wrap: on
line source
// This file (WarFoundryLoader.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 under the GNU LGPL license, either version 3 of the License or (at your option) any later version. Please see COPYING.LGPL 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; using ICSharpCode.SharpZipLib.Zip; namespace IBBoard.WarFoundry.API { public class WarFoundryLoader { private static WarFoundryLoader loader; /// <summary> /// Gets the default <see cref="WarFoundryLoader"/> used to load WarFoundry data files. /// </summary> /// <returns> /// The default <see cref="WarFoundryLoader"/> /// </returns> public static WarFoundryLoader GetDefault() { if (loader == null) { loader = new WarFoundryLoader(); } return loader; } private ICollection<DirectoryInfo> directories; private ICollection<INativeWarFoundryFactory> factories; private ICollection<INonNativeWarFoundryFactory> nonNativeFactories; private Dictionary<string, GameSystem> systemsTable; private Dictionary<string, Dictionary<string, Dictionary<string, Race>>> racesTable; //Keys are: System, Race, SubRace private Dictionary<IWarFoundryFactory, SimpleSet<IWarFoundryObject>> loadedObjects; protected WarFoundryLoader() { 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 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 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 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 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="Dictionary"/> of files that failed to load mapped against the message that their failure returned /// </returns> public List<FileLoadFailure> LoadFiles() { LogNotifier.Debug(GetType(), "Load files"); 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)); LogNotifier.Debug(GetType(), failedLoads.Count + " failed file loads"); return failedLoads; } protected void PrepareForFileLoad() { //Just set up blank dictionaries for now - may try different and more complex handling in future systemsTable = new Dictionary<string,GameSystem>(); racesTable = new Dictionary<string,Dictionary<string,Dictionary<string,Race>>>(); } private List<FileLoadFailure> FillLoadableFiles(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems) { List<FileLoadFailure> fails = new List<FileLoadFailure>(); foreach (DirectoryInfo directory in directories) { 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); } } } 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) { bool loaded = false; LogNotifier.DebugFormat(GetType(), "Loading {0} using {1}", file.FullName, factory.GetType().Name); ICollection<IWarFoundryObject> objects = factory.CreateObjectsFromFile(file); if (objects.Count > 0) { AddLoadedObjects(objects, factory); loaded = true; } return loaded; } /// <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) { string sysid = system.ID.ToLower(); if (systemsTable.ContainsKey(sysid)) { LogNotifier.WarnFormat(GetType(), "System {0} ({1}) has already been loaded. Duplicate file ({3}) discarded", system.Name, system.ID, system.SourceFile.FullName); } else { systemsTable.Add(sysid, (GameSystem)system); } } protected void StoreRace(Race race) { Dictionary<string, Dictionary<string, Race>> systemRaces; if (race.GameSystem == null) { throw new InvalidOperationException("Race cannot have null game system. Game system should be loaded before race."); } 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); } if (subRaces.ContainsKey(race.SubID.ToLower())) { LogNotifier.WarnFormat(GetType(), "Race {0} ({1} - {2}) for system {3} ({4}) has already been loaded. Duplicate file ({5}) discarded", race.Name, race.ID, race.SubID, race.GameSystem.ID, race.GameSystem.Name, race.SourceFile.Name); race = null; } else { subRaces.Add(race.SubID.ToLower(), 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 GameSystem[] GetGameSystems() { if (systemsTable==null) { LoadFiles(); } return DictionaryUtils.ToArray<string, GameSystem>(systemsTable); } /// <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 GameSystem GetGameSystem(string systemID) { if (systemsTable==null) { LoadFiles(); } GameSystem system; systemsTable.TryGetValue(systemID.ToLower(), out system); return 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 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 (racesTable==null) { 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; } /// <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 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, ""); } /// <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 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 (racesTable==null) { LoadFiles(); } Race race = null; systemID = systemID.ToLower(); Dictionary<string, Dictionary<string, Race>> races; racesTable.TryGetValue(systemID, out races); if (races!=null) { Dictionary<string, Race> subraces; races.TryGetValue(raceID, out subraces); if (subraces!=null) { subraces.TryGetValue(raceSubID, out race); } } return 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 string[] GetGameSystemIDs() { if (systemsTable==null) { LoadFiles(); } string[] keys = new string[systemsTable.Keys.Count]; int i = 0; foreach (string key in systemsTable.Keys) { keys[i++] = key; } 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 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 (racesTable == null) { 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; } } } }