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