/// <summary>
        /// <para>Calculates how much of the provided resource is gained or lost (on average) by executing this farm cycle once,
        /// given the provided effective drop rates and resource costs per cycle</para>
        /// <para>If the resource is also spent while executing a cycle, the calculation includes
        /// reducing the drop rate by the safety margin in the logical options.</para>
        /// </summary>
        /// <param name="model">A model that can be used to obtain data about the current game configuration.</param>
        /// <param name="resource">The resource to check</param>
        /// <param name="effectiveDropRates">The effective drop rates, after accounting for unneeded drops.</param>
        /// <param name="costingResources">A dictionary  of resources to their cost per cycle.</param>
        /// <returns></returns>
        private decimal CalculateResourceVariationPerCycle(SuperMetroidModel model, ConsumableResourceEnum resource,
                                                           EnemyDrops effectiveDropRates, IDictionary <ConsumableResourceEnum, int> costingResources)
        {
            // If this farm cycle has to spend some of this resource, do some adaptations:
            // - Reduce the expected drop rate by the logical safety margin
            // - Include the cost when looking at the amount obtained from drops
            decimal dropRateMultiplier;
            int     costPerCycle;

            if (costingResources.TryGetValue(resource, out costPerCycle))
            {
                dropRateMultiplier = (100 - model.LogicalOptions.SpawnerFarmingOptions.SafetyMarginPercent) / 100;
            }
            else
            {
                costPerCycle       = 0;
                dropRateMultiplier = 1M;
            }

            decimal netGainPerCycle = resource.GetRelatedDrops()
                                      .Select(drop => dropRateMultiplier * model.Rules.ConvertDropRateToPercent(effectiveDropRates.GetDropRate(drop))
                                              * FarmCycle.RoomEnemy.Quantity * model.Rules.GetDropResourceCount(drop))
                                      .Sum() - costPerCycle;

            return(netGainPerCycle);
        }
        /// <summary>
        /// <para>Calculates how much of the provided resource is gained or lost (on average) over 60 frames of farming,
        /// given the provided effective drop rates and resource costs per cycle</para>
        /// <para>If the resource is also spent while executing a cycle, the calculation includes
        /// reducing the drop rate by the safety margin in the logical options.</para>
        /// </summary>
        /// <param name="model">A model that can be used to obtain data about the current game configuration.</param>
        /// <param name="resource">The resource to check</param>
        /// <param name="effectiveDropRates">The effective drop rates, after accounting for unneeded drops.</param>
        /// <param name="costingResources">A dictionary  of resources to their cost per cycle.</param>
        /// <returns></returns>
        private decimal CalculateResourceVariationPerSecond(SuperMetroidModel model, ConsumableResourceEnum resource,
                                                            EnemyDrops effectiveDropRates, IDictionary <ConsumableResourceEnum, int> costingResources)
        {
            decimal variationPerCycle = CalculateResourceVariationPerCycle(model, resource, effectiveDropRates, costingResources);

            return(variationPerCycle / FarmCycle.CycleFrames * 60);
        }
        /// <summary>
        /// <para>Calculates and returns which resources can meet meet the logical criteria
        /// for refilling by farming this farm cycle.</para>
        /// <para>This includes resources that only meet those criteria after redistributing
        /// the drop rate from other resources that were farmed to full.</para>
        /// </summary>
        /// <param name="model">A model that can be used to obtain data about the current game configuration.</param>
        /// <param name="costingResources">A dictionary of resources that have a cost associated with executing a cycle.
        /// The resource is the key and the cost per cycle is the value.</param>
        /// <returns></returns>
        private IEnumerable <ConsumableResourceEnum> ComputeFarmableResources(SuperMetroidModel model,
                                                                              IDictionary <ConsumableResourceEnum, int> costingResources)
        {
            ISet <ConsumableResourceEnum>        farmableResources = new HashSet <ConsumableResourceEnum>();
            IEnumerable <ConsumableResourceEnum> newFarmableResources;

            // Figure out which resources meet the threshold for farmability
            // We'll keep looping as long as we find farmable resources,
            // to discover resources that go above the threshold when redistributing drop rates
            do
            {
                // Determine effective drop rates for this iteration
                EnemyDrops effectiveDropRates = FarmCycle.RoomEnemy.Enemy.GetEffectiveDropRates(model, farmableResources);

                // Look for resources that meet the logical farming threshold, but didn't in the previous loops
                newFarmableResources = Enum.GetValues(typeof(ConsumableResourceEnum))
                                       .Cast <ConsumableResourceEnum>()
                                       .Except(farmableResources)
                                       .Where(resource =>
                {
                    return(CalculateResourceVariationPerSecond(model, resource, effectiveDropRates, costingResources)
                           >= model.LogicalOptions.SpawnerFarmingOptions.MinimumRatesPerSecond[resource]);
                })
                                       // Actualize this to calculate it only once
                                       .ToArray();

                farmableResources.UnionWith(newFarmableResources);
            } // Stop iterating if the last loop revealed no new farmable resource
            while (newFarmableResources.Any());

            return(farmableResources);
        }
