view api/Objects/UnitType.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 789dfab13449
children 2f3cafb69799
line wrap: on
line source

// This file (UnitType.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.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);
		}
	}
}