// 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 description for UnitInstance. /// public class Unit : WarFoundryObject, ICostedWarFoundryObject { private UnitType type; private int size; private Unit parentUnit; private double points; private ArmyCategory cat; private Dictionary equipment = new Dictionary(); private Dictionary> equipmentSlots = new Dictionary>(); private List containedUnits = new List(); 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 list = new List(); 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 selections = DictionaryUtils.GetValue(equipmentSlots, equip.SlotName); if (selections == null) { selections = new List(); 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 selections = DictionaryUtils.GetValue(equipmentSlots, equip.SlotName); if (selections == null) { selections = new List(); 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 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 selections = GetEquipmentSlotSelections(slotName); if (selections != null) { amount = GetSelectionTotal(selections); } return amount; } internal List GetEquipmentSlotSelections(string slotName) { return DictionaryUtils.GetValue(equipmentSlots, slotName); } /// /// Gets the total amount of items taken for the item's slot, excluding the provided item /// /// the item to exclude from the count /// the total number of items public int GetEquipmentAmountInSlotExcludingItem(UnitEquipmentItem item) { int amount = 0; List selections = DictionaryUtils.GetValue(equipmentSlots, item.SlotName); if (selections != null) { selections = new List(selections); RemoveSelectionFromList(item, selections); amount = GetSelectionTotal(selections); } return amount; } private void RemoveSelectionFromList(UnitEquipmentItem item, List selections) { AbstractUnitEquipmentItemSelection selection = GetEquipmentSelection(item); if (selection != null) { selections.Remove(selection); } } private int GetSelectionTotal(List selections) { int amount = 0; foreach (AbstractUnitEquipmentItemSelection selection in selections) { amount += selection.RawNumberTaken; } return amount; } /// /// 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 /// public ICollection 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); } } } } }