/// <summary> /// Implements the processes involved in a battle between two armies in the field /// </summary> /// <returns>bool indicating whether attacking army is victorious</returns> /// <remarks> /// Predicate: assumes attacker has sufficient days /// Predicate: assumes attacker has leader /// Predicate: assumes attacker in same fief as defender /// Predicate: assumes defender not besieged in keep /// Predicate: assumes attacker and defender not same army /// </remarks> /// <param name="attacker">The attacking army</param> /// <param name="defender">The defending army</param> /// <param name="circumstance">string indicating circumstance of battle</param> public static bool GiveBattle(Army attacker, Army defender, out ProtoBattle battleResults, List <Army> attackerAllies = null, List <Army> defenderAllies = null, string circumstance = "battle") { Contract.Requires(attacker != null && defender != null && circumstance != null); battleResults = new ProtoBattle(); bool attackerVictorious = false; bool battleHasCommenced = false; bool attackerLeaderDead = false; bool defenderLeaderDead = false; // check if losing army has disbanded bool attackerDisbanded = false; bool defenderDisbanded = false; bool siegeRaised = false; uint[] battleValues = new uint[2]; double[] casualtyModifiers = new double[2]; double statureChange = 0; // if applicable, get siege Siege thisSiege = null; string thisSiegeID = defender.CheckIfBesieger(); if (!String.IsNullOrWhiteSpace(thisSiegeID)) { // get siege thisSiege = Globals_Game.siegeMasterList[thisSiegeID]; } // get starting troop numbers uint attackerStartTroops = attacker.CalcArmySize(); uint defenderStartTroops = defender.CalcArmySize(); uint attackerCasualties = 0; uint defenderCasualties = 0; if (attackerAllies != null) { if (attackerAllies.Count > 0) { for (int i = 0; i < attackerAllies.Count; i++) { attackerStartTroops += attackerAllies[i].CalcArmySize(); battleResults.attackerAllies.Add(attackerAllies[i].GetOwner().familyID); } } } if (defenderAllies != null) { if (defenderAllies.Count > 0) { for (int i = 0; i < defenderAllies.Count; i++) { defenderStartTroops += defenderAllies[i].CalcArmySize(); battleResults.defenderAllies.Add(defenderAllies[i].GetOwner().familyID); } } } // get leaders Character attackerLeader = attacker.GetLeader(); Character defenderLeader = defender.GetLeader(); // if(attackerLeader!=null) { battleResults.attackerLeader = attackerLeader.firstName + " " + attackerLeader.familyName; // } // if(defenderLeader!=null) { battleResults.defenderLeader = defenderLeader.firstName + " " + defenderLeader.familyName; // } battleResults.attackerOwner = attacker.GetOwner().firstName + " " + attacker.GetOwner().familyName; battleResults.defenderOwner = defender.GetOwner().firstName + " " + defender.GetOwner().familyName; battleResults.battleLocation = attacker.GetLocation().id; // introductory text for message switch (circumstance) { case "pillage": battleResults.circumstance = 1; break; case "siege": battleResults.circumstance = 2; break; default: battleResults.circumstance = 0; break; } // get battle values for both armies switch (circumstance) { case "pillage": { battleValues = attacker.CalculateBattleValues(defender, defenderAllies); break; } case "siege": { battleValues = attacker.CalculateBattleValues(defender, attackerAllies, defenderAllies); break; } default: { battleValues = attacker.CalculateBattleValues(defender, attackerAllies, defenderAllies); break; } } // check if attacker has managed to bring defender to battle // case 1: defending army sallies during siege to attack besieger = battle always occurs if (circumstance.Equals("siege")) { battleHasCommenced = true; } // case 2: defending militia attacks pillaging army during pollage = battle always occurs else if (circumstance.Equals("pillage")) { battleHasCommenced = true; } // case 3: defender aggression and combatOdds allows battle else if (defender.aggression != 0) { if (defender.aggression == 1) { // get odds int battleOdds = Battle.GetBattleOdds(attacker, defender, defenderAllies, attackerAllies); // if odds OK, give battle if (battleOdds <= defender.combatOdds) { battleHasCommenced = true; } // if not, check for battle else { battleHasCommenced = Battle.BringToBattle(battleValues[0], battleValues[1], circumstance); if (!battleHasCommenced) { defender.ProcessRetreat(1); } } } else { battleHasCommenced = true; } } // otherwise, check to see if the attacker can bring the defender to battle else { battleHasCommenced = Battle.BringToBattle(battleValues[0], battleValues[1], circumstance); if (!battleHasCommenced) { defender.ProcessRetreat(1); } } battleResults.battleTookPlace = battleHasCommenced; if (battleHasCommenced) { List <string> disbandedArmies = new List <string>(); List <string> retreatedArmies = new List <string>(); List <string> deadCharacters = new List <string>(); // WHO HAS WON? // calculate if attacker has won attackerVictorious = Battle.DecideBattleVictory(battleValues[0], battleValues[1]); // UPDATE STATURE if (attackerVictorious) { statureChange = 0.8 * (defender.CalcArmySize() / Convert.ToDouble(10000)); battleResults.statureChangeAttacker = statureChange; attacker.GetOwner().AdjustStatureModifier(statureChange); attacker.moraleChange(statureChange); if (attackerAllies != null) { if (attackerAllies.Count > 0) { for (int i = 0; i < attackerAllies.Count; i++) { attackerAllies[i].GetOwner().AdjustStatureModifier(statureChange); attackerAllies[i].moraleChange(statureChange); } } } statureChange = -0.5 * (attacker.CalcArmySize() / Convert.ToDouble(10000)); battleResults.statureChangeDefender = statureChange; defender.GetOwner().AdjustStatureModifier(statureChange); defender.moraleChange(statureChange); if (defenderAllies != null) { if (defenderAllies.Count > 0) { for (int i = 0; i < defenderAllies.Count; i++) { defenderAllies[i].GetOwner().AdjustStatureModifier(statureChange); defenderAllies[i].moraleChange(statureChange); } } } } else { statureChange = 0.8 * (attacker.CalcArmySize() / Convert.ToDouble(10000)); battleResults.statureChangeDefender = statureChange; defender.GetOwner().AdjustStatureModifier(statureChange); defender.moraleChange(statureChange); if (defenderAllies != null) { if (defenderAllies.Count > 0) { for (int i = 0; i < defenderAllies.Count; i++) { defenderAllies[i].GetOwner().AdjustStatureModifier(statureChange); defenderAllies[i].moraleChange(statureChange); } } } statureChange = -0.5 * (defender.CalcArmySize() / Convert.ToDouble(10000)); battleResults.statureChangeAttacker = statureChange; attacker.GetOwner().AdjustStatureModifier(statureChange); attacker.moraleChange(statureChange); if (attackerAllies != null) { if (attackerAllies.Count > 0) { for (int i = 0; i < attackerAllies.Count; i++) { attackerAllies[i].GetOwner().AdjustStatureModifier(statureChange); attackerAllies[i].moraleChange(statureChange); } } } } // CASUALTIES // calculate troop casualties for both sides casualtyModifiers = Battle.CalculateBattleCasualties(attackerStartTroops, defenderStartTroops, battleValues[0], battleValues[1], attackerVictorious); uint totalAttackTroopsLost = 0; uint totalDefendTroopsLost = 0; // if losing side sustains >= 50% casualties, disbands if (attackerVictorious) { // either indicate losing army to be disbanded if (casualtyModifiers[1] >= 0.5) { defenderDisbanded = true; disbandedArmies.Add(defender.owner); totalDefendTroopsLost = defender.CalcArmySize(); if (defenderAllies != null) { if (defenderAllies.Count > 0) { for (int i = 0; i < defenderAllies.Count; i++) { disbandedArmies.Add(defenderAllies[i].owner); totalDefendTroopsLost += defenderAllies[i].CalcArmySize(); } } } } // OR apply troop casualties to losing army else { totalDefendTroopsLost = defender.ApplyTroopLosses(casualtyModifiers[1]); if (defenderAllies != null) { if (defenderAllies.Count > 0) { for (int i = 0; i < defenderAllies.Count; i++) { totalDefendTroopsLost += defenderAllies[i].ApplyTroopLosses(casualtyModifiers[1]); } } } } // apply troop casualties to winning army totalAttackTroopsLost = attacker.ApplyTroopLosses(casualtyModifiers[0]); if (attackerAllies != null) { if (attackerAllies.Count > 0) { for (int i = 0; i < attackerAllies.Count; i++) { totalAttackTroopsLost += attackerAllies[i].ApplyTroopLosses(casualtyModifiers[0]); } } } } else { if (casualtyModifiers[0] >= 0.5) { attackerDisbanded = true; disbandedArmies.Add(attacker.owner); totalAttackTroopsLost = attacker.CalcArmySize(); if (attackerAllies != null) { if (attackerAllies.Count > 0) { for (int i = 0; i < attackerAllies.Count; i++) { disbandedArmies.Add(attackerAllies[i].owner); totalAttackTroopsLost += attackerAllies[i].CalcArmySize(); } } } } else { totalAttackTroopsLost = attacker.ApplyTroopLosses(casualtyModifiers[0]); if (attackerAllies != null) { if (attackerAllies.Count > 0) { for (int i = 0; i < attackerAllies.Count; i++) { totalAttackTroopsLost += attackerAllies[i].ApplyTroopLosses(casualtyModifiers[0]); } } } } totalDefendTroopsLost = defender.ApplyTroopLosses(casualtyModifiers[1]); if (defenderAllies != null) { if (defenderAllies.Count > 0) { for (int i = 0; i < defenderAllies.Count; i++) { totalDefendTroopsLost += defenderAllies[i].ApplyTroopLosses(casualtyModifiers[1]); } } } } battleResults.attackerCasualties = totalAttackTroopsLost; battleResults.defenderCasualties = totalDefendTroopsLost; // UPDATE TOTAL SIEGE LOSSES, if appropriate // NOTE: the defender in this battle is the attacker in the siege and v.v. if (thisSiege != null) { // update total siege attacker (defender in this battle) losses thisSiege.totalCasualtiesAttacker += Convert.ToInt32(totalDefendTroopsLost); // update total siege defender (attacker in this battle) losses if (circumstance.Equals("siege")) { thisSiege.totalCasualtiesDefender += Convert.ToInt32(totalAttackTroopsLost); } } // get casualty figures (for message) if (!attackerDisbanded) { // get attacker casualties attackerCasualties = totalAttackTroopsLost; } if (!defenderDisbanded) { // get defender casualties defenderCasualties = totalDefendTroopsLost; } // DAYS // adjust days // NOTE: don't adjust days if is a siege (will be deducted elsewhere) if (!circumstance.Equals("siege")) { if (attackerLeader != null) { attackerLeader.AdjustDays(1); } // need to check for defender having no leader if (defenderLeader != null) { defenderLeader.AdjustDays(1); } else { defender.days -= 1; } } // RETREATS // create array of armies (for easy processing) Army[] bothSides = { attacker, defender }; // check if either army needs to retreat int[] retreatDistances = Battle.CheckForRetreat(attacker, defender, casualtyModifiers[0], casualtyModifiers[1], attackerVictorious); // if is pillage or siege, attacking army (the fief's army) doesn't retreat // if is pillage, the defending army (the pillagers) always retreats if has lost if (circumstance.Equals("pillage") || circumstance.Equals("siege")) { retreatDistances[0] = 0; } if (circumstance.Equals("pillage")) { if (attackerVictorious) { retreatDistances[1] = 1; } } // if have retreated, perform it for (int i = 0; i < retreatDistances.Length; i++) { if (retreatDistances[i] > 0) { bothSides[i].ProcessRetreat(retreatDistances[i]); } } if (attackerAllies != null) { if (attackerAllies.Count > 0) { for (int i = 0; i < attackerAllies.Count; i++) { attackerAllies[i].ProcessRetreat(retreatDistances[0]); } } } if (defenderAllies != null) { if (defenderAllies.Count > 0) { for (int i = 0; i < defenderAllies.Count; i++) { defenderAllies[i].ProcessRetreat(retreatDistances[1]); } } } // If attacker has retreated add to retreat list if (retreatDistances[0] > 0) { retreatedArmies.Add(battleResults.attackerOwner); for (int i = 0; i < attackerAllies.Count; i++) { retreatedArmies.Add(attackerAllies[i].owner); } } // If defender retreated add to retreat list if (retreatDistances[1] > 0) { retreatedArmies.Add(battleResults.defenderOwner); for (int i = 0; i < defenderAllies.Count; i++) { retreatedArmies.Add(defenderAllies[i].owner); } } // PC/NPC INJURIES/DEATHS // check if any PCs/NPCs have been wounded or killed bool characterDead = false; // 1. ATTACKER uint friendlyBV = battleValues[0]; uint enemyBV = battleValues[1]; // if army leader a PC, check entourage if (attackerLeader is PlayerCharacter) { for (int i = 0; i < (attackerLeader as PlayerCharacter).myNPCs.Count; i++) { if ((attackerLeader as PlayerCharacter).myNPCs[i].inEntourage) { characterDead = (attackerLeader as PlayerCharacter).myNPCs[i].CalculateCombatInjury(casualtyModifiers[0]); } // process death, if applicable if (characterDead) { (attackerLeader as PlayerCharacter).myNPCs[i].ProcessDeath("injury"); } } } // check army leader if (attackerLeader != null) { attackerLeaderDead = attackerLeader.CalculateCombatInjury(casualtyModifiers[0]); } // process death, if applicable if (attackerLeaderDead) { deadCharacters.Add(attackerLeader.firstName + " " + attackerLeader.familyName); Character newLeader = null; // if is pillage, do NOT elect new leader for attacking army if (!circumstance.Equals("pillage")) { // if possible, elect new leader from entourage if (attackerLeader is PlayerCharacter) { if ((attackerLeader as PlayerCharacter).myNPCs.Count > 0) { // get new leader newLeader = (attackerLeader as PlayerCharacter).ElectNewArmyLeader(); } } // assign newLeader (can assign null leader if none found) attacker.AssignNewLeader(newLeader); } } else { // if pillage, if fief's army loses, make sure bailiff always returns to keep if (circumstance.Equals("pillage")) { if (!attackerVictorious) { attackerLeader.inKeep = true; } } } // 2. DEFENDER // need to check if defending army had a leader if (defenderLeader != null) { // if army leader a PC, check entourage if (defenderLeader is PlayerCharacter) { for (int i = 0; i < (defenderLeader as PlayerCharacter).myNPCs.Count; i++) { if ((defenderLeader as PlayerCharacter).myNPCs[i].inEntourage) { characterDead = (defenderLeader as PlayerCharacter).myNPCs[i].CalculateCombatInjury(casualtyModifiers[1]); } // process death, if applicable if (characterDead) { (defenderLeader as PlayerCharacter).myNPCs[i].ProcessDeath("injury"); } } } // check army leader defenderLeaderDead = defenderLeader.CalculateCombatInjury(casualtyModifiers[1]); // process death, if applicable if (defenderLeaderDead) { deadCharacters.Add(defenderLeader.firstName + " " + defenderLeader.familyName); Character newLeader = null; // if possible, elect new leader from entourage if (defenderLeader is PlayerCharacter) { if ((defenderLeader as PlayerCharacter).myNPCs.Count > 0) { // get new leader newLeader = (defenderLeader as PlayerCharacter).ElectNewArmyLeader(); } } // assign newLeader (can assign null leader if none found) defender.AssignNewLeader(newLeader); } } battleResults.deaths = deadCharacters.ToArray(); battleResults.retreatedArmies = retreatedArmies.ToArray(); battleResults.attackerVictorious = attackerVictorious; // check for SIEGE RELIEF if (thisSiege != null) { battleResults.isSiege = true; battleResults.siegeBesieger = thisSiege.GetBesiegingPlayer().firstName + " " + thisSiege.GetBesiegingPlayer().familyName; battleResults.siegeDefender = thisSiege.GetDefendingPlayer().firstName + " " + thisSiege.GetDefendingPlayer().familyName; // attacker (relieving army) victory or defender (besieging army) retreat = relief if ((attackerVictorious) || (retreatDistances[1] > 0)) { // indicate siege raised siegeRaised = true; battleResults.siegeRaised = true; } // check to see if siege raised due to death of siege owner with no heir else if ((defenderLeaderDead) && ((defenderLeader as PlayerCharacter) == thisSiege.GetBesiegingPlayer())) { // get siege owner's heir Character thisHeir = (defenderLeader as PlayerCharacter).GetHeir(); if (thisHeir == null) { battleResults.DefenderDeadNoHeir = true; // indicate siege raised siegeRaised = true; } } } } // =================== construct and send JOURNAL ENTRY // ID uint entryID = Globals_Game.GetNextJournalEntryID(); // personae // personae tags vary depending on circumstance string attackOwnTag = "|attackerOwner"; string attackLeadTag = "|attackerLeader"; string defendOwnTag = "|defenderOwner"; string defendLeadTag = "|defenderLeader"; if ((circumstance.Equals("pillage")) || (circumstance.Equals("siege"))) { attackOwnTag = "|sallyOwner"; attackLeadTag = "|sallyLeader"; defendOwnTag = "|defenderAgainstSallyOwner"; defendLeadTag = "|defenderAgainstSallyLeader"; } List <string> tempPersonae = new List <string>(); tempPersonae.Add(defender.GetOwner().charID + defendOwnTag); if (attackerLeader != null) { tempPersonae.Add(attackerLeader.charID + attackLeadTag); } if (defenderLeader != null) { tempPersonae.Add(defenderLeader.charID + defendLeadTag); } tempPersonae.Add(attacker.GetOwner().charID + attackOwnTag); tempPersonae.Add(attacker.GetLocation().owner.charID + "|fiefOwner"); if ((!circumstance.Equals("pillage")) && (!circumstance.Equals("siege"))) { tempPersonae.Add("all|all"); } string[] battlePersonae = tempPersonae.ToArray(); // location string battleLocation = attacker.GetLocation().id; // put together new journal entry JournalEntry battleResult = new JournalEntry(entryID, Globals_Game.clock.currentYear, Globals_Game.clock.currentSeason, battlePersonae, "battle", battleResults, loc: battleLocation); // add new journal entry to pastEvents Globals_Game.AddPastEvent(battleResult); // display pop-up informational message battleResults.ActionType = Actions.Update; battleResults.ResponseType = DisplayMessages.BattleResults; if (battleHasCommenced) { Globals_Game.UpdatePlayer(defender.GetOwner().playerID, DisplayMessages.BattleBringSuccess, new string[] { battleResults.attackerOwner }); } else { Globals_Game.UpdatePlayer(defender.GetOwner().playerID, DisplayMessages.BattleBringFail, new string[] { battleResults.attackerOwner }); } // end siege if appropriate if (siegeRaised) { //HACK thisSiege.SiegeEnd(false, DisplayMessages.BattleResults, new string[] { DisplaySiegeResults(battleResults) }); thisSiege = null; // ensure if siege raised correct value returned to Form1.siegeReductionRound method if (circumstance.Equals("siege")) { attackerVictorious = true; } } // process leader deaths if (defenderLeaderDead) { defenderLeader.ProcessDeath("injury"); } else if (attackerLeaderDead) { attackerLeader.ProcessDeath("injury"); } // DISBANDMENT // if is pillage, attacking (temporary) army always disbands after battle if (circumstance.Equals("pillage")) { attackerDisbanded = true; } // process army disbandings (after all other functions completed) if (attackerDisbanded) { attacker.DisbandArmy(); attacker = null; } if (defenderDisbanded) { defender.DisbandArmy(); defender = null; } return(attackerVictorious); }