Exemplo n.º 1
0
        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));
            }
        }
Exemplo n.º 2
0
        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));
            }
        }
Exemplo n.º 3
0
 private void AddStandingActionsToBag(BattleSoldier soldier)
 {
     if (soldier.RangedWeapons.Count == 0)
     {
         Debug.Log("ISoldier with no ranged weapons just standing around");
     }
     // do we have a ranged weapon equipped
     else if (soldier.EquippedRangedWeapons.Count == 0 && soldier.RangedWeapons.Count > 0)
     {
         AddEquipRangedWeaponActionToBag(soldier);
     }
     // determine if soldier was already aiming and the target is still around and not in a melee
     else if (soldier.Aim != null && _opposingSoldierIdSquadMap.ContainsKey(soldier.Aim.Item1.Soldier.Id) && !soldier.Aim.Item1.IsInMelee)
     {
         // if the aim cannot be improved, go ahead and shoot
         if (soldier.Aim.Item3 == 2)
         {
             float range = _grid.GetDistanceBetweenSoldiers(soldier.Soldier.Id, soldier.Aim.Item1.Soldier.Id);
             Tuple <float, float> effectEstimate = EstimateHitAndDamage(soldier, soldier.Aim.Item1, soldier.Aim.Item2, range, soldier.Aim.Item2.Template.Accuracy + 3);
             int shotsToFire = CalculateShotsToFire(soldier.Aim.Item2, effectEstimate.Item1, effectEstimate.Item2);
             soldier.CurrentSpeed = 0;
             _shootActionBag.Add(new ShootAction(soldier, soldier.Aim.Item2, soldier.Aim.Item1, range, shotsToFire, false, _woundResolutionBag, _log));
         }
         else
         {
             // the aim can be improved
             // current aim bonus is 1 for all-out attack, plus weapon accuracy, plus aim
             float range            = _grid.GetDistanceBetweenSoldiers(soldier.Soldier.Id, soldier.Aim.Item1.Soldier.Id);
             float currentModifiers = soldier.Aim.Item2.Template.Accuracy + soldier.Aim.Item3 + 1;
             // item1 is the pre-roll to-hit total; item2 is the expected ratio of damage to con, so 1 is a potential killshot
             Tuple <float, float> resultEstimate = EstimateHitAndDamage(soldier, soldier.Aim.Item1, soldier.Aim.Item2, range, currentModifiers);
             // it's about to attack, go ahead and shoot, you may not get another chance
             if (soldier.Aim.Item1.GetMoveSpeed() > range
                 // there's a good chance of both hitting and killing, go ahead and shoot now
                 || (resultEstimate.Item2 >= 1 && resultEstimate.Item1 >= 6.66f))
             {
                 int shotsToFire = CalculateShotsToFire(soldier.Aim.Item2, resultEstimate.Item1, resultEstimate.Item2);
                 soldier.CurrentSpeed = 0;
                 _shootActionBag.Add(new ShootAction(soldier, soldier.Aim.Item2, soldier.Aim.Item1, range, shotsToFire, false, _woundResolutionBag, _log));
             }
             else
             {
                 // keep aiming
                 soldier.CurrentSpeed = 0;
                 _shootActionBag.Add(new AimAction(soldier, soldier.Aim.Item1, soldier.Aim.Item2, _log));
             }
         }
     }
     // need to aim or shoot at a new target
     else
     {
         soldier.CurrentSpeed = 0;
         ShootIfReasonable(soldier, false);
     }
 }
Exemplo n.º 4
0
        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);
        }
Exemplo n.º 5
0
        private void AddMoveAction(BattleSoldier soldier, float moveSpeed, Tuple <int, int> line)
        {
            Tuple <int, int> desiredMove = CalculateMovementAlongLine(line, moveSpeed);
            Tuple <int, int> newLocation = new Tuple <int, int>(soldier.Location.Item1 + desiredMove.Item1, soldier.Location.Item2 + desiredMove.Item2);

            if (_grid.IsSpaceReserved(newLocation) || !_grid.IsEmpty(newLocation))
            {
                newLocation = FindBestLocation(soldier.Location, newLocation, moveSpeed);
            }
            soldier.CurrentSpeed = moveSpeed;
            _grid.ReserveSpace(newLocation);
            _moveActionBag.Add(new MoveAction(soldier, _grid, newLocation, _moveResolutionBag));
        }
Exemplo n.º 6
0
        private Tuple <float, float> EstimateHitAndDamage(BattleSoldier soldier, BattleSoldier target, RangedWeapon weapon, float range, float moveAndAimMod)
        {
            float sizeMod        = BattleModifiersUtil.CalculateSizeModifier(target.Soldier.Size);
            float armor          = target.Armor.Template.ArmorProvided;
            float con            = target.Soldier.Constitution;
            float expectedDamage = CalculateExpectedDamage(weapon, range, armor, con);
            float rangeMod       = BattleModifiersUtil.CalculateRangeModifier(range, target.CurrentSpeed);
            float rofMod         = BattleModifiersUtil.CalculateRateOfFireModifier(weapon.Template.RateOfFire);
            float weaponSkill    = soldier.Soldier.GetTotalSkillValue(weapon.Template.RelatedSkill);
            float total          = weaponSkill + rofMod + rangeMod + sizeMod + moveAndAimMod;

            return(new Tuple <float, float>(total, expectedDamage));
        }
