private void RemoveCombatant_Click(object sender, RoutedEventArgs e) { if (CombatantList.SelectedItem != null) { if (ActiveCombatant != null) { if (ActiveCombatant.Name.Equals((CombatantList.SelectedItem as DD4ECombatant).Name)) { var index = CombatantList.Items.IndexOf(ActiveCombatant); if (index >= CombatantList.Items.Count - 1) { ActiveCombatant = CombatantList.Items[0] as DD4ECombatant; } else { ActiveCombatant = CombatantList.Items[index + 1] as DD4ECombatant; } } } var removeIndex = CombatantList.SelectedIndex; CombatantList.SelectedIndex = -1; Combatants.RemoveAt(removeIndex); } }
private void SortByInitiative() { for (int i = 0; i < Combatants.Count - 1; i++) { var highestIndex = i; for (int j = i + 1; j < Combatants.Count; j++) { if (Combatants[j].Initiative > Combatants[highestIndex].Initiative) { highestIndex = j; } else if (Combatants[j].Initiative == Combatants[highestIndex].Initiative) { if (Combatants[j].InitiativeBonus > Combatants[highestIndex].InitiativeBonus) { highestIndex = j; } } } if (i != highestIndex) { Combatants = (ObservableCollection <DD4ECombatant>)Combatants.Swap(i, highestIndex); } } }
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); } } } }
private void PopulateGladiatorList(int numberToBuild) { var factory = new GladiatorFactory(); for (var i = 0; i < numberToBuild; i++) { Combatants.Add(factory.BuildGladiator()); } }
public void EnemyTurn() { var highestPresence = Combatants.Max(u => u.Player.Stats.Presence); AttackTarget = Combatants.Where(u => u.Player.Stats.Presence == highestPresence).First(); if (AttackTarget == null) { Target.Combat = null; return; } if (AttackTarget.Player.Stats.CurrHealth <= 0) { Target.Combat = null; return; } // Roll to see if the monster lands a hit int hit = Roll(100); int attack = Strike; if (hit >= AttackTarget.Player.Stats.Agility) { // Check if the attack is a critical int crit = Roll(100); if (crit >= AttackTarget.Player.Stats.CritAvoid) { AttackTarget.Connection.SendMessage(AttackCriticalFlavor); attack = attack + (Strike / 2); } if (crit < AttackTarget.Player.Stats.CritAvoid) { // If it's not critical, check if it's weak int weak = Roll(100); if (weak <= AttackTarget.Player.Stats.Luck) { AttackTarget.Connection.SendMessage(AttackWeakFlavor); attack = attack - (Strike / 2); } AttackTarget.Connection.SendMessage(AttackSuccessFlavor); } // If the player is blocking, attack gets a further strike to damage if (AttackTarget.Player.IsBlocking) { attack = attack - AttackTarget.Player.Stats.Defense; if (attack < 0) { attack = 0; } CombatSay($"{Target.Name} is blocked by {AttackTarget.Name}!"); } AttackTarget.Player.TakeDamage(attack); CombatSay($"{Target.Name} deals {Strike} damage to {AttackTarget.Name}"); AttackTarget.Connection.SendMessage($"Your current health is {AttackTarget.Player.Stats.CurrHealth}"); return; } AttackTarget.Connection.SendMessage(AttackFailFlavor); AttackTarget.Connection.SendMessage($"Your current health is {AttackTarget.Player.Stats.CurrHealth}"); }
private void MoveUp() { if (SelectedCombatant.TieBreaker != 0) { var previousCombatant = Combatants.FirstOrDefault(c => c.TieBreaker == SelectedCombatant.TieBreaker - 1); previousCombatant.TieBreaker++; SelectedCombatant.TieBreaker--; } Combatants.Sort(); }
private void MoveDown() { if (SelectedCombatant.TieBreaker != Combatants.Count - 1) { var nextCombatant = Combatants.FirstOrDefault(c => c.TieBreaker == SelectedCombatant.TieBreaker + 1); nextCombatant.TieBreaker--; SelectedCombatant.TieBreaker++; } Combatants.Sort(); }
} // EndTurn() public static void VerifyStatus(int targetId, ApplicationDbContext ctx) { Creature target = Combatants.FirstOrDefault(x => x.Id == targetId); if (target.HitPoints < 1) { target.Alive = false; if (target.HitPoints < 0) { target.HitPoints = 0; } AddToLog(new string($"W skutek odniesionych ran {target.Name} pada martwy.")); } if (Hero.Alive == false) { AddToLog(new string($"To koniec podróży. Oczy {Hero.Name} już na zawsze pozostaną zamknięte. Inni zostaną wysłani, ale czy ktokolwiek odniesie sukces?")); GameplayManager.Phase = Enumerators.GamePhase.CharacterCreation; GameplayManager.Step = 0; Status = false; } else { deadCount = 0; foreach (var creature in Enemies) { if (creature.Alive == false) { deadCount++; } } if (Enemies.Count == deadCount) { int experience = 0; foreach (var creature in Enemies) { experience += creature.Experience; } Hero.Experience += experience; AddToLog(new string($"{Hero.Name} rozgromił wszystkich przeciwników, których resztki leżą rozrzucone na ziemii. Walka skończona.")); AddToLog(new string($"{Hero.Name} Zyskuje {experience} punktów doświadczenia.")); CheckLevel(ctx); } } } // VerifyStatus()
private void FateOf(Gladiator gladiator) { if (gladiator.IsAlive()) { Combatants.Add(gladiator); } else { Graveyard.Add(gladiator); } }
private void Load() { Combatants.Clear(); var eventArgs = new SaveEventArgs(); LoadTriggered?.Invoke(this, eventArgs); foreach (Combatant combatant in eventArgs.SaveData) { Combatants.Add(combatant); } Combatants.Sort(); }
private void MoveDown_Click(object sender, RoutedEventArgs e) { if (Combatants.Count < 2) { return; } if (CombatantList.SelectedIndex >= Combatants.Count - 1) { return; } Combatants = (ObservableCollection <DD4ECombatant>)Combatants.MoveDown(CombatantList.SelectedIndex); }
private void AddCombatant(DD4ECombatant combatant) { if (Combatants.Any(c => c.Name.Equals(combatant.Name) && c.IsPlayer == false)) { int id = Combatants.Count(c => c.Name.Equals(combatant.Name) && c.IsPlayer == false); combatant.ID = id; } else { if (Combatants.Any(c => c.Name.Equals(combatant.Name))) { MessageBox.Show("You cannot add the same player character twice.", "Warning"); return; } } Combatants.Add(combatant); }
private void Previous() { Combatant activeCombatant = Combatants.FirstOrDefault(c => c.IsActive); activeCombatant.IsActive = false; int index = Combatants.IndexOf(activeCombatant); if (index == 0) { index = Combatants.Count - 1; } else { index--; } Combatants[index].IsActive = true; }
public void Run(User current, Door run) { if (Combatants.Count > 1) { current.Connection.SendMessage("You cannot abandon your allies!"); return; } if (run == null) { current.Connection.SendMessage("There is no escape!"); return; } current.Connection.SendMessage($"You manage to escape {run.Direction}!"); Combatants.Remove(current); current.Player.Combat = null; current.CurrRoomId = run.RoomsIConnect[1]; }
private void Next() { Combatant activeCombatant = Combatants.FirstOrDefault(c => c.IsActive); activeCombatant.IsActive = false; int index = Combatants.IndexOf(activeCombatant); if (index == Combatants.Count - 1) { index = 0; } else { index++; } Combatants[index].IsActive = true; }
} // OrderOfBattle() public static void PrepareBattle(ApplicationDbContext ctx) { if (Status == false) { random = new Random(); OrderOfBattle(); BattleLog = new List <string>(); combatantsCount = Combatants.Count(); combatantTurn = 0; var first = Combatants.First(); AddToLog(new string($"Walka rozpoczęta, jako pierwszy uderza {first.Name}")); deadCount = 0; Status = true; } BattleTurn(ctx); } // PrepareBattle()
} // Attack() public static void UseItem(int userId, int itemId) { Creature user = Combatants.SingleOrDefault(x => x.Id == userId); CreatureItem item = user.Items.SingleOrDefault(x => x.ItemId == itemId); if (item.Item.ItemType == Enumerators.ItemTypeEnum.Healing) { user.HitPoints += item.Item.Strength; if (user.HitPoints > user.MaxHP) { user.HitPoints = user.MaxHP; } AddToLog(new string($"{user.Name} używa {item.Item.Name}, które przywraca {item.Item.Strength} punktów zdrowia.")); } else if (item.Item.ItemType == Enumerators.ItemTypeEnum.Mana) { user.Mana += item.Item.Strength; if (user.Mana > user.MaxMana) { user.Mana = user.MaxMana; } AddToLog(new string($"{user.Name} używa {item.Item.Name}, które przywraca {item.Item.Strength} punktów many.")); } user.ActionPoints -= item.Item.ActionCost; if (item.Qty == 1) { user.Items.Remove(item); } else { item.Qty -= 1; } } // UseItem()
private void Maintenance(int fights) { var wounded = Combatants.Where(g => g.IsWounded()); foreach (var g in wounded) { g.Heal(3); } //var victoryMax = Combatants.Max(c => c.Victories); //if ((new Random()).NextDouble() < 0.05 || Combatants.Count < 10) //{ // var numberToAdd = (new Random()).Next(0, 30); // numberToAdd = Math.Min(numberToAdd, 1000 - Graveyard.Count - Combatants.Count); // numberToAdd = Math.Max(0, numberToAdd); // //View.Roster(Combatants, fights, Graveyard.Count); // PopulateGladiatorList(numberToAdd); // //View.Roster(Combatants, fights, Graveyard.Count); //} }
private (Gladiator, Gladiator) PickPair() { var r = new Random(); var eligible = EligibleToFight(Combatants); var first = eligible .OrderBy(x => r.NextDouble()) .First(); var second = eligible .Where(g => g != first) //.OrderBy(g => Math.Abs(g.Victories - first.Victories)) .OrderBy(x => r.NextDouble()) .First(); Combatants.Remove(first); Combatants.Remove(second); return(first, second); }
private void Add() { int initiative; if (!int.TryParse(Initiative, out initiative)) { throw new Exception($"{Initiative} is not a valid value."); } bool initiativeTied = Combatants.Any(c => c.Initiative == initiative); var newCombatant = new Combatant(Name, initiative, Combatants.Count == 0); Combatants.Add(newCombatant); if (initiativeTied && InitiativeTied != null) { newCombatant.TieBreaker = Combatants.Max(c => c.TieBreaker) + 1; InitiativeTied?.Invoke(this, new TieInitiativeEventArgs(Combatants.Where(c => c.Initiative == initiative))); } Combatants.Sort(); Name = null; Initiative = null; }
public Task AddCombatantAsync(GridEntity entity, ICombatant combatant, string factionKey = "") { List <IEntity> faction; if (entity.IsPlayer()) { Dead.RemoveAll(d => d.Id == entity.Id); } // some default behaviour will be to find the faction with the matching isPlayer if (string.IsNullOrEmpty(factionKey)) { var testFaction = Factions.FirstOrDefault(o => o.Value.Any(e => e.IsPlayer() == entity.IsPlayer())); if (testFaction.Value == null) { testFaction = Factions.FirstOrDefault(o => o.Value.Count == 0); } if (testFaction.Value == null) { testFaction = Factions.FirstOrDefault(); } faction = testFaction.Value; } else { faction = Factions[factionKey]; } if (faction != null) { faction.Add(entity); } Combatants.Add(entity, combatant); entity.Died += Combatant_Death; return(Task.CompletedTask); }
private void Reset() { Combatants.Clear(); Name = null; Initiative = null; }
} // AddToLog() public static void Attack(int attackerId, int attackType, int targetId, ApplicationDbContext ctx) { //attackType 0 - fast, 1 - normal, 2 - strong int chanceLevel = 0; bool HitSucces = false; Creature attacker = Combatants.FirstOrDefault(x => x.Id == attackerId); if (random.Next(1, 100) == 50) { targetId = attackerId; AddToLog(new string($"{attacker.Name} chybia cel raniąc siebie.")); } // fail - 1% chance, the attacker hitting itself Creature target = Combatants.FirstOrDefault(x => x.Id == targetId); if (attackType == 0) { attacker.ActionPoints -= (attacker.Weapon.ActionCost - 1); } else if (attackType == 1) { attacker.ActionPoints -= attacker.Weapon.ActionCost; } else { attacker.ActionPoints -= (attacker.Weapon.ActionCost + 1); } #region Hit chance if (targetId != attackerId) { if (attackType == 0) { chanceLevel = attacker.Weapon.HitChance + 10 + attacker.Agility - target.Agility; // +10 as fast attack is more likely to hit } else if (attackType == 1) { chanceLevel = attacker.Weapon.HitChance + attacker.Agility - target.Agility; } else { chanceLevel = attacker.Weapon.HitChance - 10 + attacker.Agility - target.Agility; // -10 as strong attack is less likely to hit } int calculateHit = random.Next(0, 100); if (calculateHit <= chanceLevel) { HitSucces = true; } else { HitSucces = false; AddToLog($"{attacker.Name} chybia."); } } else { HitSucces = true; } #endregion #region HitSucces true if (HitSucces == true) { double attackPower = (double)attacker.Weapon.BaseDamage; double attackMagicPower = (double)attacker.Weapon.MagicDamage; double criticalSuccess = 1; if (random.Next(1, 100) == 50) { criticalSuccess = 2; } // 1% chance to deal double damage if (attackType == 0) { if (attacker.Strength >= 4) { attackPower *= (double)attacker.Strength / 4 * (random.NextDouble() * (0.8 - 0.65) + 0.65); attackMagicPower *= (double)attacker.Strength / 4 * (random.NextDouble() * (0.8 - 0.65) + 0.65); } else { attackPower *= 2 * random.NextDouble() * (0.8 - 0.65) + 0.65; attackMagicPower *= 2 * random.NextDouble() * (0.8 - 0.65) + 0.65; } } else if (attackType == 1) { if (attacker.Strength >= 4) { attackPower *= (double)attacker.Strength / 4 * (random.NextDouble() * (1.1 - 0.9) + 0.9); attackMagicPower *= (double)attacker.Strength / 4 * (random.NextDouble() * (1.1 - 0.9) + 0.9); } else { attackPower *= 2 * random.NextDouble() * (1.1 - 0.9) + 0.9; attackMagicPower *= 2 * random.NextDouble() * (1.1 - 0.9) + 0.9; } } else { if (attacker.Strength >= 4) { attackPower *= (double)attacker.Strength / 4 * (random.NextDouble() * (1.35 - 1.2) + 1.2); attackMagicPower *= (double)attacker.Strength / 4 * (random.NextDouble() * (1.35 - 1.2) + 1.2); } else { attackPower *= 2 * random.NextDouble() * (1.35 - 1.2) + 1.2; attackMagicPower *= 2 * random.NextDouble() * (1.35 - 1.2) + 1.2; } } double damage = (attackPower * (100 / (100 + (double)target.DamageResistance)) - attackPower / 2) * criticalSuccess; double MDamage = (attackMagicPower * (100 / (100 + (double)target.MagicResistance)) - attackPower / 2) * criticalSuccess; int realDMG = (int)Math.Round(damage); int realMDMG = (int)Math.Round(MDamage); if (criticalSuccess == 2) { AddToLog(new string($"Cios jaki wyprowadza {attacker.Name} uderza ze zdwojoną siłą.")); } if (realDMG > 0 && realMDMG > 0) { target.HitPoints -= realDMG; target.HitPoints -= realMDMG; AddToLog(new string($"{attacker.Name} trafia {target.Name} zadając {realDMG} fizycznych i {realMDMG} magicznych puntków obrażeń.")); } else if (realDMG > 0) { target.HitPoints -= realDMG; AddToLog(new string($"{attacker.Name} trafia {target.Name} zadając {realDMG} puntków obrażeń.")); } else if (realMDMG > 0) { target.HitPoints -= realMDMG; AddToLog(new string($"{attacker.Name} trafia {target.Name} zadając {realMDMG} magicznych puntków obrażeń.")); } VerifyStatus(targetId, ctx); } // HitSucces true #endregion } // Attack()
/// <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);; } } }
} // UseItem() public static void CastSpell(int userId, int targetId, int spellId, ApplicationDbContext ctx) { Creature user = Combatants.SingleOrDefault(x => x.Id == userId); Creature target; Spell spell = new GetSpell(ctx).Get(spellId); user.Mana -= spell.ManaCost; user.ActionPoints -= spell.ActionCost; if ((spell.Level * 10) >= random.Next(1, 100)) { int selfharm; selfharm = (int)Math.Round((double)(spell.Strength / 4)); user.HitPoints -= selfharm; if (spell.Type == Enumerators.SpellTypeEnum.Healing) { AddToLog(new string($"Zaklęcie {spell.Name} wymyka się spod kontoli i {user.Name} zamiast uleczyć rani się tracąc {selfharm} punktów życia.")); } else { AddToLog(new string($"Zaklęcie {spell.Name} wymyka się spod kontoli i {user.Name} zamiast trafić w przeciwnika rani siebie tracąc {selfharm} punktów życia.")); } } /// higher spell level means higher chance for failure and % of dmg done to the caster 0 - 0% , 10 - 10% else { if (userId != targetId) { target = Combatants.SingleOrDefault(x => x.Id == targetId); } else { target = user; } if (spell.Type == Enumerators.SpellTypeEnum.Healing) { user.HitPoints += spell.Strength; if (user.HitPoints > user.MaxHP) { user.HitPoints = user.MaxHP; } AddToLog(new string($"{user.Name} rzuca {spell.Name}, które przywraca {spell.Strength} punktów zdrowia.")); } else { if (random.Next(0, 100) < target.Willpower) { AddToLog(new string($"{target.Name} dzięki sile woli odrzuca zaklęcie wychodząc z ataku bez szwanku.")); } else { int damage = (int)Math.Round((double)(spell.Strength * (100 / (100 + (double)target.MagicResistance)))); if (damage > 0) { target.HitPoints -= damage; } AddToLog(new string($"{user.Name} rzuca {spell.Name}, które trafia {target.Name} zadając mu {damage} punktów obrażeń.")); } } VerifyStatus(targetId, ctx); } } // CastSpell()
/// <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 locations = new SafeDictionary <ICombatant, IntVector2>(); PlaceCombatants(locations); Events = new List <IList <IBattleEvent> >(); UpdateBounds(0, locations.Values); // let all combatants scan each other foreach (var c in Combatants) { c.UpdateEmpireMemories(); } // make a query so we can check who's alive var alives = Combatants.Where(q => q.IsAlive); for (int i = 0; i < MaxRounds; i++) { var combatSpeeds = new SafeDictionary <ICombatant, double>(); var multiplex = new SafeDictionary <ICombatant, HashSet <ICombatant> >(true); foreach (var c in Combatants) { combatSpeeds[c] = c.CombatSpeed; } int GetCombatSpeedThisRound(ICombatant c) { return((int)(combatSpeeds[c] + CombatSpeedBuffer[c])); } Events.Add(new List <IBattleEvent>()); if (i == 0) { // first round, all combatants appear foreach (var c in Combatants) { Events.Last().Add(new CombatantAppearsEvent(this, c, locations[c])); } } var turnorder = alives.OrderBy(x => x is Seeker ? 1 : 0).ThenBy(x => combatSpeeds[x]).ThenShuffle(Dice).ToArray(); // phase 0: reload weapons foreach (var w in turnorder.SelectMany(q => q.Weapons)) { reloads[w]--; if (reloads[w] < 0) { reloads[w] = 0; } } // phase 1: combatants move starting with the slowest (so the faster ships get to react to their moves) - but seekers go last so they get a chance to hit foreach (var c in turnorder) { var oldpos = locations[c]; if (c is Seeker s) { if (locations[s] == null) { continue; // HACK - seeker is destroyed but still showing up in turn order } if (locations[s.Target] == null) { s.Hitpoints = 0; // seekers self destruct when their target is destroyed Events.Last().Add(new CombatantDestroyedEvent(this, s, locations[s])); continue; } s.DistanceTraveled += Math.Min(GetCombatSpeedThisRound(c), locations[s].DistanceToEightWay(locations[s.Target])); locations[s] = IntVector2.InterpolateEightWay(locations[s], locations[s.Target], GetCombatSpeedThisRound(c)); if (s.DistanceTraveled > s.WeaponInfo.MaxRange) { s.Hitpoints = 0; Events.Last().Add(new CombatantDestroyedEvent(this, s, locations[s])); } } else { // TODO - both pursue target and evade scary enemies at the same time using heatmap // find out how good each target is var targetiness = new SafeDictionary <ICombatant, double>(); foreach (var target in alives.Where(x => c.IsHostileTo(x.Owner) && (c.CanTarget(x) || (x is Planet && c is ICargoContainer cc && cc.Cargo.Units.OfType <Troop>().Any())))) { targetiness[target] = 1d / (locations[target] - locations[c]).LengthEightWay; } if (!targetiness.Any()) { // evade enemies var heatmap = new HeatMap(); foreach (var e in alives.Where(x => x.IsHostileTo(c.Owner) && x.CanTarget(c))) { int threat; if (e.Weapons.Any()) { threat = GetCombatSpeedThisRound(e) + e.Weapons.Where(w => w.CanTarget(c)).Max(w => w.Template.WeaponMaxRange); } else { threat = 0; } heatmap.AddLinearGradientEightWay(locations[e], threat, threat, -1); } if (c.FillsCombatTile) { // only one ship/base/planet per tile foreach (var tile in heatmap.ToArray()) { if (locations.Any(q => q.Key.FillsCombatTile && q.Value == tile.Key)) { heatmap.Remove(tile.Key); } } } if (heatmap.Any()) { locations[c] = heatmap.FindMin(locations[c], GetCombatSpeedThisRound(c)); } } else { // move to max range that we can inflict max damage on best target var goodTargets = targetiness.Where(x => !IgnoredTargets[c].Contains(x.Key)).WithMax(x => x.Value); ICombatant bestTarget = null; if (goodTargets.Any()) { bestTarget = goodTargets.First().Key; } if (bestTarget == null) { // try previously ignored targets IgnoredTargets[c].Clear(); goodTargets = targetiness.Where(x => !IgnoredTargets[c].Contains(x.Key)).WithMax(x => x.Value); if (goodTargets.Any()) { bestTarget = goodTargets.First().Key; } } if (bestTarget != null) { gotosAreEvil: var maxdmg = 0; var maxdmgrange = 0; if (c.Weapons.Any()) { for (var range = 0; range < c.Weapons.Max(w => w.Template.WeaponMaxRange); range++) { var dmg = c.Weapons.Where(w => w.CanTarget(bestTarget)).Sum(w => w.Template.GetWeaponDamage(range)); if (dmg >= maxdmg) { maxdmg = dmg; maxdmgrange = range; } } } if (c.Weapons.Any(w => w.Template.ComponentTemplate.WeaponInfo.IsSeeker) && locations[c].DistanceToEightWay(locations[bestTarget]) > DistancesToTargets[c]) { // adjust desired range due to seeker speed and target speed if retreating var roundsToClose = c.Weapons.Where(w => w.Template.ComponentTemplate.WeaponInfo.IsSeeker).Max(w => (int)Math.Ceiling((double)w.Template.WeaponMaxRange / (double)(w.Template.ComponentTemplate.WeaponInfo as SeekingWeaponInfo).SeekerSpeed)); var distanceAdjustment = (int)Ceiling(combatSpeeds[bestTarget] * roundsToClose); maxdmgrange -= distanceAdjustment; if (maxdmgrange < 0) { maxdmgrange = 0; } } var targetPos = locations[bestTarget]; var tiles = new HashSet <IntVector2>(); for (var x = targetPos.X - maxdmgrange; x <= targetPos.X + maxdmgrange; x++) { tiles.Add(new IntVector2(x, targetPos.Y - maxdmgrange)); tiles.Add(new IntVector2(x, targetPos.Y + maxdmgrange)); } for (var y = targetPos.Y - maxdmgrange; y <= targetPos.Y + maxdmgrange; y++) { tiles.Add(new IntVector2(targetPos.X - maxdmgrange, y)); tiles.Add(new IntVector2(targetPos.X + maxdmgrange, y)); } if (c.FillsCombatTile) { foreach (var tile in tiles.ToArray()) { if (locations.Any(q => q.Key.FillsCombatTile && q.Value == tile)) { tiles.Remove(tile); } } } if (tiles.Any()) { var closest = tiles.WithMin(t => t.DistanceToEightWay(locations[c])).First(); locations[c] = IntVector2.InterpolateEightWay(locations[c], closest, GetCombatSpeedThisRound(c), vec => locations.Values.Contains(vec)); var newdist = locations[c].DistanceToEightWay(locations[bestTarget]); if (DistancesToTargets.ContainsKey(c) && newdist >= DistancesToTargets[c] && combatSpeeds[c] <= combatSpeeds[bestTarget] && !c.Weapons.Any(w => w.Template.WeaponMaxRange >= newdist)) { DistancesToTargets.Remove(c); IgnoredTargets[c].Add(bestTarget); // can't catch it, might as well find a new target goodTargets = targetiness.Where(x => !IgnoredTargets[c].Contains(x.Key)).WithMax(x => x.Value); bestTarget = null; if (goodTargets.Any()) { bestTarget = goodTargets.First().Key; } if (bestTarget == null) { goto gotosAreVeryEvil; } goto gotosAreEvil; } else { DistancesToTargets[c] = newdist; } } } else { DistancesToTargets.Remove(c); } } } gotosAreVeryEvil: if (locations[c] != oldpos) { Events.Last().Add(new CombatantMovesEvent(this, c, oldpos, locations[c])); } } UpdateBounds(i, locations.Values); // phase 2: combatants launch units foreach (var c in turnorder) { // find launchable units var unitsToLaunch = new List <(ICombatant Launcher, SpaceVehicle Launchee)>(); if (c is Planet) { // planets can launch infinite units per turn var p = (Planet)c; if (p.Cargo != null && p.Cargo.Units != null) { foreach (var u in p.Cargo.Units.OfType <SpaceVehicle>()) { unitsToLaunch.Add((p, u)); } } } else if (c is ICargoTransferrer) { // ships, etc. can launch units based on abilities var ct = (ICargoTransferrer)c; foreach (var vt in Enum.GetValues(typeof(VehicleTypes)).Cast <VehicleTypes>().Distinct()) { var rate = ct.GetAbilityValue("Launch/Recover " + vt.ToSpacedString() + "s").ToInt(); foreach (var u in ct.Cargo.Units.Where(u => u.Design.VehicleType == vt).OfType <SpaceVehicle>().Take(rate)) { unitsToLaunch.Add((c, u)); } } } // launch them temporarily for combat foreach (var info in unitsToLaunch) { Launchers[info.Launchee] = info.Launcher; if (info.Launcher is ICargoTransferrer ct && info.Launchee is IUnit u) { ct.RemoveUnit(u); } Combatants.Add(info.Item2); StartCombatants[info.Item2.ID] = info.Item2.Copy(); for (var ix = 0; ix < info.Item2.Weapons.Count(); ix++) { var w = info.Item2.Weapons.ElementAt(ix); var wc = StartCombatants[info.Item2.ID].Weapons.ElementAt(ix); } locations[info.Launchee] = new IntVector2(locations[info.Launcher]); Events.Last().Add(new CombatantLaunchedEvent(this, info.Launcher, info.Launchee, locations[info.Launchee])); } } turnorder = alives.OrderBy(x => x.CombatSpeed).ThenShuffle(Dice).ToArray(); // phase 3: combatants fire point defense non-warhead weapons starting with the fastest (so the faster ships get to inflict damage first and possibly KO enemies preventing them from firing back) foreach (var c in turnorder.Reverse()) { foreach (var w in c.Weapons.Where(w => w.Template.ComponentTemplate.WeaponInfo.IsPointDefense && !w.Template.ComponentTemplate.WeaponInfo.IsWarhead)) { TryFireWeapon(c, w, reloads, locations, multiplex); } } turnorder = alives.OrderBy(x => x.CombatSpeed).ThenShuffle(Dice).ToArray(); // phase 4: point defense seekers detonate foreach (var s in turnorder.Reverse().OfType <Seeker>().Where(s => s.WeaponInfo.IsPointDefense)) { CheckSeekerDetonation(s, locations); } turnorder = alives.OrderBy(x => x.CombatSpeed).ThenShuffle(Dice).ToArray(); // phase 5: ships fire non-PD non-warhead weapons starting with the fastest (so the faster ships get to inflict damage first and possibly KO enemies preventing them from firing back) foreach (var c in turnorder.Reverse()) { foreach (var w in c.Weapons.Where(w => !w.Template.ComponentTemplate.WeaponInfo.IsPointDefense && !w.Template.ComponentTemplate.WeaponInfo.IsWarhead)) { TryFireWeapon(c, w, reloads, locations, multiplex); } } turnorder = alives.OrderBy(x => x.CombatSpeed).ThenShuffle(Dice).ToArray(); // phase 6: non-PD seekers detonate foreach (var s in turnorder.Reverse().OfType <Seeker>().Where(s => !s.WeaponInfo.IsPointDefense)) { CheckSeekerDetonation(s, locations); } turnorder = alives.OrderBy(x => x.CombatSpeed).ThenShuffle(Dice).ToArray(); // phase 7: ramming! only activates if ship has no other weapons foreach (var c in turnorder.Reverse()) { if (!c.Weapons.Any(w => !w.Template.ComponentTemplate.WeaponInfo.IsWarhead)) { // TODO - add damage from ship HP on both sides foreach (var w in c.Weapons.Where(w => w.Template.ComponentTemplate.WeaponInfo.IsWarhead)) { TryFireWeapon(c, w, reloads, locations, multiplex); } } } turnorder = alives.OrderBy(x => x.CombatSpeed).ThenShuffle(Dice).ToArray(); // TODO - boarding // phase 8: drop troops foreach (var c in turnorder.Reverse()) { if (c is ICargoTransferrer cc && cc.AllUnits.OfType <Troop>().Any()) { // find enemy planets in the same square var dropTargets = locations.Where(q => q.Key != c && q.Value == locations[c] && q.Key is Planet p && c.IsHostileTo(p.Owner)).Select(q => q.Key).Cast <Planet>(); var dropTarget = dropTargets.PickRandom(Dice); if (dropTarget != null) { var cd = new CargoDelta(); cd.UnitTypeTonnage.Add(VehicleTypes.Troop, null); cc.TransferCargo(cd, dropTarget, cc.Owner, true); var groundBattle = new GroundBattle(dropTarget); groundBattle.Resolve(); } } } // clear used combat speed buffer speed foreach (var x in Combatants) { CombatSpeedBuffer[x] += x.CombatSpeed - Floor(x.CombatSpeed); CombatSpeedBuffer[x] -= Floor(CombatSpeedBuffer[x]); } UpdateBounds(i, locations.Values); bool hostile = false; foreach (var a in alives) { foreach (var b in alives) { // TODO - check if ships want to ram even if they have no weapons if (a.IsHostileTo(b.Owner) && a.Weapons.Any()) { hostile = true; break; } } if (hostile) { break; } } if (!hostile) { break; } } // recover units var orphans = new List <IUnit>(); foreach (var u in Combatants.OfType <IUnit>()) { if (Launchers[u] is ICargoTransferrer cc && cc.CargoStorageFree() >= u.Design.Hull.Size && u.Owner == cc.Owner) { cc.Cargo.Units.Add(u); }
private void ACTExtension(bool isImport, LogLineEventArgs e) { string[] data = e.logLine.Split('|'); MessageType messageType = (MessageType)Convert.ToInt32(data[0]); switch (messageType) { case MessageType.LogLine: if (Convert.ToInt32(data[2], 16) == 56) { ReadFFxivEcho(data[4]); } break; case MessageType.ChangeZone: ChangeZoneEvent(data); break; case MessageType.ChangePrimaryPlayer: DetectMyName(data); break; case MessageType.AddCombatant: if (!Combatants.ContainsKey(data[2])) { CombatData cd = new CombatData(); cd.PlayerID = Convert.ToUInt32(data[2], 16); cd.PlayerJob = Convert.ToUInt32(data[4], 16); cd.PlayerName = data[3]; cd.MaxHP = cd.CurrentHP = Convert.ToInt64(data[5], 16); cd.MaxMP = cd.CurrentMP = Convert.ToInt64(data[6], 16); if (data[8] != "0") { cd.IsPet = true; cd.OwnerID = Convert.ToUInt32(data[8]); } Combatants.Add(data[2], cd); SendCombatantList(); } break; case MessageType.RemoveCombatant: if (Combatants.ContainsKey(data[2])) { Combatants.Remove(data[2]); SendCombatantList(); } break; case MessageType.PartyList: UpdatePartyList(data); break; case MessageType.NetworkStartsCasting: case MessageType.NetworkCancelAbility: case MessageType.NetworkDoT: case MessageType.NetworkDeath: case MessageType.NetworkBuff: case MessageType.NetworkTargetIcon: case MessageType.NetworkRaidMarker: case MessageType.NetworkTargetMarker: case MessageType.NetworkBuffRemove: break; case MessageType.NetworkAbility: case MessageType.NetworkAOEAbility: Ability(messageType, data); break; } }