/// <summary> /// Grabs data from the type to initialize this /// </summary> /// <remarks> /// <para> /// Doesn't initialize the graphics scene which needs to be set separately /// </para> /// </remarks> public void Init(ChunkConfiguration chunkType, CompoundCloudSystem compoundClouds, string modelPath) { this.compoundClouds = compoundClouds; // Grab data VentPerSecond = chunkType.VentAmount; Dissolves = chunkType.Dissolves; Size = chunkType.Size; Damages = chunkType.Damages; DeleteOnTouch = chunkType.DeleteOnTouch; Mass = chunkType.Mass; // These are stored for saves to work Radius = chunkType.Radius; ChunkScale = chunkType.ChunkScale; ModelNodePath = modelPath; // Copy compounds to vent if (chunkType.Compounds?.Count > 0) { // Capacity is set to 0 so that no compounds can be added // the normal way to the chunk ContainedCompounds = new CompoundBag(0); foreach (var entry in chunkType.Compounds) { ContainedCompounds.Compounds.Add(entry.Key, entry.Value.Amount); } } }
/// <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> /// Grabs data from the type to initialize this /// </summary> /// <remarks> /// <para> /// Doesn't initialize the graphics scene which needs to be set separately /// </para> /// </remarks> public void Init(ChunkConfiguration chunkType, CompoundCloudSystem compoundClouds, string modelPath) { this.compoundClouds = compoundClouds; // Grab data VentPerSecond = chunkType.VentAmount; Dissolves = chunkType.Dissolves; Size = chunkType.Size; Damages = chunkType.Damages; DeleteOnTouch = chunkType.DeleteOnTouch; Mass = chunkType.Mass; // These are stored for saves to work Radius = chunkType.Radius; ChunkScale = chunkType.ChunkScale; ModelNodePath = modelPath; // Apply physics shape var shape = GetNode <CollisionShape>("CollisionShape"); // This only works as long as the sphere shape type is not changed in the editor ((SphereShape)shape.Shape).Radius = chunkType.Radius; // Copy compounds to vent if (chunkType.Compounds != null && chunkType.Compounds.Count > 0) { // Capacity is set to 0 so that no compounds can be added // the normal way to the chunk ContainedCompounds = new CompoundBag(0); foreach (var entry in chunkType.Compounds) { ContainedCompounds.Compounds.Add(entry.Key, entry.Value.Amount); } } // Needs physics callback when this is engulfable or damaging if (Damages > 0 || DeleteOnTouch || Size > 0) { ContactsReported = Constants.DEFAULT_STORE_CONTACTS_COUNT; Connect("body_shape_entered", this, "OnContactBegin"); Connect("body_shape_exited", this, "OnContactEnd"); } }
/// <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; } } }
public CompoundBagEventArgs(CompoundBag compounds) { Compounds = compounds; }
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); } }
/// <summary> /// Absorbs compounds from clouds into a bag /// </summary> /// <remarks> /// <para> /// TODO: finding a way to add threading here probably helps quite a bit /// </para> /// </remarks> public void AbsorbCompounds(Vector3 position, float radius, CompoundBag storage, Dictionary <Compound, float> totals, float delta, float rate) { // It might be fine to remove this check but this was in the old code if (radius < 1.0f) { GD.PrintErr("Grab radius < 1 is not allowed"); return; } int resolution = Resolution; // This version is used when working with cloud local coordinates float localGrabRadius = radius / resolution; float localGrabRadiusSquared = Mathf.Pow(radius / resolution, 2); // Find clouds that are in range for absorbing foreach (var cloud in clouds) { // Skip clouds that are out of range if (!cloud.ContainsPositionWithRadius(position, radius)) { continue; } int cloudRelativeX, cloudRelativeY; cloud.ConvertToCloudLocal(position, out cloudRelativeX, out cloudRelativeY); // Calculate all circle positions and grab from all the valid // positions // For simplicity all points within a bounding box around the // relative origin point is calculated and that is restricted by // checking if the point is within the circle before grabbing int xEnd = (int)Mathf.Round(cloudRelativeX + localGrabRadius); int yEnd = (int)Mathf.Round(cloudRelativeY + localGrabRadius); for (int x = (int)Mathf.Round(cloudRelativeX - localGrabRadius); x <= xEnd; x += 1) { for (int y = (int)Mathf.Round(cloudRelativeY - localGrabRadius); y <= yEnd; y += 1) { // Negative coordinates are always outside the cloud area if (x < 0 || y < 0) { continue; } // Circle check if (Mathf.Pow(x - cloudRelativeX, 2) + Mathf.Pow(y - cloudRelativeY, 2) > localGrabRadiusSquared) { // Not in it continue; } // Then just need to check that it is within the cloud simulation array if (x < cloud.Size && y < cloud.Size) { // Absorb all compounds in the cloud cloud.AbsorbCompounds(x, y, storage, totals, delta, rate); } } } } }
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); } }