view api/Objects/Unit.cs @ 330:8ca5dd75d5b8

Re #27: Define unit requirements * Rename class to match behaviour (no longer just units) * Move to correct package
author IBBoard <dev@ibboard.co.uk>
date Sun, 27 Mar 2011 19:36:54 +0000
parents 92d10b06ab0f
children
line wrap: on
line source

// This file (Unit.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 2007, 2008, 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.Text;
using System.Xml;
using IBBoard.Lang;
using IBBoard.Limits;
using IBBoard.WarFoundry.API.Util;

namespace IBBoard.WarFoundry.API.Objects
{
	/// <summary>
	/// Summary description for UnitInstance.
	/// </summary>
	public class Unit : WarFoundryObject, ICostedWarFoundryObject
	{
		private UnitType type;
		private int size;
		private Unit parentUnit;
		private double points;
		private ArmyCategory cat;
		private Dictionary<UnitEquipmentItem, AbstractUnitEquipmentItemSelection> equipment = new Dictionary<UnitEquipmentItem, AbstractUnitEquipmentItemSelection>();
		private Dictionary<string, List<AbstractUnitEquipmentItemSelection>> equipmentSlots = new Dictionary<string, List<AbstractUnitEquipmentItemSelection>>();
		private List<Unit> containedUnits = new List<Unit>();

		public event DoubleValChangedDelegate PointsValueChanged;
		public event IntValChangedDelegate UnitSizeChanged;
		public event DoubleValChangedDelegate UnitEquipmentAmountChanged;

		public Unit(UnitType unitType, ArmyCategory parentArmyCat) : this(unitType, unitType.MinSize, parentArmyCat)
		{
			//Do nothing extra
		}

		public Unit(UnitType unitType, int startSize, ArmyCategory parentArmyCat) : this("", "", startSize, unitType, parentArmyCat)
		{
			SetInitialEquipment();
			UnitSizeChanged += new IntValChangedDelegate(RefreshUnitEquipmentAmounts);
		}	

		public Unit(string id, string name, int startSize, UnitType unitType, ArmyCategory parentArmyCat) : base(id, name)
		{
			Category = parentArmyCat;
			type = unitType;
			Size = startSize;
			CalcCost();
			UnitEquipmentAmountChanged += new DoubleValChangedDelegate(UnitEquipmentAmountChangedHandler);
			UnitSizeChanged += new IntValChangedDelegate(UnitSizeChangedHandler);
			Translation.TranslationChanged += HandleTranslationChanged;
		}

		private void UnitEquipmentAmountChangedHandler(WarFoundryObject obj, double oldVal, double newVal)
		{
			CalcCost();
		}

		private void UnitSizeChangedHandler(WarFoundryObject obj, int oldVal, int newVal)
		{
			CalcCost();
			
			if (HasDefaultName())
			{
				OnNameChanged("", Name);
			}
		}

		protected override string DefaultName()
		{
			if (type != null)
			{
				if (size == 1)
				{
					return type.Name;
				}
				else
				{
					return String.Format(Translation.GetTranslation("defaultUnitName"), size, type.Name);
				}
			}
			else
			{
				return "Unknown Unit";
			}
		}

		private void HandleTranslationChanged()
		{
			if (type != null && HasDefaultName() && size != 1)
			{
				OnNameChanged(null, DefaultName());
			}
		}

		private void SetInitialEquipment()
		{
			foreach (UnitEquipmentItem unitEquip in UnitType.GetEquipmentItems())
			{
				if (unitEquip.IsRequired)
				{
					if (CanEquipWithItem(unitEquip))
					{
						ILimit minLimit = unitEquip.MinLimit;
						
						if (minLimit is IPercentageLimit)
						{
							SetEquipmentRatio(unitEquip, UnitEquipmentUtil.GetMinEquipmentPercentage(this, unitEquip));
						}
						else
						{
							SetEquipmentAmount(unitEquip, UnitEquipmentUtil.GetMinEquipmentCount(this, unitEquip));
						}
					}
				}
			}
		}

		private void CalcCost()
		{
			double oldpoints = points;
			points = type.CostPerTrooper * AdditionalTroopers + type.BaseUnitCost;

			foreach (AbstractUnitEquipmentItemSelection equipSelection in equipment.Values)
			{
				points += equipSelection.TotalCost;
			}

			if (oldpoints != points)
			{
				OnPointsValueChanged(oldpoints, points);
			}
		}

		public int AdditionalTroopers
		{
			get { return Math.Max(Size - type.BaseSize, 0); }
		}

		public int Size
		{
			get { return size; }
			set
			{
				if (value != size)
				{
					int oldValue = size;
					size = (value > 0 ? value : 1);
					OnUnitSizeChanged(oldValue, size);
				}
			}
		}

		public UnitType UnitType
		{
			get { return type; }
		}

		public Army Army
		{
			get { return (Category == null ? null : Category.ParentArmy); }
		}

		public Race Race
		{
			get { return UnitType.Race; }
		}

