/// <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); }
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(); } } }
/// <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>(); }