view API/Factories/Xml/WarFoundryXmlRaceFactory.cs @ 380:7036bf04e955

Re #351: Add extensible requirement handling method * Move to using registered requirement factories
author IBBoard <dev@ibboard.co.uk>
date Sat, 23 Jul 2011 20:01:26 +0000
parents 04f4c2fea356
children 8811737baebf
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 : IRaceFactory<XmlDocument, XmlElement>
24 {
25 private Dictionary<Race, XmlDocument> extraData = new Dictionary<Race, XmlDocument>();
26 private WarFoundryXmlLimitParser limitParser = new WarFoundryXmlLimitParser();
27 private WarFoundryXmlFactory mainFactory;
29 public WarFoundryXmlRaceFactory(WarFoundryXmlFactory factory)
30 {
31 this.mainFactory = factory;
32 }
34 private void StoreExtraData(Race wfObject, XmlElement elem)
35 {
36 extraData[wfObject] = elem.OwnerDocument;
37 }
39 private XmlDocument GetExtraData(Race obj)
40 {
41 XmlDocument extra = null;
42 extraData.TryGetValue(obj, out extra);
43 return extra;
44 }
46 public Race CreateRace(XmlElement elem)
47 {
48 string id = elem.GetAttribute("id");
49 string subid = elem.GetAttribute("subid");
50 string systemID = elem.GetAttribute("system");
51 string name = elem.GetAttribute("name");
52 string armyDefaultName = elem.GetAttribute("defaultArmyName");
53 GameSystem gameSystem = WarFoundryLoader.GetDefault ().GetGameSystem (systemID);
55 if (gameSystem == null)
56 {
57 throw new InvalidFileException("Referenced game system, '"+systemID+"', did not exist");
58 }
60 Race race = new Race(id, subid, name, gameSystem, mainFactory);
61 race.ArmyDefaultName = armyDefaultName;
62 StoreExtraData(race, elem);
63 return race;
64 }
66 public void CompleteLoading(Race race)
67 {
68 if (!WarFoundryXmlFactoryUtils.CanCompleteLoading(race))
69 {
70 return;
71 }
73 race.SetAsLoading();
74 XmlDocument extraData = GetExtraData(race);
76 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:categories/cat:cat"))
77 {
78 CreateCategoryFromElement(node, race);
79 }
81 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:equipment/race:equipmentItem"))
82 {
83 CreateEquipmentItemFromElement(node, race);
84 }
86 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:abilities/race:ability"))
87 {
88 CreateAbilityFromElement(node, race);
89 }
91 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:memberTypes/race:memberType"))
92 {
93 CreateMemberTypeFromElement(node, race);
94 }
96 foreach (XmlElement node in WarFoundryXmlFactoryUtils.SelectNodes(extraData, "/race:race/race:units/race:unit"))
97 {
98 GetUnitTypeForElement(node, race);
99 }
101 race.SetAsFullyLoaded();
102 }
104 private Category CreateCategoryFromElement(XmlElement elem, Race parentRace)
105 {
106 Category cat = CategoryLoader.CreateFromElement(elem);
107 parentRace.AddCategory(cat);
108 return cat;
109 }
112 public UnitType GetUnitType(string id, Race parentRace)
113 {
114 return GetUnitType(id, parentRace, GetExtraData(parentRace));
115 }
117 public UnitType GetUnitType(string id, Race parentRace, XmlDocument doc)
118 {
119 UnitType type = parentRace.GetUnitType(id);
121 if (type==null)
122 {
123 type = GetUnitTypeFromDocument(doc, id, parentRace);
124 }
126 return type;
127 }
129 private UnitType GetUnitTypeFromDocument(XmlDocument doc, string id, Race parentRace)
130 {
131 XmlElement unitWithId = WarFoundryXmlFactoryUtils.SelectSingleElement (doc, "/race:race/race:units/race:unit[@id='" + id + "']");
133 if (unitWithId == null)
134 {
135 throw new InvalidFileException("Could not find unit with ID "+id);
136 }
138 return GetUnitTypeForElement(unitWithId, parentRace);
139 }
141 private UnitType GetUnitTypeForElement(XmlElement elem, Race parentRace)
142 {
143 string id = elem.GetAttribute("id");
144 UnitType type = parentRace.GetUnitType(id);
146 if (type==null)
147 {
148 type = CreateUnitTypeFromElement(elem, id, parentRace);
149 }
151 return type;
152 }
154 private UnitType CreateUnitTypeFromElement(XmlElement elem, string id, Race parentRace)
155 {
156 string name = elem.GetAttribute("typeName");
157 UnitType type = new UnitType(id, name, parentRace);
158 LoadCoreValuesForUnitType(elem, type);
159 LoadEquipmentSlotsForUnitType(elem, type);
160 LoadEquipmentForUnitType(elem, type);
161 LoadAbilitiesForUnitType(elem, type);
162 LoadContainedUnitsForUnitType(elem, type);
163 LoadRequirementsForUnitType(elem, type);
164 LoadExtraDataForUnitType(elem, type);
165 LoadNotesForUnitType(elem, type);
166 parentRace.AddUnitType(type);
167 return type;
168 }
170 private void LoadCoreValuesForUnitType(XmlElement elem, UnitType type)
171 {
172 try
173 {
174 type.MaxNumber = XmlTools.GetIntValueFromAttribute(elem, "maxNum");
175 type.MinNumber = XmlTools.GetIntValueFromAttribute(elem, "minNum");
176 type.MaxSize = XmlTools.GetIntValueFromAttribute(elem, "maxSize");
177 type.MinSize = XmlTools.GetIntValueFromAttribute(elem, "minSize");
178 type.BaseSize = XmlTools.GetIntValueFromAttribute(elem, "baseSize");
179 type.CostPerTrooper = XmlTools.GetDoubleValueFromAttribute(elem, "points");
180 type.BaseUnitCost = XmlTools.GetDoubleValueFromAttribute(elem, "basePoints");
181 }
182 catch (FormatException ex)
183 {
184 throw new InvalidFileException(ex.Message, ex);
185 }
187 Race race = type.Race;
188 string mainCatID = elem.GetAttribute("cat");
189 Category cat = race.GetCategory(mainCatID);
191 if (cat == null)
192 {
193 throw new InvalidFileException(String.Format("Category with ID '{1}' did not exist for UnitType '{0}'", type.Name, mainCatID));
194 }
196 type.MainCategory = cat;
198 XmlNodeList unitCategories = WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitCategories/race:unitCategory");
200 foreach (XmlElement unitCategory in unitCategories)
201 {
202 string catID = unitCategory.GetAttribute("catID");
203 Category unitCat = race.GetCategory(catID);
205 if (unitCat == null)
206 {
207 throw new InvalidFileException(String.Format("Category with ID '{1}' did not exist for UnitType '{0}'", type.Name, catID));
208 }
210 type.AddCategory(unitCat);
211 }
213 XmlElement statsElement = WarFoundryXmlFactoryUtils.SelectSingleElement(elem, "race:stats");
215 if (statsElement!=null)
216 {
217 Stats unitStats = ParseUnitStats(statsElement, type.GameSystem);
218 type.SetUnitStats(unitStats);
219 }
221 XmlNodeList unitMemberReferences = WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitMembers/race:unitMember");
223 foreach (XmlElement unitMemberRef in unitMemberReferences)
224 {
225 string typeID = unitMemberRef.GetAttribute("typeID");
226 UnitMemberType unitMemberType = race.GetUnitMemberType(typeID);
227 type.AddUnitMemberType(unitMemberType);
228 }
229 }
231 private void LoadEquipmentSlotsForUnitType(XmlElement elem, UnitType type)
232 {
233 foreach (XmlElement equipSlot in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:equipmentSlots/race:equipmentSlot"))
234 {
235 LoadEquipmentSlotForUnitType (type, equipSlot);
236 }
237 }
239 private void LoadEquipmentSlotForUnitType(UnitType type, XmlElement equipSlot)
240 {
241 string slotName = equipSlot.GetAttribute("name");
242 ILimit limit = GetMaxLimit(equipSlot);
244 if (limit != null)
245 {
246 type.AddEquipmentSlot(slotName, limit);
247 }
248 }
250 private ILimit GetMinLimit(XmlElement elem)
251 {
252 return limitParser.GetMinLimit(elem);
253 }
255 private ILimit GetMaxLimit(XmlElement elem)
256 {
257 return limitParser.GetMaxLimit(elem);
258 }
260 private void LoadEquipmentForUnitType(XmlElement elem, UnitType type)
261 {
262 foreach (XmlElement equip in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitEquipment/race:unitEquipmentItem"))
263 {
264 string id = equip.GetAttribute("id");
265 EquipmentItem equipItem = type.Race.GetEquipmentItem(id);
267 if (equipItem!=null)
268 {
269 string mutexGroupString = equip.GetAttribute("exclusivityGroups");
270 string[] mutexGroups;
272 if (mutexGroupString == "")
273 {
274 mutexGroupString = equip.GetAttribute("exclusivityGroup");
275 }
277 if (mutexGroupString != "")
278 {
279 string[] groups = mutexGroupString.Split(',');
280 int groupCount = groups.Length;
282 for (int i = 0; i < groupCount; i++)
283 {
284 groups[i] = groups[i].Trim();
285 }
287 mutexGroups = groups;
288 }
289 else
290 {
291 mutexGroups = new string[0];
292 }
294 UnitEquipmentItem unitEquipItem = new UnitEquipmentItem(equipItem, type, mutexGroups);
296 string equipSlot = equip.GetAttribute("equipmentSlot");
298 if (equipSlot != "")
299 {
300 if (type.HasEquipmentSlot(equipSlot))
301 {
302 unitEquipItem.SlotName = equipSlot;
303 }
304 else
305 {
306 throw new InvalidFileException("Attribute 'equipmentSlot' of unit equipment item " + id + " for " + type.Name + " was not a valid slot name");
307 }
308 }
310 ILimit limit = GetMaxLimit(equip);
312 if (limit != null)
313 {
314 unitEquipItem.MaxLimit = limit;
315 }
317 limit = GetMinLimit(equip);
319 if (limit != null)
320 {
321 unitEquipItem.MinLimit = limit;
322 }
324 unitEquipItem.RoundNumberUp = equip.GetAttribute("roundDirection").Equals("up");
326 try
327 {
328 unitEquipItem.IsRequired = XmlTools.GetBoolValueFromAttribute(equip, "required");
329 }
330 catch(FormatException e)
331 {
332 throw new InvalidFileException("Attribute 'required' of unit equipment item " + id + " for " + type.Name + " was not a valid boolean", e);
333 }
335 try
336 {
337 unitEquipItem.CostMultiplier = XmlTools.GetDoubleValueFromAttribute(equip, "costMultiplier");
338 }
339 catch (FormatException e)
340 {
341 throw new InvalidFileException("Attribute 'costMultiplier' of unit equipment item " + id + " for " + type.Name + " was not a valid decimal number", e);
342 }
344 try
345 {
346 unitEquipItem.CostRoundType = (RoundType) Enum.Parse(typeof(RoundType), equip.GetAttribute("costRounding"));
347 }
348 catch (ArgumentException e)
349 {
350 throw new InvalidFileException("Attribute 'costRounding' of unit equipment item " + id + " for " + type.Name + " was not a valid rounding type", e);
351 }
352 }
353 else
354 {
355 throw new InvalidFileException("Equipment item with ID '" + id + "' was required by " + type.Name + " but was not found");
356 }
357 }
358 }
360 private void LoadAbilitiesForUnitType(XmlElement elem, UnitType type)
361 {
362 foreach (XmlElement abilityElem in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:unitAbilities/race:unitAbility"))
363 {
364 string id = abilityElem.GetAttribute("abilityID");
365 Ability ability = type.Race.GetAbility(id);
367 if (ability == null)
368 {
369 throw new InvalidFileException("Ability for "+type.Name+ " with ID "+id+ " did not exist in race definition");
370 }
372 bool required = XmlTools.GetBoolValueFromAttribute(abilityElem, "required");
373 type.AddAbility(ability, required);
374 }
375 }
377 private void LoadContainedUnitsForUnitType(XmlElement elem, UnitType type)
378 {
379 foreach (XmlElement containedUnitType in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:contains/race:containedUnit"))
380 {
381 string id = containedUnitType.GetAttribute("containedID");
382 UnitType containedType = GetUnitTypeFromDocument(elem.OwnerDocument, id, type.Race);
384 if (containedType!=null)
385 {
386 type.AddContainedUnitType(containedType);
387 }
388 else
389 {
390 throw new InvalidFileException("Unit type " + type.Name + " tried to contain undefined unit with ID "+id);
391 }
392 }
393 }
395 private void LoadRequirementsForUnitType(XmlElement elem, UnitType type)
396 {
397 foreach (XmlElement extraData in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:requirements/race:requirement"))
398 {
399 string name = extraData.GetAttribute("requirementName");
400 IRequirementFactory reqFactory = WarFoundryLoader.GetRequirementFactory(name);
402 if (reqFactory != null) {
403 string data = WarFoundryXmlFactoryUtils.SelectSingleElement(extraData, "race:data").InnerText;
404 IRequirement req = reqFactory.CreateRequirement(type, data, this);
405 type.AddRequirement(req);
406 }
407 }
408 }
410 private void LoadExtraDataForUnitType(XmlElement elem, UnitType type)
411 {
412 foreach (XmlElement extraData in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:extraData/race:data"))
413 {
414 string id = extraData.GetAttribute("id");
415 string data = extraData.InnerXml;
416 type.AddExtraData(id, data);
417 }
418 }
420 private void LoadNotesForUnitType(XmlElement elem, UnitType type)
421 {
422 XmlNode node = WarFoundryXmlFactoryUtils.SelectSingleNode(elem, "race:notes");
424 if (node!=null)
425 {
426 type.Notes = node.InnerText;
427 }
428 }
430 private Stats ParseUnitStats(XmlElement elem, GameSystem system)
431 {
432 if (elem == null)
433 {
434 return null;
435 }
437 String statsID = elem.GetAttribute("statSet");
438 SystemStats statsSet;
440 if (statsID == "")
441 {
442 statsSet = system.StandardSystemStats;
443 }
444 else
445 {
446 statsSet = system.GetSystemStatsForID(statsID);
447 }
449 Stats stats = new Stats(statsSet);
451 foreach (XmlElement stat in WarFoundryXmlFactoryUtils.SelectNodes(elem, "race:stat"))
452 {
453 String statName = stat.GetAttribute("name");
454 stats.SetStatValue(statName, stat.InnerText);
455 }
457 return stats;
458 }
460 private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, Race race)
461 {
462 string id = elem.GetAttribute("id");
463 EquipmentItem item = race.GetEquipmentItem(id);
465 if (item == null)
466 {
467 item = CreateEquipmentItemFromElement(elem, id, race);
468 }
470 return item;
471 }
473 private EquipmentItem CreateEquipmentItemFromElement(XmlElement elem, string id, Race race)
474 {
475 string name = elem.GetAttribute("name");
476 EquipmentItem item = new EquipmentItem(id, name, race);
477 double cost = 0;
479 try
480 {
481 cost = XmlTools.GetDoubleValueFromAttribute(elem, "cost");
482 }
483 catch(FormatException ex)
484 {
485 throw new InvalidFileException("Attribute 'cost' of equipment item "+id+" was not a valid number", ex);
486 }
488 //TODO: Parse equipment stats if there are any
489 item.Cost = cost;
490 race.AddEquipmentItem(item);
491 return item;
492 }
494 private Ability CreateAbilityFromElement(XmlElement elem, Race race)
495 {
496 string id = elem.GetAttribute("id");
497 string name = elem.GetAttribute("name");
498 Ability ability = new Ability(id, name);
499 XmlNode node = WarFoundryXmlFactoryUtils.SelectSingleNode(elem, "race:description");
500 ability.Description = (node == null) ? "" : node.InnerText;
501 race.AddAbility(ability);
502 return ability;
503 }
505 private void CreateMemberTypeFromElement(XmlElement elem, Race race)
506 {
507 Stats stats = ParseUnitStats(WarFoundryXmlFactoryUtils.SelectSingleElement(elem, "race:stats"), race.GameSystem);
508 UnitMemberType unitMemberType = new UnitMemberType(elem.GetAttribute("id"), elem.GetAttribute("name"), stats);
509 race.AddUnitMemberType(unitMemberType);
510 }
511 }
512 }