		public ArmyCategory Category
		{
			get
			{
				return cat;
			}
			set { cat = value; }
		}

		public double Points
		{
			get
			{
				if (points == 0)
				{
					CalcCost();
				}

				return points;
			}
		}

		public Unit[] ContainedUnits
		{
			get { return containedUnits.ToArray(); }
		}

		public void AddContainedUnit(Unit unit)
		{
			if (UnitType.CanContainUnit(unit))
			{
				if (!containedUnits.Contains(unit))
				{
					containedUnits.Add(unit);
				}
				
				unit.ParentUnit = this;
			}
			else
			{
				throw new InvalidContainershipException(this, unit);
			}
		}

		public void RemoveContainedUnit(Unit unit)
		{
			containedUnits.Remove(unit);
		}

		public Unit ParentUnit
		{
			get { return parentUnit; }
			set
			{
				if (!(parentUnit == value || (parentUnit != null && parentUnit.Equals(value))))
				{
					parentUnit = value;
					
					if (value != null)
					{
						value.AddContainedUnit(this);
					}
				}
			}
		}

		public UnitEquipmentItem[] GetEquipment()
		{
			return DictionaryUtils.ToKeyArray(equipment);
		}

		public EquipmentItem[] GetRequiredEquipment()
		{
			List<EquipmentItem> list = new List<EquipmentItem>();

			foreach (UnitEquipmentItem item in GetEquipment())
			{
				if (item.IsRequired)
				{
					list.Add(item.EquipmentItem);
				}
			}

			return list.ToArray();
		}

		internal AbstractUnitEquipmentItemSelection GetEquipmentSelection(UnitEquipmentItem item)
		{
			return DictionaryUtils.GetValue(equipment, item);
		}

		public void SetEquipmentAmount(UnitEquipmentItem equip, int amount)
		{
			if (amount < 1 && amount != WarFoundryCore.INFINITY)
			{
				amount = 0;
			}
			
			if (amount == 0)
			{
				RemoveEquipmentItem(equip);
			}
			else
			{
				AbstractUnitEquipmentItemSelection currSelection = DictionaryUtils.GetValue(equipment, equip);
				double oldAmount = (currSelection == null ? 0 : currSelection.AmountTaken);
	
				if (amount != oldAmount)
				{
					if (oldAmount == 0)
					{
						AddEquipmentAmount(equip, amount);
					}
					else if (currSelection is UnitEquipmentNumericSelection)
					{
						//A UnitEquipmentItem shouldn't change its IsRatio value, so assume we already have the right sub-type
						currSelection.AmountTaken = amount;
					}
					else
					{
						RemoveEquipmentItem(equip);
						AddEquipmentAmount(equip, amount);
					}
	
					OnUnitEquipmentAmountChanged(equip, oldAmount, amount);
				}
			}
		}

		private void AddEquipmentAmount(UnitEquipmentItem equip, int amount)
		{
			AbstractUnitEquipmentItemSelection newItem = new UnitEquipmentNumericSelection(this, equip, amount);			
			equipment[equip] = newItem;
			List<AbstractUnitEquipmentItemSelection> selections = DictionaryUtils.GetValue(equipmentSlots, equip.SlotName);
			
			if (selections == null)
			{
				selections = new List<AbstractUnitEquipmentItemSelection>();
				equipmentSlots[equip.SlotName] = selections;
			}
			
			selections.Add(newItem);
		}

		public void SetEquipmentRatio(UnitEquipmentItem equip, double ratio)
		{
			if (!equip.IsRatioLimit)
			{
				throw new InvalidOperationException("Equipment with ID " + equip.ID + " for unit of type " + UnitType.ID + " has an absolute limit, not a ratio limit");
			}
			
			if (ratio > 100)
			{
				ratio = 100;
			}
			else if (ratio < 0)
			{
				ratio = 0;
			}
			
			if (ratio == 0)
			{
				RemoveEquipmentItem(equip);
			}
			else
			{
				AbstractUnitEquipmentItemSelection currSelection = DictionaryUtils.GetValue(equipment, equip);
				double oldRatio = (currSelection == null ? 0 : currSelection.AmountTaken);
	
				if (ratio != oldRatio)
				{
					if (oldRatio == 0)
					{
						AddEquipmentRatio(equip, ratio);
					}
					else if (currSelection is UnitEquipmentRatioSelection)
					{
						currSelection.AmountTaken = ratio;
					}
					else
					{
						RemoveEquipmentItem(equip);
						AddEquipmentRatio(equip, ratio);
					}
	
					OnUnitEquipmentAmountChanged(equip, oldRatio, ratio);
				}
			}
		}

		private void AddEquipmentRatio(UnitEquipmentItem equip, double ratio)
		{
			UnitEquipmentRatioSelection newItem = new UnitEquipmentRatioSelection(this, equip, ratio);
			equipment[equip] = newItem;
			List<AbstractUnitEquipmentItemSelection> selections = DictionaryUtils.GetValue(equipmentSlots, equip.SlotName);
			
			if (selections == null)
			{
				selections = new List<AbstractUnitEquipmentItemSelection>();
				equipmentSlots[equip.SlotName] = selections;
			}
			
			selections.Add(newItem);
		}

