public UnitTrainingPlan(UnitTrainingOrder order, Reason reason, int targetUnitTypeId = 0, int targetProvinceId = 0) { _unitTrainingOrder = order; _reason = reason; _targetUnitTypeId = targetUnitTypeId; _targetProvinceId = targetProvinceId; }
/// <summary> /// Add an order to the training queue /// </summary> /// <param name="unitType">The type of the unit to train</param> /// <param name="quantity">The number of units to train</param> /// <param name="standing">Whether the order is standing (repeating)</param> /// <returns>Whether the order was successfully processed</returns> public bool QueueTraining(UnitType unitType, int quantity, bool standing) { if (_owners.FundUnitTraining(unitType, quantity)) { _trainingOrders[unitType] = new UnitTrainingOrder(unitType, quantity, standing); return(true); } return(false); }
/// <summary> /// Prepare additional unit training plans by selecting the cheapest units /// taking into account the budget and the existing training plan, if any /// </summary> /// <param name="province">The province for which to prepare unit training plans</param> /// <param name="budget">The amount of money available for spending</param> /// <param name="existingPlan">The province's current training plan</param> /// <returns>List of unit training plans</returns> private List <UnitTrainingPlan> PlanTrainingFodder(Province province, int budget, ProvinceTrainingPlan existingPlan = null) { List <UnitTrainingPlan> result = new List <UnitTrainingPlan>(); int manpower = province.GetRemainingManpower(); if (existingPlan != null) { manpower -= existingPlan.GetManpowerCost(); } // no recruits or no money => good buy! if (manpower <= 0 || budget <= 0) { return(result); } List <UnitType> trainable = province.GetCheapestToTrainUnits(); Dictionary <UnitType, int> trainingOrder = new Dictionary <UnitType, int>(); if (trainable.Count > 0) { if (trainable.Count == 1) { // simple - train this single cheapest unit trainingOrder[trainable[0]] = Mathf.Min(manpower, budget / trainable[0].GetTrainingCost()); } else { // more complicated - select one of the options randomly for (int i = 0; i < trainable.Count; i++) { trainingOrder[trainable[i]] = 0; } for (int i = 0; i < manpower; i++) { int randomIndex = UnityEngine.Random.Range(0, trainable.Count); trainingOrder[trainable[randomIndex]] += 1; budget -= trainable[randomIndex].GetTrainingCost(); if (budget <= 0) { break; } } } } foreach (KeyValuePair <UnitType, int> entry in trainingOrder) { if (entry.Value > 0) { UnitTrainingOrder order = new UnitTrainingOrder(entry.Key, entry.Value, false); result.Add(new UnitTrainingPlan(order, UnitTrainingPlan.Reason.FODDER)); } } return(result); }
/// <summary> /// Parse the province training plan and queue its training orders /// </summary> /// <param name="province">The province where to place the unit training order</param> /// <param name="plan">Province training plan to process</param> private void ProcessProvinceTrainingPlan(Province province, ProvinceTrainingPlan plan) { List <UnitTrainingPlan> unitPlans = plan.GetUnitTrainingPlans(); for (int i = 0; i < unitPlans.Count; i++) { UnitTrainingOrder order = unitPlans[i].GetUnitTrainingOrder(); UnitType unitType = order.GetUnitType(); int quantity = order.GetQuantity(); if (quantity > 0) { FileLogger.Trace("AI", "Queueing " + quantity + " " + unitType.GetName() + "s (" + unitPlans[i].GetReason() + ") in " + province.GetName()); // re-evaluate training orders every turn, don't create standing training orders province.QueueTraining(unitType, quantity, false); } } }
/// <summary> /// Prepare the province data for serialization /// </summary> public void PrepareForSerialization() { _data.units = new UnitData[_units.Count]; for (int i = 0; i < _units.Count; i++) { _data.units[i] = new UnitData(_units[i].GetUnitType().GetId(), _units[i].GetQuantity()); } List <UnitType> types = new List <UnitType>(_trainingOrders.Keys); _data.training = new UnitTrainingOrderData[types.Count]; for (int i = 0; i < types.Count; i++) { UnitTrainingOrder order = _trainingOrders[types[i]]; _data.training[i] = new UnitTrainingOrderData(types[i].GetId(), order.GetQuantity(), order.IsStanding()); } }
/// <summary> /// Revise training plans of the cheapest units based on the money left and the existing training plan, if available /// </summary> /// <param name="province">The province for which to prepare unit training plans</param> /// <param name="budget">The amount of money available for spending</param> /// <param name="existingPlan">The province's current training plan</param> private void UpgradeTrainingFodderPlan(Province province, int budget, ProvinceTrainingPlan existingPlan) { // no money => good buy! if (budget <= 0) { return; } List <UnitType> trainable = province.GetTrainableUnits(); Dictionary <UnitType, int> trainingOrder = new Dictionary <UnitType, int>(); List <UnitType> fodder = province.GetCheapestToTrainUnits(); List <UnitType> expensives = new List <UnitType>(); for (int i = 0; i < trainable.Count; i++) { if (!fodder.Contains(trainable[i])) { trainingOrder[trainable[i]] = 0; expensives.Add(trainable[i]); } } if (trainingOrder.Count > 0) { List <UnitTrainingPlan> plans = existingPlan.GetUnitTrainingPlans(); for (int i = 0; i < plans.Count; i++) { if (plans[i].GetReason() == UnitTrainingPlan.Reason.FODDER) { int manpower = plans[i].GetManpowerCost(); int fodderCost = plans[i].GetUnitTypeTrainingCost(); for (int j = 0; j < manpower; j++) { int randomIndex = UnityEngine.Random.Range(0, expensives.Count); int costIncrease = expensives[randomIndex].GetTrainingCost() - fodderCost; if (budget >= costIncrease) { trainingOrder[expensives[randomIndex]] += 1; plans[i].DecreaseQuantity(); budget -= costIncrease; } if (budget <= 0) { break; } } } if (budget <= 0) { break; } } List <UnitTrainingPlan> newPlans = new List <UnitTrainingPlan>(); foreach (KeyValuePair <UnitType, int> entry in trainingOrder) { if (entry.Value > 0) { UnitTrainingOrder order = new UnitTrainingOrder(entry.Key, entry.Value, false); newPlans.Add(new UnitTrainingPlan(order, UnitTrainingPlan.Reason.CORE)); } } if (newPlans.Count > 0) { existingPlan.AddUnitTrainingPlans(newPlans); } } return; }
/// <summary> /// Prepare additional unit training plans based on the threats identified, the budget, and the existing training plan, if available /// </summary> /// <param name="province">The province for which to prepare unit training plans</param> /// <param name="dangers">Threats identified as a hash of enemy provice id => list of units</param> /// <param name="budget">The amount of money available for spending</param> /// <param name="existingPlan">The province's current training plan</param> /// <returns>List of unit training plans</returns> private List <UnitTrainingPlan> PlanTrainingCounters(Province province, Dictionary <int, List <Unit> > dangers, int budget, ProvinceTrainingPlan existingPlan = null) { List <UnitTrainingPlan> result = new List <UnitTrainingPlan>(); int manpower = province.GetRemainingManpower(); if (existingPlan != null) { manpower -= existingPlan.GetManpowerCost(); } // no dangers, no recruits or no money => good buy! if (dangers.Count == 0 || manpower <= 0 || budget <= 0) { return(result); } List <UnitType> trainable = province.GetTrainableUnits(); List <Unit> units = new List <Unit>(); for (int i = 0; i < trainable.Count; i++) { units.Add(new Unit(trainable[i], 1)); } List <int> targets = new List <int>(dangers.Keys); for (int i = 0; i < targets.Count; i++) { List <Unit> localDangers = dangers[targets[i]]; for (int j = 0; j < localDangers.Count; j++) { Unit counter = CombatHelper.Instance.GetAttackingCounter(localDangers[j], units); if (counter != null) { int cost = counter.GetUnitType().GetTrainingCost(); int quantity = Math.Min(budget / cost, Math.Min(province.GetRemainingManpower(), localDangers[j].GetQuantity() / 2)); if (quantity > 0) { localDangers[j].AddQuantity(-quantity); UnitTrainingOrder order = new UnitTrainingOrder(counter.GetUnitType(), quantity, false); result.Add(new UnitTrainingPlan(order, UnitTrainingPlan.Reason.COUNTER, localDangers[j].GetUnitType().GetId(), targets[i])); manpower -= quantity; budget -= cost * quantity; FileLogger.Trace("AI", counter.GetUnitType().GetName() + " is an attacking counter to " + localDangers[j].GetUnitType().GetName()); } if (manpower <= 0 || budget <= 0) { return(result); } } counter = CombatHelper.Instance.GetDefendingCounter(localDangers[j], units); if (counter != null) { int cost = counter.GetUnitType().GetTrainingCost(); int quantity = Math.Min(budget / cost, Math.Min(province.GetRemainingManpower(), localDangers[j].GetQuantity())); if (quantity > 0) { localDangers[j].AddQuantity(-quantity); UnitTrainingOrder order = new UnitTrainingOrder(counter.GetUnitType(), quantity, false); result.Add(new UnitTrainingPlan(order, UnitTrainingPlan.Reason.COUNTER, localDangers[j].GetUnitType().GetId(), targets[i])); manpower -= quantity; budget -= cost * quantity; FileLogger.Trace("AI", counter.GetUnitType().GetName() + " is an defending counter to " + localDangers[j].GetUnitType().GetName()); } if (manpower <= 0 || budget <= 0) { return(result); } } } } return(result); }
/// <summary> /// Class constructor /// </summary> /// <param name="data">Province data loaded from a save</param> /// <param name="dwellers">Race that populates the province</param> /// <param name="owners">Faction that owns the province</param> /// <param name="allUnitTypes">A hash of id => unit type that includes all unit types in the game</param> public Province(ProvinceData data, Race dwellers, Faction owners, Dictionary <int, UnitType> allUnitTypes) { _data = data; _dwellers = dwellers; _owners = owners; if (_data.raceId != dwellers.GetId()) { Debug.Log("Bad race data for province " + _data.id + ": " + _data.raceId + " is changed to " + dwellers.GetId()); _data.raceId = dwellers.GetId(); } if (_data.factionId != owners.GetId()) { Debug.Log("Bad faction data for province " + _data.id + ": " + _data.factionId + " is changed to " + owners.GetId()); _data.factionId = owners.GetId(); } _neighbors = new List <Province>(); _units = new List <Unit>(); for (int i = 0; i < data.units.Length; i++) { _units.Add(new Unit(data.units[i], allUnitTypes[data.units[i].id])); } _trainingOrders = new Dictionary <UnitType, UnitTrainingOrder>(); if (data.training != null && data.training.Length > 0) { for (int i = 0; i < data.training.Length; i++) { UnitTrainingOrderData order = data.training[i]; // order.id is unit type id just for conspiration UnitType unitType = allUnitTypes[order.id]; _trainingOrders[unitType] = new UnitTrainingOrder(order, allUnitTypes); } } _trainable = new List <UnitType>(); int minTrainingCost = 100500; // means "a lot" // if the province doesn't have defined trainable unit types, use the list of racial units if (_data.trainable == null || _data.trainable.Length == 0) { List <UnitType> racialUnits = _dwellers.GetRacialUnits(); for (int i = 0; i < racialUnits.Count; i++) { // heroes can't be trained if (racialUnits[i].IsTrainable()) { _trainable.Add(racialUnits[i]); if (!racialUnits[i].IsHoly()) { minTrainingCost = Math.Min(minTrainingCost, racialUnits[i].GetTrainingCost()); } } } } else { for (int i = 0; i < _data.trainable.Length; i++) { UnitType unitType = allUnitTypes[_data.trainable[i]]; if (unitType != null && unitType.IsTrainable()) { _trainable.Add(unitType); if (!unitType.IsHoly()) { minTrainingCost = Math.Min(minTrainingCost, unitType.GetTrainingCost()); } } } } SelectCheapestToTrainUnits(minTrainingCost); }