view API/Factories/Xml/WarFoundryXmlRaceFactory.cs @ 374:13793f3a2a2e

Re #351: Add extensible requirement handling method * Add initial conditional to fetching of factory * Exception if content isn't correct
author IBBoard <dev@ibboard.co.uk>
date Sat, 02 Jul 2011 14:59:45 +0000
parents bfdb95906075
children e50682387d63
line source
1 // This file (WarFoundryXmlRaceFactory.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
2 //
3 // 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.
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Xml;
9 using IBBoard.Xml;
10 using IBBoard.IO;
11 using IBBoard.Limits;
12 using IBBoard.CustomMath;
13 using ICSharpCode.SharpZipLib.Zip;
14 using IBBoard.WarFoundry.API.Objects;
15 using IBBoard.WarFoundry.API.Objects.Requirement;
16 using IBBoard.WarFoundry.API.Factories.Requirement;
18 namespace IBBoard.WarFoundry.API.Factories.Xml
19 {
20 /// <summary>
21 /// A sub-factory for loading WarFoundry Race XML files
22 /// </summary>
23 public class WarFoundryXmlRaceFactory : AbstractStagedLoadedSubFactory
24 {
25 private Dictionary<Race, XmlDocument> extraData = new Dictionary<Race, XmlDocument>();
26 private WarFoundryXmlLimitParser limitParser = new WarFoundryXmlLimitParser();
28 public WarFoundryXmlRaceFactory(WarFoundryXmlFactory factory) : base (factory)
29 {
30 //Do nothing special
31 }
33 private void StoreExtraData(Race wfObject, XmlElement elem)
34 {
35 extraData[wfObject] = elem.OwnerDocument;
36 }
38 private XmlDocument GetExtraData(Race obj)
39 {
40 XmlDocument extra = null;
41 extraData.TryGetValue(obj, out extra);
42 return extra;
43 }
45 public Race CreateRaceFromElement(ZipFile file, XmlElement elem)
46 {
47 string id = elem.GetAttribute("id");
48 string subid = elem.GetAttribute("subid");
49 string systemID = elem.GetAttribute("system");
50 string name = elem.GetAttribute("name");
51 string armyDefaultName = elem.GetAttribute("defaultArmyName");
52 GameSystem gameSystem = WarFoundryLoader.GetDefault ().GetGameSystem (systemID);
54 if (gameSystem == null)
55 {
56 throw new InvalidFileException("Referenced game system, '"+systemID+"', did not exist");
57 }
59 Race race = new Race(id, subid, name, gameSystem, mainFactory);
60 race.ArmyDefaultName = armyDefaultName;
61 StoreExtraData(race, elem);
62 return race;
63 }
65 public void CompleteLoading(Race race)
66 {
67 if (!WarFoundryXmlFactoryUtils.CanCompleteLoading(race))
68 {
69 return;
70 }
72 race.SetAsLoading();
73 XmlDocument extraData = GetExtraData(race);
75 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:categories/cat:cat"))
76 {
77 CreateCategoryFromElement(node, race);
78 }
80 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:equipment/race:equipmentItem"))
81 {
82 CreateEquipmentItemFromElement(node, race);
83 }
85 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:abilities/race:ability"))
86 {
87 CreateAbilityFromElement(node, race);
88 }
90 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:memberTypes/race:memberType"))
91 {
92 CreateMemberTypeFromElement(node, race);
93 }
95 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:units/race:unit"))
96 {
97 GetUnitTypeForElement(node, race);
98 }
100 race.SetAsFullyLoaded();
101 }
103 private Category CreateCategoryFromElement(XmlElement elem, Race parentRace)
104 {
105 Category cat = CreateCategoryFromElement(elem);
106 parentRace.AddCategory(cat);
107 return cat;
108 }
110 private UnitType GetUnitTypeFromDocument(XmlDocument doc, string id, Race parentRace)
111 {
112 XmlElement unitWithId = WarFoundryXmlFactoryUtils.SelectSingleElement (doc, "/race:race/race:units/race:unit[@id='" + id + "']");
114 if (unitWithId == null)
115 {
116 throw new InvalidFileException("Could not find unit with ID "+id);
117 }
119 return GetUnitTypeForElement(unitWithId, parentRace);
120 }
122 private UnitType GetUnitTypeForElement(XmlElement elem, Race parentRace)
123 {
124 string id = elem.GetAttribute("id");
125 UnitType type = parentRace.GetUnitType(id);
127 if (type==null)
128 {
129 type = CreateUnitTypeFromElement(elem, id, parentRace);
130 }
132 return type;
133 }
135 private UnitType CreateUnitTypeFromElement(XmlElement elem, string id, Race parentRace)
136 {
137 string name = elem.GetAttribute("typeName");
138 UnitType type = new UnitType(id, name, parentRace);
139 LoadCoreValuesForUnitType(elem, type);
140 LoadEquipmentSlotsForUnitType(elem, type);
141 LoadEquipmentForUnitType(elem, type);
142 LoadAbilitiesForUnitType(elem, type);
143 LoadContainedUnitsForUnitType(elem, type);
144 LoadRequirementsForUnitType(elem, type);
145 LoadExtraDataForUnitType(elem, type);
146 LoadNotesForUnitType(elem, type);
147 parentRace.AddUnitType(type);
148 return type;
149 }
151 private void LoadCoreValuesForUnitType(XmlElement elem, UnitType type)
152 {
153 try
154 {
155 type.MaxNumber = XmlTools.GetIntValueFromAttribute(elem, "maxNum");
156 type.MinNumber = XmlTools.GetIntValueFromAttribute(elem, "minNum");
157 type.MaxSize = XmlTools.GetIntValueFromAttribute(elem, "maxSize");
158 type.MinSize = XmlTools.GetIntValueFromAttribute(elem, "minSize");
159 type.BaseSize = XmlTools.GetIntValueFromAttribute(elem, "baseSize");
160 type.CostPerTrooper = XmlTools.GetDoubleValueFromAttribute(elem, "points");
161 type.BaseUnitCost = XmlTools.GetDoubleValueFromAttribute(elem, "basePoints");
162 }
163 catch (FormatException ex)
164 {
165 throw new InvalidFileException(ex.Message, ex);
166 }
168 Race race = type.Race;
169 string mainCatID = elem.GetAttribute("cat");
170 Category cat = race.GetCategory(mainCatID);
172 if (cat == null)
173 {
174 throw new InvalidFileException(String.Format("Category with ID '{1}' did not exist for UnitType '{0}'", type.Name, mainCatID));
175 }
177 type.MainCategory = cat;
179 XmlNodeList unitCategories = WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitCategories/race:unitCategory");
181 foreach (XmlElement unitCategory in unitCategories)
182 {
183 string catID = unitCategory.GetAttribute("catID");
184 Category unitCat = race.GetCategory(catID);
186 if (unitCat == null)
187 {
188 throw new InvalidFileException(String.Format("Category with ID '{1}' did not exist for UnitType '{0}'", type.Name, catID));
189 }
191 type.AddCategory(unitCat);
192 }
194 XmlElement statsElement = WarFoundryXmlFactoryUtils.SelectSingleElement(elem, "race:stats");
196 if (statsElement!=null)
197 {
198 Stats unitStats = ParseUnitStats(statsElement, type.GameSystem);
199 type.SetUnitStats(unitStats);
200 }
202 XmlNodeList unitMemberReferences = WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitMembers/race:unitMember");
204 foreach (XmlElement unitMemberRef in unitMemberReferences)
205 {
206 string typeID = unitMemberRef.GetAttribute("typeID");
207 UnitMemberType unitMemberType = race.GetUnitMemberType(typeID);
208 type.AddUnitMemberType(unitMemberType);
209 }
210 }
212 private void LoadEquipmentSlotsForUnitType(XmlElement elem, UnitType type)
213 {
214 foreach (XmlElement equipSlot in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:equipmentSlots/race:equipmentSlot"))
215 {
216 LoadEquipmentSlotForUnitType (type, equipSlot);
217 }
218 }
220 private void LoadEquipmentSlotForUnitType(UnitType type, XmlElement equipSlot)
221 {
222 string slotName = equipSlot.GetAttribute("name");
223 ILimit limit = GetMaxLimit(equipSlot);
225 if (limit != null)
226 {
227 type.AddEquipmentSlot(slotName, limit);
228 }
229 }
231 private ILimit GetMinLimit(XmlElement elem)
232 {
233 return limitParser.GetMinLimit(elem);
234 }
236 private ILimit GetMaxLimit(XmlElement elem)
237 {
238 return limitParser.GetMaxLimit(elem);
239 }
241 private void LoadEquipmentForUnitType(XmlElement elem, UnitType type)
242 {
243 foreach (XmlElement equip in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitEquipment/race:unitEquipmentItem"))
244 {
245 string id = equip.GetAttribute("id");
246 EquipmentItem equipItem = type.Race.GetEquipmentItem(id);
248 if (equipItem!=null)
249 {
250 string mutexGroupString = equip.GetAttribute("exclusivityGroups");
251 string[] mutexGroups;
253 if (mutexGroupString == "")
254 {
255 mutexGroupString = equip.GetAttribute("exclusivityGroup");
256 }
258 if (mutexGroupString != "")
259 {
260 string[] groups = mutexGroupString.Split(',');
261 int groupCount = groups.Length;
263 for (int i = 0; i < groupCount; i++)
264 {
265 groups[i] = groups[i].Trim();
266 }
268 mutexGroups = groups;
269 }
270 else
271 {
272 mutexGroups = new string[0];
273 }
275 UnitEquipmentItem unitEquipItem = new UnitEquipmentItem(equipItem, type, mutexGroups);
277 string equipSlot = equip.GetAttribute("equipmentSlot");
279 if (equipSlot != "")
280 {
281 if (type.HasEquipmentSlot(equipSlot))
282 {
283 unitEquipItem.SlotName = equipSlot;
284 }
285 else
286 {
287 throw new InvalidFileException("Attribute 'equipmentSlot' of unit equipment item " + id + " for " + type.Name + " was not a valid slot name");
288 }
289 }
291 ILimit limit = GetMaxLimit(equip);
293 if (limit != null)
294 {
295 unitEquipItem.MaxLimit = limit;
296 }
298 limit = GetMinLimit(equip);
300 if (limit != null)
301 {
302 unitEquipItem.MinLimit = limit;
303 }
305 unitEquipItem.RoundNumberUp = equip.GetAttribute("roundDirection").Equals("up");
307 try
308 {
309 unitEquipItem.IsRequired = XmlTools.GetBoolValueFromAttribute(equip, "required");
310 }
311 catch(FormatException e)
312 {
313 throw new InvalidFileException("Attribute 'required' of unit equipment item " + id + " for " + type.Name + " was not a valid boolean", e);
314 }
316 try
317 {
318 unitEquipItem.CostMultiplier = XmlTools.GetDoubleValueFromAttribute(equip, "costMultiplier");
319 }
320 catch (FormatException e)
321 {
322 throw new InvalidFileException("Attribute 'costMultiplier' of unit equipment item " + id + " for " + type.Name + " was not a valid decimal number", e);
323 }
325 try
326 {
327 unitEquipItem.CostRoundType = (RoundType) Enum.Parse(typeof(RoundType), equip.GetAttribute("costRounding"));
328 }
329 catch (ArgumentException e)
330 {
331 throw new InvalidFileException("Attribute 'costRounding' of unit equipment item " + id + " for " + type.Name + " was not a valid rounding type", e);
332 }
333 }
334 else
335 {
336 throw new InvalidFileException("Equipment item with ID '" + id + "' was required by " + type.Name + " but was not found");
337 }
338 }
339 }
341 private void LoadAbilitiesForUnitType(XmlElement elem, UnitType type)
342 {
343 foreach (XmlElement abilityElem in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitAbilities/race:unitAbility"))
344 {
345 string id = abilityElem.GetAttribute("abilityID");
346 Ability ability = type.Race.GetAbility(id);
348 if (ability == null)
349 {
350 throw new InvalidFileException("Ability for "+type.Name+ " with ID "+id+ " did not exist in race definition");
351 }
353 bool required = XmlTools.GetBoolValueFromAttribute(abilityElem, "required");
354 type.AddAbility(ability, required);
355 }
356 }
358 private void LoadContainedUnitsForUnitType(XmlElement elem, UnitType type)
359 {
360 foreach (XmlElement containedUnitType in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:contains/race:containedUnit"))
361 {
362 string id = containedUnitType.GetAttribute("containedID");
363 UnitType containedType = GetUnitTypeFromDocument(elem.OwnerDocument, id, type.Race);
365 if (containedType!=null)
366 {
367 type.AddContainedUnitType(containedType);
368 }
369 else
370 {
371 throw new InvalidFileException("Unit type " + type.Name + " tried to contain undefined unit with ID "+id);
372 }
373 }
374 }
376 private void LoadRequirementsForUnitType(XmlElement elem, UnitType type)
377 {
378 foreach (XmlElement extraData in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:requirements/race:requirement"))
379 {
380 string name = extraData.GetAttribute("requirementName");
381 UnitRequiresAtLeastNUnitsRequirementFactory reqFactory = GetRequirementFactory(name);
383 if (reqFactory != null) {
384 string data = WarFoundryXmlFactoryUtils.SelectSingleElement(extraData, "race:data").InnerText;
385 UnitRequiresAtLeastNUnitsRequirement req = reqFactory.CreateRequirement(type, data);
386 type.AddRequirement(req);
387 }
388 }
389 }
391 private UnitRequiresAtLeastNUnitsRequirementFactory GetRequirementFactory (string name)
392 {
394 UnitRequiresAtLeastNUnitsRequirementFactory factory = null;
396 if ("RequiresAtLeastNUnits".Equals(name))
397 {
398 factory = new UnitRequiresAtLeastNUnitsRequirementFactory();
399 }
401 return factory;
402 }
404 private void LoadExtraDataForUnitType(XmlElement elem, UnitType type)
405 {
406 foreach (XmlElement extraData in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:extraData/race:data"))
407 {
408 string id = extraData.GetAttribute("id");
409 string data = extraData.InnerXml;
410 type.AddExtraData(id, data);
411 }
412 }
414 private void LoadNotesForUnitType(XmlElement elem, UnitType type)
415 {
416 XmlNode node = WarFoundryXmlFactoryUtils.SelectSingleNode(elem, "race:notes");
418 if (node!=null)
419 {
420 type.Notes = node.InnerText;
421 }
422 }
424 private Stats ParseUnitStats(XmlElement elem, GameSystem system)
425 {
426 if (elem == null)
427 {
428 return null;
429 }
431 String statsID = elem.GetAttribute("statSet");
432 SystemStats statsSet;
434 if (statsID == "")
435 {
436 statsSet = system.StandardSystemStats;
437 }
438 else
439 {
440 statsSet = system.GetSystemStatsForID(statsID);
441 }
443 Stats stats = new Stats(statsSet);
445 foreach (XmlElement stat in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:stat"))
446 {
447 String statName = stat.GetAttribute("name");
448 stats.SetStatValue(statName, stat.InnerText);
449 }
451 return stats;
452 }
454 private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, Race race)
455 {
456 string id = elem.GetAttribute("id");
457 EquipmentItem item = race.GetEquipmentItem(id);
459 if (item == null)
460 {
461 item = CreateEquipmentItemFromElement(elem, id, race);
462 }
464 return item;
465 }
467 private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, string id, Race race)
468 {
469 string name = elem.GetAttribute("name");
470 EquipmentItem item = new EquipmentItem(id, name, race);
471 double cost = 0;
473 try
474 {
475 cost = XmlTools.GetDoubleValueFromAttribute(elem, "cost");
476 }
477 catch(FormatException ex)
478 {
479 throw new InvalidFileException("Attribute 'cost' of equipment item "+id+" was not a valid number", ex);
480 }
482 //TODO: Parse equipment stats if there are any
483 item.Cost = cost;
484 race.AddEquipmentItem(item);
485 return item;
486 }
488 private Ability CreateAbilityFromElement(XmlElement elem, Race race)
489 {
490 string id = elem.GetAttribute("id");
491 string name = elem.GetAttribute("name");
492 Ability ability = new Ability(id, name);
493 XmlNode node = WarFoundryXmlFactoryUtils.SelectSingleNode(elem, "race:description");
494 ability.Description = (node == null) ? "" : node.InnerText;
495 race.AddAbility(ability);
496 return ability;
497 }
499 private void CreateMemberTypeFromElement(XmlElement elem, Race race)
500 {
501 Stats stats = ParseUnitStats(WarFoundryXmlFactoryUtils.SelectSingleElement(elem, "race:stats"), race.GameSystem);
502 UnitMemberType unitMemberType = new UnitMemberType(elem.GetAttribute("id"), elem.GetAttribute("name"), stats);
503 race.AddUnitMemberType(unitMemberType);
504 }
505 }
506 }