Esempio n. 1
0
        public ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false)
        {
            bool enemyInitiallyFull = EnemyWithHealth.Health == EnemyWithHealth.Enemy.Hp;

            EnemyWithHealth.Enemy.WeaponSusceptibilities.TryGetValue(Weapon.Name, out WeaponSusceptibility susceptibility);
            if (susceptibility == null)
            {
                return(null);
            }
            int numberOfShots = susceptibility.NumberOfHits(EnemyWithHealth.Health);

            ExecutionResult result = Weapon.ShotRequires.Execute(model, inGameState, times: times * numberOfShots, usePreviousRoom: usePreviousRoom);

            if (result != null)
            {
                // Record the kill

                // If the enemy was full, then the prior splash attack (if any) didn't affect it. Ignore it.
                if (enemyInitiallyFull)
                {
                    result.AddKilledEnemy(EnemyWithHealth.Enemy, Weapon, numberOfShots);
                }
                // If the enemy was not full, the splash attack contributed to its death
                else
                {
                    result.AddKilledEnemy(EnemyWithHealth.Enemy, new [] { (PriorSplashWeapon, PriorSplashShots), (Weapon, numberOfShots) });
Esempio n. 2
0
        public ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false)
        {
            ExecutionResult result = null;
            // We'll need to track the health of individual enemies, so create an EnemyWithHealth for each
            IEnumerable <EnemyWithHealth> enemiesWithHealth = EnemyGroup.Select(e => new EnemyWithHealth(e));

            // If using a splash weapon, spend the ammo then apply the damage
            if (SplashWeapon != null && SplashWeapon.HitsGroup)
            {
                result = SplashWeapon.ShotRequires.Execute(model, inGameState, times: times * SplashShots, usePreviousRoom: usePreviousRoom);

                // If we can't spend the ammo, fail immediately
                if (result == null)
                {
                    return(null);
                }

                // Apply the splash attack to each enemy
                enemiesWithHealth = enemiesWithHealth
                                    .Select(e =>
                {
                    e.Enemy.WeaponSusceptibilities.TryGetValue(SplashWeapon.Name, out WeaponSusceptibility susceptibility);
                    // If the splash weapon hurts the enemy, apply damage
                    if (susceptibility != null)
                    {
                        int damage = susceptibility.DamagePerShot * SplashShots;
                        e.Health  -= damage;
                    }

                    // Return the new state of the enemy.
                    if (e.IsAlive())
                    {
                        return(e);
                    }
                    // If the enemy is dead, record the kill and return null
                    else
                    {
                        // No matter how many shots we dealt to the group, record how many shots it took to actually kill this enemy.
                        result.AddKilledEnemy(e.Enemy, SplashWeapon, susceptibility.Shots);
                        return(null);
                    }
                })
                                    // Remove dead enemies
                                    .Where(e => e != null);
            }
            // No else: If no splashWeapon is provided (or it's somehow not a splash weapon), skip that step and only do the individual kill


            // Iterate over each remaining enemy, killing it with the cheapest non-splash weapon
            foreach (EnemyWithHealth currentEnemy in enemiesWithHealth)
            {
                var(_, killResult) = model.ExecuteBest(NonSplashWeapons.Select(weapon => currentEnemy.ToExecutable(weapon, SplashWeapon, SplashShots)),
                                                       result?.ResultingState ?? inGameState, times: times, usePreviousRoom: usePreviousRoom);

                // If we can't kill one of the enemies, give up
                if (killResult == null)
                {
                    return(null);
                }

                // Update the sequential ExecutionResult
                result = result == null ? killResult : result.ApplySubsequentResult(killResult);
            }

            return(result);
        }
Esempio n. 3
0
        public override ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false)
        {
            // Create an ExecutionResult immediately so we can record free kills in it
            ExecutionResult result = new ExecutionResult(inGameState.Clone());

            // Filter the list of valid weapons, to keep only those we can actually use right now
            IEnumerable <Weapon> usableWeapons = ValidWeapons.Where(w => w.UseRequires.Execute(model, inGameState, times: times, usePreviousRoom: usePreviousRoom) != null);

            // Find all usable weapons that are free to use. That's all weapons without an ammo cost, plus all weapons whose ammo is farmable in this EnemyKill
            // Technically if a weapon were to exist with a shot cost that requires something other than ammo (something like energy or ammo drain?),
            // this wouldn't work. Should that be a worry?
            IEnumerable <Weapon> freeWeapons = usableWeapons.Where(w => !w.ShotRequires.LogicalElements.Where(le => le is Ammo ammo && !FarmableAmmo.Contains(ammo.AmmoType)).Any());

            // Remove all enemies that can be killed by free weapons
            IEnumerable <IEnumerable <Enemy> > nonFreeGroups = GroupedEnemies
                                                               .RemoveEnemies(e =>
            {
                // Look for a free usable weapon this enemy is susceptible to.
                var firstWeaponSusceptibility = e.WeaponSusceptibilities.Values
                                                .Where(ws => freeWeapons.Contains(ws.Weapon, ObjectReferenceEqualityComparer <Weapon> .Default))
                                                .FirstOrDefault();

                // If we found a weapon, record a kill and return true (to remove the enemy)
                if (firstWeaponSusceptibility != null)
                {
                    result.AddKilledEnemy(e, firstWeaponSusceptibility.Weapon, firstWeaponSusceptibility.Shots);
                    return(true);
                }
                // If we didn't find a weapon, return false (to retain the enemy)
                else
                {
                    return(false);
                }
            });

            // If there are no enemies left, we are done!
            if (!nonFreeGroups.Any())
            {
                return(result);
            }

            // The remaining enemies require ammo
            IEnumerable <Weapon> nonFreeWeapons           = usableWeapons.Except(freeWeapons, ObjectReferenceEqualityComparer <Weapon> .Default);
            IEnumerable <Weapon> nonFreeSplashWeapons     = nonFreeWeapons.Where(w => w.HitsGroup);
            IEnumerable <Weapon> nonFreeIndividualWeapons = nonFreeWeapons.Where(w => !w.HitsGroup);

            // Iterate over each group, killing it and updating the resulting state.
            // We'll test many scenarios, each with 0 to 1 splash weapon and a fixed number of splash weapon shots (after which enemies are killed with single-target weapons).
            // We will not test multiple combinations of splash weapons.
            foreach (IEnumerable <Enemy> currentEnemyGroup in nonFreeGroups)
            {
                // Build a list of combinations of splash weapons and splash shots (including one entry for no splash weapon at all)
                IEnumerable <(Weapon splashWeapon, int splashShots)> splashCombinations = nonFreeSplashWeapons.SelectMany(w =>
                                                                                                                          // Figure out what different shot counts for this weapon will lead to different numbers of casualties
                                                                                                                          currentEnemyGroup
                                                                                                                          .Select(e => e.WeaponSusceptibilities.TryGetValue(w.Name, out WeaponSusceptibility susceptibility) ? susceptibility.Shots : 0)
                                                                                                                          .Where(shots => shots > 0)
                                                                                                                          // Convert each different number of shot into a combination of this weapon and the number of shots
                                                                                                                          .Select(shots => (splashWeapon: w, splashShots: shots))
                                                                                                                          )
                                                                                          // Add the one entry for not using a splash weapon at all
                                                                                          .Append((splashWeapon: null, splashShots: 0));

                // Evaluate all combinations and apply the cheapest to our current resulting state
                (_, ExecutionResult killResult) = model.ExecuteBest(splashCombinations.Select(combination => new EnemyGroupAmmoExecutable(currentEnemyGroup, nonFreeIndividualWeapons, combination.splashWeapon, combination.splashShots)),
                                                                    result.ResultingState, times: times, usePreviousRoom: usePreviousRoom);

                // If we failed to kill an enemy group, we can't kill all enemies
                if (killResult == null)
                {
                    return(null);
                }

                // Update the sequential ExecutionResult
                result = result.ApplySubsequentResult(killResult);
            }

            return(result);
        }