view api/Objects/UnitType.cs @ 150:b36cc4af435b

Re #176: Bug when saving recently edited army * Try to make sure that we clear up more of our open streams Bug seems to be state related in some way since I can only trigger it when loading the file as the first action, but doesn't seem to be related to file loading of other data files since a diagnostic hard-coded "LoadFiles()" call in the FrmMain constructor doesn't change the behaviour
author IBBoard <dev@ibboard.co.uk>
date Sat, 26 Sep 2009 10:43:28 +0000
parents 5145b7c61ae0
children 0c0e14f03785
line wrap: on
line source

// 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.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>();


		public UnitType(string id, string typeName, Race parentRace) : base(id, typeName)
		{
			race = parentRace;
		}

		[Obsolete("Use three parameter constructor and setters")]
		public UnitType(string id, string typeName, string mainCategoryID, string[] allCategoryIDs, int minNum, int maxNum, int minimumSize, int maximumSize, double unitCost, double trooperCost, Stats unitStats, UnitRequirement[] unitRequirements, Race parentRace) : this (id, typeName, parentRace)
		{
			mainCat = race.GetCategory(mainCategoryID);			
			MinNumber = minNum;			
			MaxNumber = maxNum;
			MinSize = minimumSize;			
			MaxSize = maximumSize;
			BaseUnitCost = unitCost;
			CostPerTrooper = trooperCost;
			SetUnitStats(unitStats);

			foreach (UnitRequirement requirement in requirements)
			{
				AddRequirement(requirement);
			}
		}

		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 <see cref=" Category"/> that this unit type is a member of.
		/// </value>
		public virtual Category MainCategory
		{
			get
			{					
				return mainCat;
			}
			set
			{
				mainCat = value;
			}
		}

		/// <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 set of <see cref="Stat"/>s for the unit in a format that is valid for the game system.
		/// </value>
		public Stat[] UnitStatsArray
		{
			get 
			{
				return stats.StatsArray;
			}
		}

		//// <value>
		/// The set of <see cref="Stat"/>s for the unit including an additional column that contains the unit type name
		/// </value>
		public Stat[] UnitStatsArrayWithName
		{
			get
			{
				Stat[] extendedStats = new Stat[stats.StatCount+1];
				extendedStats[0] = new Stat(new StatSlot("Name"), Name);
				stats.StatsArray.CopyTo(extendedStats, 1);
				return extendedStats;
			}
		}

		public void SetUnitStats(Stats newStats)
		{
			stats = newStats;
		}

		public string GetStatValue(string statName)
		{
			return stats.GetStatValue(statName);
		}
		
		internal void AddEquipmentItem(UnitEquipmentItem item)
		{
			if (!equipment.ContainsKey(item.ID))
			{
				equipment.Add(item.ID, item);
				equipmentKeyOrder.Add(item.ID);
				AddToMutexGroup(item);
			}
		}
		
		private void AddToMutexGroup(UnitEquipmentItem item)
		{
			string mutexGroup = item.MutexGroup;
			
			if (mutexGroup!="" && mutexGroup!=null)
			{
				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)
		{
			List<UnitEquipmentItem> list = DictionaryUtils.GetValue(equipmentExclusionGroups, group);

			if (list == null)
			{
				return new UnitEquipmentItem[0];
			}
			else
			{
				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;
			}
		}
	}
}