		private void RemoveEquipmentItem(UnitEquipmentItem equip)
		{
			double oldAmount = UnitEquipmentUtil.GetEquipmentAmount(this, equip);
		
			if (oldAmount != 0)
			{
				AbstractUnitEquipmentItemSelection selection = DictionaryUtils.GetValue(equipment, equip);
				equipment.Remove(equip);
				List<AbstractUnitEquipmentItemSelection> slotSelections = DictionaryUtils.GetValue(equipmentSlots, equip.SlotName);
				slotSelections.Remove(selection);
				OnUnitEquipmentAmountChanged(equip, oldAmount, 0);
			}
		}

		public bool CanEquipWithItem(UnitEquipmentItem item)
		{
			string[] mutexes = item.MutexGroups;
			bool canEquip = false;

			if (mutexes.Length == 0)
			{
				canEquip = true;
			}
			else
			{
				canEquip = UnitEquipmentUtil.GetBlockingEquipmentItems(this, item).Count == 0;
			}

			return canEquip;
		}

		public bool CanEquipWithItem(string equipID)
		{
			return CanEquipWithItem(UnitType.GetEquipmentItem(equipID));
		}

		private void OnPointsValueChanged(double oldValue, double newValue)
		{
			if (PointsValueChanged != null)
			{
				PointsValueChanged(this, oldValue, newValue);
			}
		}

		private void OnUnitSizeChanged(int oldValue, int newValue)
		{
			if (UnitSizeChanged != null)
			{
				UnitSizeChanged(this, oldValue, newValue);
			}
		}

		private void OnUnitEquipmentAmountChanged(UnitEquipmentItem equip, double oldValue, double newValue)
		{
			if (UnitEquipmentAmountChanged != null)
			{
				UnitEquipmentAmountChanged(equip, oldValue, newValue);
			}
		}

		public Stat[][] UnitStatsArrays
		{
			get { return UnitType.UnitStatsArrays; }
		}

		public Stat[][] UnitStatsArraysWithName
		{
			get { return UnitType.UnitStatsArraysWithName; }
		}

		public string[] UnitStatsArrayIDs
		{
			get
			{
				return UnitType.UnitStatsArrayIDs;
			}
		}

		public string GetStatValue(string statName)
		{
			return UnitType.GetStatValue(statName);
		}

		public int GetEquipmentAmountInSlot(string slotName)
		{
			int amount = 0;

			List<AbstractUnitEquipmentItemSelection> selections = GetEquipmentSlotSelections(slotName);
			
			if (selections != null)
			{
				amount = GetSelectionTotal(selections);
			}			
			
			return amount;
		}

		internal List<AbstractUnitEquipmentItemSelection> GetEquipmentSlotSelections(string slotName)
		{
			return DictionaryUtils.GetValue(equipmentSlots, slotName);
		}

		/// <summary>
		/// Gets the total amount of items taken for the item's slot, excluding the provided item
		/// </summary>
		/// <param name="item">the item to exclude from the count</param>
		/// <returns>the total number of items</returns>
		public int GetEquipmentAmountInSlotExcludingItem(UnitEquipmentItem item)
		{
			int amount = 0;

			List<AbstractUnitEquipmentItemSelection> selections = DictionaryUtils.GetValue(equipmentSlots, item.SlotName);

			if (selections != null)
			{
				selections = new List<AbstractUnitEquipmentItemSelection>(selections);
				RemoveSelectionFromList(item, selections);
				amount = GetSelectionTotal(selections);
			}

			return amount;
		}

		private void RemoveSelectionFromList(UnitEquipmentItem item, List<AbstractUnitEquipmentItemSelection> selections)
		{
			AbstractUnitEquipmentItemSelection selection = GetEquipmentSelection(item);

			if (selection != null)
			{
				selections.Remove(selection);
			}
		}

		private int GetSelectionTotal(List<AbstractUnitEquipmentItemSelection> selections)
		{
			int amount = 0;
			
			foreach (AbstractUnitEquipmentItemSelection selection in selections)
			{
				amount += selection.NumberTaken;
			}
			
			return amount;
		}
		
		/// <summary>
		/// Default stub implementation of getting the unit's abilities - defaults to just the unit type's required abilities until we get a way to modify them
		/// </summary>
		public ICollection<Ability> Abilities
		{
			get
			{
				return UnitType.GetRequiredAbilities();
			}
		}

		private void RefreshUnitEquipmentAmounts(WarFoundryObject obj, int oldValue, int newValue)
		{
			foreach (UnitEquipmentItem item in equipment.Keys)
			{
				AbstractUnitEquipmentItemSelection selection = equipment[item];
				
				if (selection is UnitEquipmentRatioSelection)
				{
					OnUnitEquipmentAmountChanged(item, selection.AmountTaken, selection.AmountTaken);
				}
			}
		}	
	}
}