/// <summary> /// Checks all in-progress buildings and researches of the country, /// and adds any completed ones to it. Does not delete in progress values that are completed. /// This method does not perform any safety check regarding the amount of buildings or researches! /// </summary> /// <param name="country">The country to build in. The buildings, researches, /// in progress buildings, researches, and their buildings and researches, must be included.</param> /// <returns>If the building could be started.</returns> protected void CheckAddCompleted(Model.Entities.Country country) { var researches = country.InProgressResearches .Where(r => r.TimeLeft == 0) .GroupBy(r => r.Research) .ToList(); if (researches.Count > 0) { foreach (var research in researches) { var existing = country.Researches.FirstOrDefault(r => r.Research.Equals(research.Key)); // Add a new research, or update an existing one if (existing == null) { country.Researches.Add(new CountryResearch { Research = research.Key, Count = research.Count() }); } else { existing.Count += research.Count(); } } } // Get and complete buildings var buildings = country.InProgressBuildings .Where(r => r.TimeLeft == 0) .GroupBy(b => b.Building) .ToList(); if (buildings.Count > 0) { foreach (var building in buildings) { var existing = country.Buildings.FirstOrDefault(b => b.Building.Equals(building.Key)); // Add a new building, or update an existing one if (existing == null) { country.Buildings.Add(new CountryBuilding() { Building = building.Key, Count = building.Count() }); } else { existing.Count += building.Count(); } } } }
/// <summary> /// Handles pre-turn claculations, like building and research completitions, and income. /// Returns the <see cref="CountryModifierBuilder"/> containing the modifications for the country. /// </summary> /// <param name="context">The <see cref="UnderSeaDatabaseContext"/> that is used to access the database.</param> /// <param name="country">The country to handle. The following must be included: Buildings, In progress buildings, /// researches, in progress researches, the buildings and researches of these, events, and the corresponding effects, commands, divisions and units.</param> /// <param name="globals">The <see cref="GlobalValue"/>s to use.</param> /// <param name="allEvents">The collection of all events that may occur.</param> /// <param name="cancel">The <see cref="CancellationToken"/> that can be used to cancel the operation.</param> /// <exception cref="ArgumentNullException">Thrown if an argument was null.</exception> public void HandlePreCombat(UnderSeaDatabaseContext context, Model.Entities.Country country, GlobalValue globals, IList <RandomEvent> allEvents) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (country == null) { throw new KeyNotFoundException(); } if (globals == null) { throw new ArgumentNullException(nameof(globals)); } // #1: Add a random event if (country.CreatedRound + globals.RandomEventGraceTimer <= globals.Round && rng.NextDouble() <= globals.RandomEventChance) { country.CurrentEvent = allEvents[rng.Next(allEvents.Count)]; } else if (country.CurrentEvent != null) { country.CurrentEvent = null; } // Apply permanent effects here var builder = country.ParseAllEffectForCountry(context, globals, Parsers, true); if (builder.WasEventIgnored) { country.CurrentEvent = null; } // #2: Tax country.Pearls += (long)Math.Round(builder.Population * globals.BaseTaxation * builder.TaxModifier + builder.PearlProduction); // #3: Coral (harvest) country.Corals += (long)Math.Round(builder.CoralProduction * builder.HarvestModifier); // #4: Pay soldiers DesertUnits(country); // #5: Add buildings that are completed CheckAddCompleted(country); }
/// <summary> /// Deletes units until the supply / maintenance consumption can be satisfied by the country. /// Starts with the cheapest units. The commands, divisions and units must be included. /// </summary> /// <param name="country">The country to delete from.</param> protected void DesertUnits(Model.Entities.Country country) { long pearlUpkeep = 0; long coralUpkeep = 0; foreach (var comm in country.Commands) { foreach (var div in comm.Divisions) { pearlUpkeep += div.Count * div.Unit.MaintenancePearl; coralUpkeep += div.Count * div.Unit.MaintenanceCoral; } } if (coralUpkeep > country.Corals || pearlUpkeep > country.Pearls) { long pearlDeficit = Math.Max(pearlUpkeep - country.Pearls, 0); long coralDeficit = Math.Max(coralUpkeep - country.Corals, 0); foreach (var div in country.Commands.SelectMany(c => c.Divisions).OrderBy(d => d.Unit.CostPearl)) { long requiredPearlReduction = (long)Math.Ceiling((double)pearlDeficit / div.Unit.MaintenancePearl); long requiredCoralReduction = (long)Math.Ceiling((double)coralDeficit / div.Unit.MaintenanceCoral); int desertedAmount = (int)Math.Min(Math.Max(requiredCoralReduction, requiredPearlReduction), div.Count); div.Count -= desertedAmount; pearlDeficit -= desertedAmount * div.Unit.MaintenancePearl; pearlUpkeep -= desertedAmount * div.Unit.MaintenancePearl; coralDeficit -= desertedAmount * div.Unit.MaintenanceCoral; coralUpkeep -= desertedAmount * div.Unit.MaintenanceCoral; if (coralUpkeep <= country.Corals && pearlUpkeep <= country.Pearls) { break; } } } pearlUpkeep = Math.Max(0, pearlUpkeep); coralUpkeep = Math.Max(0, coralUpkeep); country.Pearls -= pearlUpkeep; country.Corals -= coralUpkeep; }
/// <summary> /// Handles post-combat calculations for a country, like calculating the score, and merging commands into the defense command. /// </summary> /// <param name="context">The <see cref="UnderSeaDatabaseContext"/> that is used to access the database.</param> /// <param name="country">The country to handle. The commands, their divisions and units, buildings and researches, events, and their effects must be loaded.</param> /// <param name="globals">The <see cref="GlobalValue"/>s to use.</param> /// <exception cref="ArgumentNullException">Thrown if an argument was null.</exception> public void HandlePostCombat(UnderSeaDatabaseContext context, Model.Entities.Country country, GlobalValue globals) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (country == null) { throw new ArgumentNullException(nameof(country)); } if (globals == null) { throw new ArgumentNullException(nameof(globals)); } var builder = country.ParseAllEffectForCountry(context, globals, Parsers, false); long divisionScore = 0; foreach (var comm in country.Commands) { divisionScore += comm.Divisions.Sum(d => d.Count); } country.Score = (long)Math.Round( builder.Population * globals.ScorePopulationMultiplier + country.Buildings.Count * globals.ScoreBuildingMultiplier + divisionScore * globals.ScoreUnitMultiplier + country.Researches.Count * globals.ScoreResearchMultiplier); // Merge all attacking commands into the defense command, delete attacking commands, and add the loot var defenders = country.GetAllDefending(); foreach (var attack in country.Attacks.Where(x => x.DidAttackerWin && x.Round == globals.Round)) { country.Pearls += attack.PearlLoot; country.Corals += attack.CoralLoot; } foreach (var attack in country.Commands.Where(c => c.Id != defenders.Id).ToList()) { attack.MergeInto(defenders, context); } }
public async Task CreateAsync(string username, string countryName, CancellationToken turnEndWaitToken) { using (var lck = await TurnEndLock.ReaderLockAsync(turnEndWaitToken)) { var user = await Context.Users.SingleAsync(u => u.UserName == username); var globals = await Context.GlobalValues .Include(g => g.FirstStartingBuilding) .Include(g => g.SecondStartingBuilding) .SingleAsync(); var country = new Model.Entities.Country() { Name = countryName, ParentUser = user, Corals = globals.StartingCorals, Pearls = globals.StartingPearls, Score = -1, Rank = -1, CreatedRound = globals.Round }; var defenders = new Command { ParentCountry = country, TargetCountry = country }; Context.Countries.Add(country); Context.Commands.Add(defenders); Context.CountryBuildings.AddRange( new CountryBuilding { ParentCountry = country, Count = 1, Building = globals.FirstStartingBuilding }, new CountryBuilding { ParentCountry = country, Count = 1, Building = globals.SecondStartingBuilding }); await Context.SaveChangesAsync(); } }
/// <summary> /// Handles combat calculations for all incoming attacks of a country. The order of the attacks is randomized. /// </summary> /// <param name="context">The <see cref="UnderSeaDatabaseContext"/> that is used to access the database.</param> /// <param name="country">The country to handle. The commands, incoming attacks, the attack's divisions, parent country, /// parent country builidings, researches, events, and effects must be loaded.</param> /// <param name="globals">The <see cref="GlobalValue"/>s to use.</param> /// <exception cref="ArgumentNullException">Thrown if an argument was null.</exception> public void HandleCombat(UnderSeaDatabaseContext context, Model.Entities.Country country, GlobalValue globals) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (country == null) { throw new ArgumentNullException(nameof(country)); } if (globals == null) { throw new ArgumentNullException(nameof(globals)); } // Randomize the incoming attacks, excluding the defending forces var incomingAttacks = country.IncomingAttacks .Where(c => !c.ParentCountry.Equals(country)) .Select(c => new { Order = rng.Next(), Command = c }) .OrderBy(x => x.Order) .Select(x => x.Command) .ToList(); var defenders = country.GetAllDefending(); var builder = country.ParseAllEffectForCountry(context, globals, Parsers, false); foreach (var attack in incomingAttacks) { (double attackPower, double attackMods, double attackBase) = GetCurrentUnitPower(attack, globals, true, attack.ParentCountry.ParseAllEffectForCountry(context, globals, Parsers, false)); (double defensePower, double defenseMods, double defenseBase) = GetCurrentUnitPower(defenders, globals, false, builder); var losses = attackPower > defensePower ? CullUnits(defenders, globals.UnitLossOnLostBatle) : CullUnits(attack, globals.UnitLossOnLostBatle); var report = new CombatReport { Attacker = attack.ParentCountry, Defender = country, Attackers = attack.Divisions.Select(d => new Division { Count = d.Count, Unit = d.Unit }).ToList(), Defenders = defenders.Divisions.Select(d => new Division { Count = d.Count, Unit = d.Unit }).ToList(), TotalAttackPower = attackPower, TotalDefensePower = defensePower, AttackModifier = attackMods, DefenseModifier = defenseMods, BaseAttackPower = attackBase, BaseDefensePower = defenseBase, Round = globals.Round, PearlLoot = 0, CoralLoot = 0, Losses = losses }; if (attackPower > defensePower) { var pearlLoot = (long)Math.Round(country.Pearls * globals.LootPercentage); var coralLoot = (long)Math.Round(country.Corals * globals.LootPercentage); country.Pearls -= pearlLoot; country.Corals -= coralLoot; report.CoralLoot = coralLoot; report.PearlLoot = pearlLoot; } context.Reports.Add(report); } }