/// <summary> /// Absorbs compounds from this cloud /// </summary> public void AbsorbCompounds(int localX, int localY, CompoundBag storage, Dictionary <Compound, float> totals, float delta, float rate) { var fractionToTake = 1.0f - (float)Math.Pow(0.5f, delta / Constants.CLOUD_ABSORPTION_HALF_LIFE); for (int i = 0; i < Constants.CLOUDS_IN_ONE; i++) { if (Compounds[i] == null) { break; } // Skip if compound is non-useful if (!storage.IsUseful(Compounds[i])) { continue; } // Overestimate of how much compounds we get float generousAmount = HackyAddress(Density[localX, localY], i) * Constants.SKIP_TRYING_TO_ABSORB_RATIO; // Skip if there isn't enough to absorb if (generousAmount < MathUtils.EPSILON) { continue; } float freeSpace = storage.Capacity - storage.GetCompoundAmount(Compounds[i]); float multiplier = 1.0f * rate; if (freeSpace < generousAmount) { // Allow partial absorption to allow cells to take from high density clouds multiplier = freeSpace / generousAmount; } float taken = TakeCompound(Compounds[i], localX, localY, fractionToTake * multiplier) * Constants.ABSORPTION_RATIO; storage.AddCompound(Compounds[i], taken); // Keep track of total compounds absorbed for the cell if (!totals.ContainsKey(Compounds[i])) { totals.Add(Compounds[i], taken); } else { totals[Compounds[i]] += taken; } } }
/// <summary> /// Gives organelles more compounds to grow /// </summary> public void GrowOrganelle(CompoundBag compounds) { float totalTaken = 0; var keys = new List <string>(compoundsLeft.Keys); foreach (var key in keys) { var amountNeeded = compoundsLeft[key]; if (amountNeeded <= 0.0f) { continue; } // Take compounds if the cell has what we need // ORGANELLE_GROW_STORAGE_MUST_HAVE_AT_LEAST controls how // much of a certain compound must exist before we take // some var amountAvailable = compounds.GetCompoundAmount(key) - Constants.ORGANELLE_GROW_STORAGE_MUST_HAVE_AT_LEAST; if (amountAvailable <= 0.0f) { continue; } // We can take some var amountToTake = Mathf.Min(amountNeeded, amountAvailable); var amount = compounds.TakeCompound(key, amountToTake); var left = amountNeeded - amount; if (left < 0.0001) { left = 0; } compoundsLeft[key] = left; totalTaken += amount; } if (totalTaken > 0) { growthValueDirty = true; ApplyScale(); } }
/// <summary> /// Absorbs compounds from this cloud /// </summary> public void AbsorbCompounds(int localX, int localY, CompoundBag storage, Dictionary <string, float> totals, float delta) { var fractionToTake = 1.0f - (float)Math.Pow(0.5f, delta / Constants.CLOUD_ABSORPTION_HALF_LIFE); foreach (var slot in slots) { // Overestimate of how much compounds we get float generousAmount = slot.Density[localX, localY] * Constants.SKIP_TRYING_TO_ABSORB_RATIO; // Skip if there isn't enough to absorb if (generousAmount < MathUtils.EPSILON) { continue; } var compound = slot.Compound.InternalName; float freeSpace = storage.Capacity - storage.GetCompoundAmount(compound); float multiplier = 1.0f; if (freeSpace < generousAmount) { // Allow partial absorption to allow cells to take from high density clouds multiplier = freeSpace / generousAmount; } float taken = slot.TakeCompound(localX, localY, fractionToTake * multiplier) * Constants.ABSORPTION_RATIO; storage.AddCompound(compound, taken); // Keep track of total compounds absorbed for the cell if (!totals.ContainsKey(compound)) { totals.Add(compound, taken); } else { totals[compound] += taken; } } }
private void RunProcess(float delta, BioProcess processData, CompoundBag bag, TweakedProcess process, SingleProcessStatistics currentProcessStatistics, float inverseDelta) { // Can your cell do the process bool canDoProcess = true; // Loop through to make sure you can follow through with your // whole process so nothing gets wasted as that would be // frustrating. float environmentModifier = 1.0f; // First check the environmental compounds so that we can build the right environment modifier for accurate // check of normal compound input amounts foreach (var entry in processData.Inputs) { // Set used compounds to be useful, we dont want to purge // those bag.SetUseful(entry.Key); if (!entry.Key.IsEnvironmental) { continue; } var dissolved = GetDissolved(entry.Key); // currentProcessStatistics?.AddInputAmount(entry.Key, entry.Value * inverseDelta); currentProcessStatistics?.AddInputAmount(entry.Key, dissolved); // do environmental modifier here, and save it for later environmentModifier *= dissolved / entry.Value; if (environmentModifier <= MathUtils.EPSILON) { currentProcessStatistics?.AddLimitingFactor(entry.Key); } } if (environmentModifier <= MathUtils.EPSILON) { canDoProcess = false; } foreach (var entry in processData.Inputs) { if (entry.Key.IsEnvironmental) { continue; } var inputRemoved = entry.Value * process.Rate * delta; currentProcessStatistics?.AddInputAmount(entry.Key, inputRemoved * inverseDelta); // currentProcessStatistics?.AddInputAmount(entry.Key, 0); // If not enough compound we can't do the process if (bag.GetCompoundAmount(entry.Key) < inputRemoved) { canDoProcess = false; currentProcessStatistics?.AddLimitingFactor(entry.Key); } } // Output // This is now always looped (even when we can't do the process) // because the is useful part is needs to be always be done foreach (var entry in processData.Outputs) { // For now lets assume compounds we produce are also // useful bag.SetUseful(entry.Key); // Apply the general modifiers and // apply the environmental modifier var outputAdded = entry.Value * process.Rate * delta * environmentModifier; currentProcessStatistics?.AddOutputAmount(entry.Key, outputAdded * inverseDelta); // currentProcessStatistics?.AddOutputAmount(entry.Key, 0); // If no space we can't do the process, and if environmental // right now this isn't released anywhere if (entry.Key.IsEnvironmental) { continue; } if (bag.GetCompoundAmount(entry.Key) + outputAdded > bag.Capacity) { canDoProcess = false; currentProcessStatistics?.AddCapacityProblem(entry.Key); } } // Only carry out this process if you have all the required // ingredients and enough space for the outputs if (!canDoProcess) { return; } if (currentProcessStatistics != null) { currentProcessStatistics.CurrentSpeed = process.Rate * environmentModifier; } // Consume inputs foreach (var entry in processData.Inputs) { if (entry.Key.IsEnvironmental) { continue; } var inputRemoved = entry.Value * process.Rate * delta * environmentModifier; currentProcessStatistics?.AddInputAmount(entry.Key, inputRemoved * inverseDelta); // This should always succeed (due to the earlier check) so // it is always assumed here that the process succeeded bag.TakeCompound(entry.Key, inputRemoved); } // Add outputs foreach (var entry in processData.Outputs) { if (entry.Key.IsEnvironmental) { continue; } var outputGenerated = entry.Value * process.Rate * delta * environmentModifier; currentProcessStatistics?.AddOutputAmount(entry.Key, outputGenerated * inverseDelta); bag.AddCompound(entry.Key, outputGenerated); } }
private void RunProcess(float delta, BioProcess processData, CompoundBag bag, TweakedProcess process, SingleProcessStatistics currentProcessStatistics, float inverseDelta) { // Can your cell do the process bool canDoProcess = true; float environmentModifier = 1.0f; // This modifies the process overall speed to allow really fast processes to run, for example if there are // a ton of one organelle it might consume 100 glucose per go, which might be unlikely for the cell to have // so if there is *some* but not enough space for results (and also inputs) this can run the process as // fraction of the speed to allow the cell to still function well float spaceConstraintModifier = 1.0f; // First check the environmental compounds so that we can build the right environment modifier for accurate // check of normal compound input amounts foreach (var entry in processData.Inputs) { // Set used compounds to be useful, we dont want to purge those bag.SetUseful(entry.Key); if (!entry.Key.IsEnvironmental) { continue; } var dissolved = GetDissolved(entry.Key); // currentProcessStatistics?.AddInputAmount(entry.Key, entry.Value * inverseDelta); currentProcessStatistics?.AddInputAmount(entry.Key, dissolved); // do environmental modifier here, and save it for later environmentModifier *= dissolved / entry.Value; if (environmentModifier <= MathUtils.EPSILON) { currentProcessStatistics?.AddLimitingFactor(entry.Key); } } if (environmentModifier <= MathUtils.EPSILON) { canDoProcess = false; } // Compute spaceConstraintModifier before updating the final use and input amounts foreach (var entry in processData.Inputs) { if (entry.Key.IsEnvironmental) { continue; } var inputRemoved = entry.Value * process.Rate * environmentModifier; // currentProcessStatistics?.AddInputAmount(entry.Key, 0); // We don't multiply by delta here because we report the per-second values anyway. In the actual process // output numbers (computed after testing the speed), we need to multiply by inverse delta currentProcessStatistics?.AddInputAmount(entry.Key, inputRemoved); inputRemoved = inputRemoved * delta * spaceConstraintModifier; // If not enough we can't run the process unless we can lower spaceConstraintModifier enough var availableAmount = bag.GetCompoundAmount(entry.Key); if (availableAmount < inputRemoved) { bool canRun = false; if (availableAmount > MathUtils.EPSILON) { var neededModifier = availableAmount / inputRemoved; if (neededModifier > Constants.MINIMUM_RUNNABLE_PROCESS_FRACTION) { spaceConstraintModifier = neededModifier; canRun = true; // Due to rounding errors there can be very small disparity here between the amount available // and what we will take with the modifiers. See the comment in outputs for more details } } if (!canRun) { canDoProcess = false; currentProcessStatistics?.AddLimitingFactor(entry.Key); } } } foreach (var entry in processData.Outputs) { // For now lets assume compounds we produce are also useful bag.SetUseful(entry.Key); var outputAdded = entry.Value * process.Rate * environmentModifier; // currentProcessStatistics?.AddOutputAmount(entry.Key, 0); currentProcessStatistics?.AddOutputAmount(entry.Key, outputAdded); outputAdded = outputAdded * delta * spaceConstraintModifier; // if environmental right now this isn't released anywhere if (entry.Key.IsEnvironmental) { continue; } // If no space we can't do the process, if we can't adjust the space constraint modifier enough var remainingSpace = bag.Capacity - bag.GetCompoundAmount(entry.Key); if (outputAdded > remainingSpace) { bool canRun = false; if (remainingSpace > MathUtils.EPSILON) { var neededModifier = remainingSpace / outputAdded; if (neededModifier > Constants.MINIMUM_RUNNABLE_PROCESS_FRACTION) { spaceConstraintModifier = neededModifier; canRun = true; } // With all of the modifiers we can lose a tiny bit of compound that won't fit due to rounding // errors, but we ignore that here } if (!canRun) { canDoProcess = false; currentProcessStatistics?.AddCapacityProblem(entry.Key); } } } // Only carry out this process if you have all the required ingredients and enough space for the outputs if (!canDoProcess) { if (currentProcessStatistics != null) { currentProcessStatistics.CurrentSpeed = 0; } return; } float totalModifier = process.Rate * delta * environmentModifier * spaceConstraintModifier; if (currentProcessStatistics != null) { currentProcessStatistics.CurrentSpeed = process.Rate * environmentModifier * spaceConstraintModifier; } // Consume inputs foreach (var entry in processData.Inputs) { if (entry.Key.IsEnvironmental) { continue; } var inputRemoved = entry.Value * totalModifier; currentProcessStatistics?.AddInputAmount(entry.Key, inputRemoved * inverseDelta); // This should always succeed (due to the earlier check) so it is always assumed here that this succeeded bag.TakeCompound(entry.Key, inputRemoved); } // Add outputs foreach (var entry in processData.Outputs) { if (entry.Key.IsEnvironmental) { continue; } var outputGenerated = entry.Value * totalModifier; currentProcessStatistics?.AddOutputAmount(entry.Key, outputGenerated * inverseDelta); bag.AddCompound(entry.Key, outputGenerated); } }
/// <summary> /// Tries to fire a toxin if possible /// </summary> public void EmitToxin(Compound agentType = null) { if (AgentEmissionCooldown > 0) { return; } // Only shoot if you have an agent vacuole. if (AgentVacuoleCount < 1) { return; } agentType ??= SimulationParameters.Instance.GetCompound("oxytoxy"); float amountAvailable = Compounds.GetCompoundAmount(agentType); // Emit as much as you have, but don't start the cooldown if that's zero float amountEmitted = Math.Min(amountAvailable, Constants.MAXIMUM_AGENT_EMISSION_AMOUNT); if (amountEmitted < Constants.MINIMUM_AGENT_EMISSION_AMOUNT) { return; } Compounds.TakeCompound(agentType, amountEmitted); // The cooldown time is inversely proportional to the amount of agent vacuoles. AgentEmissionCooldown = Constants.AGENT_EMISSION_COOLDOWN / AgentVacuoleCount; float ejectionDistance = Membrane.EncompassingCircleRadius + Constants.AGENT_EMISSION_DISTANCE_OFFSET; if (Species.IsBacteria) { ejectionDistance *= 0.5f; } var props = new AgentProperties(); props.Compound = agentType; props.Species = Species; // Find the direction the microbe is facing var direction = (LookAtPoint - Translation).Normalized(); var position = Translation + (direction * ejectionDistance); var agent = SpawnHelpers.SpawnAgent(props, amountEmitted, Constants.EMITTED_AGENT_LIFETIME, position, direction, GetStageAsParent(), SpawnHelpers.LoadAgentScene(), this); ModLoader.ModInterface.TriggerOnToxinEmitted(agent); if (amountEmitted < Constants.MAXIMUM_AGENT_EMISSION_AMOUNT / 2) { PlaySoundEffect("res://assets/sounds/soundeffects/microbe-release-toxin-low.ogg"); } else { PlaySoundEffect("res://assets/sounds/soundeffects/microbe-release-toxin.ogg"); } }