public override void PlaceCombatants(SafeDictionary <ICombatant, IntVector2> locations) { if (Sector.SpaceObjects.OfType <WarpPoint>().Any()) { // HACK - warp point in sector, assume someone warped // TODO - do this for warp point exits instead since warp points may be one way // warp battles start with everyone mashed together to allow blockades foreach (var c in Combatants.OrderByDescending(q => q.Size)) { PlaceCombatant(locations, 0, 0, c); } } else { // place all combatants at the points of a regular polygon var sideLength = 20 + (int)Math.Ceiling((double)Combatants.GroupBy(q => q.Owner).Max(q => q.Count())); // make sure no one can shoot each other at the start // https://stackoverflow.com/questions/32169875/calculating-the-coordinates-of-a-regular-polygon-given-its-center-and-its-side-l var radius = sideLength / (2 * Sin(PI / Empires.Count())); var combs = Combatants.ToArray(); for (int i = 0; i < Empires.Count(); i++) { var x = radius * Cos(PI / Empires.Count() * (1 + 2 * i)); var y = radius * Sin(PI / Empires.Count() * (1 + 2 * i)); foreach (var comb in Combatants.Where(q => q.Owner == Empires.ElementAt(i)).OrderByDescending(q => q.Size)) { PlaceCombatant(locations, x, y, comb); } } } }
/// <summary> /// Resolves the battle. /// </summary> public void Resolve() { // update memories foreach (var sobj in StarSystem.SpaceObjects.Where(x => !x.IsMemory).ToArray()) { sobj.UpdateEmpireMemories(); } Current.Add(this); var reloads = new SafeDictionary <Component, double>(); var seekers = new Dictionary <Seeker, int>(); // let all combatants scan each other foreach (var c in Combatants) { c.UpdateEmpireMemories(); } for (int i = 0; i < Mod.Current.Settings.SpaceCombatTurns; i++) { LogRound(i + 1); // TODO - real 2D combat mechanics foreach (var seeker in seekers.Keys.ToArray()) { seekers[seeker]--; if (seekers[seeker] <= 0) { seekers.Remove(seeker); var minrng = seeker.LaunchingComponent.Template.WeaponMinRange; var maxrng = seeker.LaunchingComponent.Template.WeaponMinRange; var range = Dice.Next(maxrng - minrng) + minrng; // just pick a random valid range var shot = new Shot(seeker.LaunchingCombatant, seeker.LaunchingComponent, seeker.Target, range); Log.Add(seeker.CreateLogMessage(seeker + " detonates! " + seeker.Target + " takes " + shot.FullDamage + " damage.")); seeker.Target.TakeDamage(new Hit(shot, seeker.Target, seeker.Damage.Value)); } else { Log.Add(seeker.CreateLogMessage(seeker + " moves closer to " + seeker.Target + " (" + seekers[seeker] + " rounds to detonation)")); } } foreach (var launcher in Combatants.ToArray()) { // find launchable units var unitsToLaunch = new List <SpaceVehicle>(); if (launcher is Planet) { // planets can launch infinite units per turn var p = (Planet)launcher; if (p.Cargo != null && p.Cargo.Units != null) { unitsToLaunch.AddRange(p.Cargo.Units.OfType <SpaceVehicle>()); } } else if (launcher is ICargoTransferrer) { // ships, etc. can launch units based on abilities var ct = (ICargoTransferrer)launcher; foreach (var vt in Enum.GetValues(typeof(VehicleTypes)).Cast <VehicleTypes>().Distinct()) { var rate = ct.GetAbilityValue("Launch/Recover " + vt.ToSpacedString() + "s").ToInt(); unitsToLaunch.AddRange(ct.Cargo.Units.Where(u => u.Design.VehicleType == vt).OfType <SpaceVehicle>().Take(rate)); } } // launch them temporarily for combat foreach (var unit in unitsToLaunch) { Combatants.Add(unit); } } foreach (var attacker in Combatants.Shuffle(Dice).Where(sobj => sobj.Weapons.Any()).ToArray()) { if (!attacker.IsAlive) { continue; } var defenders = Combatants.Where(sobj => attacker.CanTarget(sobj) && sobj.IsAlive); if (!defenders.Any()) { continue; // no one to shoot at } var defender = defenders.PickRandom(Dice); int dmg = 0; foreach (var weapon in attacker.Weapons.Where(w => w.CanTarget(defender))) { while (reloads[weapon] <= 0) { // fire var winfo = weapon.Template.ComponentTemplate.WeaponInfo; if (winfo is SeekingWeaponInfo) { // launch a seeker var swinfo = (SeekingWeaponInfo)winfo; var seeker = new Seeker(this, attacker.Owner, attacker, weapon, defender); seekers.Add(seeker, 20 / swinfo.SeekerSpeed); LogLaunch(seeker); } else { // direct fire var minrng = weapon.Template.WeaponMinRange; var maxrng = weapon.Template.WeaponMinRange; var range = Dice.Next(maxrng - minrng) + minrng; // just pick a random valid range var shot = new Shot(attacker, weapon, defender, range); dmg += shot.FullDamage; defender.TakeDamage(new Hit(shot, defender, weapon.Template.GetWeaponDamage(range))); } // TODO - mounts that affect reload rate? reloads[weapon] += weapon.Template.ComponentTemplate.WeaponInfo.ReloadRate; } // reload reloads[weapon] -= 1; } LogSalvo(attacker, defender, dmg); } } // validate fleets since some ships might have died foreach (var fleet in Sector.SpaceObjects.OfType <Fleet>()) { fleet.Validate(); } // replenish combatants' shields foreach (var combatant in Sector.SpaceObjects.OfType <ICombatant>()) { combatant.ReplenishShields(); } // mark battle complete Current.Remove(this); Previous.Add(this); // update memories foreach (var sobj in Combatants.OfType <ISpaceObject>().Where(x => !x.IsMemory).ToArray()) { foreach (var emp in Empires) { emp.UpdateMemory(sobj);; } } }