Exemplo n.º 7
0
        private void AddEquipRangedWeaponActionToBag(BattleSoldier soldier)
        {
            int handsFree = soldier.HandsFree;

            // we're standing here without a readied ranged weapon; we should do something about that
            if (soldier.RangedWeapons.Count == 1 && handsFree >= 1)
            {
                // the easiest case... ready our one ranged weapon
                soldier.CurrentSpeed = 0;
                _shootActionBag.Add(new ReadyRangedWeaponAction(soldier, soldier.RangedWeapons[0]));
            }
            else if (soldier.RangedWeapons.Count > 1 && handsFree >= 1)
            {
                // ugh, this is a decision with a lot of factors that will only come up rarely
                // for now, let's go with the longer ranged weapon
                soldier.CurrentSpeed = 0;
                _shootActionBag.Add(new ReadyRangedWeaponAction(soldier, soldier.RangedWeapons.OrderByDescending(w => w.Template.MaximumRange).First()));
            }
        }
Exemplo n.º 8
0
        private Tuple <float, float, RangedWeapon> ShouldShootAtRange(BattleSoldier soldier, BattleSoldier target, float range, bool useBulk)
        {
            RangedWeapon bestWeapon   = null;
            float        bestAccuracy = 0;
            float        bestDamage   = -0;

            foreach (RangedWeapon weapon in soldier.EquippedRangedWeapons.OrderByDescending(w => w.Template.DamageMultiplier))
            {
                Tuple <float, float> hitAndDamage = EstimateHitAndDamage(soldier, target, weapon, range, useBulk ? -2 : 0);
                // if not likely to break through armor, there's little point
                if (hitAndDamage.Item1 > 6.66f && hitAndDamage.Item2 > bestDamage)
                {
                    // about a 1/10 chance of hitting
                    bestAccuracy = hitAndDamage.Item1;
                    bestDamage   = hitAndDamage.Item2;
                    bestWeapon   = weapon;
                }
            }
            return(new Tuple <float, float, RangedWeapon>(bestAccuracy, bestDamage, bestWeapon));
        }
Exemplo n.º 9
0
 private void AddMeleeActionsToBag(BattleSoldier soldier)
 {
     // for now just attack, don't worry about cooler moves
     // if we have melee weapons but none are equipped, we should change that
     if (soldier.EquippedMeleeWeapons.Count == 0 && soldier.MeleeWeapons.Count > 0)
     {
         soldier.CurrentSpeed = 0;
         _shootActionBag.Add(new ReadyMeleeWeaponAction(soldier, soldier.MeleeWeapons[0]));
     }
     else
     {
         float distance = _grid.GetNearestEnemy(soldier.Soldier.Id, out int closestEnemyId);
         if (distance != 1)
         {
             throw new InvalidOperationException("Attempting to melee with no adjacent enemy");
         }
         BattleSoldier enemy = _opposingSoldierIdSquadMap[closestEnemyId].Soldiers.Single(s => s.Soldier.Id == closestEnemyId);
         soldier.CurrentSpeed = 0;
         MeleeWeapon weapon = soldier.EquippedMeleeWeapons.Count == 0 ?
                              _defaultMeleeWeapon : soldier.EquippedMeleeWeapons[0];
         _meleeActionBag.Add(new MeleeAttackAction(soldier, enemy, weapon, false, _woundResolutionBag, _log));
     }
 }
Exemplo n.º 10
0
        private float CalculateOptimalDistance(BattleSoldier soldier, float targetSize, float targetArmor, float targetCon)
        {
            int freeHands = soldier.Soldier.FunctioningHands;

            if (freeHands == 0)
            {
                // with no hands free, there's not much combat left for this soldier
                return(-1);
            }
            float range   = 0;
            var   weapons = soldier.EquippedRangedWeapons.OrderByDescending(w => w.Template.MaximumRange);

            foreach (RangedWeapon weapon in weapons)
            {
                float hitRange = EstimateHitDistance(soldier.Soldier, weapon, targetSize, freeHands);
                float damRange = EstimateKillDistance(weapon, targetArmor, targetCon);
                float minVal   = Mathf.Min(hitRange, damRange);
                if (minVal > range)
                {
                    range = minVal;
                }
            }
            return(range);
        }
Exemplo n.º 11
0
        private void AddAdvanceActionsToBag(BattleSoldier soldier)
        {
            // for now advance toward closest enemy;
            // down the road, we may want to advance toward a rearward enemy, ignoring the closest enemy

            Tuple <int, int> currentPosition = _grid.GetSoldierPosition(soldier.Soldier.Id);
            float            distance        = _grid.GetNearestEnemy(soldier.Soldier.Id, out int closestEnemyId);
            float            moveSpeed       = soldier.GetMoveSpeed();

            if (distance < moveSpeed)
            {
                AddChargeActionsToBag(soldier);
            }
            else
            {
                Tuple <int, int> enemyPosition = _grid.GetSoldierPosition(closestEnemyId);
                Tuple <int, int> line          = new Tuple <int, int>(enemyPosition.Item1 - currentPosition.Item1, enemyPosition.Item2 - currentPosition.Item2);
                // soldier can't get there in one move, advance as far as possible
                AddMoveAction(soldier, moveSpeed, line);

                // should the soldier shoot along the way?
                ShootIfReasonable(soldier, true);
            }
        }
Exemplo n.º 12
0
 public void RemoveSoldier(BattleSoldier soldier)
 {
     Soldiers.Remove(soldier);
 }
Exemplo n.º 13
0
        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);
                    }
                }
            }
        }