Beispiel #4
0
    public void Damage(int damageValue)
    {
        _currentHealth -= damageValue;
        if (_currentHealth < 0)
        {
            _currentHealth = 0;
        }
        else
        {
            if (_hitSound != null && _hitSound.Length > 0)
            {
                AudioSource audio      = GetComponent <AudioSource>();
                AudioClip   soundToUse = _hitSound [Random.Range(0, _hitSound.Length)];
                audio.clip = soundToUse;
                audio.Play();
            }
        }

        if (_currentHealth == 0)
        {
            if (_hitSound != null)
            {
                AudioSource audio = GetComponent <AudioSource>();
                audio.clip = _deathSound;
                audio.Play();
            }
            // musuhAudio.Play();
            // Destroy (gameObject);
            Animation anim = GetComponentInChildren <Animation> ();
            anim.Stop();

            _playerStats.ZombieKilled++;

            EnemyDrops ed = GetComponent <EnemyDrops>();
            ed.onDeath();

            EnemySpawnManager.onEnemyDeath();
            Destroy(GetComponent <EnemyMovement>());
            Destroy(GetComponent <EnemyAttack>());
            Destroy(GetComponent <CharacterController> ());
            Destroy(gameObject, 8.0f);

            // Destroy (GetComponent<PlayerMovement> ());
            // Destroy (GetComponent<PlayerAnimation> ());

            Ragdoll r = GetComponent <Ragdoll> ();
            if (r != null)
            {
                r.onDeath();
            }
        }
    }
