// 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; using IBBoard.WarFoundry.API.Objects.Requirement; namespace IBBoard.WarFoundry.API.Objects { /// /// A UnitType is a type for a , 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. /// 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 requirements = new List(); private Dictionary equipment = new Dictionary(); private Dictionary> equipmentExclusionGroups = new Dictionary>(); private List equipmentKeyOrder = new List(); private Dictionary requiredAbilities = new Dictionary(); private Dictionary optionalAbilities = new Dictionary(); private String notes = ""; private List containedTypes = new List(); private Dictionary extraData = new Dictionary(); private Dictionary slotLimits = new Dictionary(); private Dictionary unitMemberTypes = new Dictionary(); private List cats = new List(); public UnitType(string id, string typeName, Race parentRace) : base(id, typeName) { race = parentRace; } public GameSystem GameSystem { get { return Race.GameSystem; } } /// /// Gets the that this unit belongs to. /// public Race Race { get { return race; } } /// /// Gets or sets the default that this unit type is a member of. /// If it is not already in the collection of categories then it will be added. /// public virtual Category MainCategory { get { return mainCat; } set { mainCat = value; AddCategory(value); } } /// /// Gets the collection of objects that this UnitType can be a member of /// public Category[] Categories { get { return cats.ToArray(); } } /// /// Adds a category to the set of categories that this unit can be taken from. The first category added will automatically become the MainCategory. /// /// /// A that this unit can be taken from /// public void AddCategory(Category cat) { if (!cats.Contains(cat)) { cats.Add(cat); if (MainCategory == null) { MainCategory = cat; } } } /// /// 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 /// public int MinSize { get { return minSize; } set { minSize = (value >= 0 ? value : 0); CheckMinimumSize(); } } /// /// 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 /// public int MaxSize { get { return maxSize; } set { maxSize = (value >= 0 ? value : WarFoundryCore.INFINITY); CheckMinimumSize(); } } /// /// 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 /// public int MinNumber { get { return min; } set { min = (value >= 0 ? value : 0); CheckMinimumNumber(); } } /// /// 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 /// public int MaxNumber { get { return max; } set { max = (value >= 0 ? value : WarFoundryCore.INFINITY); CheckMinimumNumber(); } } /// /// Makes sure that the minimum number isn't more than the maximum number, hence the warning on the properties /// 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); } } /// /// Makes sure that the minimum unit size isn't more than the maximum unit size, hence the warning on the properties /// 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); } } //// /// 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. /// public int BaseSize { get { return baseSize; } set { baseSize = (value >= 0 ? value : 0); } } /// /// The number of points that a "base unit" of BaseSize models costs. Additional models are charged at CostPerTrooper each. /// public double BaseUnitCost { get { return baseUnitCost; } set { baseUnitCost = (value >= 0 ? value : 0); } } //// /// 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. /// 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"); } /// /// The array of s for each of the unit's stat lines /// 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; } } //// /// The array of s for each of the unit's stat lines including an additional column that contains the unit type name /// 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 items = DictionaryUtils.GetValue(equipmentExclusionGroups, mutexGroup); if (items == null) { items = new List(); equipmentExclusionGroups.Add(mutexGroup, items); } items.Add(item); } } /// /// Gets a for the given ID string, or null if nothing exists for that ID /// /// /// The ID of the UnitEquipmentItem to get /// /// /// The for the given ID string, or null if nothing exists for that ID /// public UnitEquipmentItem GetEquipmentItem(string id) { return DictionaryUtils.GetValue(equipment, id); } /// /// Gets a for the given , or null if the unit can't take that EquipmentItem /// /// /// The to get the /// /// /// The that definies the UnitType's restrictions for taking the /// public UnitEquipmentItem GetEquipmentItem(EquipmentItem item) { return GetEquipmentItem(item.ID); } /// /// Gets an array of all available s for this UnitType /// /// /// An array of all available s for this UnitType /// public UnitEquipmentItem[] GetEquipmentItems() { return DictionaryUtils.ToArray(equipment); } public UnitEquipmentItem[] GetEquipmentItemsByExclusionGroup(string group) { return GetEquipmentItemsByExclusionGroups(new string[] { group }); } public UnitEquipmentItem[] GetEquipmentItemsByExclusionGroups(string[] groups) { List list = new List(); foreach (string group in groups) { List 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 GetRequiredAbilities() { return requiredAbilities.Values; } public ICollection 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(IRequirement requirement) { requirements.Add(requirement); } public IRequirement[] GetRequirements() { return requirements.ToArray(); } [Obsolete] public List CanAddToArmy(Army army) { List failures = new List(); return failures; } [Obsolete] public List CanRemoveFromArmy(Army army) { List failures = new List(); 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); } /// /// Gets the maximum limit on the number of items allowed in a single slot /// /// The name of the equipment slot to get the limit for /// 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 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); } } }