private Tuple <int, int> PlaceSquadVertically(BattleSquad squad, Tuple <int, int> bottomLeft, Tuple <int, int> squadBoxSize) { Tuple <int, int> startingLocation = new Tuple <int, int>(bottomLeft.Item1 + squadBoxSize.Item2 - 1, bottomLeft.Item2 + ((squadBoxSize.Item1 - 1) / 2)); for (int i = 0; i < squad.Soldiers.Count; i++) { // 0th soldier goes in the coordinate given, then alternate to each side up to membersPerRow, then repeat in additional rows as necessary int xMod = i / squadBoxSize.Item1 * (squad.IsPlayerSquad ? -1 : 1); int yMod = ((i % squadBoxSize.Item1) + 1) / 2 * (i % 2 == 0 ? -1 : 1); if (squad.IsPlayerSquad) { _playerSoldierIds.Add(squad.Soldiers[i].Soldier.Id); } else { _opposingSoldierIds.Add(squad.Soldiers[i].Soldier.Id); } Tuple <int, int> location = new Tuple <int, int>(startingLocation.Item1 + xMod, startingLocation.Item2 + yMod); _soldierLocationMap[squad.Soldiers[i].Soldier.Id] = location; _locationSoldierMap[location] = squad.Soldiers[i].Soldier.Id; squad.Soldiers[i].Location = location; } return(startingLocation); }
private void ShootIfReasonable(BattleSoldier soldier, bool isMoving) { if (soldier.RangedWeapons.Count == 0) { return; } float range = _grid.GetNearestEnemy(soldier.Soldier.Id, out int closestEnemyId); BattleSquad oppSquad = _opposingSoldierIdSquadMap[closestEnemyId]; BattleSoldier target = oppSquad.GetRandomSquadMember(); range = _grid.GetDistanceBetweenSoldiers(soldier.Soldier.Id, target.Soldier.Id); // decide whether to shoot or aim Tuple <float, float, RangedWeapon> weaponProfile = ShouldShootAtRange(soldier, target, range, isMoving); if (weaponProfile.Item3 != null) { int shotsToFire = CalculateShotsToFire(weaponProfile.Item3, weaponProfile.Item1, weaponProfile.Item2); _shootActionBag.Add(new ShootAction(soldier, weaponProfile.Item3, target, range, shotsToFire, isMoving, _woundResolutionBag, _log)); } else if (!isMoving) { // aim with longest ranged weapon _shootActionBag.Add(new AimAction(soldier, target, soldier.EquippedRangedWeapons.OrderByDescending(w => w.Template.MaximumRange).First(), _log)); } }
private void AddRetreatingActionsToBag(BattleSoldier soldier, BattleSquad soldierSquad) { float moveSpeed = soldier.GetMoveSpeed(); int newY = (int)(soldierSquad.IsPlayerSquad ? -moveSpeed : moveSpeed); AddMoveAction(soldier, moveSpeed, new Tuple <int, int>(0, newY)); // determine if soldier will shoot as he falls back ShootIfReasonable(soldier, true); }
private static List <BattleSquad> CreateBattleSquadList(IReadOnlyList <Squad> squads, bool isPlayerSquad) { List <BattleSquad> battleSquadList = new List <BattleSquad>(); foreach (Squad squad in squads) { BattleSquad bs = new BattleSquad(isPlayerSquad, squad); battleSquadList.Add(bs); } return(battleSquadList); }
public BattleSoldier(ISoldier soldier, BattleSquad squad) { Soldier = soldier; BattleSquad = squad; MeleeWeapons = new List <MeleeWeapon>(); RangedWeapons = new List <RangedWeapon>(); EquippedMeleeWeapons = new List <MeleeWeapon>(); EquippedRangedWeapons = new List <RangedWeapon>(); Location = null; Aim = null; IsInMelee = false; Stance = Stance.Standing; CurrentSpeed = 0; EnemiesTakenDown = 0; }
public void PlaceBattleSquad(BattleSquad squad, Tuple <int, int> bottomLeft, bool longHorizontal) { // if any squad member is already on the map, we have a problem if (squad.Soldiers.Any(s => _soldierLocationMap.ContainsKey(s.Soldier.Id))) { throw new InvalidOperationException(squad.Name + " has soldiers already on BattleGrid"); } if (squad.Soldiers.Count == 0) { throw new InvalidOperationException("No soldiers in " + squad.Name + " to place"); } Tuple <int, int> squadBoxSize = squad.GetSquadBoxSize(); Tuple <int, int> startingLocation; if (longHorizontal) { startingLocation = PlaceSquadHorizontally(squad, bottomLeft, squadBoxSize); } else { startingLocation = PlaceSquadVertically(squad, bottomLeft, squadBoxSize); } OnSquadPlaced.Invoke(squad, startingLocation); }
public void PrepareActions(BattleSquad squad) { int retreatVotes = 0; int advanceVotes = 0; int standVotes = 0; int chargeVotes = 0; // need some concept of squad disposition... stance, whether they're actively aiming // determine closest enemy // determine our optimal range // determine closest enemy optimal range // if the enemy wants to advance, we want to stay put, and vice versa // if we both want to get closer or both want to stay put, it's more interesting if (squad.IsInMelee) { // it doesn't really matter what the soldiers want to do, it's time to fight foreach (BattleSoldier soldier in squad.Soldiers) { if (_grid.IsAdjacentToEnemy(soldier.Soldier.Id)) { AddMeleeActionsToBag(soldier); } else { AddChargeActionsToBag(soldier); } } } else { foreach (BattleSoldier soldier in squad.Soldiers) { float distance = _grid.GetNearestEnemy(soldier.Soldier.Id, out int closestSoldierId); BattleSquad closestSquad = _opposingSoldierIdSquadMap[closestSoldierId]; float targetSize = closestSquad.GetAverageSize(); float targetArmor = closestSquad.GetAverageArmor(); float targetCon = closestSquad.GetAverageConstitution(); float preferredHitDistance = CalculateOptimalDistance(soldier, targetSize, targetArmor, targetCon); if (preferredHitDistance == -1) { // this soldier wants to run retreatVotes++; } else if (preferredHitDistance == 0) { if (soldier.EquippedRangedWeapons.Count >= 1) { float desperateHitDistance = EstimateArmorPenDistance(soldier.EquippedRangedWeapons[0], targetArmor); desperateHitDistance = Mathf.Min(desperateHitDistance, EstimateHitDistance(soldier.Soldier, soldier.EquippedRangedWeapons[0], targetSize, soldier.HandsFree)); if (desperateHitDistance > 0) { float targetPreferredDistance = CalculateOptimalDistance(closestSquad.GetRandomSquadMember(), soldier.Soldier.Size, soldier.Armor.Template.ArmorProvided, soldier.Soldier.Constitution); if (desperateHitDistance < targetPreferredDistance) { // advance advanceVotes++; } else { // don't advance standVotes++; } } else { advanceVotes++; chargeVotes++; } } else { advanceVotes++; chargeVotes++; } } else if (distance > preferredHitDistance * 3) { advanceVotes++; } else { float targetPreferredDistance = CalculateOptimalDistance(closestSquad.GetRandomSquadMember(), soldier.Soldier.Size, soldier.Armor.Template.ArmorProvided, soldier.Soldier.Constitution); if (preferredHitDistance < targetPreferredDistance) { // advance advanceVotes++; } else { // don't advance standVotes++; } } } if (advanceVotes > standVotes && advanceVotes > retreatVotes) { _log.Enqueue(squad.Name + " advances"); if (chargeVotes >= advanceVotes / 2) { foreach (BattleSoldier soldier in squad.Soldiers) { AddChargeActionsToBag(soldier); } } else { foreach (BattleSoldier soldier in squad.Soldiers) { AddAdvanceActionsToBag(soldier); } } } else if (retreatVotes > standVotes && retreatVotes > advanceVotes) { foreach (BattleSoldier soldier in squad.Soldiers) { AddRetreatingActionsToBag(soldier, squad); } } else { foreach (BattleSoldier soldier in squad.Soldiers) { AddStandingActionsToBag(soldier); } } } }
private void AddChargeActionsHelper(BattleSoldier soldier, int closestEnemyId, Tuple <int, int> currentPosition, float distance, BattleSquad oppSquad, Tuple <int, int> newPos) { Tuple <int, int> move = new Tuple <int, int>(newPos.Item1 - currentPosition.Item1, newPos.Item2 - currentPosition.Item2); float distanceSq = ((move.Item1 * move.Item1) + (move.Item2 * move.Item2)); float moveSpeed = soldier.GetMoveSpeed(); if (distance > moveSpeed + 1) { // we can't make it to an enemy in one move // soldier can't get there in one move, advance as far as possible Tuple <int, int> realMove = CalculateMovementAlongLine(move, moveSpeed); AddMoveAction(soldier, moveSpeed, realMove); // should the soldier shoot along the way? ShootIfReasonable(soldier, true); } else if (soldier.EquippedMeleeWeapons.Count == 0 && soldier.MeleeWeapons.Count > 0) { soldier.CurrentSpeed = 0; _shootActionBag.Add(new ReadyMeleeWeaponAction(soldier, soldier.MeleeWeapons[0])); } else { Debug.Log(soldier.Soldier.Name + " charging " + moveSpeed.ToString("F0")); soldier.CurrentSpeed = moveSpeed; _grid.ReserveSpace(newPos); _moveActionBag.Add(new MoveAction(soldier, _grid, newPos, _moveResolutionBag)); BattleSoldier target = oppSquad.Soldiers.Single(s => s.Soldier.Id == closestEnemyId); _meleeActionBag.Add(new MeleeAttackAction(soldier, target, soldier.MeleeWeapons.Count == 0 ? null : soldier.EquippedMeleeWeapons[0], distance >= 2, _woundResolutionBag, _log)); } }
private void AddChargeActionsToBag(BattleSoldier soldier) { if (soldier.IsInMelee) { // determine what sort of manuver to make AddMeleeActionsToBag(soldier); } else { // get stuck in // move adjacent to nearest enemy // TODO: handle when someone else in the same squad wants to use the same spot // TODO: probably by letting the one with the lower id have it, and the higher id has to Tuple <int, int> currentPosition = _grid.GetSoldierPosition(soldier.Soldier.Id); float distance = _grid.GetNearestEnemy(soldier.Soldier.Id, out int closestEnemyId); float moveSpeed = soldier.GetMoveSpeed(); Tuple <int, int> enemyPosition = _grid.GetSoldierPosition(closestEnemyId); if (distance > moveSpeed + 1) { Tuple <int, int> moveVector = new Tuple <int, int>(enemyPosition.Item1 - currentPosition.Item1, enemyPosition.Item2 - currentPosition.Item2); // we can't make it to an enemy in one move // soldier can't get there in one move, advance as far as possible AddMoveAction(soldier, moveSpeed, moveVector); // should the soldier shoot along the way? ShootIfReasonable(soldier, true); } else { Tuple <int, int> newPos = _grid.GetClosestOpenAdjacency(currentPosition, enemyPosition); BattleSquad oppSquad = _opposingSoldierIdSquadMap[closestEnemyId]; if (newPos == null) { // find the next closest // okay, this is one of those times where I made something because it made me feel smart, // but it's probably unreadable so I should change it later // basically, foreach soldier in the squad of the closest enemy, except the closest enemy (who we already checked) // get their locations, and then sort it according to distance square // PROTIP: SQRT is a relatively expensive operation, so sort by distance squares when it's about comparative, not absolute, distance var map = oppSquad.Soldiers .Where(s => s.Soldier.Id != closestEnemyId) .Select(s => new Tuple <int, Tuple <int, int> >(s.Soldier.Id, _grid.GetSoldierPosition(s.Soldier.Id))) .Select(t => new Tuple <int, Tuple <int, int>, Tuple <int, int> >(t.Item1, t.Item2, new Tuple <int, int>(t.Item2.Item1 - currentPosition.Item1, t.Item2.Item2 - currentPosition.Item2))) .Select(u => new Tuple <int, Tuple <int, int>, int>(u.Item1, u.Item2, (u.Item3.Item1 * u.Item3.Item1 + u.Item3.Item2 * u.Item3.Item2))) .OrderBy(u => u.Item3); foreach (Tuple <int, Tuple <int, int>, int> soldierData in map) { newPos = _grid.GetClosestOpenAdjacency(currentPosition, soldierData.Item2); if (newPos != null) { AddChargeActionsHelper(soldier, soldierData.Item1, currentPosition, Mathf.Sqrt(soldierData.Item3), oppSquad, newPos); break; } } if (newPos == null) { // we weren't able to find an enemy to get near, guess we try to find someone to shoot, instead? Debug.Log("ISoldier in squad engaged in melee couldn't find anyone to attack"); AddStandingActionsToBag(soldier); } } else { AddChargeActionsHelper(soldier, closestEnemyId, currentPosition, distance, oppSquad, newPos); } } } }