Beispiel #5
0
        /// <summary>
        /// Calculates the real in-game drop rate (out of DROP_RATE_DIVIDER) for the provided initial drop rates, after adjusting for the elimination of some drops.
        /// </summary>
        /// <param name="enemyDrops">The enemy drops (out of DROP_RATE_DIVIDER), as they would be if all resources can drop.</param>
        /// <param name="unneededDrops">The enumeration of drops that cannot drop (due to their resource being full).</param>
        /// <returns>The adjusted drop rates (out of DROP_RATE_DIVIDER).</returns>
        public virtual EnemyDrops CalculateEffectiveDropRates(EnemyDrops enemyDrops, IEnumerable <EnemyDropEnum> unneededDrops)
        {
            // Calculate the base drop rates for the formula, replacing unneeded drops by a rate of 0.
            // Our drop rates are out of DROP_RATE_DIVIDER. The formula needs them to be out of 255.
            EnemyDrops baseHexDropRates = new EnemyDrops();

            // Tier 1 drops
            baseHexDropRates.NoDrop      = enemyDrops.NoDrop * 255 / DROP_RATE_DIVIDER;
            baseHexDropRates.SmallEnergy = unneededDrops.Contains(EnemyDropEnum.SMALL_ENERGY) ? 0M : enemyDrops.SmallEnergy * 255 / DROP_RATE_DIVIDER;
            baseHexDropRates.BigEnergy   = unneededDrops.Contains(EnemyDropEnum.BIG_ENERGY) ? 0M : enemyDrops.BigEnergy * 255 / DROP_RATE_DIVIDER;
            baseHexDropRates.Missile     = unneededDrops.Contains(EnemyDropEnum.MISSILE) ? 0M : enemyDrops.Missile * 255 / DROP_RATE_DIVIDER;

            // Tier 2 drops
            baseHexDropRates.Super     = unneededDrops.Contains(EnemyDropEnum.SUPER) ? 0M : enemyDrops.Super * 255 / DROP_RATE_DIVIDER;
            baseHexDropRates.PowerBomb = unneededDrops.Contains(EnemyDropEnum.POWER_BOMB) ? 0M : enemyDrops.PowerBomb * 255 / DROP_RATE_DIVIDER;

            // Create functions for calculating effective drop rates. One for tier 1 drops and one for tier 2 drops.

            // Formula for tier one drops is (255 - super - pb) / (small + big + missile + nothing) * (current item), truncated
            Func <EnemyDrops, decimal, decimal> calculateTierOneRate = (baseHexDropRates, individualHexDropRate) =>
            {
                decimal tierTwoValue = 255 - baseHexDropRates.Super - baseHexDropRates.PowerBomb;
                decimal tierOneValue = baseHexDropRates.SmallEnergy + baseHexDropRates.BigEnergy
                                       + baseHexDropRates.Missile + baseHexDropRates.NoDrop;
                decimal result = decimal.Floor(tierTwoValue / tierOneValue * individualHexDropRate);
                return(result * DROP_RATE_DIVIDER / 255);
            };

            // Formula for tier two is simply the drop rate itself
            Func <EnemyDrops, decimal, decimal> calculateTierTwoRate = (baseHexDropRates, baseHexDropRate) =>
            {
                return(baseHexDropRate * DROP_RATE_DIVIDER / 255);
            };

            // Calculate new drop rates using the appropriate calculation, except for no drop
            EnemyDrops returnValue = new EnemyDrops
            {
                SmallEnergy = calculateTierOneRate(baseHexDropRates, baseHexDropRates.SmallEnergy),
                BigEnergy   = calculateTierOneRate(baseHexDropRates, baseHexDropRates.BigEnergy),
                Missile     = calculateTierOneRate(baseHexDropRates, baseHexDropRates.Missile),
                Super       = calculateTierTwoRate(baseHexDropRates, baseHexDropRates.Super),
                PowerBomb   = calculateTierTwoRate(baseHexDropRates, baseHexDropRates.PowerBomb)
            };

            // No drop is just whatever's not another type of drop. It grabs the leftover from truncating on top of its own increase.
            returnValue.NoDrop = DROP_RATE_DIVIDER - returnValue.SmallEnergy - returnValue.BigEnergy - returnValue.Missile - returnValue.Super - returnValue.PowerBomb;

            return(returnValue);
        }
        public ExecutionResult Execute(SuperMetroidModel model, InGameState inGameState, int times = 1, bool usePreviousRoom = false)
        {
            var requirementsResult = FarmCycle.RequirementExecution.Execute(model, inGameState, times: times, usePreviousRoom: usePreviousRoom);

            // Can't even execute one cycle, so return a failure
            if (requirementsResult == null)
            {
                return(null);
            }

            // Build a dictionary of resources that are spent while doing the execution of a cycle
            ResourceCount resourceVariation = requirementsResult.ResultingState.GetResourceVariationWith(inGameState);
            // Start with all consumable resources
            IDictionary <ConsumableResourceEnum, int> costingResources = Enum.GetValues(typeof(ConsumableResourceEnum))
                                                                         .Cast <ConsumableResourceEnum>()
                                                                         // Invert the resource variation to convert it to a resource cost
                                                                         .Select(resource => (resource: resource, cost: resourceVariation.GetAmount(resource) * -1))
                                                                         // Keep only pairs where some of the resource has been spent
                                                                         .Where(resourceCost => resourceCost.cost > 0)
                                                                         // Finally, build a dictionary from the pairs
                                                                         .ToDictionary(resourceCost => resourceCost.resource, resourceCost => resourceCost.cost);

            // Identify all resources that can be refilled by this farm cycle
            IEnumerable <ConsumableResourceEnum> farmableResources = ComputeFarmableResources(model, costingResources);

            // Calculate the initial effective drop rates, taking into account currently full resources.
            // However, resources that are full but not farmable here should not be taken into account.
            IEnumerable <ConsumableResourceEnum> fullResources = inGameState.GetFullConsumableResources().Intersect(farmableResources).ToArray();
            EnemyDrops initialEffectiveDropRates = FarmCycle.RoomEnemy.Enemy.GetEffectiveDropRates(model, fullResources);

            // Build a dictionary containing the variation per cycle for each consmable resource
            IDictionary <ConsumableResourceEnum, decimal> resourceVariationPerCycle = Enum.GetValues(typeof(ConsumableResourceEnum))
                                                                                      .Cast <ConsumableResourceEnum>()
                                                                                      .ToDictionary(resource => resource, resource => CalculateResourceVariationPerCycle(model, resource, initialEffectiveDropRates, costingResources));

            // Identify resources that are creeping down as we farm
            IEnumerable <ConsumableResourceEnum> initiallyUnstableResources = costingResources
                                                                              .Where(pair => resourceVariationPerCycle[pair.Key] < 0)
                                                                              .Select(pair => pair.Key)
                                                                              .ToArray();

            // If there's no resources we can farm, just return now
            if (!farmableResources.Any())
            {
                // If any of the resources initially lose out per farm cycle, we're not even able to farm. Return a failure.
                if (initiallyUnstableResources.Any())
                {
                    return(null);
                }

                // Otherwise, we're able to farm but it doesn't do anything according to logical options
                return(new ExecutionResult(inGameState.Clone()));
            }

            // If there's no resource that initially loses out, we're not concerned about losing any resources.
            // We can refill all farmable resources and report a success
            if (!initiallyUnstableResources.Any())
            {
                return(ExecuteRefill(model, inGameState, farmableResources));
            }

            // If we have resources that initially lose out, they must eventually turn farmable.
            // Otherwise, we consider this a failure.
            if (initiallyUnstableResources.Except(farmableResources).Any())
            {
                return(null);
            }

            // Now we know we have at least one resource that currently loses out per cycle, but can eventually recharge.
            // Execute some farming to see if we can stabilize those resources before we run out.

            IEnumerable <ConsumableResourceEnum>          notFullFarmableResources = farmableResources.Except(fullResources).ToArray();
            IDictionary <ConsumableResourceEnum, decimal> resourceCounts           = Enum.GetValues(typeof(ConsumableResourceEnum))
                                                                                     .Cast <ConsumableResourceEnum>()
                                                                                     .ToDictionary(resource => resource, resource => (decimal)inGameState.GetCurrentAmount(resource));
            EnemyDrops effectiveDropRates = initialEffectiveDropRates;

            // Execute farm cycles until a resource runs out or all costing resources have stabilized
            while (costingResources
                   .Where(pair => resourceVariationPerCycle[pair.Key] < 0)
                   .Any())
            {
                // Figure out how many cycles we need to execute in order to refill something farmable and stable
                int cyclesToRefillSomething = notFullFarmableResources.Select(resource =>
                                                                              decimal.ToInt32(decimal.Ceiling((inGameState.GetMaxAmount(resource) - resourceCounts[resource]) / resourceVariationPerCycle[resource])))
                                              .Where(cycleCount => cycleCount > 0)
                                              .Min();

                // Apply to each farmable resource the resource variation from executing that many cycles.
                // We don't care if it goes over maximum since we won't apply these to the in-game state
                foreach (ConsumableResourceEnum resource in notFullFarmableResources)
                {
                    resourceCounts[resource] += resourceVariationPerCycle[resource] * cyclesToRefillSomething;
                }

                // If an unstable resource has dipped below the cost per cycle, we can't go on. Return a failure.
                if (costingResources.Where(costingResource => resourceCounts[costingResource.Key] < costingResource.Value).Any())
                {
                    return(null);
                }

                // If we haven't run out of anything, prepare the next loop

                // Update full resources
                fullResources = resourceCounts
                                .Where(pair => pair.Value >= inGameState.GetMaxAmount(pair.Key))
                                .Select(pair => pair.Key)
                                .Intersect(farmableResources)
                                .ToArray();

                // Update farmable resources by excluding newly-full resources
                notFullFarmableResources = notFullFarmableResources.Except(fullResources).ToArray();

                // Calculate a new effective drop rate using the new list of full resources
                // If that new effective drop rate stabilizes all unstable resources, we'll make it out of the loop
                effectiveDropRates = model.Rules.CalculateEffectiveDropRates(FarmCycle.RoomEnemy.Enemy.Drops, model.Rules.GetUnneededDrops(fullResources));

                // Use the new effective drop rate to calculate the new resourceVariationPerCycle for resources we still care about
                resourceVariationPerCycle = notFullFarmableResources
                                            .ToDictionary(resource => resource, resource => CalculateResourceVariationPerCycle(model, resource, effectiveDropRates, costingResources));
            }

            // All resources are now stable. We already checked beforehand that all costing resources eventually become farmable,
            // so we can just apply a refill for all farmable resources and return a success.
            return(ExecuteRefill(model, inGameState, farmableResources));
        }
 private void Awake()
 {
     enemyDrops = FindObjectOfType <EnemyDrops>();
 }